> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chainstack.com/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP batch request VS multicall contract

> Compare HTTP batch requests and Multicall contracts for efficient blockchain data fetching, with performance benchmarks and code examples on Chainstack.

**TLDR:**

* Both HTTP batch requests and multicall contracts can bundle multiple calls to reduce client-server overhead and improve response times.
* In the tests, HTTP batch requests often slightly outperformed multicall contracts, but both outperformed sending multiple single requests by a significant margin.
* Batch requests still count each call against your RPC usage, whereas multicall typically counts as a single request. However, multicall requires extra contract deployment and can lead to “request timeout” or “response size too big” errors for more complex calls.
* The best choice depends on your specific use cases, desired simplicity, and performance requirements.

## Main article

This article is about two approaches many developers believe can help them save on RPC consumptions: batch request and multicall smart contract.

In this article, we will explore how to use them and compare their performances.

## HTTP batch request

HTTP batch request is a feature most Ethereum clients support, for example, [Geth](https://geth.ethereum.org/docs/interacting-with-geth/rpc/batch). With batch requests enabled, multiple HTTP requests can be packaged into one single request and sent to the server. Server process this bulk request and returns a bulk result. All of these are done in a single round trip.

This feature can be useful for reducing the load on the server and improving the performance to a certain extent.

## How to implement

To implement an HTTP batch request, just send a request with a payload containing multiple request objects in an array like below:

<CodeGroup>
  ```Json JSON theme={"system"}
  [
      {"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1},
      {"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":2},
      {"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":3},
      {"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":4}
  ]
  ```
</CodeGroup>

The server sends back the results in one response. For example:

<Warning>
  **Important: Response order is not guaranteed**

  According to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification#batch), the server may return batch responses in any order. Always use the `id` field to match requests with responses, not the array position.

  This is especially important when using ethers.js v5, which had an issue that incorrectly relied on response ordering. If you're using ethers.js v5 and experiencing ordering issues, upgrade to ethers.js v6 where this has been fixed. See [ethers.js issue #3431](https://github.com/ethers-io/ethers.js/issues/3431) for details.
</Warning>

<CodeGroup>
  ```Json JSON theme={"system"}
  [
      {
          "jsonrpc": "2.0",
          "id": 1,
          "result": "Geth/v1.10.26-stable-e5eb32ac/linux-amd64/go1.18.8"
      },
      {
          "jsonrpc": "2.0",
          "id": 2,
          "result": "0x10058f8"
      },
      {
          "jsonrpc": "2.0",
          "id": 3,
          "result": false
      },
      {
          "jsonrpc": "2.0",
          "id": 4,
          "result": "0x1"
      }
  ]
  ```
</CodeGroup>

To run it in curl:

<CodeGroup>
  ```bash cURL theme={"system"}
  curl 'YOUR_CHAINSTACK_ENDPOINT' \
  --header 'Content-Type: application/json' \
  --data '[
      {"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1},
      {"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":2},
      {"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":3},
      {"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":4}
  ]'
  ```
</CodeGroup>

Popular Web3 libraries like [web3.js](https://web3js.readthedocs.io/en/v1.2.11/web3-eth-personal.html?highlight=batch) and [ethers.js](https://docs.ethers.org/v5/api/providers/other/#JsonRpcBatchProvider) support this feature too. Below is an example of getting ether's balance from multiple accounts using web3.js.

<Info>
  ### Web3.js install instructions

  Run `npm i web3` to install web3.js. The code in this guide is compatible with `web3.js V4`.
</Info>

<CodeGroup>
  ```js JavaScript theme={"system"}
  const { Web3 } = require("web3");
  const NODE_URL =
    "YOUR_CHAINSTACK_ENDPOINT";
  const web3 = new Web3(NODE_URL);

  const addresses = [
    "0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326",
    "0x2bB42C655DdDe64EE508a0Bf29f9D1c6150Bee5F",
  ];

  async function getBalances() {
    const startTime = Date.now();

    // Create a batch request object
    const batch = new web3.BatchRequest();

    // Array to hold promises for each request
    const promises = [];

    // Loop through each address and add a getBalance request to the batch
    addresses.forEach((address, index) => {
      const request = {
        jsonrpc: "2.0",
        id: index + 1,
        method: "eth_getBalance",
        params: [address, "latest"],
      };
      // Add request to the batch and store the promise
      const requestPromise = batch.add(request);
      promises.push(requestPromise);
    });

    // Send the batch request and wait for all responses
    const responses = await batch.execute();

    // Process responses
    responses.forEach((response, index) => {
      if (response.error) {
        console.error(response.error);
      } else {
        const balance = response.result;
        const timeFromStart = Date.now() - startTime;
        console.log(
          `${addresses[index]} has a balance of ${Number(
            web3.utils.fromWei(balance, "ether")
          ).toFixed(3)} ETH retrieved in: ${timeFromStart / 1000} seconds.`
        );
      }
    });
  }

  getBalances();
  ```
</CodeGroup>

The `getBalances` function creates a new `BatchRequest` object using `web3.BatchRequest()`.

The function then loops through each address in the `addresses` array and creates a new request to get the balance of that address using `web3.eth.getBalance.request()`. It adds each request to the batch using `batch.add()`.

Finally, the function executes the batch request using `batch.execute()`. When executed, the requests in the batch are sent to the Ethereum network simultaneously, and the callback functions are executed when the responses are received.

## Multicall contract

A multicall contract is a smart contract that takes in the function call objects as parameters and executes them together. A developer can use the multicall contract as a proxy to call other contracts on Ethereum.

The implementation of a multicall contract is, in fact, very simple: it leverages Solidity’s [call function](https://solidity-by-example.org/call/) to broadcast contract calls. This is a sample implementation of multicall’s aggregate function:

<CodeGroup>
  ```solidity solidity theme={"system"}
  function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {
          blockNumber = block.number;
          returnData = new bytes[](calls.length);
          for(uint256 i = 0; i < calls.length; i++) {
              (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
              require(success);
              returnData[i] = ret;
          }
      }
  ```
</CodeGroup>

In summary, this function takes an array of `Call`, calls each one, and returns an array of the results along with the block number in which the function was called. It is designed to be used as a general-purpose aggregator for calling other contracts on the Ethereum blockchain.

## How to implement

Anyone can deploy their own multicall contract. In this article, we leverage [MakerDAO’s multicall contract](https://github.com/makerdao/multicall) on the Ethereum mainnet; which is deployed at [0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441](https://etherscan.io/address/0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441).

Below is an example calling the smart contract with MakerDAO’s helper library [multicall.js](https://github.com/makerdao/multicall.js?files=1); it essentially does the same thing as the previous example:

<CodeGroup>
  ```js JavaScript theme={"system"}
  const multicall = require("@makerdao/multicall")

  const config = {
      rpcUrl: "YOUR_CHAINSTACK_ENDPOINT",
      multicallAddress: "0xeefba1e63905ef1d7acba5a8513c70307c1ce441"
  };

  const addressArr = [
    "0x2B6ee955a98cE90114BeaF8762819d94C107aCc7",
    "0x2bB42C655DdDe64EE508a0Bf29f9D1c6150Bee5F"
  ];

  async function main() {
    const startTime = Date.now();
    console.log("Started...");

    const calls = [];

    // Retrieve the Ether balance of each Ethereum address in addressArr using the multicall library.
    for (let i = 0; i < addressArr.length; i++) {
        const callObj = {
            call: [
                'getEthBalance(address)(uint256)',
                addressArr[i]
            ],
            returns: [
                [`ETH_BALANCE ${addressArr[i]}`, val => val / 10 ** 18]
            ]
        };
        calls.push(callObj);
    }

    const result = await multicall.aggregate(calls, config);
    console.log(result);

    const timeFromStart = Date.now() - startTime;
    console.log(`Result received in ${timeFromStart / 1000} seconds`);
  }

  main();
  ```
</CodeGroup>

The `main` function iterates through each address in the `addressArr` array and creates a call object for each address. These call objects use the multicall library to retrieve the ether balance for each address.

Once all of the call objects have been created and pushed to the `calls` array, the multicall library's `aggregate` function is called with the array of call objects and the configuration object. This function aggregates the results of all of the calls into a single object, which is stored in the `result` variable.

Finally, the code logs the "result" to the console and calculates the time it took to receive the "result", which is also logged to the console.

You will need to [install the multicall.js library](https://github.com/makerdao/multicall.js?files=1#installation) to run this code.

## Performance comparison

In this section, we compare the performance of 3 different approaches:

* Sending multiple HTTP requests in parallel
* Sending a batch HTTP request
* Using a multicall contract

We will test based on two common use cases:

1. Getting account balance
2. Calling a smart contract

## Getting account balance for 30 distinct addresses

The testing script for batch requests and multicall contract is already included in the previous sections. Below is the code for sending multiple HTTP requests in parallel:

<CodeGroup>
  ```js JavaScript theme={"system"}
  const Web3 = require('web3');
  const web3 = new Web3(new Web3.providers.HttpProvider('YOUR_CHAINSTACK_ENDPOINT'));

  var addressArr = [
      "0x2B6ee955a98cE90114BeaF8762819d94C107aCc7",
      "0x2bB42C655DdDe64EE508a0Bf29f9D1c6150Bee5F"
  ]

  async function main() {
      var startTime = Date.now()
      console.log("started")
      for (i = 0; i < addressArr.length; i++) {
          web3.eth.getBalance(addressArr[i]).then(function(result) {
              var timeFromStart = Date.now() - startTime
              console.log("Result received in:" + timeFromStart / 1000 + " seconds")
          })
      }
  }

  main();
  ```
</CodeGroup>

### Result

|         | Parallel single requests | Batch request | Multicall |
| ------- | ------------------------ | ------------- | --------- |
| Round 1 | 1.789                    | 1.49          | 1.447     |
| Round 2 | 1.896                    | 1.159         | 1.54      |
| Round 3 | 2.337                    | 1.113         | 2.132     |
| Round 4 | 2.942                    | 1.224         | 1.609     |
| Round 5 | 1.638                    | 1.602         | 2.012     |

The test was conducted between a server in Europe and a client in Singapore. A total of 15 measurements were averaged, which shows the performance of batch request > Multicall > normal request. Compared with sending single requests in parallel, batch request reduces 38% of the total request time, and multicall reduces 18% of the total request time.

## Getting the owners of BAYC tokens

Below are the testing scripts using web3.js for making smart contract calls. The tests are based on an ERC-721 standard method [`ownerOf`](https://docs.openzeppelin.com/contracts/2.x/api/token/erc721) from BAYC’s smart contract.

Sending multiple HTTP requests in parallel:

<CodeGroup>
  ```js JavaScript theme={"system"}
  const Web3 = require('web3');
  const web3 = new Web3(new Web3.providers.HttpProvider('YOUR_CHAINSTACK_ENDPOINT'));

  const abi = [{ "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], "name": "ownerOf", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }]
  const contract = new web3.eth.Contract(abi, "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D");

  async function main() {
      const startTime = Date.now()
      console.log("started")
      for (i = 0; i < 30; i++) {
          contract.methods.ownerOf(i).call().then(function(result) {
              console.log(result)
              var timeFromStart = Date.now() - startTime
              console.log("result received in: " + timeFromStart / 1000 + " seconds")
          })
      }
  }

  main();
  ```
</CodeGroup>

Sending batch request:

<CodeGroup>
  ```js JavaScript theme={"system"}
  const { Web3 } = require('web3');
  const web3 = new Web3('YOUR_CHAINSTACK_ENDPOINT);

  const abi = [
    {
      inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }],
      name: "ownerOf",
      outputs: [{ internalType: "address", name: "", type: "address" }],
      stateMutability: "view",
      type: "function",
    },
  ];
  const contract = new web3.eth.Contract(
    abi,
    "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"
  );

  async function main() {
    const startTime = Date.now();
    const batch = new web3.BatchRequest();
    console.log("started");

    // Array to hold promises for each request
    const promises = [];

    for (let i = 0; i < 30; i++) {
      const request = {
        jsonrpc: "2.0",
        id: i + 1,
        method: "eth_call",
        params: [
          {
            to: contract.options.address,
            data: contract.methods.ownerOf(i).encodeABI(),
          },
          "latest",
        ],
      };

      // Add request to the batch and store the promise
      const requestPromise = batch.add(request);
      promises.push(requestPromise);
    }

    // Send the batch request and wait for all responses
    const responses = await batch.execute();

    // Process responses
    responses.forEach((response, index) => {
      if (response.error) {
        console.error(response.error);
      } else {
        const ownerAddress = web3.eth.abi.decodeParameter(
          "address",
          response.result
        );
        const timeFromStart = Date.now() - startTime;
        console.log(
          `${index} token owner is ${ownerAddress} received in: ${
            timeFromStart / 1000
          } seconds`
        );
      }
    });
  }

  main();
  ```
</CodeGroup>

Multicall contract:

<CodeGroup>
  ```js JavaScript theme={"system"}
  const multicall = require("@makerdao/multicall")
  const config = {
      rpcUrl: "YOUR_CHAINSTACK_ENDPOINT",
      multicallAddress: "0xeefba1e63905ef1d7acba5a8513c70307c1ce441"
  };

  async function main() {
      var startTime = Date.now()
      console.log("started")
      var calls = []

      for (i = 0; i < 30; i++) {
          var callObj = {
              target: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
              call: ['ownerOf(uint256)(address)', i],
              returns: [
                  ['OWNER_ADDR ' + i]
              ]
          }
          calls.push(callObj)
      }

      const result = await multicall.aggregate(calls, config);
      console.log(result.results);

      var timeFromStart = Date.now() - startTime
      console.log("Result received in: " + timeFromStart / 1000 + " seconds")
  }

  main();
  ```
</CodeGroup>

### Result

|         | Parallel single requests | Batch request | Multicall |
| ------- | ------------------------ | ------------- | --------- |
| Round 1 | 1.693                    | 1.931         | 1.878     |
| Round 2 | 1.717                    | 1.592         | 1.195     |
| Round 3 | 1.712                    | 1.617         | 2.183     |
| Round 4 | 2.103                    | 1.589         | 1.3       |
| Round 5 | 2.785                    | 1.416         | 1.429     |

The same test was conducted for read contract calls. The result shows that both batch requests and multicall contracts save around 20% of total request time compared with sending single requests.

## Common questions

<AccordionGroup>
  <Accordion title="If I package 100 requests into a single batch request, does that count as 1 request or 100 requests on Chainstack?">
    As an RPC provider, Chainstack counts “request” as RPC calls. After a server receives an HTTP batch request, it “unpacks” the request and processes the calls separately. So from the server’s point of view, 1 batch request of 100 calls consumes 100 requests instead of 1.
  </Accordion>

  <Info>
    Check the [Understanding your request consumption](https://support.chainstack.com/hc/en-us/articles/4412534652313-Understanding-your-request-consumption-over-HTTP-and-WebSocket) page on Chainstack support docs for more details.
  </Info>

  <Accordion title="If I package 100 calls into a single multicall request, does that count as 1 request or 100 requests?">
    In this case, even though it is a very heavy call, it counts as a single request.
  </Accordion>

  <Accordion title="Is there any hard limit for the number of calls to multicall contracts?">
    The BAYC testing script stops working with 1,040 calls.
  </Accordion>
</AccordionGroup>

## Which approach works better for me

Even though tests show that batch request and multicall contract improves performance significantly, they do have their own limitations.

Requests in a batch request are executed in order, which means if a new block is received during execution, the subsequent results are likely to be different.

If you want to use a multicall contract, you should probably deploy your own contract for production just to ensure its availability.

Both batch request and multicall contract return multiple results in a single response. Both of them require much more computational resources. They can easily trigger “request timeout” errors and “response size too big” errors, which makes them not suitable for complex calls.

<Info>
  ### See also

  <CardGroup>
    <Card title="HTTP batch request VS multicall contract" href="/docs/http-batch-request-vs-multicall-contract" icon="angle-right" horizontal />

    <Card title="How to store your Web3 DApp secrets: Guide to environment variables" href="/docs/how-to-store-your-web3-dapp-secrets-guide-to-environment-variables" icon="angle-right" horizontal />

    <Card title="Goerli Sepolia transition" href="/docs/goerli-to-sepolia-transition" icon="angle-right" horizontal />
  </CardGroup>
</Info>

<CardGroup>
  <Card title="Wuzhong Zhu">
    <img src="https://mintcdn.com/chainstack/UN3rP7zhB69idvnC/images/docs/profile_images/1548860905064017921/xCKUgveS_400x400.jpg?fit=max&auto=format&n=UN3rP7zhB69idvnC&q=85&s=075e8dc4db13d9384c63bf4904eb520d" alt="Wuzhong Zhu" style={{width: '80px', height: '80px', borderRadius: '50%', objectFit: 'cover', display: 'block', margin: '0 auto'}} noZoom width="400" height="400" data-path="images/docs/profile_images/1548860905064017921/xCKUgveS_400x400.jpg" />

    <Icon icon="code" iconType="solid" /> Developer Advocate @ Chainstack
    <br /><Icon icon="screwdriver-wrench" iconType="solid" /> BUIDLs on Ethereum, zkEVMs, and The Graph protocol

    <div style={{display: "flex", justifyContent: "center", gap: "12px"}}>
      <a href="https://twitter.com/wuzhongzhu" style={{textDecoration: "none", borderBottom: "none"}}>
        <Icon icon="twitter" iconType="brands" />
      </a>

      <a href="https://www.linkedin.com/in/wuzhong-zhu-44563589/" style={{textDecoration: "none", borderBottom: "none"}}>
        <Icon icon="linkedin" iconType="brands" />
      </a>

      <a href="https://github.com/wuzhong-zhu" style={{textDecoration: "none", borderBottom: "none"}}>
        <Icon icon="github" iconType="brands" />
      </a>
    </div>
  </Card>
</CardGroup>
