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 🙏

© 2026 – Pkg Stats / Ryan Hefner

evm-batchcall

v1.0.1

Published

Advanced EVM batch calling library with caching, transformations, and unlimited nesting depth

Readme

EVM BatchCall

Advanced EVM batch calling library with caching, transformations, and unlimited nesting depth. Built on top of Multicall3 for efficient blockchain data retrieval.

Features

  • 🚀 Batch Execution: Execute multiple contract calls in a single request
  • 💾 Smart Caching: Built-in caching with TTL support (instance or shared)
  • 🔄 Transformations: Transform results on-the-fly
  • 🎯 MapEach: Map over arrays and create nested calls with unlimited depth
  • 🛡️ Error Handling: Fallback values and retry mechanisms
  • Performance: Automatic chunking for large batches
  • 🎨 Type Safe: Full TypeScript support

Installation

npm install evm-batchcall ethers

Quick Start

import { BatchCall } from 'evm-batchcall';
import { ethers } from 'ethers';

// Initialize provider
const provider = new ethers.JsonRpcProvider('YOUR_RPC_URL');

// Create contract instances
const tokenContract = new ethers.Contract(TOKEN_ADDRESS, TOKEN_ABI, provider);

// Execute batch calls
const result = await new BatchCall(provider)
  .contract('token', tokenContract)
  .method('balanceOf', [userAddress])
  .as('balance')
  .method('totalSupply', [])
  .as('totalSupply')
  .executeBatch();

console.log(result.balance); // User's balance
console.log(result.totalSupply); // Token total supply

Core Concepts

1. Basic Batching

Execute multiple calls efficiently:

const result = await new BatchCall(provider)
  .registerContracts({
    token: tokenContract,
    nft: nftContract
  })
  .contract('token')
  .method('balanceOf', [user])
  .as('tokenBalance')
  .contract('nft')
  .method('balanceOf', [user])
  .as('nftBalance')
  .executeBatch();

2. Transformations

Transform results on-the-fly:

const result = await new BatchCall(provider)
  .contract('token', tokenContract)
  .method('balanceOf', [user])
  .as('balance')
  .transform(balance => ethers.formatEther(balance))
  .executeBatch();

console.log(result.balance); // "1.5" instead of BigInt

3. Fallback Values

Handle errors gracefully:

const result = await new BatchCall(provider)
  .contract('token', tokenContract)
  .method('balanceOf', [user])
  .as('balance')
  .fallback('0')
  .executeBatch();

4. Caching

Enable caching to avoid redundant calls:

// Shared cache (default) - cached across all instances
const result1 = await new BatchCall(provider)
  .withCache(30000) // Cache for 30 seconds
  .contract('token', tokenContract)
  .method('totalSupply', [])
  .executeBatch();

// This will hit the cache!
const result2 = await new BatchCall(provider)
  .withCache(30000)
  .contract('token', tokenContract)
  .method('totalSupply', [])
  .executeBatch();

// Clear shared cache
BatchCall.clearSharedCache();

// Or use instance-only cache
const result3 = await new BatchCall(provider)
  .withCache(30000, false) // shared = false
  .contract('token', tokenContract)
  .method('totalSupply', [])
  .executeBatch();

5. MapEach - Nested Calls

Map over array results and create nested calls:

const result = await new BatchCall(provider)
  .contract('launchpad', launchpadContract)
  .contract('token', tokenContract)
  .contract('launchpad')
  .method('getAllLaunchpads', [])
  .as('launchpadIds')
  .mapEach('launchpadIds', (id) => [
    { 
      contract: 'launchpad', 
      method: 'launchpads', 
      args: [id], 
      alias: 'data',
      transform: (data) => ({ ...data, id })
    },
    { 
      contract: 'token', 
      method: 'balanceOf', 
      args: [id], 
      alias: 'balance',
      fallback: 0n
    }
  ])
  .as('details')
  .executeBatch();

console.log(result.details); // Array of { data, balance }

6. Filtering MapEach Results

Filter items before creating nested calls:

const result = await new BatchCall(provider)
  .contract('nft', nftContract)
  .method('getAllTokenIds', [])
  .as('tokenIds')
  .mapEach('tokenIds', (tokenId) => [
    { contract: 'nft', method: 'ownerOf', args: [tokenId], alias: 'owner' }
  ])
  .filter((tokenId) => tokenId < 1000) // Only process first 1000
  .as('owners')
  .executeBatch();

7. Conditional Calls

Add calls conditionally:

const includeAllowance = true;

const result = await new BatchCall(provider)
  .contract('token', tokenContract)
  .method('balanceOf', [user])
  .as('balance')
  .when(includeAllowance, (bc) => {
    bc.method('allowance', [user, spender]).as('allowance');
  })
  .executeBatch();

8. Advanced Options

Customize execution behavior:

