Autonomous MEV-Triggered Liquidation Hook for Uniswap V4
ReQuard is a Uniswap V4 hook that uses Reactive Contracts to autonomously monitor collateralized LP positions and trigger on-chain liquidations the moment a position's health factor drops below a safe threshold — no keeper bots, no manual intervention.
ReQuard combines:
- Uniswap V4 Hooks - Customizable pool logic for LP position management
- Reactive Network - Event-driven smart contract execution layer
- Minimal Lending Protocol - Custom lending system for LP-backed collateral
-
ReQuardLending (
src/ReQuardLending.sol)- Minimal custom lending protocol
- Tracks LP positions as collateral
- Manages borrowing and health factors
- Handles liquidations
-
ReQuardHook (
src/ReQuardHook.sol)- Uniswap V4 hook implementing
IHooksinterface - Tracks LP positions and their collateral values
- Emits
PositionHealthUpdatedevents for Reactive Network - Executes liquidations by unwinding LP positions
- Uniswap V4 hook implementing
-
ReQuardDestination (
src/ReQuardDestination.sol)- Receives callbacks from Reactive Network
- Calls hook's liquidation function
- Deployed on Base Sepolia
-
ReQuardReactive (
src/ReQuardReactive.sol)- Reactive Contract deployed on Reactive Network
- Monitors
PositionHealthUpdatedevents from Base Sepolia - Emits
Callbackevents to trigger liquidations - Implements correct Reactive Network patterns
Reactive Network is the automation layer that turns hook-emitted health signals into cross-chain liquidation transactions.
Whenever a registered collateral LP position changes (via a Uniswap V4 pool action), ReQuardHook.afterModifyPosition(...) updates internal LP state and, if the LP is linked to a lending position, emits:
PositionHealthUpdated(bytes32 indexed positionId, address indexed owner, uint256 collateralValue, uint256 debtValue, uint256 healthFactor)
On Reactive Network, you create a subscription that monitors ReQuardHook on Base Sepolia for PositionHealthUpdated events. When an event is observed, Reactive Network calls your Reactive contract handler:
ReQuardReactive.onPositionHealthUpdated(bytes32,address,uint256,uint256,uint256)
ReQuardReactive compares healthFactor against minHealthFactor:
- If
healthFactor >= minHealthFactor: it returns without emitting anything. - If
healthFactor < minHealthFactor: it emits:Callback(destinationChainId, destinationContract, callbackGasLimit, payload)
The emitted callback payload encodes the destination call:
abi.encodeWithSignature("liquidate(address,bytes32)", address(0), positionId)
The first address argument is reserved for Reactive Network ABI conventions (Reactive Network overwrites it at execution time).
ReQuardDestination receives the callback via:
liquidate(address rvmAddress, bytes32 positionId)
The destination enforces access control using:
onlyReactiveVm(it checksmsg.sender == reactiveVm)
After validation it calls:
hook.liquidatePosition(positionId)
For subscription topic hashes, required handler signature checks, and operational troubleshooting, see REACTIVE_SETUP.md.
- ✅ Autonomous Liquidations - No keeper bots required
- ✅ Real-time Monitoring - Reactive Network monitors positions continuously
- ✅ Uniswap V4 Integration - Proper hook interfaces and LP management
- ✅ Health Factor Tracking - Automatic health calculation and threshold monitoring
- ✅ Fee Redistribution - Liquidation fees redistributed to LPs
- Foundry installed
- Base Sepolia testnet access
- Reactive Network Lasna Testnet access
forge build-
Configure environment variables used by the scripts (
PRIVATE_KEY, and for Reactive deploymentDESTINATION_CONTRACT, plus optional overrides for chain ids and thresholds). -
Deploy contracts on Base Sepolia:
forge script script/Deploy.s.sol:DeployScript --rpc-url $BASE_SEPOLIA_RPC_URL --broadcast --verify-
Deploy Reactive Contract on Reactive Network Lasna Testnet
-
Configure Reactive Network subscription (see
REACTIVE_SETUP.md)
Chain IDs:
- Base Sepolia:
84531 - Reactive Network Lasna Testnet:
5318007
Health Factor Threshold:
- Default:
1.2e18(120%) - Positions below this threshold are liquidated
Gas Limits:
- Callback Gas Limit:
500000
src/
├── interfaces/
│ └── IUniswapV4Hooks.sol # Uniswap V4 hook interfaces
├── ReQuardLending.sol # Minimal lending protocol
├── ReQuardHook.sol # Uniswap V4 hook
├── ReQuardDestination.sol # Callback receiver
└── ReQuardReactive.sol # Reactive Contract
script/
└── Deploy.s.sol # Deployment script
REACTIVE_SETUP.md # Reactive Network setup guide
ReQuard-PRD.md # Product requirements document
- A pool action triggers
ReQuardHook.afterModifyPosition(...)(Uniswap V4 hook callback). - If the LP position has been registered as collateral in
ReQuardLending, the hook emitsPositionHealthUpdated(...). - Reactive Network detects the event and calls
ReQuardReactive.onPositionHealthUpdated(...). ReQuardReactivecheckshealthFactoragainstminHealthFactor.- If unhealthy,
ReQuardReactiveemitsCallback(...)with a payload encodingliquidate(address,bytes32)and thepositionId. - Reactive Network executes the callback on the destination chain by calling
ReQuardDestination.liquidate(...). ReQuardDestinationverifies the Reactive VM and forwards toReQuardHook.liquidatePosition(positionId).- The hook liquidates via
ReQuardLending.liquidatePosition(positionId), unwinds LP liquidity, and accumulates liquidation fees for distribution.
forge testThis repo includes:
- Unit tests for
ReQuardReactive,ReQuardLending, andReQuardHook. - An integration test that drives the liquidation flow through
ReQuardHook -> ReQuardReactive -> ReQuardDestination -> ReQuardHook -> ReQuardLending. - Fuzz tests for callback gating, health-factor math, and liquidation branching.
UNLICENSED