Evolution SDK
Architecture

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:

  1. State Management: Immediate execution creates shared mutable state, making builders unsafe to reuse
  2. Type Safety: Runtime capability checking allows invalid operations to compile successfully
  3. 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:

  1. Deferred Execution - Store operations as data, execute on demand with fresh state
  2. Separation of Concerns - Provider (network) ≠ Wallet (keys) ≠ Client (logic)
  3. 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