YoorQuezt uses three on-chain settlement contracts for verifiable, trustless settlement of MEV operations. Built with Solidity 0.8.21, OpenZeppelin v5.6.1, and tested with Foundry v1.6.0.
AuctionSettlement
Handles on-chain settlement for sealed-bid MEV auctions.
Functions
| Function | Description |
|---|---|
deposit() | Searchers deposit ETH as bidding collateral |
settle(auctionId, bundleHash, winner, bidAmount, blockNumber) | Auctioneer records winning result and locks protocol fee |
claimPayout(auctionId) | Winner claims payout (bid minus protocol fee) |
withdrawFees() | Owner withdraws accumulated protocol fees |
Security Features
- Protocol fee locked per-auction at settlement time -- cannot be changed retroactively.
- ReentrancyGuard on all ETH-transferring functions (
deposit,claimPayout,withdrawFees). - MAX_FEE_BPS = 2000 (20% hard cap on protocol fees).
- Ownable2Step for safe two-step ownership transfer.
- Pausable -- emergency pause halts all operations.
- Pull pattern -- winners must claim payouts (no automatic push).
- Event emissions on every state change (
Deposited,Settled,PayoutClaimed,FeesWithdrawn).
Flow
Searcher → deposit(ETH)
│
Auctioneer → settle(auctionId, winner, bidAmount)
│
Winner → claimPayout(auctionId) → receives (bid - protocolFee)
│
Owner → withdrawFees() → receives accumulated fees
RebateDistributor
Distributes MEV rebates to users via Merkle proofs. Off-chain computation generates the Merkle tree; on-chain contract verifies proofs and distributes funds.
Functions
| Function | Description |
|---|---|
publishEpoch(merkleRoot) | Distributor publishes new epoch with ETH funding |
claim(epochId, amount, proof) | Users claim rebate with Merkle proof |
recoverExpired(epochId) | Owner recovers unclaimed funds from expired epoch |
Security Features
- Per-epoch fund isolation -- each epoch is tracked independently, preventing cross-contamination.
- Claim cap --
claimedAmount + amount <= totalRebatesenforced per epoch. - One claim per user per epoch -- duplicate claims are rejected.
- 90-day configurable claim window -- after expiry, unclaimed funds can be recovered.
- Recovery scoped to specific expired epochs only -- cannot recover from active epochs.
- ReentrancyGuard on
claimandrecoverExpired. - MerkleProof verification using OpenZeppelin's library.
- Pausable for emergency situations.
- Event emissions --
EpochPublished,RebateClaimed,ExpiredRecovered.
Flow
Off-chain: compute Merkle tree from rebate data
│
Distributor → publishEpoch(merkleRoot) + ETH
│
User → claim(epochId, amount, merkleProof) → receives rebate
│
(after 90 days)
Owner → recoverExpired(epochId) → recovers unclaimed funds
IntentRegistry
On-chain registry for user intents with a solver marketplace. Users submit intents with tips; solvers stake ETH, match intents, and earn tips on fulfillment.
Functions
| Function | Description |
|---|---|
submitIntent(intentHash, maxGasCost, deadline) | Submit intent with tip (payable) |
registerSolver() | Stake ETH to become an active solver |
matchIntent(intentId) | Solver claims an intent for fulfillment |
fulfillIntent(intentId, resultHash) | Report fulfillment, receive tip |
slashSolver(solver, amount, intentId) | Slash misbehaving solver's stake |
deregisterSolver() | Unstake and deregister |
Security Features
- Minimum stake requirement -- solvers must stake above a configurable minimum.
- Auto-deregistration -- solver is automatically deregistered when stake drops below minimum after slashing.
- ReentrancyGuard on all fund-transferring functions.
- Tip refund -- tips are refunded on intent cancellation or expiry.
- Performance tracking -- fulfillments and failures are tracked per solver.
- Deadline enforcement -- intents expire after their deadline.
- Ownable2Step for admin operations (slashing, parameter updates).
- Pausable for emergency situations.
- Event emissions --
IntentSubmitted,SolverRegistered,IntentMatched,IntentFulfilled,SolverSlashed,SolverDeregistered.
Flow
User → submitIntent(hash, maxGas, deadline) + tip
│
Solver → registerSolver() + stake
│
Solver → matchIntent(intentId)
│
Solver → fulfillIntent(intentId, resultHash) → receives tip
│
(on misbehavior)
Owner → slashSolver(solver, amount, intentId) → stake reduced
│
(if stake < minimum)
Auto → solver deregistered
Deployment
Contracts are deployed via Foundry deploy scripts:
# Deploy all contracts to Sepolia
forge script script/DeployAll.s.sol \
--rpc-url sepolia \
--broadcast \
--verify
# Deploy to Arbitrum Sepolia
forge script script/DeployAll.s.sol \
--rpc-url arb-sepolia \
--broadcast \
--verify
# Deploy to Base Sepolia
forge script script/DeployAll.s.sol \
--rpc-url base-sepolia \
--broadcast \
--verify
Target testnets:
- Sepolia (chain ID: 11155111)
- Arbitrum Sepolia (chain ID: 421614)
- Base Sepolia (chain ID: 84532)
Testing
96 Foundry tests covering all functions, edge cases, access control, and fuzz testing:
# Run all tests
forge test
# Verbose output with traces
forge test -vvv
# CI profile with extended fuzz runs (1024 iterations)
forge test --profile ci
# Run specific contract tests
forge test --match-contract AuctionSettlementTest
forge test --match-contract RebateDistributorTest
forge test --match-contract IntentRegistryTest
# Gas report
forge test --gas-report
Test breakdown:
- AuctionSettlement: 10 tests (deposit, settle, claim, fees, access control, edge cases)
- RebateDistributor: 27 tests (publish, claim, recover, proof verification, expiry)
- IntentRegistry: 24 tests (submit, register, match, fulfill, slash, deregister)
- ArbExecutor: 35 tests (swap execution, routing, profit calculation)
TypeScript Contract Interaction
AuctionSettlement
import { ethers } from "ethers";
const AUCTION_ABI = [
"function deposit() payable",
"function settle(uint256 auctionId, bytes32 bundleHash, address winner, uint256 bidAmount, uint256 blockNumber)",
"function claimPayout(uint256 auctionId)",
"function withdrawFees()",
"function getAuction(uint256 auctionId) view returns (tuple(address winner, uint256 bidAmount, uint256 protocolFee, bool settled, bool claimed))",
"event Deposited(address indexed depositor, uint256 amount)",
"event Settled(uint256 indexed auctionId, address indexed winner, uint256 bidAmount)",
"event PayoutClaimed(uint256 indexed auctionId, address indexed winner, uint256 amount)",
];
const auction = new ethers.Contract(AUCTION_ADDRESS, AUCTION_ABI, signer);
// Deposit collateral
await auction.deposit({ value: ethers.parseEther("1.0") });
// Claim payout after winning
await auction.claimPayout(auctionId);
RebateDistributor
const REBATE_ABI = [
"function claim(uint256 epochId, uint256 amount, bytes32[] calldata proof)",
"function getEpoch(uint256 epochId) view returns (tuple(bytes32 merkleRoot, uint256 totalRebates, uint256 claimedAmount, uint256 expiresAt))",
"event RebateClaimed(uint256 indexed epochId, address indexed claimant, uint256 amount)",
];
const rebate = new ethers.Contract(REBATE_ADDRESS, REBATE_ABI, signer);
// Claim rebate with Merkle proof
await rebate.claim(epochId, amount, merkleProof);
// Check epoch info
const epoch = await rebate.getEpoch(epochId);
console.log(`Claimed: ${ethers.formatEther(epoch.claimedAmount)} / ${ethers.formatEther(epoch.totalRebates)} ETH`);
IntentRegistry
const INTENT_ABI = [
"function submitIntent(bytes32 intentHash, uint256 maxGasCost, uint256 deadline) payable",
"function registerSolver() payable",
"function matchIntent(uint256 intentId)",
"function fulfillIntent(uint256 intentId, bytes32 resultHash)",
"function deregisterSolver()",
"event IntentSubmitted(uint256 indexed intentId, address indexed submitter, bytes32 intentHash)",
"event IntentFulfilled(uint256 indexed intentId, address indexed solver, bytes32 resultHash)",
];
const intent = new ethers.Contract(INTENT_ADDRESS, INTENT_ABI, signer);
// Submit an intent
const intentHash = ethers.keccak256(
ethers.toUtf8Bytes(JSON.stringify({
type: "swap",
tokenIn: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
tokenOut: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
amount: "1000000000000000000",
}))
);
await intent.submitIntent(
intentHash,
ethers.parseGwei("50"), // max gas cost
Math.floor(Date.now() / 1000) + 3600, // 1 hour deadline
{ value: ethers.parseEther("0.01") } // tip
);
// Register as a solver
await intent.registerSolver({
value: ethers.parseEther("0.5"), // stake
});
// Fulfill an intent
await intent.fulfillIntent(intentId, resultHash);