const result = await new BatchCall(provider)
  .contract('token', tokenContract)
  .method('balanceOf', [user])
  .executeBatch({
    chunkSize: 50,          // Split into chunks of 50 calls
    retryAttempts: 5,       // Retry failed calls 5 times
    retryDelay: 2000,       // Wait 2s between retries
    continueOnError: true,  // Don't stop on errors
    timeout: 60000          // 60s timeout per call
  });

9. Multiple Methods at Once

Add multiple method calls in one go:

const result = await new BatchCall(provider)
  .contract('token', tokenContract)
  .methods(
    { method: 'name', args: [] },
    { method: 'symbol', args: [] },
    { method: 'decimals', args: [] }
  )
  .executeBatch();

10. Clone and Reuse

Reuse configurations:

const baseCall = new BatchCall(provider)
  .contract('token', tokenContract);

const balance1 = await baseCall.clone()
  .method('balanceOf', [user1])
  .executeBatch();

const balance2 = await baseCall.clone()
  .method('balanceOf', [user2])
  .executeBatch();

API Reference

Constructor

new BatchCall(provider: ethers.Provider)

Methods

Configuration

  • withCache(ttl?: number, shared?: boolean) - Enable caching
  • clearCache(clearShared?: boolean) - Clear cache
  • setMulticallAddress(address: string) - Set custom Multicall3 address

Contract Management

  • contract(name: string, instance?: ethers.Contract) - Register/select contract
  • registerContracts(registry: ContractRegistryMap) - Register multiple contracts

Building Calls

  • method(name: string, args?: any[]) - Add a method call
  • methods(...calls) - Add multiple method calls
  • as(alias: string) - Alias the last call
  • transform(fn: (result) => any) - Transform the last call result
  • fallback(value: any) - Set fallback for the last call
  • when(condition: boolean, callback) - Conditional calls
  • mapEach(sourcePath: string, mapper) - Map over array results
  • filter(predicate) - Filter mapEach items (chain after mapEach)

Execution

  • execute(options?) - Execute sequentially (no batching)
  • executeBatch(options?) - Execute in batch using Multicall3

Utilities

  • buildCalls() - Get raw multicall structure
  • getCallCount() - Get number of calls
  • reset() - Clear all calls
  • clone() - Clone the instance

Static Methods

  • BatchCall.clearSharedCache() - Clear all shared cache

Execution Options

interface ExecutionOptions {
  chunkSize?: number;        // Default: 100
  retryAttempts?: number;    // Default: 3
  retryDelay?: number;       // Default: 1000ms
  continueOnError?: boolean; // Default: false
  timeout?: number;          // Default: 30000ms
}

Advanced Examples

Complex DeFi Query

const defiData = await new BatchCall(provider)
  .withCache(60000) // Cache for 1 minute
  .registerContracts({
    pool: poolContract,
    token0: token0Contract,
    token1: token1Contract,
    router: routerContract
  })
  .contract('pool')
  .method('getReserves', [])
  .as('reserves')
  .transform(([r0, r1]) => ({ reserve0: r0, reserve1: r1 }))
  .method('totalSupply', [])
  .as('lpTotalSupply')
  .contract('token0')
  .method('balanceOf', [userAddress])
  .as('token0Balance')
  .method('decimals', [])
  .as('token0Decimals')
  .contract('token1')
  .method('balanceOf', [userAddress])
  .as('token1Balance')
  .method('decimals', [])
  .as('token1Decimals')
  .executeBatch();

NFT Collection Analysis

const nftData = await new BatchCall(provider)
  .contract('nft', nftContract)
  .method('totalSupply', [])
  .as('totalSupply')
  .transform(supply => Number(supply))
  .mapEach('totalSupply', (_, index) => {
    const tokenId = index + 1;
    return [
      { 
        contract: 'nft', 
        method: 'ownerOf', 
        args: [tokenId], 
        alias: 'owner',
        fallback: ethers.ZeroAddress
      },
      { 
        contract: 'nft', 
        method: 'tokenURI', 
        args: [tokenId], 
        alias: 'uri',
        fallback: ''
      }
    ];
  })
  .filter((_, index) => index < 100) // First 100 only
  .as('tokens')
  .executeBatch({ chunkSize: 25 });

Network Support

Works on any EVM-compatible network that has Multicall3 deployed:

  • Ethereum Mainnet
  • Polygon
  • Arbitrum
  • Optimism
  • Base
  • BSC
  • Avalanche
  • And more...

The default Multicall3 address (0xcA11bde05977b3631167028862bE2a173976CA11) is deployed on most networks. For custom deployments, use setMulticallAddress().

Performance Tips

  1. Use batching: Always prefer executeBatch() over execute()
  2. Enable caching: Use withCache() for frequently accessed data
  3. Set appropriate chunk sizes: Tune chunkSize based on your RPC limits
  4. Use transformations: Process data during batch execution
  5. Filter mapEach results: Reduce unnecessary calls with .filter()

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues and questions, please open an issue on GitHub.