web3swift icon indicating copy to clipboard operation
web3swift copied to clipboard

Multicall - how to encode a function and passing parameters

Open anmassdevs opened this issue 3 years ago • 2 comments

I am new to this and I really need help on how to make a multicall request. I will appreciate for following helps.

  1. How can I encode a ABI function to send to the parameters to "aggregate" ABI?
  2. How to pass parameters as tuple[]?

Below is multicall ABI in json:

{ "constant": true, "inputs": [ { "components": [ { "name": "target", "type": "address" }, { "name": "callData", "type": "bytes" } ], "name": "calls", "type": "tuple[]" } ], "name": "aggregate", "outputs": [ { "name": "blockNumber", "type": "uint256" }, { "name": "returnData", "type": "bytes[]" } ], "payable": false, "stateMutability": "view", "type": "function" }

anmassdevs avatar Jan 04 '22 08:01 anmassdevs

Hereunder is my test code to encode the "decimals" ABI of the ERC20 and then pass to the "aggregate" but it is not luck. The readTx at the last line is returning nil for me and I wasn't able to call that transaction.

I am using latest web3swift version 2.5.0

`let anAddress = "" let ethereumAddress = EthereumAddress("anAddress")! let erc20Contract = web3.contract(Web3.Utils.erc20ABI, at: ethereumAddress, abiVersion: 2)

let decimalsFunc = erc20Contract.read("decimals") let encodedFunc = decimalsFunc.transaction.encode()

let params = [[(anAddress, encodedFunc)]] as [AnyObject] let readTx = multiCallContract.read("aggregate", parameters: params)`

anmassdevs avatar Jan 04 '22 15:01 anmassdevs

this would work

let requests: [[AnyObject]] = ....compactMap { some in let contract = EthereumContract(Your_ABIString) let methodName = "methodName"

        guard let data = contract?.methods[methodName]?.encodeParameters([parameterOfMethod as AnyObject]) else {
            return nil
        }
        return ["address_for_ABI", data] as [AnyObject]
    }
    

guard let aggregated = multiCall2Contract?.read("aggregate", parameters: [requests] as [AnyObject]) else { return }

aggregated.call....

6od9i avatar May 18 '22 06:05 6od9i

So, web3swift is support multicall contract? I try many way but can't not call muticall.

duongvanthai avatar Dec 13 '22 07:12 duongvanthai

So, web3swift is support multicall contract? I try many way but can't not call muticall.

Yes, it works well, what's the problem?

6od9i avatar Dec 13 '22 08:12 6od9i

Can you post a clear example for multicall? In my case: I want to get all tokenId over tokenOfOwnerByIndex of NFTs. But I don't understand the parameters pass to mutiCallContract.

For example, when I get a token ID I will create ReadOperation like this:

let ethContractAddress = EthereumAddress(contractAddress, ignoreChecksum: true)!
let enumerableContract = web3.contract(enumerableABI, at: ethContractAddress)!
let readOp = enumerableContract.createReadOperation(TokenOfOwnerByIndexOperation, parameters: [ownerWalletAddress, BigUInt(index)] as [AnyObject])!
readOp.transaction.from = EthereumAddress(ownerWalletAddress)
let response = try await readOp.callContractMethod()

How about when I get all at once.

duongvanthai avatar Dec 13 '22 09:12 duongvanthai

Can you post a clear example for multicall? In my case: I want to get all tokenId over tokenOfOwnerByIndex of NFTs. But I don't understand the parameters pass to mutiCallContract.

For example, when I get a token ID I will create ReadOperation like this:

let ethContractAddress = EthereumAddress(contractAddress, ignoreChecksum: true)!
let enumerableContract = web3.contract(enumerableABI, at: ethContractAddress)!
let readOp = enumerableContract.createReadOperation(TokenOfOwnerByIndexOperation, parameters: [ownerWalletAddress, BigUInt(index)] as [AnyObject])!
readOp.transaction.from = EthereumAddress(ownerWalletAddress)
let response = try await readOp.callContractMethod()

How about when I get all at once.

You need multicall smart contract in network you want, than take ABI from it, create Web3.Contract and this multicall contract to multicall-contract address in your network

Use function like "aggregate(Call[] calldata calls)" from multicall-contract with parameters like - array of calls - [Call] as [AnyObject], where Call is tupples - [AnyObject] with address of call and binary data of encoded function with parameters

Binary data get from EthereumContract (not web3.Contract) created from ABI of contracts from your network - like "enumerableABI", or another contracts

like this let contract = EthereumContract(enumerableABI) let method = contract?.methods[TokenOfOwnerByIndexOperation]?.first Call = [contractAddress, method?.encodeParameters([walletAddress as AnyObject]) ?? Data()] as [AnyObject] ......

let multicallContractAddress = EthereumAddress(multicallAddress, ignoreChecksum: true) let multicallContract = web3.contract(multicallABI, at: multicallContractAddress) let readMulticall = multicallContract?.createReadOperation("aggregate", parameters: [[Call]] as [AnyObject]) ....

let responseData = try readMulticall?.callContractMethod() let encodedResponses = responseData["returnData"] as? [Data] /// count equal calls count ....

for decode responses use method?.decodeReturnData(response) in the same order as requests

responseDictionary[i] = method[i].decodeReturnData(encodedResponses[i])

6od9i avatar Dec 13 '22 10:12 6od9i

This is my implement

let web3 = try await Web3.new(urlRPC)

var methodEncode = [ABI.Element.Function]()
var multiRequest = [[AnyObject]]()

for i in 0..<numberOfCall {
      let contract = try EthereumContract(enumerableABI)
      let method = contract.methods[TokenOfOwnerByIndexOperation]?.first
      let call = [contractAddress, method?.encodeParameters([ownerWalletAddress, BigUInt(i)] as [AnyObject]) ?? Data()] as [AnyObject]
      methodEncode.append(method!)
      multiRequest.append(call)
  }
  
let multicallContractAddress = EthereumAddress(MulticallContractAddress, ignoreChecksum: true)
let multicallContract = web3.contract(multicallABI, at: multicallContractAddress)
let readMulticall = multicallContract?.createReadOperation(AggregateOperation, parameters: multiRequest as [AnyObject])
            
let responseData = try await readMulticall?.callContractMethod() -> still return nil 
print("multiCallGetNFTIDs... responseData: \(responseData)")

But still get responseData = nil. Have I been wrong in any step?

duongvanthai avatar Dec 13 '22 12:12 duongvanthai

try one more [] in parameters of multicallContract?.createReadOperation(AggregateOperation

like this let readMulticall = multicallContract?.createReadOperation(AggregateOperation, parameters: [multiRequest] as [AnyObject])

6od9i avatar Dec 13 '22 12:12 6od9i

Because Calls is array of tuples, and tuple in this representation is array too Tell me please what result you have now

6od9i avatar Dec 13 '22 12:12 6od9i

Because Calls is array of tuples, and tuple in this representation is array too Tell me please what result you have now

Thank you very much. I got it. 💯

duongvanthai avatar Dec 13 '22 12:12 duongvanthai

@yaroslavyaroslav

6od9i avatar Dec 13 '22 13:12 6od9i

Great, thank you for your support! @6od9i I've labeled this issue as FAQ one, so other folks could reach it more easily.

yaroslavyaroslav avatar Dec 14 '22 13:12 yaroslavyaroslav