Architecture Overview
System Stack
flowchart TD
app["Application<br/>(wiring + lifecycle)"]
http["HTTP Server<br/>(Servant REST API + Swagger UI)"]
idx["CageFollower<br/>(ChainSync block processing)<br/>unified transaction per block"]
mpf["MPF Trie<br/>(merkle-patricia-forestry)<br/>Proofs, insertion, deletion"]
txb["TxBuilder<br/>(MPFS protocol operations)<br/>boot, update, retract, end"]
bal["Balance<br/>(fee estimation fixpoint)"]
n2c["Node Client<br/>(node-to-client)<br/>ChainSync + LSQ + LTxS"]
app --> http
http --> txb
http --> idx
app --> idx --> mpf
app --> txb --> bal --> n2c
idx --> n2c
The service connects to a Cardano node via two N2C connections:
- ChainSync — blocks are processed by the
CageFollower, which
applies UTxO, cage state, and trie mutations in a single atomic
RocksDB transaction per block (see
Block Processing).
- LocalStateQuery + LocalTxSubmission — the
Provider queries
UTxOs and protocol parameters; the Submitter sends signed
transactions.
The TxBuilder constructs MPFS protocol transactions (boot, update,
retract, end) and balanceTx handles fee estimation through a
fixpoint loop.
Singleton Dependency Graph
Every major component is a record of functions (no typeclasses).
Records are created bottom-up and torn down top-down using bracket
patterns.
graph TD
APP["Application<br/>(withApplication)"]
CTX["Context<br/>(facade record)"]
PRV["Provider<br/>(N2C LocalStateQuery)"]
TM["TrieManager<br/>(per-token MPF tries)"]
ST["State<br/>(tokens, requests, checkpoints)"]
IDX["Indexer<br/>(skeleton / chain sync)"]
SUB["Submitter<br/>(N2C LocalTxSubmission)"]
TXB["TxBuilder<br/>(MPFS protocol ops)"]
NODE["Cardano Node<br/>(Unix socket)"]
APP --> CTX
CTX --> PRV
CTX --> TM
CTX --> ST
CTX --> IDX
CTX --> SUB
CTX --> TXB
PRV --> NODE
SUB --> NODE
Application Wiring
withApplication creates and wires all components:
graph LR
DB["RocksDB<br/>(11 CFs)"]
DB --> ST["State<br/>(persistent)"]
DB --> TM["TrieManager<br/>(persistent)"]
DB --> CF["CageFollower<br/>(unified txn)"]
N2C1["N2C #1<br/>(ChainSync)"] --> CF
N2C2["N2C #2<br/>(LSQ + LTxS)"] --> PRV["Provider"]
N2C2 --> SUB["Submitter"]
PRV & SUB & ST & TM --> TXB["TxBuilder"]
PRV & SUB & ST & TM & TXB --> CTX["Context"]
All components use real implementations backed by RocksDB and N2C
connections. The CageFollower runs on connection 1 (ChainSync),
processing each block in a single atomic transaction. The Provider,
Submitter, and TxBuilder use connection 2 (LSQ + LTxS).
External Dependencies
graph TD
OFFCHAIN["cardano-mpfs-offchain<br/>Service interfaces + N2C client"]
MPF["merkle-patricia-forestry<br/>MPF trie library"]
ONCHAIN["cardano-mpfs-onchain<br/>Aiken validators"]
LEDGER["cardano-ledger<br/>Conway era types"]
OUROBOROS["ouroboros-network<br/>N2C protocols"]
PLUTUS["plutus-core / plutus-tx<br/>PlutusData encoding"]
OFFCHAIN --> MPF
OFFCHAIN --> LEDGER
OFFCHAIN --> OUROBOROS
OFFCHAIN --> PLUTUS
OFFCHAIN -.->|"on-chain types<br/>& script hash"| ONCHAIN
style OFFCHAIN fill:#e1f5fe
style MPF fill:#e8f5e9
style ONCHAIN fill:#fff3e0
style LEDGER fill:#f3e5f5
style OUROBOROS fill:#f3e5f5
style PLUTUS fill:#f3e5f5
| Color |
Meaning |
| Blue |
This project |
| Green |
MPF trie library (same repo) |
| Orange |
On-chain Aiken validators (separate repo) |
| Purple |
Cardano ecosystem dependencies |
Module Hierarchy
The cardano-mpfs-offchain library is organized in layers.
All modules live under Cardano.MPFS.
Core — domain types and pure logic
| Module |
Purpose |
Core.Types |
TokenId, Root, Request, TokenState, CageConfig |
Core.OnChain |
Datum/redeemer encodings, cagePolicyId, cageAddress, PlutusV3 script |
Core.Blueprint |
CIP-57 blueprint schema loading and validation |
Core.Proof |
MPF proof to on-chain ProofStep conversion |
Core.Balance |
balanceTx — fee estimation fixpoint loop |
Interfaces — record-of-functions singletons
| Module |
Purpose |
Context |
Facade bundling all singletons |
Provider |
queryUTxOs, queryProtocolParams |
State |
Tokens, Requests, Checkpoints sub-records |
Trie |
TrieManager — per-token MPF trie access |
TxBuilder |
Cage protocol operations (boot, request, update, retract, end) |
Indexer |
Chain follower lifecycle (start, stop, getTip) |
Submitter |
submitTx :: Tx ConwayEra -> m SubmitResult |
Application |
withApplication — wiring and lifecycle |
HTTP — REST API
Indexer — chain sync and persistence
NodeClient — N2C protocol clients
TxBuilder — cage protocol transactions
Trie — MPF backends
Mock — test doubles
Utilities
| Module |
Purpose |
Trace |
AppTrace structured tracing type, jsonLinesTracer for stderr JSON-lines logging |
Design Principles
- No typeclasses — closed world with explicit records of functions.
- All types from cardano-ledger —
Tx ConwayEra, PParams ConwayEra, Addr, TxIn, etc.
- Visible dependency graph — no implicit resolution surprises.
- Trivial testing — swap the record for a mock backend.
- No orphan instances.
Implementation Phases
graph LR
P0["Phase 0<br/>MPF Library ✓"]
P1["Phase 1<br/>Service Interfaces ✓"]
P2["Phase 2<br/>N2C Client +<br/>Provider ✓"]
P3["Phase 3<br/>Transaction<br/>Builders"]
P4["Phase 4<br/>ChainSync Indexer +<br/>Persistent State"]
P5["Phase 5<br/>HTTP API +<br/>Deployment"]
P0 --> P1 --> P2 --> P3 --> P4 --> P5
style P0 fill:#2d6,color:#fff
style P1 fill:#2d6,color:#fff
style P2 fill:#2d6,color:#fff
style P3 fill:#2d6,color:#fff
style P4 fill:#2d6,color:#fff
style P5 fill:#2d6,color:#fff
| Phase |
Description |
Status |
| 0 |
MPF library — 16-ary Merkle Patricia Forestry, Blake2b-256 hashing, insertion/deletion/proofs, pure and RocksDB backends |
Done |
| 1 |
Service interfaces — Provider, Submitter, TxBuilder, State, Indexer, TrieManager, Context records; mock implementations; balanceTx with fixpoint fee estimation; on-chain type encodings; CIP-57 blueprint validation; Aiken-compatible proof serialization |
Done |
| 2 |
N2C client + Provider — ouroboros-network LocalStateQuery and LocalTxSubmission clients; mkNodeClientProvider for UTxO and PParams queries; mkN2CSubmitter for transaction submission; E2E tests with cardano-node subprocess |
Done |
| 3 |
Transaction builders — real TxBuilder implementations for boot, update, retract, end operations with Plutus script witnesses, proof embedding, and on-chain datum construction |
Done |
| 4 |
ChainSync indexer + persistent state — replace skeleton indexer with real ChainSync follower; RocksDB-backed State and TrieManager; block processing with rollback support |
Done |
| 5 |
HTTP API + deployment — Servant HTTP layer with Swagger UI, token lifecycle and trie query endpoints, transaction building and submission REST API, WAI application wiring |
Done |