ethereum-multicall

repository·master·Indexed 18 days ago

https://github.com/joshstevens19/ethereum-multicall

A lightweight TypeScript library for interacting with the Multicall3 smart contract. It allows multiple constant function calls to be aggregated into a single JSON RPC request, improving network efficiency and ensuring atomicity by guaranteeing all returned values are from the same block. The library is unopinionated and compatible with web3.js, ethers.js, or custom node URLs.

Tokens
8.8K
Snippets
29
Records
35
Agent score
61%

What's inside ethereum-multicall

  1. What is ethereum-multicall?

    master

    ethereum-multicall is a lightweight TypeScript library designed for interacting with the Multicall3 smart contract.

    It allows you to group multiple smart contract constant function calls into a single JSON RPC request. This provides two main benefits:

    1. Efficiency: Reduces the number of network requests (useful when using providers like Infura).
    2. Atomicity: Guarantees that all returned values are from the same block. The library also returns the latest block number used for the call.

    The library is unopinionated; it works with web3, ethers, or a custom nodeUrl. It handles data decoding by default, but this can be disabled if preferred.

  2. Use tryAggregate to prevent batch failure

    master

    By default, if a single eth_call within a multicall fails, the entire batch will throw an error. To prevent this, set tryAggregate: true in the Multicall constructor options.

    When tryAggregate is enabled, failed calls will still return in the expected order, but their success property in the callsReturnContext will be false.

    Note: This requires a Multicall2 deployment. It will not work with Multicall version 1 deployments.

    const multicall = new Multicall({ 
      nodeUrl: 'https://some.local-or-remote.node:8546', 
      tryAggregate: true 
    });
    
    // If a call fails, the result will look like this:
    // callsReturnContext: [{ 
    //    success: false, 
    //    returnValues: [], 
    //    decoded: false, 
    //    ... 
    // }]
  3. Handle overloaded contract methods

    master

    When a contract has multiple functions with the same name but different signatures (overloading), you must use the full typed signature in the methodName field of your ContractCallContext instead of just the function name. This ensures the correct function is targeted during the multicall.

    const contractCallContext: ContractCallContext = {
      reference: 'upV2Controller',
      contractAddress: '0x19891DdF6F393C02E484D7a942d4BF8C0dB1d001',
      abi: [
        { name: 'getVirtualPrice', type: 'function', inputs: [], outputs: [{ type: 'uint256' }], stateMutability: 'view' },
        { name: 'getVirtualPrice', type: 'function', inputs: [{ type: 'uint256' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' }
      ],
      calls: [
        { reference: 'getVirtualPriceWithInput', methodName: 'getVirtualPrice(uint256)', methodParameters: ['0xFFFFFFFFFFFFF'] },
        { reference: 'getVirtualPriceWithoutInput', methodName: 'getVirtualPrice()', methodParameters: [] },
      ],
    };
  4. Import ethereum-multicall

    master

    Depending on your environment, use the appropriate import syntax:

    JavaScript (ES3)

    var ethereumMulticall = require('ethereum-multicall');

    JavaScript (ES5 or ES6)

    const ethereumMulticall = require('ethereum-multicall');

    JavaScript (ES6) / TypeScript

    import {
      Multicall,
      ContractCallResults,
      ContractCallContext,
    } from 'ethereum-multicall';
  5. Configure a custom Multicall contract address

    master

    The library automatically detects the network from your provider and uses a known multicall address (defaulting to 0xcA11bde05977b3631167028862bE2a173976CA11, except on etherlite). If you need to use a specific multicall deployment, provide the address via the multicallCustomContractAddress option when instantiating Multicall.

    const multicall = new Multicall({
      multicallCustomContractAddress: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
      // ... other config
    });
  6. Initialize the Multicall client

    master

    To use the Multicall class, you must instantiate it by providing one of three supported provider options. The constructor automatically detects which provider type you are using based on the keys present in the options object.

    Supported options:

    • Ethers: Provide an ethersProvider.
    • Web3: Provide a web3Instance.
    • Custom HTTP: Provide a nodeUrl for a custom JSON-RPC provider.

    If the provided options do not match one of these interfaces, the constructor will throw an error.

    import { Multicall } from 'ethereum-multicall';
    import { ethers } from 'ethers';
    
    // Example using Ethers provider
    const multicall = new Multicall({
      ethersProvider: new ethers.providers.JsonRpcProvider('YOUR_RPC_URL')
    });
  7. Use Multicall with web3.js

    master

    Initialize the Multicall class by passing a web3Instance. Use the .call() method with an array of ContractCallContext objects to execute batch calls.

    import { Multicall, ContractCallResults, ContractCallContext } from 'ethereum-multicall';
    import Web3 from 'web3';
    
    const web3 = new Web3('https://some.local-or-remote.node:8546');
    const multicall = new Multicall({ web3Instance: web3, tryAggregate: true });
    
    const contractCallContext: ContractCallContext[] = [
        {
            reference: 'testContract',
            contractAddress: '0x6795b15f3b16Cf8fB3E56499bbC07F6261e9b0C3',
            abi: [ { name: 'foo', type: 'function', inputs: [ { name: 'example', type: 'uint256' } ], outputs: [ { name: 'amounts', type: 'uint256' }] } ],
            calls: [{ reference: 'fooCall', methodName: 'foo', methodParameters: [42] }]
        }
    ];
    
    const results: ContractCallResults = await multicall.call(contractCallContext);
    console.log(results);
  8. Use Multicall with ethers.js

    master

    Initialize the Multicall class by passing an ethersProvider. You can pass any ethers provider context (e.g., a Wallet or Signer). Use the .call() method with an array of ContractCallContext objects to execute batch calls.

    import { Multicall, ContractCallResults, ContractCallContext } from 'ethereum-multicall';
    import { ethers } from 'ethers';
    
    let provider = ethers.getDefaultProvider();
    const multicall = new Multicall({ ethersProvider: provider, tryAggregate: true });
    
    const contractCallContext: ContractCallContext[] = [
        {
            reference: 'testContract',
            contractAddress: '0x6795b15f3b16Cf8fB3E56499bbC07F6261e9b0C3',
            abi: [ { name: 'foo', type: 'function', inputs: [ { name: 'example', type: 'uint256' } ], outputs: [ { name: 'amounts', type: 'uint256' }] } ],
            calls: [{ reference: 'fooCall', methodName: 'foo', methodParameters: [42] }]
        }
    ];
    
    const results: ContractCallResults = await multicall.call(contractCallContext);
    console.log(results);
  9. Specify a block number for calls

    master

    When calling .call(), you can pass an optional second argument of type ContractCallOptions to specify a blockNumber. This allows you to retrieve data from a specific block height. This is compatible with both ethers and web3 providers.

    const results = await multicall.call(contractCallContext, {
        blockNumber: '14571050'
    });
  10. Pass extra context to ContractCallContext

    master

    You can attach arbitrary state or metadata to a ContractCallContext using the context property. This data is returned within the ContractCallResults object, allowing you to easily map results back to your application state without manual array lookups. Use generics to define the shape of your context: ContractCallContext<{yourKey: string}>.

    const contractCallContext: ContractCallContext<{extraContext: string, foo4: boolean}>[] = [
        {
            reference: 'testContract',
            contractAddress: '0x6795b15f3b16Cf8fB3E56499bbC07F6261e9b0C3',
            abi: [ { name: 'foo', type: 'function', inputs: [ { name: 'example', type: 'uint256' } ], outputs: [ { name: 'amounts', type: 'uint256' }] } ],
            calls: [{ reference: 'fooCall', methodName: 'foo', methodParameters: [42] }],
            context: {
              extraContext: 'extraContext',
              foo4: true
            }
        }
    ];
    
    const results = await multicall.call(contractCallContext);
    // results.results.testContract.originalContractCallContext.context will contain the provided object
  11. Configure Multicall options

    master

    When instantiating Multicall, you can configure several behaviors via the options object:

    OptionTypeDescription
    ethersProviderethers.providers.ProviderUsed if you want to use the Ethers.js provider pattern.
    web3InstanceWeb3Used if you want to use the Web3.js provider pattern.
    nodeUrlstringUsed to connect to a custom JSON-RPC endpoint.
    tryAggregatebooleanIf true, the client uses tryBlockAndAggregate on the multicall contract, allowing individual calls in a batch to fail without reverting the entire transaction.
    multicallCustomContractAddressstringAllows you to override the default multicall contract address for the detected network.