Contents

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.git

Swift 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.io

Documentation

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