Unistrata: When Settlement Can’t Wait For The Next Trade
Unistrata was the second of the six projects in our look at the UHI9 hookathon, and it draws a different lesson from the same limit: a Uniswap hook only runs when someone trades, but some decisions can’t wait for the next trade. Here is how it handles that. The full project, contracts and tests included, is open here.
Unistrata treats a single liquidity pool the way finance treats a bond. It splits the pool into two tranches, senior and junior. The senior tranche (called Bedrock) has protected principal and earns a fixed return priced from the pool's own volatility. The junior tranche (Sediment) absorbs losses first and is paid only from what remains after the senior's protected amount, and in exchange it keeps the extra trading fees. At the end of each epoch (a fixed settlement period), the pool settles: it works out what everything is worth and pays the senior tranche its protected amount first, then the junior gets the remainder. That ordering is called the waterfall.
Two things are worth noting. First, it uses no price oracle: the pool measures its own volatility directly from its trading activity and prices the senior return from that. Second, settlement has to happen at the right moment, and the hook can’t start it on its own. It only runs when someone trades.
There are two moments when settlement matters, and they are very different. One is routine: the epoch ends, and it is time to settle. The other is urgent: the price is falling right now, and you want to settle immediately, while the pool still holds enough to cover the senior tranche's protected amount. Settle in time and the loss falls on the junior tranche, which signed up to absorb it; wait too long and the shortfall starts eating into senior principal. That urgent moment is the one you can’t afford to wait on, and the one a fixed schedule would miss. Unistrata's reactive contract handles both.
Two things to watch
The reactive contract lives on Reactive Lasna testnet and does nothing but watch and act. It subscribes to two very different signals:
- a periodic CRON event on Reactive, so it can settle on a regular schedule, and
- the pool's volatility reading, published on the origin chain whenever trading moves the price, so it can settle as soon as volatility jumps.
That second subscription is the useful part. The pool already measures its own volatility (it needs to, in order to price the senior return) and emits that number on-chain, and the reactive contract listens for it.
// The network's cron event, on the Reactive chain...
service.subscribe(block.chainid, address(service), cronTopic, ...);
// ...and the pool's own volatility readings, over on the origin chain.
service.subscribe(originChainId, unistrataHook, UNISTRATA_OBSERVATION_TOPIC, ...);One subscription is a timer; the other reads the pool's volatility.
Settling on schedule
After a set number of CRON ticks, the contract treats an epoch as elapsed and asks the pool to settle:
function _onHeartbeat() internal {
if (++cronTicks >= ticksPerEpoch) { // a full epoch's worth of ticks
cronTicks = 0;
_emitCallback("settleEpoch(address)");
}
}Even in a quiet market where nobody trades for a long stretch, the books still close on time.
Settling early on a volatility spike
This is the more interesting case. Every time the pool emits a new volatility reading, the contract checks how much volatility has built up since the last settlement. If that increase crosses a threshold, the contract does not wait for the epoch to end; it settles immediately:
function _onObservation(bytes memory data) internal {
(, uint256 varAcc) = abi.decode(data, (int24, uint256));
latestVarAcc = varAcc;
if (varAcc - lastSpikeVarAcc >= spikeThreshold) { // volatility just spiked
lastSpikeVarAcc = varAcc;
_emitCallback("emergencySettle(address)"); // settle now, do not wait
}
}Settling early matters because of what a fall does to the two tranches. Settle while the pool still holds enough to cover the senior tranche's protected amount, and the loss falls on the junior. Wait too long, and there may not be enough left, so the senior principal, the part meant to be protected, starts taking losses instead. The threshold triggers an early settlement precisely to avoid that.
One detail is worth noting. The threshold measures volatility since the last settlement, not since the pool launched. After every settlement, routine or early, the baseline resets, so the trigger responds to a fresh jump in volatility rather than to volatility that accumulated over the pool's whole history.
Crossing the chain, and who is allowed to settle
A reactive contract can’t reach the origin chain on its own. As with the other projects in this series, it emits a `Callback`, and Reactive Network's callback proxy delivers it as a real transaction on the origin chain, paying the gas and calling the pool as an authorized sender.

The pool does not accept that call from just anyone. Both settlement entry points are gated two ways: the caller must be the official callback proxy, and the request must carry the identity of the exact reactive contract registered at setup.
function emergencySettle(address rvm_id)
external
nonReentrant
authorizedSenderOnly // must come through the official proxy
rvmIdOnly(rvm_id) // and carry our registered controller's identity
{ ... }And because an automated system that fails silently is worse than none at all, there is a manual fallback: a permissionless settlement that anyone can call, but only after the epoch has ended and a grace period has passed. If a callback is ever missed, the pool can’t get stuck; someone can always settle it directly.
About Reactive Network
Reactive Network is an EVM automation layer built around reactive contracts, event-driven smart contracts for cross-chain, on-chain automation. It runs on CometBFT consensus, providing instant finality and roughly 1-second block times while maintaining full EVM compatibility.
Reactive contracts subscribe to event logs across EVM chains and execute Solidity logic automatically when matching events occur, deciding autonomously when to send cross-chain callback transactions. This model supports conditional cross-chain state changes and continuous cross-chain workflows.
Website | Blog | X | Telegram | Discord | Docs
Build once — react everywhere!