Batch Verifier
The Batch Verifier lets you check a Restake-built EIP-7702 batch before the
user signs it, with a free eth_call on your own RPC node. It answers one
question:
Does this exact user operation do only what the user asked for, touch only allowlisted contracts, and is this the hash I am about to sign?
Source and full specification: unodev-core/restake-verify-contract.
Status: design stage
The specification is public and under review. Contracts, tests and deploy scripts are not written yet, so there are no addresses to call. This page will list them per chain once they are deployed and audited - see the roadmap.
Why
The API builds unsigned ERC-4337 batches (EntryPoint v0.7, Alchemy Modular Account v2 as the 7702 delegate) for staking, unstaking, stock trades and bridging. Today your wallet has to trust whatever the API returns. A compromised API could return a batch that:
- grants an infinite approval to an attacker,
- deposits or swaps with an attacker as
receiver, - delegates the user's address to a malicious 7702 implementation,
- installs a backdoor module through a call from the account to itself.
The verifier moves that trust from the API to public, timelocked on-chain code that you call on your own node.
How it works
Code
- You receive
{ op, userOpHash, authorization, intentId }from the build call. - You call
verify(...)on your own node, passing the intent the user actually saw in your UI - not values copied from the API response. - The verifier returns
verifiedHashand the batchdeadline, or reverts with a custom error. - The user signs
verifiedHash, never the hash from the API response, plus the 7702 authorization when the bundle carries one (it isnullonce the wallet is delegated). The signature format is unchanged:personal_signover the raw 32 bytes, sent as0xff00 ‖ sig. When there is an authorization, you also checkauth.nonce == eth_getTransactionCount(user)off-chain. - You submit the signatures as usual, and the API submits the operation.
Interface
Code
Intent carries action (STAKE, UNSTAKE, BUY, SELL, BRIDGE),
user, target, payToken, amount, minOut, maxFee, and for bridges
dstChainId and recipient.
Use check when you want to show the user which call failed: it returns the
error code and the index of the offending call instead of reverting.
Architecture
Code
| Component | Role |
|---|---|
| BatchVerifier | Pure view logic with a stable proxy address you hard-code. EntryPoint v0.7 and the Modular Account v2 delegate are pinned as constants. One decoder library per action: account calls, allowances, fees, ERC-4626, Aave, 1inch, Across. |
| AllowlistRegistry | Per-chain lists of allowed addresses by category (paymasters, vaults, Aave pools, assets, Ondo tokens, swap routers and pools, bridges, fee recipients), plus bridge routes and fee/TTL caps. Every list can be read on-chain. |
| DeadlineGuard | requireBefore(deadline). Binds an expiry into the signed batch itself. Never upgradeable. |
| TimelockController | The only upgrader of both proxies, with a 48-hour delay. |
One deployment per chain, each with its own registry. Base and BSC come first; Ethereum, Arbitrum, Optimism, Polygon, Avalanche and Plasma follow with staking only.
What gets checked
The rule is default deny: every call in the batch must match a rule, and anything else fails.
- Envelope -
userOpHashrecomputed locally; delegate is Modular Account v2;chainId ≠ 0;sender == user; noinitCode; paymaster allowlisted or gas capped;callDataisexecuteBatchonly, with no calls from the account to itself and no native value. - Deadline - call 0 is
DeadlineGuard.requireBefore(d)withnow < d ≤ now + 600 s. - Action rules, bound to the intent - see below.
- Shared rules - exact approvals, no allowance left behind, and at most one
fee transfer, placed last, within
intent.maxFeeand a hard cap.
Accepted batch shape
Code
Supported actions (v1)
| Action | Main call | Key checks |
|---|---|---|
STAKE | ERC-4626 deposit / Aave v3 supply | Vault or pool allowlisted, asset read live, receiver / onBehalfOf == user |
UNSTAKE | ERC-4626 redeem / withdraw, Aave withdraw | receiver == owner == user, no share approvals |
BUY / SELL | 1inch v6 swap or unoswap* (Ondo stocks on BSC) | Tokens match the intent, dstReceiver == user, minReturn ≥ intent.minOut, no partial fill; every unoswap hop goes through an allowlisted pool |
BRIDGE | Across SpokePool deposit (USDC Base ↔ BSC) | Allowlisted route pins the destination token, recipient == refundTo == user, outputAmount ≥ minOut, bounded fill deadline and quote age, no destination message |
Security features
| Feature | Protects against |
|---|---|
| 7702 delegate pinned to Modular Account v2, for new and existing delegations | Account takeover through a malicious implementation. The most important check, because the v0.7 userOpHash does not commit to the delegate |
chainId ≠ 0 and == block.chainid | Cross-chain replay of the authorization |
Local v0.7 userOpHash recomputation | Signing a hash that does not match the verified operation |
Allowlist on the account entry point, SelfCallForbidden() | Module installs, upgradeToAndCall, performCreate, delegatecall |
| Exact approvals, zero leftover allowance | Infinite or leftover approvals; also cleans older infinite allowances |
Receiver / owner / recipient / refund == user | Funds redirected to an attacker |
minOut from your own quote | Sandwiching or bad routes through the API |
Fee cap (intent.maxFee + 50 bps + $0.10 backstop) | Fee inflation |
| In-batch DeadlineGuard | A signed operation held and executed much later |
| Strict decoding: exact selectors and calldata length, no dynamic dispatch | Trailing-data and selector-confusion tricks |
| Live reads only from allowlisted contracts | Malicious contracts lying through view calls |
| Paymaster allowlist or gas caps | Gas griefing on the user's native balance |
Governance
| Operation | Delay | Who |
|---|---|---|
| Add an address or bridge route, change a config value | 48 h | MANAGER schedules, anyone executes |
| Upgrade the verifier or the registry | 48 h | TimelockController (Safe multisig proposer) |
| Remove an address or route, cancel a pending add | Instant | GUARDIAN or MANAGER |
pause() - every verification fails | Instant | GUARDIAN |
Anything that loosens the rules waits 48 hours and emits an event, so you can
watch and object. Anything that tightens them is instant. Subscribe to
AdditionScheduled, Upgraded and CallScheduled on the deployments you use.
Benefits
- You no longer trust the API. You trust public code you call on your own node.
- 48-hour public reaction window. Even a compromised Restake admin cannot add a malicious target or change the logic without notice.
- Intent binding. The batch is checked against what the user saw, not against what the API claims.
- Fail closed. Unknown selectors, routes, pools, tokens or ABI changes all revert.
- Precise errors. Custom errors, and
checkwith the failingcallIndex, make UI messaging simple. - Free at runtime. Verification is an
eth_call; the only gas it adds to the user's operation is the small deadline call.
Trust assumptions and limitations
- Verifier bugs are the main risk, hence strict decoders, fuzzing, invariant tests and an external audit before mainnet.
- The 1inch executor route is opaque. Safety rests on the exact approve,
dstReceiver == user,minReturnand allowlisted pools. Also simulate balance changes (eth_simulateV1ordebug_traceCall). - Bridge fills happen on the other chain. The verifier relies on Across'
fill-or-refund guarantee: the worst case after verification is funds locked
until
fillDeadline, then refunded. - Fees on non-USDC pools are bounded only by
intent.maxFee, so compute it from your fee tiers (partner panel → Fees; the platform defaults if you have not set any). - Standard ERC-20s only. USDC is upgradeable and has a blocklist; Ondo tokens may restrict transfers - a verified operation can still revert, safely.
- Your off-chain duties: sign
verifiedHash, check the 7702 nonce, and deriveminOutandmaxFeeindependently of the API.
Roadmap
- Discovery - capture real payloads; pin the Modular Account v2 implementation, the 1inch flag layout and the Across ABI.
- Scaffold - Foundry, OpenZeppelin Upgradeable v5, CI with Slither.
- AllowlistRegistry - timelocked adds, instant removes, pause.
- BatchVerifier core - envelope, DeadlineGuard, hash recomputation, account call decoder.
- Rules - allowances and fees, then ERC-4626, Aave, 1inch, Across.
- Governance wiring - timelock, Safe proposer, guardian, deploy scripts for Base and BSC.
- Tests - unit, fork tests on real payloads, fuzzing of malicious mutations, invariants.
- Partner kit - viem snippet, fee formula, ABI, addresses.
- Audit, then Base and BSC mainnet.
