/multicall.py

aggregate results of multiple smart contract calls into one

Primary LanguagePythonMIT LicenseMIT

multicall.py

python interface for makerdao's multicall and a port of multicall.js

installation

pip install git+https://github.com/nucypher/multicall.py

example

from web3 import Web3
from multicall import Call, Multicall

# web3 instance is required
w3 = Web3()

# assuming you are on kovan
MKR_TOKEN = '0xaaf64bfcc32d0f15873a02163e7e500671a4ffcd'
MKR_WHALE = '0xdb33dfd3d61308c33c63209845dad3e6bfb2c674'
MKR_FISH = '0x2dfcedcb401557354d0cf174876ab17bfd6f4efd'

def from_wei(value):
    return value / 1e18

multi = Multicall(
    w3,
    [
        Call(MKR_TOKEN, ['balanceOf(address)(uint256)', MKR_WHALE], [('whale', from_wei)]),
        Call(MKR_TOKEN, ['balanceOf(address)(uint256)', MKR_FISH], [('fish', from_wei)]),
        Call(MKR_TOKEN, 'totalSupply()(uint256)', [('supply', from_wei)]),
    ],
)

multi()  # {'whale': 566437.0921992733, 'fish': 7005.0, 'supply': 1000003.1220798912}

# seth-style calls
Call(MKR_TOKEN, ['balanceOf(address)(uint256)', MKR_WHALE], w3=w3)()
Call(MKR_TOKEN, 'balanceOf(address)(uint256)', w3=w3)(MKR_WHALE)
# return values processing
Call(MKR_TOKEN, 'totalSupply()(uint256)', [('supply', from_wei)], w3=w3)()

for a full example, see implementation of daistats. original daistats.com made by nanexcool.

api

Signature(signature)

  • signature is a seth-style function signature of function_name(input,types)(output,types). it also supports structs which need to be broken down to basic parts, e.g. (address,bytes)[].

use encode_data(args) with input args to get the calldata. use decode_data(output) with the output to decode the result.

Call(target, function, returns, w3=w3)

  • w3 is the instance of Web3
  • target is the to address which is supplied to eth_call.
  • function can be either seth-style signature of method(input,types)(output,types) or a list of [signature, *args].
  • returns is a list of tuples of (name, handler) for return values. if returns argument is omitted, you get a tuple, otherwise you get a dict. to skip processing of a value, pass None as a handler.

use Call(...)() with predefined args or Call(...)(args) to reuse a prepared call with different args.

use decode_output(output) with to decode the output and process it with returns handlers.

Multicall(w3, calls)

  • w3 is the instance of Web3
  • calls is a list of calls with prepared values.

use Multicall(...)() to get the result of a prepared multicall.

Environment Variables

  • GAS_LIMIT: sets overridable default gas limit for Multicall to prevent out of gas errors. Default: 50,000,000
  • MULTICALL_DEBUG: if set, sets logging level for all library loggers to logging.DEBUG
  • AIOHTTP_TIMEOUT: sets aiohttp timeout period in seconds for async calls to node. Default: 30

Tests

It is necessary to declare environment variables with a .env file:

WEB3_PROVIDER_URI=https://mainnet.infura.io/v3/d5xxxxxxxxxxxxxxxxxxxxxxxxxxxxab
pip install -e ".[dev]"
pytest tests/