A production-ready Swift client library for Ogmios, providing complete coverage of all Ogmios protocols. SwiftOgmios offers both HTTP/HTTPS and WebSocket transport mechanisms with full async/await support and comprehensive documentation.
Ogmios offers a JSON-RPC interface to interact with a Cardano node. SwiftOgmios provides complete coverage of all Ogmios protocols:
- Ledger State Queries (17 queries): Current ledger state, stake distribution, protocol parameters, UTxO sets, governance data, etc.
- Network Queries (4 queries): Block height, network tip, genesis configuration, start time
- Chain Synchronization (2 operations): Follow the Cardano blockchain in real-time with intersection finding and block streaming
- Transaction Submission (2 operations): Submit transactions to the network and evaluate execution
- Mempool Monitoring (5 operations + helpers): Monitor transaction mempool changes, acquire snapshots, check transaction status
- ✅ Modern Swift Concurrency: Full async/await support
- ✅ Multiple Transport Types: HTTP/HTTPS and WebSocket connections
- ✅ Type Safety: Strongly typed responses using Swift's type system
- ✅ JSON-RPC 2.0 Compliant: Full compliance with JSON-RPC 2.0 specification
- ✅ Thread Safe: Concurrent operations using dispatch queues
- ✅ Comprehensive Error Handling: Detailed error responses from Ogmios
- ✅ Sendable Compliance: Safe for concurrent environments
- ✅ Complete Protocol Coverage: All Ogmios protocols fully implemented
- ✅ Production Ready: Comprehensive documentation and examples
- iOS 14.0+ / macOS 15.0+ / watchOS 7.0+ / tvOS 14.0+
- Swift 6.1+
- Xcode 16.0+
Add SwiftOgmios to your Package.swift:
dependencies: [
.package(url: "https://github.com/Kingpin-Apps/swift-ogmios.git", from: "1.0.0")
]Or add it through Xcode:
- Go to File → Add Package Dependencies
- Enter the repository URL:
https://github.com/Kingpin-Apps/swift-ogmios.git - Choose the version and add to your target
SwiftOgmios provides 100% coverage of the Ogmios JSON-RPC API with 30 total operations across all protocols:
| Protocol | Operations | Coverage |
|---|---|---|
| Ledger State Queries | 17 operations | ✅ 100% Complete |
| Network Queries | 4 operations | ✅ 100% Complete |
| Chain Synchronization | 2 operations | ✅ 100% Complete |
| Transaction Submission | 2 operations | ✅ 100% Complete |
| Mempool Monitoring | 5 operations + 2 helpers | ✅ 100% Complete |
| Server Health | 1 endpoint | ✅ 100% Complete |
SwiftOgmios provides two methods for each operation, giving you flexibility in how you handle responses:
The .result() method returns just the data you need, making your code cleaner and more focused:
// Returns just the epoch number (e.g., 220)
let currentEpoch: Epoch = try await client.ledgerStateQuery.epoch.result()
print("Current epoch: \(currentEpoch)")
// Returns just the tip information
let tip: LedgerStateTip = try await client.ledgerStateQuery.tip.result()
print("Current slot: \(tip.slot)")The .execute() method returns the complete JSON-RPC response, useful when you need metadata:
// Returns the full JSON-RPC response with metadata
let response = try await client.ledgerStateQuery.epoch.execute(id: .string("my-id"))
print("Epoch: \(response.result), Request ID: \(response.id!)")
print("Method: \(response.method), JSON-RPC: \(response.jsonrpc)")Use .result() when: |
Use .execute() when: |
|---|---|
| You only need the actual data | You need request/response correlation |
| Writing simple, clean code | Implementing custom logging |
| Building typical applications | Debugging JSON-RPC communication |
| Following best practices | Handling request IDs manually |
| Examples: Wallet apps, DApps, data analysis | Examples: Testing tools, monitoring systems |
💡 Tip: Most applications should use .result() for cleaner, more readable code.
// ✅ Preferred: Clean and focused
let epoch = try await client.ledgerStateQuery.epoch.result()
let tip = try await client.ledgerStateQuery.tip.result()
print("Epoch \(epoch) at slot \(tip.slot)")
// 🔧 Advanced: When you need metadata
let epochResponse = try await client.ledgerStateQuery.epoch.execute()
let tipResponse = try await client.ledgerStateQuery.tip.execute()
print("Request \(epochResponse.id!) returned epoch \(epochResponse.result)")
print("Request \(tipResponse.id!) returned tip at slot \(tipResponse.result.slot)")First, you'll need a running Ogmios server. The easiest way is a cloud-based environment on (demeter.run)[https://demeter.run] Or install cardano-node and Ogmios server as described here. (Docker installation is recommended.) You can start one using the Docker or by building from source. Here's an example using Docker Compose:
services:
cardano-node:
container_name: cardano-node
image: ghcr.io/intersectmbo/cardano-node:${CARDANO_NODE_VERSION:-latest}
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f 127.0.0.1:12788 || exit 1"]
interval: 60s
timeout: 10s
retries: 5
environment:
- NETWORK=${NETWORK:-preview}
ports:
- "3001:3001"
volumes:
- node-db:/data/db
- node-ipc:/ipc
- node-config:/opt/cardano/config
ogmios:
container_name: ogmios
image: cardanosolutions/ogmios:${OGMIOS_VERSION:-latest}
restart: on-failure
environment:
- NETWORK=${NETWORK:-preview}
command:
[
"--host",
"0.0.0.0",
"--node-socket",
"/socket/node.socket",
"--node-config",
"/opt/cardano/config/${NETWORK:-preview}/cardano-node/config.json",
]
volumes:
- node-config:/opt/cardano/config:ro
- node-ipc:/socket
ports:
- ${OGMIOS_PORT:-1337}:1337
depends_on:
cardano-node:
condition: service_healthy
volumes:
node-db:
node-ipc:
node-config:
SwiftOgmios provides 5 protocol namespaces with 30 operations for complete Ogmios coverage:
import SwiftOgmios
// Create HTTP client (recommended for simple queries)
let httpClient = try await OgmiosClient(httpOnly: true)
// Or create WebSocket client (required for streaming protocols)
let wsClient = try await OgmiosClient(httpOnly: false)
// 📋 Ledger State Queries (17 operations)
let epoch = try await httpClient.ledgerStateQuery.epoch.result()
let tip = try await httpClient.ledgerStateQuery.tip.result()
let stakeDistribution = try await httpClient.ledgerStateQuery.liveStakeDistribution.result()
let utxos = try await httpClient.ledgerStateQuery.utxo.result(addresses: addresses)
// 🌐 Network Queries (4 operations)
let networkTip = try await httpClient.networkQuery.tip.result()
let blockHeight = try await httpClient.networkQuery.blockHeight.result()
// 🔄 Chain Synchronization (2 operations, WebSocket required)
let intersection = try await wsClient.chainSync.findIntersection.result(points: points)
let nextBlock = try await wsClient.chainSync.nextBlock.result()
// 📤 Transaction Submission (2 operations)
let submitResult = try await httpClient.transactionSubmission.submitTransaction.result(transaction: txBytes)
let evalResult = try await httpClient.transactionSubmission.evaluateTransaction.result(transaction: txBytes)
// 🕰️ Mempool Monitoring (5 operations + helpers, WebSocket required)
let allTransactions = try await wsClient.mempoolMonitor.getMempoolTransactions() // Convenience helper
try await wsClient.mempoolMonitor.waitForEmptyMempool(timeoutSeconds: 30.0) // Convenience helper// Connect to custom Ogmios instance
let client = try await OgmiosClient(
host: "your-ogmios-server.com",
port: 1337,
secure: true // Use HTTPS/WSS
)
// Most queries using .result() (recommended)
let epoch = try await client.ledgerStateQuery.epoch.result()
print("Current epoch: \(epoch)")
// When you need to track request IDs, use .execute()
let epochResponse = try await client.ledgerStateQuery.epoch.execute(
id: .string("my-custom-id")
)
print("Epoch: \(epochResponse.result), ID: \(epochResponse.id!)")| Query | Description | Status |
|---|---|---|
constitution |
Current constitution definition (Conway era) | ✅ |
constitutionalCommittee |
Constitutional committee state (Conway era) | ✅ |
delegateRepresentatives |
Registered delegate representatives | ✅ |
dump |
Complete ledger state dump | ✅ |
epoch |
Current epoch number | ✅ |
eraStart |
Start of current era | ✅ |
eraSummaries |
Era boundaries and parameters | ✅ |
governanceProposals |
Active governance proposals | ✅ |
liveStakeDistribution |
Current stake distribution across pools | ✅ |
nonces |
Consensus nonces for randomness | ✅ |
operationalCertificates |
Pool operational certificate counters | ✅ |
projectedRewards |
Projected rewards for stake pools | ✅ |
protocolParameters |
Current protocol parameters | ✅ |
proposedProtocolParameters |
Proposed protocol parameter updates | ✅ |
rewardAccountSummaries |
Stake account summaries and rewards | ✅ |
rewardsProvenance |
Rewards provenance (deprecated, use stakePoolsPerformances) | ✅ |
stakePools |
Stake pool information | ✅ |
stakePoolsPerformances |
Pool performance metrics | ✅ |
tip |
Current ledger tip | ✅ |
treasuryAndReserves |
Treasury and reserves Ada amounts | ✅ |
utxo |
UTxO set (by addresses, output references, or whole set) | ✅ |
| Query | Description | Status |
|---|---|---|
blockHeight |
Network's highest block number | ✅ |
genesisConfiguration |
Genesis configuration for specific era | ✅ |
startTime |
Network start time | ✅ |
tip |
Network tip information | ✅ |
| Method | Description | Status |
|---|---|---|
findIntersection |
Find intersection point for chain sync | ✅ |
nextBlock |
Get next block in chain synchronization | ✅ |
| Method | Description | Status |
|---|---|---|
submitTransaction |
Submit transaction to network | ✅ |
evaluateTransaction |
Evaluate transaction execution | ✅ |
| Method | Description | Status |
|---|---|---|
acquireMempool |
Acquire mempool snapshot | ✅ |
hasTransaction |
Check if transaction exists in mempool | ✅ |
nextTransaction |
Get next transaction from mempool | ✅ |
releaseMempool |
Release mempool snapshot | ✅ |
sizeOfMempool |
Get mempool size information | ✅ |
Helper: getMempoolTransactions() |
Get all mempool transactions | ✅ |
Helper: waitForEmptyMempool() |
Wait for mempool to be empty | ✅ |
// Query current epoch
let currentEpoch = try await client.ledgerStateQuery.epoch.result()
print("Current epoch: \(currentEpoch)")
// Query era summaries
let eraSummaries = try await client.ledgerStateQuery.eraSummaries.result()
for eraSummary in eraSummaries {
print("Era: \(eraSummary.start.epoch) - \(eraSummary.end?.epoch ?? "current")")
}
// Query UTxO set by addresses
let addresses = [Address("addr_test1qz66ue36465w2qq40005h2hadad6pnjht8mu6sgplsfj74qdjnshguewlx4ww0eet26y2pal4xpav5prcydf28cvxtjqx46x7f")]
let utxos = try await client.ledgerStateQuery.utxo.result(
addresses: addresses,
id: .generateNextNanoId()
)
print("Found \(utxos.count) UTxOs")
// Query protocol parameters
let protocolParams = try await client.ledgerStateQuery.protocolParameters.result()
print("Min fee A: \(protocolParams.minFeeCoefficient)")// Query network tip
let networkTip = try await client.networkQuery.tip.result()
print("Network tip slot: \(networkTip.slot)")
// Query block height
let blockHeight = try await client.networkQuery.blockHeight.result()
print("Block height: \(blockHeight)")
// Query genesis configuration
let genesis = try await client.networkQuery.genesisConfiguration.result(
era: "shelley",
id: .generateNextNanoId()
)
print("Network: \(genesis.networkId)")// Find intersection and start syncing
let points = [Point(slot: 123456, id: "abc123...")]
let intersection = try await client.chainSync.findIntersection.result(
points: points,
id: .generateNextNanoId()
)
print("Found intersection at slot: \(intersection.tip.slot)")
// Get next block
let nextBlock = try await client.chainSync.nextBlock.result()
if case .block(let block) = nextBlock.block {
print("Received block: \(block.header.blockHeight)")
}// Submit a transaction (transaction building not shown)
let txBytes = Data(/* your transaction CBOR bytes */)
let submitResult = try await client.transactionSubmission.submitTransaction.result(
transaction: txBytes,
id: .generateNextNanoId()
)
print("Transaction submitted: \(submitResult.transaction.id)")
// Evaluate transaction
let evalResult = try await client.transactionSubmission.evaluateTransaction.result(
transaction: txBytes,
additionalUtxo: [:],
id: .generateNextNanoId()
)
print("Execution units: \(evalResult.executionUnits)")// Get all mempool transactions (helper method)
let mempoolTxs = try await client.mempoolMonitor.getMempoolTransactions()
print("Mempool contains \(mempoolTxs.count) transactions")
// Check if specific transaction is in mempool
let txId = "abc123..."
let hasTransaction = try await client.mempoolMonitor.hasTransaction.result(
transaction: TransactionId(txId),
id: .generateNextNanoId()
)
print("Transaction in mempool: \(hasTransaction)")
// Wait for empty mempool (helper method)
try await client.mempoolMonitor.waitForEmptyMempool(timeoutSeconds: 30.0)
print("Mempool is now empty")SwiftOgmios provides comprehensive error handling for various scenarios:
do {
let epoch = try await client.ledgerStateQuery.epoch.result()
print("Success: \(epoch)")
} catch OgmiosError.httpError(let message) {
print("HTTP Error: \(message)")
} catch OgmiosError.websocketError(let message) {
print("WebSocket Error: \(message)")
} catch OgmiosError.responseError(let message) {
print("Ogmios Response Error: \(message)")
} catch OgmiosError.invalidResponse(let message) {
print("Invalid Response: \(message)")
} catch OgmiosError.timeoutError(let message) {
print("Timeout Error: \(message)")
} catch {
print("Unknown error: \(error)")
}func queryWithRetry<T>(maxRetries: Int = 3, delay: TimeInterval = 1.0, operation: @escaping () async throws -> T) async throws -> T {
var lastError: Error?
for attempt in 1...maxRetries {
do {
return try await operation()
} catch OgmiosError.httpError(let message) where attempt < maxRetries {
print("HTTP error on attempt \(attempt): \(message). Retrying in \(delay) seconds...")
lastError = OgmiosError.httpError(message)
try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
} catch OgmiosError.websocketError(let message) where attempt < maxRetries {
print("WebSocket error on attempt \(attempt): \(message). Retrying in \(delay) seconds...")
lastError = OgmiosError.websocketError(message)
try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
} catch {
// Non-retryable error or final attempt
throw error
}
}
// Should never reach here, but throw the last error if we do
throw lastError ?? OgmiosError.responseError("Max retries exceeded")
}
// Usage example
do {
let epoch = try await queryWithRetry {
return try await client.ledgerStateQuery.epoch.result()
}
print("Current epoch: \(epoch)")
} catch {
print("Failed after retries: \(error)")
}SwiftOgmios provides detailed error mapping for transaction submission failures:
do {
let result = try await client.transactionSubmission.submitTransaction.result(
transaction: transactionBytes,
id: .generateNextNanoId()
)
print("Transaction submitted successfully: \(result.transaction.id)")
} catch let error {
// SwiftOgmios automatically maps Ogmios error codes to specific failure types
switch error {
case OgmiosError.responseError(let message):
if message.contains("3005") {
print("Era mismatch - transaction not valid in current era")
} else if message.contains("3100") {
print("Invalid signatures")
} else if message.contains("3123") {
print("Value not conserved - input/output mismatch")
} else if message.contains("3122") {
print("Transaction fee too small")
} else {
print("Transaction submission failed: \(message)")
}
default:
print("Unexpected error: \(error)")
}
}Check if your Ogmios server is running and responsive:
do {
let health = try await client.getServerHealth()
print("Server Status: \(health.connectionStatus)")
print("Current Era: \(health.currentEra)")
print("Network: \(health.network)")
print("Version: \(health.version)")
} catch {
print("Server health check failed: \(error)")
}Best for simple request-response patterns:
let httpClient = try await OgmiosClient(
host: "localhost",
port: 1337,
secure: false, // Use HTTP
httpOnly: true
)Best for real-time applications and chain following:
let wsClient = try await OgmiosClient(
host: "localhost",
port: 1337,
secure: false, // Use WS
httpOnly: false
)- SwiftCardanoCore: Core Cardano types and utilities
Note: SwiftOgmios uses SwiftCardanoCore's JSON codable types internally but this dependency is automatically managed by Swift Package Manager when you add SwiftOgmios to your project.
This library is part of the Kingpin-Apps Cardano ecosystem:
- swift-cardano-core: Core Cardano utilities and types
- swift-cardano-chain: Chain interaction utilities
- swift-cardano-txbuilder: Transaction building tools
- swift-blockfrost-api: Blockfrost API client
Contributions are welcome! This project is production-ready and feature-complete, but we're always looking to improve. Areas where contributions are particularly valuable:
- Performance Optimization: Connection pooling, caching strategies, and response processing improvements
- Testing: Additional edge cases, stress testing, and integration test scenarios
- Examples & Tutorials: Real-world usage examples, sample applications, and advanced patterns
- Developer Experience: IDE integrations, debugging tools, and enhanced logging
- Platform Support: Additional platform optimizations and deployment strategies
This project is licensed under the MIT License - see the LICENSE file for details.
- Ogmios team for creating an excellent Cardano bridge
- Input Output Global for the Cardano blockchain
- The Swift community for excellent async/await and concurrency tools