Settlement Contracts

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

FunctionDescription
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

FunctionDescription
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 <= totalRebates enforced 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 claim and recoverExpired.
  • 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

FunctionDescription
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);
Edit this page