Multicall Contracts Development (Batch Operations)

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Multicall Contracts Development (Batch Operations)
Medium
from 1 business day to 3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1218
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    853
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1047
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Developing Multicall Contracts (Batch Operations)

Reading state from ten contracts means ten RPC requests, ten round-trips to a node. On public endpoints, this is 2-5 seconds of loading time for a dApp. Multicall solves this from both sides: for reads — aggregating requests into one RPC call, for writes — multiple on-chain operations in one transaction.

Multicall for Reading: eth_call Aggregation

Multicall3 is deployed at address 0xcA11bde05977b3631167028862bE2a173976CA11 on 50+ networks. Takes an array of (address target, bytes callData), executes all call-s and returns results. Important: it's read-only from the user's perspective, but Multicall3 uses aggregate3 — can be called as state-changing for writing or as a pure read via eth_call.

Typical use via wagmi/viem:

import { useReadContracts } from 'wagmi';

const { data } = useReadContracts({
  contracts: [
    { address: token1, abi: erc20Abi, functionName: 'balanceOf', args: [user] },
    { address: token2, abi: erc20Abi, functionName: 'balanceOf', args: [user] },
    { address: pool, abi: poolAbi, functionName: 'getReserves' },
    { address: oracle, abi: oracleAbi, functionName: 'latestAnswer' },
  ]
});
// one RPC request instead of four

wagmi uses Multicall3 under the hood via multicall batching — all calls in one useReadContracts collapse into one eth_call to Multicall3. If the network doesn't support Multicall3, wagmi falls back to parallel eth_call-s.

Custom Multicall: When Multicall3 Doesn't Fit

Multicall3 doesn't store state, doesn't check authorization, doesn't support native ETH for separate calls. For write operations with custom logic, we write our own contract.

The self-multicall pattern — a contract calls itself through multiple functions in one transaction. OpenZeppelin implements this via Multicall.sol:

abstract contract Multicall {
    function multicall(bytes[] calldata data)
        external
        virtual
        returns (bytes[] memory results)
    {
        results = new bytes[](data.length);
        for (uint256 i = 0; i < data.length; i++) {
            (bool success, bytes memory result) = address(this).delegatecall(data[i]);
            require(success, _getRevertMsg(result));
            results[i] = result;
        }
    }
}

delegatecall to address(this) — the contract calls its own functions on behalf of the original msg.sender. This allows doing approve + deposit in one call, where both steps see the same msg.sender.

Critical warning: delegatecall to address(this) with user-provided data — potential vulnerability. If a contract is not isolated from privileged functions, an attacker can craft data calling transferOwnership. OpenZeppelin's Multicall explicitly limits use through documentation: don't use with contracts where authorization depends on msg.sender in privileged functions, unless they are protected separately.

Stateful Multicall: Atomic Multi-Step Operations

For complex DeFi operations (flash loan → swap → repay), we need a contract that holds intermediate state between calls and rolls back everything if any step fails.

contract AtomicBatcher {
    struct Step {
        address target;
        bytes callData;
        uint256 value;
        uint256 minReturnValue; // result check
    }
    
    function executeBatch(Step[] calldata steps) 
        external 
        payable 
        returns (bytes[] memory results) 
    {
        results = new bytes[](steps.length);
        for (uint256 i = 0; i < steps.length; i++) {
            (bool success, bytes memory result) = steps[i].target.call{
                value: steps[i].value
            }(steps[i].callData);
            
            require(success, string(abi.encodePacked("Step ", i, " failed")));
            
            if (steps[i].minReturnValue > 0) {
                uint256 returnValue = abi.decode(result, (uint256));
                require(returnValue >= steps[i].minReturnValue, "Slippage exceeded");
            }
            
            results[i] = result;
        }
        
        // return unspent ETH
        if (address(this).balance > 0) {
            (bool sent,) = msg.sender.call{value: address(this).balance}("");
            require(sent);
        }
    }
}

minReturnValue — built-in slippage protection on each step. Swap returned less than minimum — entire transaction reverts.

When Custom Contract Is Needed Instead of Multicall3

Scenario Multicall3 Custom
Aggregating read requests Sufficient Excessive
Multiple ERC-20 transfers Sufficient Excessive
Approve + protocol action (one token) Sufficient Sufficient
Flash loan + arbitrage + repay Not suitable Needed
Conditional actions (if step X > Y) Not suitable Needed
Distributing ETH to addresses with different amounts Not suitable Needed

Developing a custom Multicall contract with test coverage: 1-3 days depending on result verification logic complexity.