Architecture
Understanding Evolution SDK's design principles and system architecture
Abstract
Evolution SDK implements a capability-based architecture where transaction building, network access, and key management are separated through type constraints and deferred execution. The system prevents invalid states at compile time while enabling composition of complex transaction logic.
Purpose and Scope
This section describes the fundamental architectural principles that drive Evolution SDK's design: deferred execution, separation of concerns, and type-safe capability composition. It covers the conceptual model and decision flows that guide implementation. It does not cover implementation details, API usage patterns, or provider-specific protocols—those are in their respective module documentation.
Introduction
The Problem
Cardano transaction building faces three core challenges:
- State Management: Immediate execution creates shared mutable state, making builders unsafe to reuse
- Type Safety: Runtime capability checking allows invalid operations to compile successfully
- Composition: Tightly coupled components prevent independent testing and flexible provider/wallet swapping
Traditional approaches force developers to choose between safety and flexibility.
Design Philosophy
Evolution SDK's architecture provides three guarantees:
- Deferred Execution - Store operations as data, execute on demand with fresh state
- Separation of Concerns - Provider (network) ≠ Wallet (keys) ≠ Client (logic)
- Type Safety Through Constraints - Compiler prevents invalid operations
These principles work together to enable safe composition without sacrificing correctness.
Visual Overview
Architectural Principles
1. Deferred Execution
Transaction builders in Evolution SDK don't execute immediately. Instead, they store a description of operations to perform. When you call .build(), the SDK creates fresh state and executes the program, ensuring each execution is isolated from previous runs.
This design emerged from a common problem: shared mutable state makes builders dangerous to reuse. By deferring execution until explicitly requested, the same builder can safely generate multiple transactions without interference.
The benefit extends beyond safety. Programs-as-values can be inspected before running, tested without blockchain access, and composed together like building blocks. The transaction logic becomes pure data until you decide to execute it.
See Deferred Execution for execution model and state management.
2. Separation of Concerns
The SDK separates three responsibilities that transaction systems often conflate:
Provider layer handles blockchain access—querying UTxOs, fetching protocol parameters, evaluating scripts, and submitting transactions. It abstracts the underlying service (Blockfrost, Kupmios, Maestro, Koios) behind a common interface.
Wallet layer manages keys and signing. Whether using a seed phrase (HD wallet), single private key, hardware wallet, or browser extension (CIP-30), the wallet interface remains consistent. Read-only wallets provide addresses without signing capability.
Client layer coordinates the provider and wallet, building transactions using their capabilities. The client enforces constraints through types—you cannot sign without a signing wallet or query without a provider.
This separation means you can swap Blockfrost for Kupmios without touching transaction logic, test with mock providers and wallets, and deploy the same code across different infrastructure.
See Provider Layer and Wallet Layer for interface contracts and selection criteria.
3. Type Safety Through Constraints
The type system prevents impossible states at compile time. A client without a provider cannot call newTx(). A client without a signing wallet cannot call signTx(). A read-only wallet produces unsigned transactions. These aren't runtime checks—they're enforced by TypeScript's type checker.
The approach turns capabilities into types. When you attach a provider, the client's type changes to include provider operations. When you attach a signing wallet, signing operations become available. The type system documents what a client can do and prevents you from calling operations it cannot support.
This compile-time safety eliminates a class of runtime errors, makes refactoring safer, and serves as living documentation of each client configuration's capabilities.
See Wallet Layer for the capability-based type system.
Integration Points
The architecture maintains strict boundaries between layers. The client never directly manages network or keys—it delegates to provider and wallet. This delegation enables swapping providers or wallets without touching transaction logic.
[1] MinimalClient: Network context only, no blockchain access or signing capability
[2] ProviderOnlyClient: Can query blockchain and submit transactions, cannot sign
[3] WalletClient: Can sign transactions and messages, must attach provider to build transactions
[4] FullClient: Either ReadOnlyClient (query + unsigned transactions) or SigningClient (query + sign + submit) depending on wallet type
When This Matters
Building Custom Integrations
When adding new providers or wallet types, the architecture provides clear extension points:
- Implement provider interface → works with all transaction logic
- Implement wallet interface → integrates with client capability system
Debugging Production Issues
Understanding the layered architecture helps trace errors:
- Provider errors → network/blockchain issues
- Wallet errors → signing/key management issues
- Transaction builder errors → logic/validation issues
Optimizing Performance
The deferred execution model enables optimization:
- Reuse builders across transactions (fresh state guaranteed)
- Parallelize independent operations
- Cache provider responses without affecting correctness
Contributing to the SDK
Architectural patterns provide consistency:
- New features follow separation of concerns
- Type constraints prevent capability violations
- Deferred execution maintains composability
Architecture Pages
Transaction Flow
Build → sign → submit phase transitions with fresh state execution model
Deferred Execution
Immutable builder pattern, program-as-value semantics, and composition
Coin Selection
UTxO selection algorithms for covering transaction requirements
Redeemer Indexing
Deferred redeemer construction for Plutus script index optimization
Script Evaluation
Plutus validator execution and ExUnits calculation
Provider Layer
Blockchain access abstraction across Blockfrost, Kupmios, Maestro, Koios
Wallet Layer
Capability-based type system for read-only vs signing wallets
Related Topics
- Getting Started - Practical usage examples
- Client Basics - Client API and capabilities
- Transactions - Transaction building guide