Stellar SDK for Flutter
Overview
The Stellar Flutter SDK (stellar_flutter_sdk) is a full-featured Dart library for building Stellar blockchain applications on Android, iOS, and Web. It covers the Horizon API, the Soroban RPC API, and the SEP implementations listed in the SEP guide. All APIs use Dart Future (async/await) for asynchronous operations and Stream for real-time event subscriptions. 64-bit integer types are BigInt, so the same code runs on the web platform.
Installation
yaml
dependencies:
stellar_flutter_sdk: ^3.10.0
iOS: set the app's deployment target to 15.0 or higher. Passkey smart-account features need iOS 16 at runtime.
All code examples below assume import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart';
1. Stellar Basics
Fundamental Stellar concepts and SDK patterns.
Keys and KeyPairs
dart
// Generate new keypair
KeyPair keyPair = KeyPair.random();
String accountId = keyPair.accountId; // G... public address
String secretSeed = keyPair.secretSeed; // S... secret seed
// IMPORTANT: Store secretSeed securely. Never log or expose it.
// From existing seed
KeyPair restored = KeyPair.fromSecretSeed(secretSeed);
KeyPair publicOnly = KeyPair.fromAccountId(accountId); // public-key-only, cannot sign
Accounts
dart
KeyPair keyPair = KeyPair.random();
String accountId = keyPair.accountId;
// Fund on testnet using FriendBot (10,000 test XLM)
await FriendBot.fundTestAccount(accountId);
// Query account (a missing account throws a 404 ErrorResponse: Account Not Found in the Troubleshooting Guide)
StellarSDK sdk = StellarSDK.TESTNET;
AccountResponse account = await sdk.accounts.account(accountId);
print('Sequence: ${account.sequenceNumber}');
print('Subentries: ${account.subentryCount}');
for (Balance balance in account.balances) {
if (balance.assetType == Asset.TYPE_NATIVE) {
print('XLM: ${balance.balance}');
} else {
print('${balance.assetCode}: ${balance.balance}');
}
}
// Check signers
for (Signer signer in account.signers) {
// WRONG: signer.ed25519PublicKey -- does NOT exist
// CORRECT: signer.key returns the public key string, signer.type returns the type
print('${signer.key} weight=${signer.weight} type=${signer.type}');
}
Assets
dart
// From your app: asset (Asset, for example balance.asset, operation.asset or offer.selling)
Asset xlm = Asset.NATIVE; // Returns AssetTypeNative instance
// 1-4 char code -> AssetTypeCreditAlphaNum4; 5-12 char -> AssetTypeCreditAlphaNum12
Asset usdc = Asset.createNonNativeAsset('USDC', 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN');
// From canonical form
Asset? parsed = Asset.createFromCanonicalForm('USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN');
// WRONG: asset.assetCode -- Asset base class has NO assetCode property
// CORRECT: For type-specific access, cast to AssetTypeCreditAlphaNum
if (asset is AssetTypeCreditAlphaNum) {
String code = (asset as AssetTypeCreditAlphaNum).code;
String issuer = (asset as AssetTypeCreditAlphaNum).issuerId;
}
// BETTER for printing/logging: use canonical form (works for ALL asset types)
String display = Asset.canonicalForm(asset); // "native" for XLM, "USDC:GISSUER..." for custom
print('Asset: $display');
Networks
dart
// Pre-configured: StellarSDK.TESTNET, StellarSDK.PUBLIC, StellarSDK.FUTURENET
// Network passphrases: Network.TESTNET, Network.PUBLIC, Network.FUTURENET
StellarSDK sdk = StellarSDK.TESTNET;
Network network = Network.TESTNET;
// Custom Horizon
StellarSDK customSdk = StellarSDK('https://my-horizon.example.com');
2. Horizon API - Fetching Data
Query patterns for retrieving blockchain data. All request builders support .cursor(), .limit(), .order() for pagination.
Query Accounts
dart
// From the section Accounts: accountId (String)
StellarSDK sdk = StellarSDK.TESTNET;
// Single account
AccountResponse account = await sdk.accounts.account(accountId);
// Builder pattern: forSigner(), forAsset(), limit(), order(), cursor()
Page<AccountResponse> bySigner = await sdk.accounts
.forSigner(accountId)
.limit(10)
.order(RequestBuilderOrder.DESC)
.execute();
for (AccountResponse acct in bySigner.records) {
print('Account: ${acct.accountId}');
}
Query Transactions
dart
// From the section Accounts: accountId (String)
StellarSDK sdk = StellarSDK.TESTNET;
// Same builder pattern as accounts: forAccount(), limit(), order(), cursor()
Page<TransactionResponse> txPage = await sdk.transactions
.forAccount(accountId)
.order(RequestBuilderOrder.DESC)
.limit(5)
.execute();
for (TransactionResponse tx in txPage.records) {
print('${tx.hash} ledger=${tx.ledger}');
}
// Next page: .cursor(txPage.records.last.pagingToken); single tx: sdk.transactions.transaction(hash)
For all Horizon endpoints, advanced queries, and pagination patterns: Horizon API Reference
3. Horizon API - Streaming
Real-time update patterns using Server-Sent Events (SSE). Set cursor to "now" for real-time events. Always store the StreamSubscription to cancel later.
Stream Payments
dart
import 'dart:async';
// From the section Accounts: accountId (String), sdk (StellarSDK)
// Stream pattern: same for payments, transactions, ledgers, operations, effects, offers
StreamSubscription<OperationResponse> subscription = sdk.payments
.forAccount(accountId)
.cursor('now')
.stream()
.listen((OperationResponse response) {
if (response is PaymentOperationResponse) {
print('${response.amount} ${response.assetCode ?? "XLM"} from ${response.from}');
}
});
// Cancel when no longer needed (e.g., in Flutter widget dispose())
// subscription.cancel();
When Horizon sends an SSE close event or an event fails to parse, the SDK opens a new connection with the same URI and no last event id. If the response ends without a close event, it does not reconnect. So keep the last pagingToken and resubscribe with .cursor(...). Connection failures do not reach onError but surface as uncaught asynchronous errors; catch them with runZonedGuarded or the app's global error handler and watch for silence. Always cancel subscriptions to prevent resource leaks.
For reconnection patterns and all streaming endpoints: Horizon Streaming Guide
4. Transactions & Operations
Complete transaction lifecycle: Build -> Sign -> Submit.
Transaction Lifecycle
dart
// From your app: destinationAccountId (String), senderSecret (String)
StellarSDK sdk = StellarSDK.TESTNET;
Network network = Network.TESTNET;
// 1. Load sender keypair
KeyPair senderKeyPair = KeyPair.fromSecretSeed(senderSecret);
String senderAccountId = senderKeyPair.accountId;
// 2. Fetch source account (provides sequence number)
AccountResponse senderAccount = await sdk.accounts.account(senderAccountId);
// 3. Build transaction (up to 100 operations)
Transaction transaction = TransactionBuilder(senderAccount)
.addOperation(
PaymentOperationBuilder(destinationAccountId, Asset.NATIVE, '100.50')
.build(),
)
.addMemo(Memo.text('payment'))
.setMaxOperationFee(200) // stroops per operation (default: 100)
.build();
// 4. Sign transaction
transaction.sign(senderKeyPair, network);
// 5. Submit to Horizon
SubmitTransactionResponse response = await sdk.submitTransaction(transaction);
if (response.success) {
print('Success! Hash: ${response.hash}');
} else {
print('Failed: ${response.extras?.resultCodes?.transactionResultCode}');
print('Op codes: ${response.extras?.resultCodes?.operationsResultCodes}');
}
Common Operations
Change Trust (Establish Trustline):
dart
Asset usdc = Asset.createNonNativeAsset('USDC', 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN');
Operation trustline = ChangeTrustOperationBuilder(usdc, ChangeTrustOperationBuilder.MAX_LIMIT).build();
// A lower limit instead of the maximum: ChangeTrustOperationBuilder(usdc, '1000.00')
Manage Sell Offer (DEX):
dart
Asset selling = AssetTypeNative();
Asset buying = Asset.createNonNativeAsset('USDC', 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN');
// Create new offer (offerId defaults to "0" = new offer)
Operation newOffer = ManageSellOfferOperationBuilder(
selling, buying, '100.0', '0.5') // sell 100 XLM at 0.5 USDC/XLM
.build();
For every operation with parameters and examples: Operations Reference
5. Soroban RPC API
RPC endpoint patterns for Soroban smart contract queries.
dart
SorobanServer server = SorobanServer('https://soroban-testnet.stellar.org:443');
server.enableLogging = true; // optional: debug JSON-RPC requests/responses
GetHealthResponse health = await server.getHealth(); // .status, .latestLedger
Assign server.logger to route log lines to your sink; without a logger they go to the dart:developer log.
For the RPC methods, including event queries and transaction simulation: RPC Reference
6. Smart Contracts
Contract deployment and invocation patterns using the high-level SorobanClient.
Deploy Contract
dart
import 'dart:typed_data';
// From your app: secretSeed (String), wasmBytes (Uint8List)
KeyPair keyPair = KeyPair.fromSecretSeed(secretSeed);
String rpcUrl = 'https://soroban-testnet.stellar.org:443';
// Step 1: Install WASM bytecode (returns wasm hash)
String wasmHash = await SorobanClient.install(
installRequest: InstallRequest(
wasmBytes: wasmBytes,
sourceAccountKeyPair: keyPair,
network: Network.TESTNET,
rpcUrl: rpcUrl,
),
);
// Step 2: Deploy contract instance
SorobanClient client = await SorobanClient.deploy(
deployRequest: DeployRequest(
sourceAccountKeyPair: keyPair,
wasmHash: wasmHash,
network: Network.TESTNET,
rpcUrl: rpcUrl,
constructorArgs: [XdrSCVal.forSymbol('init')], // optional
),
);
print('Contract ID: ${client.getContractId()}');
Invoke Contract Function
dart
// From your app: contractId (String)
// From the section Deploy Contract: keyPair (KeyPair), rpcUrl (String)
// Create client for an existing contract
SorobanClient client = await SorobanClient.forClientOptions(
options: ClientOptions(
sourceAccountKeyPair: keyPair,
contractId: contractId, // the C... id of the deployed contract
network: Network.TESTNET,
rpcUrl: rpcUrl,
),
);
// Read call (simulation only)
XdrSCVal result = await client.invokeMethod(name: 'get_count');
print('Count: ${result.u32!.uint32}');
// Write call (simulates, signs, submits automatically)
XdrSCVal incremented = await client.invokeMethod(
name: 'increment', args: [XdrSCVal.forU32(5)],
);
// Custom fee/timeout: pass methodOptions: MethodOptions(fee: 10000, timeoutInSeconds: 60)
For contract authorization, multi-auth workflows, protocol 27 credentials (ADDRESS_V2 / WITH_DELEGATES, useUpgradedAuth), and low-level deploy/invoke: Smart Contracts Guide
7. Smart Accounts (OpenZeppelin)
Passkey-authenticated Soroban smart accounts: biometric auth, multiple signers (passkey / delegated / Ed25519), context rules, policies, and optional fee sponsoring via a relayer. Entry point: OZSmartAccountKit.create(config:).
- Smart Accounts Guide: kit config, wallet create/connect, signer types, transactions, credentials, events,
submit / fundWallet, the externalSigners manager, indexer - Context Rules & Policies: signer management, context rules, policies, multi-signer operations, common scenarios (recovery, rotation,
__check_auth debugging), contract error codes - WebAuthn Platform Setup: Android, iOS, and Web WebAuthn providers and storage adapters, DAL / AASA / RP-ID, cross-device passkeys