stellar-ios-sdk
$
npx mdskill add Soneso/stellar-ios-mac-sdk/stellar-ios-sdkBuild complete Stellar blockchain applications in Swift, handling transactions and smart contracts.
- Handles transaction construction, signing, and querying the Horizon API.
- Integrates with Soroban RPC, XDR encoding, and various SEP protocols.
- Executes operations using modern Swift async/await patterns and callbacks.
- Delivers fully functional, dependency-free Swift code for mobile and desktop.
SKILL.md
.github/skills/stellar-ios-sdkView on GitHub ↗
--- 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.1" sdk_version: "3.4.6" last_updated: "2026-03-29" --- # 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 ```swift .package(name: "stellarsdk", url: "[email protected]:Soneso/stellar-ios-mac-sdk.git", from: "3.4.6") ``` ### CocoaPods ```ruby pod 'stellar-ios-mac-sdk', '~> 3.4.6' ``` > 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 ```swift // Generate new keypair let keyPair = try KeyPair.generateRandomKeyPair() let accountId = keyPair.accountId // G-address guard let secretSeed = keyPair.secretSeed else { throw StellarSDKError.invalidArgument(message: "Failed to get secret seed") } // WARNING: Store secretSeed securely (iOS Keychain). Never log or hardcode it. // From existing seed let keyPair = try KeyPair(secretSeed: seed) let publicOnly = try KeyPair(accountId: "GABC...") // public-only, cannot sign ``` ### Accounts ```swift // Fund testnet account (FriendBot) let keyPair = try KeyPair.generateRandomKeyPair() let sdk = StellarSDK.testNet() let responseEnum = await sdk.accounts.createTestAccount(accountId: keyPair.accountId) // Query account let responseEnum = await sdk.accounts.getAccountDetails(accountId: accountId) switch responseEnum { case .success(let accountResponse): print("Sequence: \(accountResponse.sequenceNumber)") print("Subentry count: \(accountResponse.subentryCount)") // Trustlines, offers, data entries for balance in accountResponse.balances { print("Asset: \(balance.assetType), Balance: \(balance.balance)") // WRONG: balance.assetType == AssetType.ASSET_TYPE_NATIVE (comparing String to Int32) // CORRECT: balance.assetType == "native" (response fields are strings) 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 ```swift // Native XLM let xlm = Asset(type: AssetType.ASSET_TYPE_NATIVE)! // Issued asset (4-char code) let issuerKeyPair = try KeyPair(accountId: "GISSUER...") let usdc = Asset(type: AssetType.ASSET_TYPE_CREDIT_ALPHANUM4, code: "USDC", issuer: issuerKeyPair)! // From canonical form let asset = Asset(canonicalForm: "USDC:GISSUER...")! ``` ### Networks ```swift // Pre-configured: Network.testnet, Network.public, Network.futurenet let testnetSdk = StellarSDK.testNet() let publicSdk = StellarSDK.publicNet() // Custom Horizon URL 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 ```swift let sdk = StellarSDK.testNet() // Account details let accountEnum = await sdk.accounts.getAccountDetails(accountId: "GABC...") // Transactions for account let txEnum = await sdk.transactions.getTransactions( forAccount: "GABC...", from: nil, order: .descending, limit: 10 ) // Pagination: use cursor from last record's pagingToken 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](./references/horizon_api.md) ## 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. ```swift class PaymentMonitor { private var streamItem: OperationsStreamItem? // Strong reference required! 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](./references/horizon_streaming.md) ## 4. Transactions & Operations Complete transaction lifecycle: Build -> Sign -> Submit. ```swift let sdk = StellarSDK.testNet() // 1. Load sender account (AccountResponse conforms to TransactionAccount) let accountEnum = await sdk.accounts.getAccountDetails(accountId: senderKeyPair.accountId) guard case .success(let accountResponse) = accountEnum else { return } // 2. Create payment operation let paymentOp = try PaymentOperation( sourceAccountId: nil, destinationAccountId: "GDEST...", asset: Asset(type: AssetType.ASSET_TYPE_NATIVE)!, amount: 100.0 // Decimal type ) // 3. Build transaction let transaction = try Transaction( sourceAccount: accountResponse, operations: [paymentOp], memo: Memo.text("Payment"), maxOperationFee: 100 ) // 4. Sign try transaction.sign(keyPair: senderKeyPair, network: Network.testnet) // 5. Submit 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](./references/operations.md) ## 5. Soroban RPC API RPC endpoint patterns for Soroban smart contract queries. ```swift let server = SorobanServer(endpoint: "https://soroban-testnet.stellar.org") server.enableLogging = true // Optional: debug logging // Health check let healthEnum = await server.getHealth() // Network info let networkEnum = await server.getNetwork() ``` For all 12 RPC methods (getAccount, simulateTransaction, getEvents, etc.): [RPC Reference](./references/rpc.md) ## 6. Smart Contracts Contract deployment and invocation patterns using `SorobanClient`. ### Deploy Contract ```swift let keyPair = try KeyPair(secretSeed: secretSeed) let rpcUrl = "https://soroban-testnet.stellar.org" // Step 1: Install WASM let wasmHash = try await SorobanClient.install( installRequest: InstallRequest( rpcUrl: rpcUrl, network: Network.testnet, sourceAccountKeyPair: keyPair, wasmBytes: wasmData ) ) // Step 2: Deploy instance let client = try await SorobanClient.deploy( deployRequest: DeployRequest( rpcUrl: rpcUrl, network: Network.testnet, sourceAccountKeyPair: keyPair, wasmHash: wasmHash, constructorArgs: [SCValXDR.u32(1000)], // optional — see soroban_contracts.md enableServerLogging: false ) ) print("Contract ID: \(client.contractId)") ``` ### Invoke Contract Function ```swift // Create client for existing contract let client = try await SorobanClient.forClientOptions( options: ClientOptions( sourceAccountKeyPair: keyPair, contractId: "CABC...", network: Network.testnet, rpcUrl: rpcUrl ) ) // Invoke method (handles simulation, signing, submission) 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](./references/soroban_contracts.md) ## 7. XDR Encoding & Decoding XDR is Stellar's binary serialization format. ```swift // Encode transaction to XDR let xdrBase64 = try transaction.encodedEnvelope() // Decode XDR to transaction let transaction = try Transaction(envelopeXdr: xdrBase64) // Soroban contract values let boolVal = SCValXDR.bool(true) let u32Val = SCValXDR.u32(42) let symbolVal = SCValXDR.symbol("transfer") let addressVal = SCValXDR.address(try SCAddressXDR(accountId: "GABC...")) // SCAddressXDR throws, .address() does not ``` For all XdrSCVal types and encoding/decoding utilities: [XDR Reference](./references/xdr.md) ## 8. Error Handling & Troubleshooting ### Horizon Errors ```swift 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 ```swift 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](./references/troubleshooting.md) ## 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](./references/security.md) ## 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](./references/sep.md) ## 11. Advanced Features Multi-signature accounts, sponsored reserves, claimable balances, liquidity pools, muxed accounts (M-addresses), fee-bump transactions, path payments. [Advanced Features Reference](./references/advanced.md) ## Reference Documentation - [Operations Reference](./references/operations.md) - All 26+ Stellar operations with Swift examples - [Horizon API Reference](./references/horizon_api.md) - Complete Horizon endpoint coverage (50/50) - [Horizon Streaming Guide](./references/horizon_streaming.md) - SSE patterns and reconnection - [RPC Reference](./references/rpc.md) - All 12 Soroban RPC methods - [Smart Contracts Guide](./references/soroban_contracts.md) - Contract deployment, invocation, auth - [XDR Guide](./references/xdr.md) - XDR encoding/decoding and debugging - [Troubleshooting Guide](./references/troubleshooting.md) - Error codes and solutions - [Security Best Practices](./references/security.md) - Keychain storage, transaction verification - [SEP Implementations](./references/sep.md) - All 17 SEP protocol implementations - [Advanced Features](./references/advanced.md) - Multi-sig, sponsorship, claimable balances, liquidity pools - [API Reference (Signatures)](./references/api_reference.md) - All public class/method signatures **External Resources:** - [Stellar Developer Docs](https://developers.stellar.org/) - [SDK Repository](https://github.com/Soneso/stellar-ios-mac-sdk) - [Soroban Docs](https://soroban.stellar.org/) ## Common Pitfalls **Module name is lowercase:** ```swift // WRONG: import StellarSDK // CORRECT: import stellarsdk ``` **Stream items must be retained:** ```swift // WRONG: stream closes immediately func bad() { let _ = sdk.payments.stream(for: .paymentsForAccount(account: "G...", cursor: nil)) } // CORRECT: store as instance property class Monitor { var streamItem: OperationsStreamItem? // Strong reference func start() { streamItem = sdk.payments.stream(for: .paymentsForAccount(account: "G...", cursor: nil)) } } ``` **Amounts are Decimal, not String:** ```swift // Operations use Decimal let payment = try PaymentOperation( sourceAccountId: nil, destinationAccountId: "GDEST...", asset: Asset(type: AssetType.ASSET_TYPE_NATIVE)!, amount: 100.0 // Decimal type ) // Horizon responses return String balances with 7 decimal places // e.g., "100.0000000" for 100 XLM — always 7 decimals, never "100" 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:** ```swift // WRONG: Int64(accountResponse.sequenceNumber)! -- compile error // CORRECT: use directly let account = try Account( accountId: accountResponse.accountId, sequenceNumber: accountResponse.sequenceNumber // Already Int64 ) ``` **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. ```swift // CORRECT: reload account, Transaction increments sequence internally 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) // account.sequenceNumber is now N+1 // WRONG: manually incrementing — Transaction already does this // account.incrementSequenceNumber() // now N+1 // let tx = try Transaction(sourceAccount: account, ...) // uses N+2 — tx_bad_seq! ``` **Network passphrase must match SDK:** ```swift // WRONG: Mismatched network let sdk = StellarSDK.publicNet() try transaction.sign(keyPair: keyPair, network: Network.testnet) // ERROR! // CORRECT: Match SDK and signing network let sdk = StellarSDK.publicNet() try transaction.sign(keyPair: keyPair, network: .public) ``` **KeyPair from accountId is public-only (cannot sign):** ```swift // WRONG: Trying to sign with public-only KeyPair let publicKeyPair = try KeyPair(accountId: "GABC...") try transaction.sign(keyPair: publicKeyPair, network: Network.testnet) // ERROR! // CORRECT: Load from secretSeed for signing let signingKeyPair = try KeyPair(secretSeed: "SABC...") try transaction.sign(keyPair: signingKeyPair, network: Network.testnet) ``` **Insufficient signatures return `op_bad_auth`, not `tx_bad_auth`:** ```swift // Multi-sig auth failures appear in operation codes, not transaction code 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 { // WRONG: checking transaction code for auth failure if resultCodes.transaction == "tx_bad_auth" { /* never matches */ } // CORRECT: check operation codes for op_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:** ```swift // Build transaction, then simulate to get footprint/fees let simRequest = SimulateTransactionRequest(transaction: tx) let simEnum = await server.simulateTransaction(simulateTxRequest: simRequest) guard case .success(let simResponse) = simEnum else { return } // Apply simulation results before signing 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](./references/troubleshooting.md)