Merkelized Validator Pattern
Extend the Stake Validator Pattern to allow spending validators to read verified data from the stake validator’s redeemer.
The Challenge
When processing multiple UTxOs in a batch operation:
- Each spending validator runs independently
- Expensive computations (e.g., calculating a clearing price) would run N times
- No built-in way to share verified results between spending validator executions
Even with the Stake Validator pattern, spending validators can only verify that the stake validator ran—they cannot access its computation results.
How It Works
- Off-chain code computes expensive values (e.g., clearing price, settlement amounts)
- Values are included in the stake validator’s redeemer
- Stake validator verifies the values are correct (runs once)
- Spending validators read the verified values via
MerkelizedValidator(runs per UTxO)
This reduces complexity from O(N²) to O(N) for batch operations while giving each spending validator access to shared, verified data.
When to Use This Pattern
Best for:
- Batch auctions where all bids need the same clearing price
- Settlement operations with shared computation results
- Any scenario where spending validators need verified global state
Not ideal for:
- Simple validation that doesn’t need shared data (use Stake Validator instead)
- Single-input transactions
- When each input needs completely independent validation
| Pattern | Use Case |
|---|---|
| StakeValidator.spendMinimal | Only need to check stake validator ran |
| MerkelizedValidator.verifyAndGetRedeemer | Need to read verified data |
API Reference
| Function | Description |
|---|---|
getStakeRedeemer(hash, txInfo) | Retrieves the stake validator’s redeemer |
verifyAndGetRedeemer(hash, txInfo) | Verifies withdrawal exists AND returns redeemer |
Implementation Guide
Stake Validator Redeemer
Define a redeemer type that carries the verified computation results:
case class AuctionSettlementRedeemer(
clearingPrice: BigInt,
totalUnitsAvailable: BigInt
) derives ToData, FromDataSpending Validator
Read the verified data using MerkelizedValidator:
import scalus.patterns.MerkelizedValidator
@Compile
object BatchAuctionValidator extends Validator {
inline override def spend(
datum: Option[Data],
redeemer: Data,
tx: TxInfo,
ownRef: TxOutRef
): Unit = {
val ownScriptHash = tx.findOwnInputOrFail(ownRef).resolved.address.credential
.scriptOption.getOrFail("Own address must be Script")
// Read verified settlement data from stake validator
val stakeRedeemer = MerkelizedValidator.verifyAndGetRedeemer(ownScriptHash, tx)
val settlement = stakeRedeemer.to[AuctionSettlementRedeemer]
// Use the verified clearing price
val bid = datum.getOrFail("Missing datum").to[BidDatum]
if bid.bidPrice >= settlement.clearingPrice then
// Fill the bid - verify bidder receives tokens
else
// Refund the bid - verify bidder receives ADA back
}
}Stake Validator (Reward Endpoint)
Verify the computation results are correct:
inline override def reward(redeemer: Redeemer, stakingKey: Credential, tx: TxInfo): Unit = {
val settlement = redeemer.to[AuctionSettlementRedeemer]
// Verify clearing price calculation
require(settlement.clearingPrice > BigInt(0), "Clearing price must be positive")
// Verify supply/demand balance
val totalDemand = calculateTotalDemand(tx, settlement.clearingPrice)
require(totalDemand <= settlement.totalUnitsAvailable, "Demand exceeds supply")
// ... additional verification logic
}Performance Benefit: When spending N UTxOs with iteration-heavy logic:
- Without pattern: O(N²) - each spending validator iterates all inputs/outputs
- With pattern: O(N) - stake validator iterates once, spending validators just read
Run BatchAuctionTest to see actual memory/CPU savings.
Script Configuration: The spending script address must have a staking credential that points to your withdrawal validator. Configure this when deploying scripts.
Example: Batch Auction
See scalus.examples.BatchAuctionValidator for a complete implementation where:
- Stake validator: Verifies the clearing price calculation once
- Spending validator: Reads the verified clearing price to determine if each bid is filled or refunded
Related Patterns
- Stake Validator - Base pattern when you don’t need to read data
- Transaction Level Minter - Alternative using minting instead of staking
Resources
- Anastasia Labs: Merkelized Validator - Original pattern documentation
- Scalus Design Patterns - Implementation and tests
- BatchAuctionTest - Test suite with budget comparison