npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ts-multicall

v1.0.3

Published

High-weight optimized call-processor

Downloads

4

Readme

ts-multicall

a fork of @1inch/multicall that utilizes ES Modules and more modern javasscript

Installation

Node

npm install ts-multicall

Onchain addresses

  • Ethereum mainnet: 0x8d035edd8e09c3283463dade67cc0d49d6868063
  • BSC mainnet: 0x804708de7af615085203fa2b18eae59c5738e2a9
  • Polygon mainnet: 0x59a0A6d73e6a5224871f45E6d845ce1574063ADe

Motivation

The MultiCall contract is designed to execute multiple view calls at one time.
For example, you have a list of tokens, and you need to get balances for all the items on that list.

Let's try to do it in the most obvious way:

const provider = new Web3ProviderConnector(new Web3('...'));
const walletAddress = '0x1111111111111111111111111111111111111111';

const tokens = [
  '0x6b175474e89094c44da98b954eedeac495271d0f',
  '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
  '0xdac17f958d2ee523a2206206994597c13d831ec7',
];

const contractCalls = tokens.map((tokenAddress) => {
  const callData = provider.contractEncodeABI(
    ERC20ABI,
    tokenAddress,
    'balanceOf',
    [walletAddress],
  );

  return provider.ethCall(tokenAddress, callData);
});

const balances = await Promise.all(contractCalls);

The downside to this solution is that you make as many requests for a contract as you have tokens on the list.
And if the list is large enough, you will create a significant load on the provider.

Simple MultiCall

A multiCallService.callByChunks() contract takes a list of requests, splits them into chunks and calls the provider in batches.

Default params

  • maxChunkSize: 100
  • retriesLimit: 3
  • blockNumber: 'latest'

Example:

const provider = new Web3ProviderConnector(new Web3('...'));
const walletAddress = '0x1111111111111111111111111111111111111111';
const contractAddress = '0x8d035edd8e09c3283463dade67cc0d49d6868063';
const multiCallService = new MultiCallService(provider, contractAddress);

const tokens = [
  '0x6b175474e89094c44da98b954eedeac495271d0f',
  '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
  '0xdac17f958d2ee523a2206206994597c13d831ec7',
];

// The parameters are optional, if not specified, the default will be used
const params: MultiCallParams = {
  chunkSize: 100,
  retriesLimit: 3,
  blockNumber: 'latest',
};

const callDatas = tokens.map((tokenAddress) => {
  return {
    to: tokenAddress,
    data: provider.contractEncodeABI(ERC20ABI, tokenAddress, 'balanceOf', [
      walletAddress,
    ]),
  };
});

const balances = await multiCallService.callByChunks(callDatas, params);

Got better! Instead of making a separate request to the provider for each item, we group them into butches and make much fewer requests.

Note: If the call to this method exceeds the gas limit, then the entire request will be reverted.

MultiCall by gas limit

Problem:
The point is that the node has a limit for gas per call of the contract.
And it may happen that by making a simple MultiCall we will not meet this limit.
If the gas limit on the node is large enough, we may face a time limit on the execution of the contract method.

In total, there are 2 restrictions on a node:

  • by gas
  • by time

To avoid these limitations, there is a more advanced method:
multiCallService.callByGasLimit()

Default params

  • maxChunkSize: 500
  • retriesLimit: 3
  • blockNumber: 'latest'
  • gasBuffer: 3000000
  • maxGasLimit: 150000000

Example:

const contractAddress = '0x8d035edd8e09c3283463dade67cc0d49d6868063';
const provider = new Web3ProviderConnector(new Web3('...'));

const gasLimitService = new GasLimitService(provider, contractAddress);
const multiCallService = new MultiCallService(provider, contractAddress);

const balanceOfGasUsage = 30_000;

const tokens = [
  '0x6b175474e89094c44da98b954eedeac495271d0f',
  '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
  '0xdac17f958d2ee523a2206206994597c13d831ec7',
];

const requests: MultiCallRequest[] = tokens.map((tokenAddress) => {
  return {
    to: tokenAddress,
    data: provider.contractEncodeABI(ERC20ABI, tokenAddress, 'balanceOf', [
      walletAddress,
    ]),
    gas: balanceOfGasUsage,
  };
});

const gasLimit: number = await gasLimitService.calculateGasLimit();

// The parameters are optional, if not specified, the default will be used
const params: MultiCallParams = {
  maxChunkSize: 500,
  retriesLimit: 3,
  blockNumber: 'latest',
  gasBuffer: 100_000,
};

const response = await multiCallService.callByGasLimit(
  requests,
  gasLimit,
  params,
);

The idea is that we request the gas limit from the node and split the requests into chunks regarding this limit.
Accordingly, we must set the gas limit for each request.

It is noteworthy that if suddenly the request does not fit into the gas limit, the entire request will not be reverted, and the request will return the results of those calls that fit into the gas limit.

If the call to the contract all the same does not fit into the gas limit, then the callByGasLimit() will automatically re-request those elements that have not been fulfilled.

You can see a more detailed description of the library's work in the diagrams below.

GasLimitService

This service is used to correctly calculate the gas limit for calling a MultiCall.
The basic formula for calculating the limit is as follows:

const gasLimitForMultiCall =
  Math.min(gasLimitFromNode, maxGasLimit) - gasBuffer;

Where:
gasLimitFromNode - is the gas limit taken from the node
maxGasLimit - limiter on top, in case the gas limit from the node is too large (may cause timeout)
gasBuffer - is some safe buffer that allows you to avoid crossing the limit in case of unforeseen situations

Example:

const gasLimitForMultiCall = Math.min(12_000_000, 40_000_000) - 100_000; // 11_990_000

We believe that the multicall call should fit into 11_990_000 gas.

Default params:

  • gasBuffer: 3000000
  • maxGasLimit: 150000000

Params for GasLimitService.calculateGasLimit() are optional, if not specified, then gas limit will be requested from the node and the default params will be used.

Example:

const contractAddress = '0x8d035edd8e09c3283463dade67cc0d49d6868063';
const provider = new Web3ProviderConnector(new Web3('...'));

const gasLimitService = new GasLimitService(provider, contractAddress);

const gasLimit: number = await gasLimitService.calculateGasLimit();

Alternatively, you can specify your own parameters:

const contractAddress = '0x8d035edd8e09c3283463dade67cc0d49d6868063';
const provider = new Web3ProviderConnector(new Web3('...'));

const gasLimitService = new GasLimitService(provider, contractAddress);

// 190_000
const gasLimit: number = await gasLimitService.calculateGasLimit({
  gasLimit: 200_000,
  maxGasLimit: 200_000,
  gasBuffer: 10_000,
});

Contract code


Algorithm activity diagram

approve


Algorithm visualization