Provider Layer
Why and how providers abstract blockchain data access
Abstract
Cardano has multiple blockchain data providers (Blockfrost, Kupmios, Maestro, Koios) with different APIs, authentication methods, and data formats. The provider layer creates a unified interface over these differences—application code works identically regardless of provider choice. The abstraction is deliberately thin: it provides exactly what the SDK needs (UTxO queries, protocol parameters, transaction submission, script evaluation) without exposing provider-specific features. This focused design prevents leaky abstractions and enables transparent provider switching.
Purpose and Scope
This document explains why the SDK abstracts blockchain data access, what operations providers must support, and how the type system ensures correct provider configuration. It covers the provider interface contract, error normalization, and provider selection considerations.
Not covered: Specific provider API details (see provider documentation), rate limiting strategies (implementation-specific), or caching mechanisms (see provider implementations).
Design Philosophy
Without abstraction, application code couples directly to provider APIs. Switching from Blockfrost to Kupmios requires rewriting every blockchain query—different authentication, different response formats, different error handling. Testing requires real blockchain access or complex mocking of provider-specific APIs.
The architecture establishes a thin interface capturing what transaction building requires: query UTxOs, fetch protocol parameters, submit transactions, evaluate scripts. Each provider implementation translates between this interface and their native API. Application code depends on the interface, not the implementation. Provider selection becomes a configuration choice, not a code change.
The abstraction remains thin by design—it does not attempt to expose every provider's unique features. Focused scope keeps the interface stable and implementations maintainable.
The abstraction remains thin by design—it does not attempt to expose every provider's unique features. Focused scope keeps the interface stable and implementations maintainable.
Provider Interface Contract
All providers implement the same core operations required for transaction building:
[1] Provider Interface: Defines required operations:
getUtxos(address)- Query unspent outputs at addressgetProtocolParameters()- Fetch current protocol parameterssubmitTx(cbor)- Submit signed transactionevaluateTx(tx, utxos)- Calculate script execution costsgetDatum(hash)- Retrieve datum by hashawaitTx(hash)- Wait for transaction confirmation
[2] Provider Implementations: Each translates interface calls to their native API. Application code depends only on interface, never on specific implementation.
[2] Provider Implementations: Each translates interface calls to their native API. Application code depends only on interface, never on specific implementation.
Type-Driven Configuration
Provider configuration uses discriminated unions to enforce correctness at compile time:
[1] ProviderConfig: Discriminated union type. The type field determines which configuration properties are required.
[2] Provider Factory: Pattern matches on type to construct appropriate implementation. Invalid configurations produce compile-time errors.
[3] Provider Instance: Implements unified interface. Type system guarantees all required configuration is present.
TypeScript enforces: type: "blockfrost" requires url and projectId. type: "kupmios" requires kupoUrl and ogmiosUrl. Mismatches are compilation errors, not runtime failures.
TypeScript enforces: type: "blockfrost" requires url and projectId. type: "kupmios" requires kupoUrl and ogmiosUrl. Mismatches are compilation errors, not runtime failures.
Integration Points
The provider layer integrates with other architectural components:
Transaction Builder: During build() execution, the builder queries the provider for:
- Protocol parameters (fee calculation, transaction size limits)
- Available UTxOs at wallet addresses (coin selection)
- Script evaluation (calculating execution units for Plutus scripts)
Client Configuration: Provider is attached via createClient() or attachProvider(). The client stores the provider reference and passes it to transaction builders.
Error Handling: Provider methods return Effect<Result, ProviderError>. All provider-specific errors (HTTP failures, WebSocket disconnections, rate limits, authentication) normalize to ProviderError with consistent structure. Application code handles one error type, not provider-specific exceptions.
Effect-TS Integration: Providers expose both Promise-based API (auto-wrapped) and Effect-based API (provider.Effect.*). Transaction builder uses Effect API for compositional error handling and resource management.
Related Topics
- Client Architecture - How providers attach to clients
- Transaction Flow - Provider queries during build phase
- Deferred Execution - Provider integration with program execution