kingpin-apps/swift-kupo
A Swift Package Manager library providing a type-safe client for the [Kupo API](https://github.com/CardanoSolutions/kupo). SwiftKupo generates Swift client code from the OpenAPI 3.0 specification using Apple's Swift OpenAPI Generator, providing strongly-typed interfaces for iOS,
What is Kupo?
Kupo is a fast, lightweight chain indexer for the Cardano blockchain that synchronizes UTxOs, datums, scripts, and other on-chain data according to configurable patterns. It provides:
- UTxO Indexing: Track unspent transaction outputs by address patterns
- Datum Resolution: Access Plutus data associated with UTxOs
- Script Access: Retrieve native and Plutus scripts
- Pattern Matching: Flexible address, asset, and transaction filtering
- Rollback Handling: Proper chain reorganization support
Features
- ✅ Auto-generated Client: Swift code generated from OpenAPI 3.0 specification
- ✅ Type Safety: Strongly-typed interfaces for all API endpoints and models
- ✅ Semantic Extensions: Enhanced Pattern types with meaningful property names
- ✅ Multi-platform: Support for iOS, macOS, watchOS, and tvOS
- ✅ Async/Await: Modern Swift concurrency support
- ✅ OpenAPI-driven: Automatically stays in sync with Kupo API changes
Installation
Swift Package Manager
Add SwiftKupo to your Package.swift dependencies:
dependencies: [
.package(url: "https://github.com/Kingpin-Apps/swift-kupo", from: "1.0.0")
]Xcode
- In Xcode, go to File ▸ Add Packages...
- Enter the repository URL:
https://github.com/Kingpin-Apps/swift-kupo - Click Add Package
Requirements
- iOS 14.0+ / macOS 13.0+ / watchOS 7.0+ / tvOS 14.0+
- Swift 6.2+
- Xcode 15.0+
Quick Start
Prerequisites
First, you'll need a running Kupo server. The easiest way is a cloud-based environment on (demeter.run)[https://demeter.run] Or install cardano-node and Kupo 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
kupo:
image: cardanosolutions/kupo:${KUPO_VERSION:-latest}
container_name: kupo
command:
[
"--host",
"0.0.0.0",
"--node-socket",
"/ipc/node.socket",
"--node-config",
"/config/${NETWORK:-preview}/cardano-node/config.json",
"--workdir",
"/db",
"--match",
"*",
"--since",
"origin",
]
ports:
- "1442:1442"
volumes:
- node-config:/config:ro
- node-ipc:/ipc
- kupo-db:/db
volumes:
node-db:
node-ipc:
node-config:
kupo-db:
import SwiftKupo
import OpenAPIURLSession
// Create client
let kupo = try Kupo(basePath: "http://localhost:1442")
// Query unspent UTxOs for an address
let response = try await kupo.client.matchPattern(
path: .init(
pattern: .init(
addressPattern: .init(
shelleyAddressPattern: .bech32("addr1vy3qpx09uscywhpp0ekg9zwmq2yj5vp08husfq6qyh2mpps865j6t")
)
)
),
query: .init(
resolveHashes: true,
unspent: true,
order: .mostRecentFirst
),
headers: .init(accept: [
.init(contentType: .applicationJsonCharsetUtf8)
])
)
// Process results
switch response {
case .ok(let okResponse):
let matches = try okResponse.body.applicationJsonCharsetUtf8
for match in matches {
print("UTxO: \(match.transactionId)#\(match.outputIndex)")
print("Value: \(match.value.coins) lovelace")
if let datum = match.datum {
print("Datum: \(datum)")
}
}
default:
print("Request failed")
}API Overview
SwiftKupo provides type-safe access to all Kupo API endpoints:
GET /matches/{pattern}: Query UTxOs by address, asset, or output reference patternsGET /matches: Get all indexed matches with optional filteringGET /datums/{datum-hash}: Retrieve datum by hashGET /scripts/{script-hash}: Retrieve script by hashGET /health: Check indexer status and sync progressGET /checkpoints: View synchronization checkpoints
Common Query Parameters
resolveHashes: Include full datums and scripts in responsesunspent: Filter to only unspent UTxOsspent: Filter to only spent UTxOsorder: Control result ordering (mostRecentFirstoroldestFirst)
Pattern Extensions
SwiftKupo includes semantic extensions for better developer experience with Pattern types. The generated Pattern types use generic names (value1, value2, etc.), but our extensions provide meaningful property names:
Pattern Type Extensions
// Pattern with semantic properties
var pattern = Components.Parameters.Pattern()
pattern.wildcard = ._ast_ // Matches everything (*)
pattern.addressPattern = someAddressPattern // Address-specific patterns
pattern.assetIdPattern = "policy.asset" // Asset-specific patterns
pattern.outputReferencePattern = "tx#index" // Output reference patterns
// Pattern with convenience initializer
let pattern = Components.Parameters.Pattern(
wildcard: ._ast_,
addressPattern: nil,
assetIdPattern: "1220099e5e430475c219518179efc7e6c8289db028904834025d5b086.*",
outputReferencePattern: nil
)AddressPattern Extensions
// AddressPattern with semantic properties
let addressPattern = Components.Schemas.AddressPattern(
credentialsPattern: "*/*",
shelleyAddressPattern: .bech32("addr1vy3qpx09uscywhpp0ekg9zwmq2yj5vp08husfq6qyh2mpps865j6t"),
stakeAddressPattern: nil,
bootstrapAddressPattern: nil
)
// Using convenience constructors
let shelleyAddr = Components.Schemas.AddressPattern.Value2Payload.bech32("addr_test1...")
let byronAddr = Components.Schemas.AddressPattern.Value4Payload.base58("DdzFFz...")
// Extract address string regardless of format
if let shelleyPattern = addressPattern.shelleyAddressPattern {
let addressString = shelleyPattern.addressString
print("Address: \(addressString)")
}Development
Building the Library
# Clean build with code generation
swift build
# Build for release
swift build -c releaseRunning Tests
# Run all tests
swift test
# Run specific test
swift test --filter SwiftKupoTests
# Run tests with verbose output
swift test --verboseCode Generation
The OpenAPI Generator runs automatically during build via Swift Package Manager plugins. Generated code includes:
Types.swift: All data models, enums, and schemas from the APIClient.swift: HTTP client with type-safe methods for all endpoints
Generated files are placed in .build/plugins/outputs/swift-kupo/SwiftKupo/destination/OpenAPIGenerator/GeneratedSources/
Adding New Endpoints
- Edit
Sources/SwiftKupo/openapi.yamlto add the new endpoint specification - Run
swift buildto trigger code regeneration - Add tests in
Tests/SwiftKupoTests/to cover new functionality - Run tests with
swift test
Development Workflow
- Making Changes: Edit OpenAPI spec or configuration files
- Code Generation: Run
swift buildto regenerate client - Testing: Use
swift testto verify functionality - Integration: Generated types work seamlessly with SwiftUI and Combine
Platform Support
| Platform | Minimum Version | |----------|----------------| | iOS | 14.0+ | | macOS | 13.0+ | | watchOS | 7.0+ | | tvOS | 14.0+ | | Swift | 6.2+ |
Health Check Example
// Check Kupo server health
let healthResponse = try await kupo.client.getHealth(
headers: .init(accept: [
.init(contentType: .applicationJsonCharsetUtf8)
])
)
switch healthResponse {
case .ok(let okResponse):
let health = try okResponse.body.applicationJsonCharsetUtf8
print("Connection Status: \(health.connectionStatus)")
print("Version: \(health.version)")
print("Indexes: \(health.configuration.indexes)")
default:
print("Health check failed")
}Troubleshooting
Build Failures
- Ensure OpenAPI specification is valid YAML
- Check that all parameter definitions include either
schemaorcontent - Clean build:
swift package clean && swift build
API Integration Issues
- Verify Kupo server is running and accessible at the specified URL
- Check network connectivity and firewall settings
- Review Kupo logs for API errors
Links & Resources
Kupo Documentation & Installation
- Kupo GitHub Repository: Main repository with documentation
- Kupo API Documentation: Interactive API documentation
- Kupo Installation Guide: Installation via Homebrew, Docker, or from source
Swift OpenAPI Generator
- Swift OpenAPI Generator: Apple's code generation tool
- Swift OpenAPI Runtime: Runtime support library
Related Cardano Swift Packages
- swift-cardano-core: Core Cardano types and utilities
- swift-cardano-chain: Blockchain data structures
- swift-blockfrost-api: Blockfrost API client
- swift-ogmios: Ogmios WebSocket client
Cardano Documentation
- Cardano Developer Portal: Official Cardano development resources
- Cardano Documentation: Comprehensive Cardano documentation
License
This project is licensed under the MPL-2.0 License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Run tests (
swift test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Package Metadata
Repository: kingpin-apps/swift-kupo
Default branch: main
README: README.md