| name | stellar-ios-sdk |
| description | Build Stellar blockchain applications in Swift using stellar-ios-mac-sdk. Use when generating Swift code for transaction building, signing, Horizon API queries, Soroban RPC, smart contract deployment and invocation, XDR encoding/decoding, and SEP protocol integration. Covers 26+ operations, 50 Horizon endpoints, 12 RPC methods, and 17 SEP implementations with Swift async/await and callback-based streaming patterns. Full Swift 6 strict concurrency support (all types Sendable). |
| license | Apache 2.0 |
| compatibility | Requires Swift 6.0+, iOS 15+, macOS 12+. Zero external dependencies. |
| metadata | {"version":"1.0.2","sdk_version":"3.4.7","last_updated":"2026-04-24"} |
Stellar SDK for iOS & Mac
Overview
The Stellar iOS/Mac SDK (stellarsdk) is a native Swift library for building Stellar applications on iOS 15+ and macOS 12+. It provides 100% Horizon API coverage (50/50 endpoints), 100% Soroban RPC coverage (12/12 methods), and 17 SEP implementations. All public APIs use Swift async/await with Swift 6 strict concurrency. The SDK has zero external dependencies.
Module name: stellarsdk (always lowercase in import statements)
Installation
Swift Package Manager
.package(name: "stellarsdk", url: "git@github.com:Soneso/stellar-ios-mac-sdk.git", from: "3.4.7")
CocoaPods
pod 'stellar-ios-mac-sdk', '~> 3.4.7'
All code examples below assume import stellarsdk.
If you can't find a constructor or method signature in this file or the topic references, grep references/api_reference.md — it has all public class/method signatures.
1. Stellar Basics
Fundamental Stellar concepts and SDK patterns.
Keys and KeyPairs
let keyPair = try KeyPair.generateRandomKeyPair()
let accountId = keyPair.accountId
guard let secretSeed = keyPair.secretSeed else {
throw StellarSDKError.invalidArgument(message: "Failed to get secret seed")
}
let keyPair = try KeyPair(secretSeed: seed)
let publicOnly = try KeyPair(accountId: "GABC...")
Accounts
let keyPair = try KeyPair.generateRandomKeyPair()
let sdk = StellarSDK.testNet()
let responseEnum = await sdk.accounts.createTestAccount(accountId: keyPair.accountId)
let responseEnum = await sdk.accounts.getAccountDetails(accountId: accountId)
switch responseEnum {
case .success(let accountResponse):
print("Sequence: \(accountResponse.sequenceNumber)")
print("Subentry count: \(accountResponse.subentryCount)")
for balance in accountResponse.balances {
print("Asset: \(balance.assetType), Balance: \(balance.balance)")
if balance.assetType == "native" {
print(" → Native XLM balance")
} else if balance.assetType == "credit_alphanum4" || balance.assetType == "credit_alphanum12" {
print(" → Custom asset: \(balance.assetCode ?? ""):\(balance.assetIssuer ?? "")")
}
}
case .failure(let error):
print("Error: \(error)")
}
Assets
let xlm = Asset(type: AssetType.ASSET_TYPE_NATIVE)!
let issuerKeyPair = try KeyPair(accountId: "GISSUER...")
let usdc = Asset(type: AssetType.ASSET_TYPE_CREDIT_ALPHANUM4,
code: "USDC",
issuer: issuerKeyPair)!
let asset = Asset(canonicalForm: "USDC:GISSUER...")!
Networks
let testnetSdk = StellarSDK.testNet()
let publicSdk = StellarSDK.publicNet()
let customSdk = StellarSDK(withHorizonUrl: "https://my-horizon.example.com")
2. Horizon API - Fetching Data
Query patterns for retrieving blockchain data. All queries return result enums (.success/.failure).
Query Accounts & Transactions
let sdk = StellarSDK.testNet()
let accountEnum = await sdk.accounts.getAccountDetails(accountId: "GABC...")
let txEnum = await sdk.transactions.getTransactions(
forAccount: "GABC...",
from: nil,
order: .descending,
limit: 10
)
switch txEnum {
case .success(let page):
if let lastRecord = page.records.last {
let nextPage = await sdk.transactions.getTransactions(
forAccount: "GABC...",
from: lastRecord.pagingToken,
order: .descending,
limit: 10
)
}
case .failure(let error):
print("Error: \(error)")
}
For all Horizon endpoints (50/50), advanced queries, and memo inspection:
Horizon API Reference
3. Horizon API - Streaming
Real-time update patterns using Server-Sent Events. You must hold a strong reference to the stream item or it will close immediately.
class PaymentMonitor {
private var streamItem: OperationsStreamItem?
private let sdk = StellarSDK.testNet()
func startStreaming(accountId: String) {
streamItem = sdk.payments.stream(
for: .paymentsForAccount(account: accountId, cursor: "now")
)
streamItem?.onReceive { response in
switch response {
case .response(let id, let operationResponse):
if let payment = operationResponse as? PaymentOperationResponse {
print("[\(id)] Payment: \(payment.amount) \(payment.assetCode ?? "XLM")")
}
case .error(let error):
print("Stream error: \(error?.localizedDescription ?? "unknown")")
default:
break
}
}
}
func stopStreaming() {
streamItem?.closeStream()
streamItem = nil
}
}
For reconnection patterns and all streaming endpoints:
Horizon Streaming Guide
4. Transactions & Operations
Complete transaction lifecycle: Build -> Sign -> Submit.
let sdk = StellarSDK.testNet()
let accountEnum = await sdk.accounts.getAccountDetails(accountId: senderKeyPair.accountId)
guard case .success(let accountResponse) = accountEnum else { return }
let paymentOp = try PaymentOperation(
sourceAccountId: nil,
destinationAccountId: "GDEST...",
asset: Asset(type: AssetType.ASSET_TYPE_NATIVE)!,
amount: 100.0
)
let transaction = try Transaction(
sourceAccount: accountResponse,
operations: [paymentOp],
memo: Memo.text("Payment"),
maxOperationFee: 100
)
try transaction.sign(keyPair: senderKeyPair, network: Network.testnet)
let submitEnum = await sdk.transactions.submitTransaction(transaction: transaction)
switch submitEnum {
case .success(let response):
print("Success! Hash: \(response.transactionHash)")
case .destinationRequiresMemo(let accountId):
print("SEP-29: Destination \(accountId) requires memo")
case .failure(let error):
print("Failed: \(error)")
}
For all 26+ operations (ChangeTrust, ManageSellOffer, CreateAccount, etc.):
Operations Reference
5. Soroban RPC API
RPC endpoint patterns for Soroban smart contract queries.
let server = SorobanServer(endpoint: "https://soroban-testnet.stellar.org")
server.enableLogging = true
let healthEnum = await server.getHealth()
let networkEnum = await server.getNetwork()
For all 12 RPC methods (getAccount, simulateTransaction, getEvents, etc.):
RPC Reference
6. Smart Contracts
Contract deployment and invocation patterns using SorobanClient.
Deploy Contract
let keyPair = try KeyPair(secretSeed: secretSeed)
let rpcUrl = "https://soroban-testnet.stellar.org"
let wasmHash = try await SorobanClient.install(
installRequest: InstallRequest(
rpcUrl: rpcUrl,
network: Network.testnet,
sourceAccountKeyPair: keyPair,
wasmBytes: wasmData
)
)
let client = try await SorobanClient.deploy(
deployRequest: DeployRequest(
rpcUrl: rpcUrl,
network: Network.testnet,
sourceAccountKeyPair: keyPair,
wasmHash: wasmHash,
constructorArgs: [SCValXDR.u32(1000)],
enableServerLogging: false
)
)
print("Contract ID: \(client.contractId)")
Invoke Contract Function
let client = try await SorobanClient.forClientOptions(
options: ClientOptions(
sourceAccountKeyPair: keyPair,
contractId: "CABC...",
network: Network.testnet,
rpcUrl: rpcUrl
)
)
let result = try await client.invokeMethod(
name: "hello",
args: [SCValXDR.symbol("world")]
)
For multi-auth workflows, low-level deploy/invoke, and contract authorization:
Smart Contracts Guide
7. XDR Encoding & Decoding
XDR is Stellar's binary serialization format.
let xdrBase64 = try transaction.encodedEnvelope()
let transaction = try Transaction(envelopeXdr: xdrBase64)
let boolVal = SCValXDR.bool(true)
let u32Val = SCValXDR.u32(42)
let symbolVal = SCValXDR.symbol("transfer")
let addressVal = SCValXDR.address(try SCAddressXDR(accountId: "GABC..."))
For all XdrSCVal types and encoding/decoding utilities:
XDR Reference
8. Error Handling & Troubleshooting
Horizon Errors
let responseEnum = await sdk.accounts.getAccountDetails(accountId: "GINVALID...")
switch responseEnum {
case .success(let account):
print("Found: \(account.accountId)")
case .failure(let error):
switch error {
case .notFound(let message, _):
print("Account not found: \(message)")
case .rateLimitExceeded(let message, _):
print("Rate limited: \(message)")
default:
print("Other error: \(error)")
}
}
Transaction Errors
let submitEnum = await sdk.transactions.submitTransaction(transaction: transaction)
switch submitEnum {
case .success(let response):
print("Success: \(response.transactionHash)")
case .failure(let error):
if case .badRequest(_, let errorResponse) = error {
if let resultCodes = errorResponse?.extras?.resultCodes {
print("TX code: \(resultCodes.transaction ?? "unknown")")
print("Op codes: \(resultCodes.operations ?? [])")
}
}
case .destinationRequiresMemo:
print("SEP-29 memo required")
}
For comprehensive error catalog and solutions:
Troubleshooting Guide
9. Security Best Practices
Never hardcode secret seeds. Use iOS Keychain for storage. Always verify transaction details before signing. Validate network passphrases to prevent mainnet accidents.
Security Best Practices
10. SEP Implementations
The SDK implements 17 Stellar Ecosystem Proposals (SEPs): SEP-01 (TOML), SEP-02 (Federation), SEP-05 (Key Derivation), SEP-10 (Web Auth), SEP-24 (Interactive deposit/withdrawal), and more.
SEP Implementations Reference
11. Advanced Features
Multi-signature accounts, sponsored reserves, claimable balances, liquidity pools, muxed accounts (M-addresses), fee-bump transactions, path payments.
Advanced Features Reference
Reference Documentation
External Resources:
Common Pitfalls
Module name is lowercase:
import stellarsdk
Stream items must be retained:
func bad() {
let _ = sdk.payments.stream(for: .paymentsForAccount(account: "G...", cursor: nil))
}
class Monitor {
var streamItem: OperationsStreamItem?
func start() {
streamItem = sdk.payments.stream(for: .paymentsForAccount(account: "G...", cursor: nil))
}
}
Amounts are Decimal, not String:
let payment = try PaymentOperation(
sourceAccountId: nil,
destinationAccountId: "GDEST...",
asset: Asset(type: AssetType.ASSET_TYPE_NATIVE)!,
amount: 100.0
)
let balance = accountResponse.balances[0]
guard let amountDecimal = Decimal(string: balance.balance) else { throw ... }
let payment = try PaymentOperation(..., amount: amountDecimal)
Sequence number is already Int64:
let account = try Account(
accountId: accountResponse.accountId,
sequenceNumber: accountResponse.sequenceNumber
)
Sequence number mutation: Transaction(sourceAccount:) increments the source account's sequence number internally. Reload the account before building a new transaction. Don't increment manually.
let accountResponse = await sdk.accounts.getAccountDetails(accountId: accountId)
guard case .success(let account) = accountResponse else { return }
let tx = try Transaction(sourceAccount: account, operations: [op], memo: Memo.none)
Network passphrase must match SDK:
let sdk = StellarSDK.publicNet()
try transaction.sign(keyPair: keyPair, network: Network.testnet)
let sdk = StellarSDK.publicNet()
try transaction.sign(keyPair: keyPair, network: .public)
KeyPair from accountId is public-only (cannot sign):
let publicKeyPair = try KeyPair(accountId: "GABC...")
try transaction.sign(keyPair: publicKeyPair, network: Network.testnet)
let signingKeyPair = try KeyPair(secretSeed: "SABC...")
try transaction.sign(keyPair: signingKeyPair, network: Network.testnet)
Insufficient signatures return op_bad_auth, not tx_bad_auth:
let submitEnum = await sdk.transactions.submitTransaction(transaction: transaction)
switch submitEnum {
case .failure(let error):
if case .badRequest(_, let errorResponse) = error {
if let resultCodes = errorResponse?.extras?.resultCodes {
if resultCodes.transaction == "tx_bad_auth" { }
if let opCodes = resultCodes.operations, opCodes.contains("op_bad_auth") {
print("Insufficient signatures!")
}
}
}
default:
break
}
Fee calculation:
The fee is per operation. For a transaction with N operations at maxOperationFee: 200, the total fee is N × 200 stroops. The minimum base fee is 100 stroops per operation.
Soroban transactions require simulation first:
let simRequest = SimulateTransactionRequest(transaction: tx)
let simEnum = await server.simulateTransaction(simulateTxRequest: simRequest)
guard case .success(let simResponse) = simEnum else { return }
if let transactionData = simResponse.transactionData {
tx.setSorobanTransactionData(data: transactionData)
}
if let minResourceFee = simResponse.minResourceFee {
tx.addResourceFee(resourceFee: minResourceFee)
}
tx.setSorobanAuth(auth: simResponse.sorobanAuth)
For error handling patterns and troubleshooting:
Troubleshooting Guide