Security & Audit
MightyWallEscrow was reviewed line-by-line for every known Solidity exploit pattern before mainnet deployment. All findings have been resolved and verified with new regression tests.
Audit summary
48
Tests passing
1
Medium finding
3
Low findings
0
Critical / High
Security properties verified
Reentrancy — not possible
Every state-changing function is guarded by OpenZeppelin's ReentrancyGuard. State is updated before any external call (Checks-Effects-Interactions). A malicious ERC1155 callback or ETH-receive hook attempting to re-enter cancelOffer, acceptOffer, or any other function is blocked by the mutex.
Contract funds cannot be drained
ETH, NFTs, and ERC-20s can only leave the escrow via acceptOffer (to the counterparties) or cancelOffer (back to the maker). There is no owner withdrawal function. The deployer cannot touch user assets.
Atomic settlement — no partial fills
acceptOffer executes every transfer in a single transaction. If any transfer fails for any reason — wrong approval, wrong balance, malicious token — the entire transaction reverts. Neither party is left holding only their side.
ETH accounting is exact
msg.value must equal makerBundle.ethAmount + tip exactly when creating an offer, and takerBundle.ethAmount exactly when accepting. The contract cannot accumulate unaccounted ETH from protocol operations.
SafeERC20 throughout
All ERC-20 transfers use OpenZeppelin's SafeERC20 wrapper, which handles tokens like USDT and USDC that do not conform to the standard return-value spec. Silent failures are impossible.
Private offers are enforced on-chain
When a taker address is specified, the smart contract rejects any other caller. A third party cannot intercept a private offer even if they know the offer ID.
Findings
No critical or high severity issues were found. All medium and low findings have been fixed and covered by regression tests.
acceptOffer succeeded on non-existent offer IDs
Calling acceptOffer with an ID that had never been created returned success and emitted a false OfferAccepted event. No assets moved, but the event would confuse off-chain indexers.
Resolution
Added a check that the offer's maker address is non-zero before proceeding. Non-existent offers now revert with OfferNotOpen.
supportsInterface violated ERC165 specification
The ERC165 standard requires a contract to return true when its own interface ID (0x01ffc9a7) is queried. The contract returned false, making it technically non-compliant and potentially breaking some ERC1155 integrations.
Resolution
Added type(IERC165).interfaceId to the supportsInterface return value.
setFeeRecipient emitted no event
Admin fee-recipient changes were invisible to on-chain monitors, Etherscan event feeds, and front-end listeners.
Resolution
Added a FeeRecipientUpdated(oldRecipient, newRecipient) event, emitted on every change.
ERC721 amount field not validated
ERC721's transferFrom takes only a token ID — there is no quantity. The NFTItem.amount field was stored but silently ignored for ERC721 transfers, meaning a maker could specify amount = 99 and only 1 token would actually move.
Resolution
Added a validation that amount == 1 for every ERC721 item in both bundles. Invalid amounts now revert with InvalidAmount.
Directly-sent NFTs cannot be recovered
If a user calls nft.safeTransferFrom directly to the escrow address outside of any offer, the contract accepts the transfer but has no mechanism to return it. This is a user-error scenario, not an exploit.
Resolution
No code change. Adding an owner rescue function would introduce a new trust surface (the owner could potentially misuse it). The current behavior is the safer design choice for a trustless escrow.
Drain vector analysis
| Vector | Status |
|---|---|
| ETH drain via reentrancy | ✓ Blocked — nonReentrant + CEI |
| NFT drain via reentrancy | ✓ Blocked — nonReentrant + CEI |
| ERC-20 drain via reentrancy | ✓ Blocked — nonReentrant + CEI |
| Owner withdrawal backdoor | ✓ None — no such function exists |
| Double-accept exploit | ✓ Blocked — status set before transfers |
| Double-cancel exploit | ✓ Blocked — status set before transfers |
| ETH stuck in contract | ✓ Impossible under normal operation |
| Unauthorized acceptance | ✓ Blocked for private offers by on-chain enforcement |
| Flash loan attack | ✓ N/A — no oracle or lending integration |
| Signature replay | ✓ N/A — pure on-chain transaction model |
Contract addresses
Sepolia testnet
0x1bf1aE8D2E9D5c22b758B32C6d26cECb7544bAC3Mainnet
Deploying soon