Collect revert reasons from failed sub-calls in a batch: Multicall3 tryAggregate with requireSuccess=false, then decode the error bytes
Problem: you batch several calls through a Multicall contract; some sub-calls revert and the whole thing surfaces as one opaque failure, so you end up replaying each call in a debugger to find out which one failed and why.
Approach: use Multicall3.tryAggregate(bool requireSuccess, Call[] calldata calls) with requireSuccess = false. Instead of bubbling the first revert, it returns Result[] { bool success; bytes returnData; } — one entry per call, in order, where returnData holds the return value for successes and the **raw revert payload** for failures. aggregate3 behaves the same with a per-call allowFailure flag.
Decoding returnData for a failure, off chain or on chain:
- require("msg") / revert("msg") → payload starts with the Error(string) selector 0x08c379a0; strip 4 bytes and ABI-decode a string.
- Panic(uint256) (assert, division by zero, overflow) → selector 0x4e487b71, decode a uint256 code.
- custom errors → the first 4 bytes are keccak256("MyError(uint256,address)")[0:4]; match against selectors you compute from each candidate contract's ABI, then decode the remaining bytes with that error's parameter types. Empty returnData usually means out-of-gas or a call to a non-contract address.
Non-obvious points: a reverted sub-call has its own state changes rolled back but the outer transaction still succeeds, so success == false entries are silent unless you inspect them — do not treat a successful multicall receipt as "all calls worked". A custom error selector carries no name on chain, so you cannot recover the label without an ABI. And gas is not refunded for the failed attempts, which matters for the arbitrage/trade batches this pattern is usually used for.
Source: adapted from a Stack Exchange answer by SYED ASAD KAZMI (question by Mikko Ohtamaa), https://ethereum.stackexchange.com/questions/167455/multicall-and-collecting-all-revert-reasons — CC BY-SA 4.0