Validation Hook
Smart contract reference for UmiaValidationHook — bid verification, hookData format, and server permits
The UmiaValidationHook is the smart contract that enforces per-step bid eligibility during a Tailored Auction. It is called by the CCA on every bid submission and decides whether the bidder is allowed to participate in the current auction step.
Overview
Each auction can have one validation hook attached. The hook supports three verification methods that can be mixed per step:
- Pre-submitted zkTLS proof — proof submitted ahead of time via
submitProof(). - Pre-submitted server permit — EIP-712 signed permit submitted via
submitServerPermit(). - Inline verification — proof or permit passed directly in the bid's
hookData.
Verification is monotonic: a user verified at step N is automatically eligible for steps N, N+1, N+2, and beyond. This models tiered access where earlier tiers are supersets of later ones.
hookData Format
When a user submits a bid, the CCA forwards a hookData bytes payload to validate(). The first byte acts as a type flag that determines how the payload is decoded:
| First byte | Type | Payload (remaining bytes) |
|---|---|---|
0x01 | Server permit | abi.encode(uint256 permitStep, uint256 deadline, bytes signature) |
| Any other value | zkTLS proof | First 32 bytes: uint256 proofStep (the step the proof targets), followed by abi.encode(Reclaim.Proof) |
Server permit inline (0x01)
hookData = 0x01 ++ abi.encode(permitStep, deadline, signature)- permitStep (
uint256): The 0-indexed auction step the permit was signed for. Must be ≤ the current step (monotonic). - deadline (
uint256): Unix timestamp after which the permit expires. - signature (
bytes): EIP-712 signature from the authorized signer over theServerPermitstruct.
The hook enforces monotonic inline verification: permitStep must be ≤ the current auction step. The permit bitmap is checked against permitStep, and the EIP-712 signature is verified for permitStep. This allows a user who obtained a permit at step N to bid at step M (M ≥ N) even if step M is not permit-enabled.
zkTLS proof inline (default)
hookData = abi.encodePacked(uint256 proofStep) ++ abi.encode(Reclaim.Proof)If the first byte is anything other than 0x01, the hookData is treated as a step-prefixed zkTLS proof:
- proofStep (
uint256, first 32 bytes): The 0-indexed auction step the proof was originally verified for. - proof (remaining bytes): ABI-encoded
Reclaim.Proof.
The hook enforces monotonic inline verification: proofStep must be ≤ the current auction step. The proof is verified against the proofStep's provider set, not the current step's. This allows a user who verified at step N (with provider X) to bid at step M (M ≥ N) even if step M uses different providers.
After verification, the user is registered from proofStep, making them eligible for all subsequent steps.
Empty hookData
If hookData is empty and the user has no pre-stored verification, the bid reverts with NotVerified(owner).
If a user has already been verified (via submitProof() or submitServerPermit()), the hookData is ignored entirely. Pre-verification is checked first.
Server Permit System
Server permits use EIP-712 typed signatures to gate access. A trusted backend signer issues permits to eligible wallets, and the contract verifies the signature on-chain.
EIP-712 Domain
EIP712Domain(
string name, // "UmiaValidationHook"
string version, // "1"
uint256 chainId, // Bound at deployment
address verifyingContract // Hook contract address
)Permit Struct
ServerPermit(
address wallet, // The wallet being permitted
uint256 step, // The auction step index
uint256 deadline // Unix timestamp expiry
)Submission Paths
Pre-submission (recommended): Call submitServerPermit(user, stepIndex, deadline, signature) before the user bids. This is permissionless — anyone (including a keeper/relayer) can submit on behalf of the user.
Inline: Include the permit in the bid's hookData with the 0x01 prefix. The permit is verified and stored during the bid transaction.
Step Configuration
Each step can independently be configured for zkTLS verification, server permit verification, or both.
Bitmaps
The hook uses two bitmaps to track which steps enforce verification:
- Step enabled bitmap (
_stepEnabledBitmap): Bitiset means stepirequires verification of any kind (zkTLS or server permit). If a step is not enabled, any wallet can bid without verification. - Step permit enabled bitmap (
_stepPermitEnabledBitmap): Bitiset means stepiaccepts server-permit signatures. A server permit submitted for a step where this bit is not set will revert withServerPermitNotEnabled.
Admin Functions
All admin functions are restricted to the hook owner.
| Function | Description |
|---|---|
enableStep(stepIndex, providerHashes, providerIds) | Enable zkTLS verification for a step with specific provider requirements |
disableStep(stepIndex) | Disable verification for a step (anyone can bid) |
enableStepPermit(stepIndex) | Enable server-permit verification for a step |
disableStepPermit(stepIndex) | Disable server-permit verification for a step |
setSigner(address) | Set the authorized EIP-712 signer (set to address(0) to disable all permits) |
addStepProviders(stepIndices, hashes, ids) | Add required zkTLS provider hashes to steps |
removeStepProvider(stepIndex, hash) | Remove a provider hash from a step |
setCCA(address) | Pair the hook with a CCA contract (one-time, called by owner or router) |
Permit Wallet Lists
The server-side permit signing service maintains an off-chain wallet allowlist per auction step. When a wallet requests a permit:
- If no wallets are configured for that step, any wallet receives a permit (open access).
- If wallets are configured, only listed wallets receive permits.
Wallet lists are managed via the Umia CLI:
# List permitted wallets
umia permit-wallets-list --auction 0x... --step 0
# Add a single wallet
umia permit-wallets-add --auction 0x... --step 0 --wallet 0xabc...
# Bulk add from file (one address per line)
umia permit-wallets-add --auction 0x... --step 0 --file wallets.txt
# Replace entire list atomically
umia permit-wallets-set --auction 0x... --step 0 --file wallets.txt
# Remove a wallet
umia permit-wallets-remove --auction 0x... --step 0 --wallet 0xabc...Validation Flow
When validate() is called on a bid:
- If no CCA is paired, the bid passes.
- If
sender != owner, revert withSenderNotBidOwner(no delegated bidding). - Resolve the current auction step from block number.
- If the step is not enabled for verification, the bid passes.
- Check pre-stored verification: if the user is verified from a step ≤ current step, the bid passes.
- If no pre-verification exists, decode
hookData:- Empty: revert
NotVerified. - First byte
0x01: verify server permit inline. - Otherwise: decode
proofStepfrom the first 32 bytes. IfproofStep > currentStep, revertProofStepTooHigh. Verify the zkTLS proof againstproofStep's providers.
- Empty: revert
- On successful verification, store the user's verified step (monotonic — only lowers, never raises).
Events
| Event | Emitted when |
|---|---|
Registered(stepIndex, user) | User is verified for a step (any method) |
SignerSet(oldSigner, newSigner) | Permit signer is changed |
StepPermitEnabled(stepIndex) | Server permit enabled for a step |
StepPermitDisabled(stepIndex) | Server permit disabled for a step |
StepEnabled(stepIndex) | Verification enabled for a step |
StepDisabled(stepIndex) | Verification disabled for a step |
ProofVerified(user, stepIndex, proofIdentifier) | zkTLS proof verified |
Error Reference
| Error | Cause |
|---|---|
NotVerified(user) | User has no verification and provided empty hookData |
SenderNotBidOwner() | Bid sender is not the bid owner |
ServerPermitNotEnabled(stepIndex) | Server permit submitted for a step that doesn't accept permits |
SignerNotSet() | No signer configured (signer is address(0)) |
ExpiredDeadline() | Permit deadline has passed |
InvalidSignature() | EIP-712 signature doesn't match the configured signer |
StepIndexOutOfBounds(stepIndex) | Step index exceeds the auction's step count |
NoCCA() | No CCA paired yet |
ProofStepTooHigh(proofStep, currentStep) | Inline proof targets a future step (proofStep > currentStep) |
ProviderHashMismatch(expected, actual) | zkTLS proof provider doesn't match step requirements |