kingpin-apps/swift-blockfrost-api
Swift clients for the Blockfrost.io APIs:
Requirements
| Platform | Minimum version | | -------- | --------------- | | iOS | 14.0 | | macOS | 13.0 | | watchOS | 7.0 | | tvOS | 14.0 |
- Swift 6.0+
- Xcode 16+
Installation
Swift Package Manager (Xcode)
In Xcode, choose File ▸ Add Package Dependencies… and enter:
https://github.com/Kingpin-Apps/swift-blockfrost-api.gitSwift Package Manager (Package.swift)
dependencies: [
.package(url: "https://github.com/Kingpin-Apps/swift-blockfrost-api.git", from: "0.1.5"),
],
targets: [
.target(
name: "YourTarget",
dependencies: [
// Cardano REST client
.product(name: "SwiftBlockfrostAPI", package: "swift-blockfrost-api"),
// Optional — Midnight GraphQL client (only if you need it)
.product(name: "SwiftBlockfrostMidnight", package: "swift-blockfrost-api"),
]
),
]Then import SwiftBlockfrostAPI (and/or import SwiftBlockfrostMidnight) in your source files. The Cardano product has no extra dependencies; the Midnight product pulls in swift-nio and swift-nio-ssl for its WebSocket subscription transport.
Getting a Project ID
Create a free account at blockfrost.io and generate a project ID for the network you want to target (mainnet, preprod, or preview). Make sure the project's network matches the Network value you pass to Blockfrost.
Usage
Creating a client
Pass the project ID directly:
import SwiftBlockfrostAPI
let api = try Blockfrost(
network: .mainnet,
projectId: "mainnet_xxxxxxxxxxxxxxxxxxxx"
)Or load it from an environment variable (defaults to BLOCKFROST_API_KEY):
let api = try Blockfrost(network: .preprod)You can also point at a self-hosted Blockfrost instance with a custom base path:
let api = try Blockfrost(
network: .mainnet,
projectId: "self_hosted",
basePath: "https://blockfrost.example.internal/api/v0"
)Calling an endpoint
Every endpoint returns a generated response enum; switch on it (or use try response.ok) to get the typed body.
// Health check
let health = try await api.client.getHealth().ok.body.json
print("Healthy:", health.isHealthy)
// Latest epoch
let epoch = try await api.client.getEpochsLatest().ok.body.json
print("Epoch:", epoch.epoch, "fees:", epoch.fees)
// Latest block
let block = try await api.client.getBlocksLatest().ok.body.json
print("Block height:", block.height ?? 0)Handling responses
Each operation produces a response that distinguishes success from documented error codes. Use a switch for full coverage:
let response = try await api.client.getEpochsLatest()
switch response {
case .ok(let ok):
let json = try ok.body.json
print("Current epoch:", json.epoch)
case .badRequest(let err):
print("Bad request:", try err.body.json)
case .unauthorized:
print("Invalid project ID")
case .forbidden:
print("Project ID is over its usage limit")
case .notFound:
print("Resource not found")
case .undocumented(statusCode: let code, _):
print("Unexpected status:", code)
default:
break
}Submitting a transaction
let cborBytes: [UInt8] = /* serialized transaction */
let response = try await api.client.postTxSubmit(
body: .applicationCbor(HTTPBody(cborBytes))
)
let txHash = try response.ok.body.plainText
print("Submitted:", try await String(collecting: txHash, upTo: 1024))Testing with a mock transport
Blockfrost accepts a pre-built Client, so you can swap in an in-memory transport for unit tests:
let api = try Blockfrost(
network: .mainnet,
projectId: "fake-project-id",
client: Client(
serverURL: URL(string: "https://cardano-mainnet.blockfrost.io/api/v0")!,
transport: MyMockTransport()
)
)Midnight (`SwiftBlockfrostMidnight`)
Midnight uses GraphQL, not REST: queries and mutations are POSTed as JSON, and subscriptions stream over WebSocket using the graphql-transport-ws protocol. The Midnight client wraps both, plus exposes the JSON-RPC node URL for callers who want to talk to the node directly.
Creating a client
import SwiftBlockfrostMidnight
let midnight = try Midnight(
network: .preview,
projectId: "midnight_preview_xxxxxxxxxxxxxxxx"
)The project ID can also be loaded from the BLOCKFROST_MIDNIGHT_API_KEY environment variable (configurable via environmentVariable:). Use a Midnight-specific project ID — the Cardano one will not authenticate.
Running a query
struct LatestBlockData: Decodable, Sendable {
let block: Block
struct Block: Decodable, Sendable { let height: Int; let hash: String }
}
let latest = try await midnight.query(
"query { block { height hash } }",
as: LatestBlockData.self
)
print("Block height:", latest.block.height)Variables are typed via GraphQLValue, which accepts Swift literals directly:
let result = try await midnight.query(
"query Block($n: Int!) { block(height: $n) { hash } }",
variables: ["n": 12345],
as: BlockData.self
)Subscribing to live updates
subscribe(...) returns an AsyncThrowingStream. The connection is opened on first iteration and torn down cleanly when the loop exits:
for try await frame in midnight.subscribe(
"subscription { block { height hash } }",
as: LatestBlockData.self
) {
print("New block:", frame.block.height)
}The default WebSocket transport, NIOWebSocketSubscriptionTransport, opens one fresh connection per subscription. If you create many Midnight instances, share an event loop group:
import NIOPosix
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let transport = NIOWebSocketSubscriptionTransport(eventLoopGroup: group)
let midnight = try Midnight(network: .preview, subscriptionTransport: transport)
// ...
try group.syncShutdownGracefully()Testing
Both transports are protocols. Drive them from tests with the supplied closure-based mocks — no network access:
let http = ClosureGraphQLHTTPTransport { _, _, _ in
Data(#"{"data":{"block":{"height":42,"hash":"x"}}}"#.utf8)
}
let ws = ClosureGraphQLSubscriptionTransport { _, _, _, continuation in
continuation.yield(Data(#"{"data":{"block":{"height":1,"hash":"a"}}}"#.utf8))
continuation.finish()
}
let midnight = try Midnight(
network: .preview,
projectId: "fake",
httpTransport: http,
subscriptionTransport: ws
)Network URLs
MidnightNetwork exposes the three Blockfrost-hosted Midnight endpoints:
MidnightNetwork.mainnet.indexerHTTPURL // https://midnight-mainnet.blockfrost.io/api/v0
MidnightNetwork.mainnet.indexerWebSocketURL // wss://midnight-mainnet.blockfrost.io/api/v0/ws
MidnightNetwork.mainnet.nodeRPCURL // https://rpc.midnight-mainnet.blockfrost.ioDocumentation
Generated DocC documentation is hosted on the Swift Package Index.
For the full list of available Cardano endpoints, request parameters, and response models, see the Blockfrost OpenAPI spec — every operation in the spec is exposed on Blockfrost.client. For Midnight, see the Blockfrost Midnight API reference.
License
This project is released under the MIT License. See LICENSE for details.
Package Metadata
Repository: kingpin-apps/swift-blockfrost-api
Default branch: main
README: README.md