如果需要一次调用很多合约请求的话,直接通过rpc请求会等很久,我们可以通过Multicall.js
来batch rpc请求为一个rpc,这样可以节省时间。
安装
Multicall.js
1
| npm i @makerdao/multicall
|
用法
在代码中引用:
1
| const {createWatcher} = require('@makerdao/multicall');
|
首先需要设定config:
1 2 3 4
| const config = { preset: 'rinkeby' }
|
因为目前是在测试网测试的,所以采用rinkeby
的预设。
但感觉这个预设好像有点问题,所以我们还是手动配置一下:
1 2 3 4
| const multiCallConfig = { multicallAddress: '0x5ba1e12693dc8f9c48aad8770482f4739beed696', rpcUrl: 'https://rinkeby.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', }
|
multicallAddress
可以在github multicall找到。
rpcUrl
可以在rpc.info中找到。
然后创建watcher:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const watcher = createWatcher( [ { target: ContractId, call: ['balanceOf(address)(uint256)', userAddress], return [['balanceOf']], } ] )
watcher.batch().subscribe(updates => { for (const item of updates) { console.log('item', item) } }); watcher.start();
|
这样就是一个简单的例子。
watcher.start()
就表示正在调用rpc请求了,请求成功之后会在watcher.batch().subscribe()
里面能收到。
mulitcall
的官网文档没有写清楚,导致我试了好久才正直用上了它。
calls解析
我们给createWatcher
传递的是一个calls数组:
- target 代表的是请求的合约地址
- call 里面配置具体要请求什么方法,以及传递的参数
- returns 代表的是接收rpc请求返回的key。
看下call的配置
1
| call: ['balanceOf(address)(uint256)', userAddress],
|
传给call的是一个数组,数组的第一项会被解析为合约的方法,之后的项会被解析为方法的参数。
第一项里面,前面是方法名称,第一个括号里面是方法的参数,注意这里是参数的类型,而不是参数的名字,第二个括号里面是返回值的类型。
然后还需要注意returns里面的key配置,如果key一样的话,multiCall会把结果合并为一个,所以需要特别注意retures里面的key都需要不同。
实例
假设我们现在需要获取某个用户在合约里面的所有nft列表。
思路:
- 调用
balanceOf
方法获取账户拥有的代币数量。
- 调用
tokenOfOwnerByIndex
方法获取账号在给定索引处拥有的tokenId。
balanceOf
和tokenOfOwnerByIndex
这两个方法是ERC721
标准里面的标准方法,每个合约都会有该方法。可以查看ERC721 标准。
调用balanceOf
会得到用户的nft数量,然后枚举这个数量,可以通过tokenOfOwnerByIndex
方法得到该索引的tokenId,这样就能获得该用户的所有nft。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| const multiCallConfig = { multicallAddress: '0x5ba1e12693dc8f9c48aad8770482f4739beed696', rpcUrl: 'https://rinkeby.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', }
getAllNFTs() { return fromPromise(this.getAccount()).pipe( switchMap(() => { const contractInstance = this.getContractInstance(); return fromPromise(contractInstance.methods.balanceOf(this._account[0]).call()) }), switchMap((spacePunksBalance) => { return new Promise((resolve, reject) => { try {
const start = new Date().getTime(); console.log('start rpc get token') const tokenList = []; const queryList = [] for (let i = 0; i < spacePunksBalance; i++) { queryList.push( { target: ContractAddress, call: ['tokenOfOwnerByIndex(address,uint256)(uint256)', this._account[0], i], returns: [[`tokenId${i}`]] }, )
} const watcher = createWatcher( queryList, multiCallConfig ); watcher.batch().subscribe(updates => { for (const item of updates) { tokenList.push(item.value.toNumber()); } const end = new Date().getTime() console.log('[get all token] rpc progress time: ', (end - start) / 1000) resolve(tokenList); }); watcher.start(); } catch (e) { reject(e) }
})
}) ) }
|
可以看到这个去请求100个nft的时候,时间开销猜不到1秒,快了很多了。