为什么要对ethers-multicall进行二次开发?
在项目中,假设有100处使用了multicall同时发了600个call请求,每个muticall里有1~N个call,则至少要发送100个multicall rpc请求,在浏览器中很容易造成堵塞或者失败的情况
实现:100处muticall600个call,只用2个rpc进行请求思路:
1、重写multicall.all方法
export function all(
calls: ContractCall[],
multicallAddress: string,
provider: any,
): Promise {
return new Promise(async (resolve, reject) => {
if (QData['call_' + callId[chainId] + chainId].queryCalls.length + calls.length > MAX_CALLS) {
await handleData();
debounce(resolve);
} else {
debounce(resolve);
}
});
}
2、在all方法中收集所有的calls,provider
3、设置muticall最大容量(容器)以及使用溢出id与chainId区分
const MAX_CALLS = 500;
const callId = {
//128:0,
//56:0
};
const QData = {
// call_1280: {
// queryCalls: [],
// resolveList: [],
// timeDebounce: null,
// lenArr: []
// }
};
4、,初始化:设置callId区分每一个容器,callId需要使用chainId进行区分,provider可以获取
const chainId = provider._network.chainId;
if (!callId[chainId]) {
callId[chainId] = 0;
}
if (!QData['call_' + callId[chainId] + chainId]) {
QData['call_' + callId[chainId] + chainId] = {
queryCalls: [],
resolveList: [],
timeDebounce: null,
lenArr: []
};
}
5、当每一次容器满了之后,再去发请求获取数据
async function handleData() {
const qKey = 'call_' + callId[chainId] + chainId;
const {queryCalls, resolveList, timeDebounce, lenArr} = QData[qKey];
clearTimeout(timeDebounce);
delete QData[qKey];
callId[chainId] = callId[chainId] + 1;
QData[qKey] = {
queryCalls: [],
resolveList: [],
timeDebounce: null,
lenArr: []
};
const multicall = new ethers.Contract(multicallAddress, multicallAbi, provider);
const callRequests = queryCalls.map(call => {
const callData = Abi.encode(call.name, call.inputs, call.params);
return {
target: call.contract.address,
callData,
};
});
const response: any = await multicall.callStatic.aggregate(callRequests);
const callResult = [] as T;
for (let i = 0; i {
const data = callResult.splice(0, lenArr.shift());
resolve(data);
});
}
6、设置debounce,避免容器不满导致过长的请求,以及不满的情况下进行push处理,calls,resolve,calls.length
function debounce(resolve) {
const qKey = 'call_' + callId[chainId] + chainId;
QData[qKey].queryCalls.push(...calls);
QData[qKey].resolveList.push(resolve);
QData[qKey].lenArr.push(calls.length);
clearTimeout(QData[qKey].timeDebounce);
QData[qKey].timeDebounce = setTimeout(async () => {
await handleData();
}, 100);
}
code
const MAX_CALLS = 500;
const callId = {};
// tslint:disable-next-line:variable-name
const QData = {
// call_0: {
// queryCalls: [],
// resolveList: [],
// timeDebounce: null,
// lenArr: []
// }
};
export function all(
calls: ContractCall[],
multicallAddress: string,
provider: any,
): Promise {
const chainId = provider._network.chainId;
if (!callId[chainId]) {
callId[chainId] = 0;
}
if (!QData['call_' + callId[chainId] + chainId]) {
QData['call_' + callId[chainId] + chainId] = {
queryCalls: [],
resolveList: [],
timeDebounce: null,
lenArr: []
};
}
async function handleData() {
const qKey = 'call_' + callId[chainId] + chainId;
const {queryCalls, resolveList, timeDebounce, lenArr} = QData[qKey];
clearTimeout(timeDebounce);
delete QData[qKey];
callId[chainId] = callId[chainId] + 1;
QData[qKey] = {
queryCalls: [],
resolveList: [],
timeDebounce: null,
lenArr: []
};
const multicall = new ethers.Contract(multicallAddress, multicallAbi, provider);
const callRequests = queryCalls.map(call => {
const callData = Abi.encode(call.name, call.inputs, call.params);
return {
target: call.contract.address,
callData,
};
});
const response: any = await multicall.callStatic.aggregate(callRequests);
const callResult = [] as T;
for (let i = 0; i {
const data = callResult.splice(0, lenArr.shift());
resolve(data);
});
}
function debounce(resolve) {
const qKey = 'call_' + callId[chainId] + chainId;
QData[qKey].queryCalls.push(...calls);
QData[qKey].resolveList.push(resolve);
QData[qKey].lenArr.push(calls.length);
clearTimeout(QData[qKey].timeDebounce);
QData[qKey].timeDebounce = setTimeout(async () => {
await handleData();
}, 100);
}
return new Promise(async (resolve, reject) => {
if (QData['call_' + callId[chainId] + chainId].queryCalls.length + calls.length > MAX_CALLS) {
await handleData();
debounce(resolve);
} else {
debounce(resolve);
}
});
}
插件地址
https://www.npmjs.com/package/ethers-multicall-x
npm i ethers-multicall-x
用法,同ethers-multicall,以上优化只在插件内部实现