原创

二次开发ethers-multicall


为什么要对ethers-multicall进行二次开发?

在项目中,假设有100处使用了multicall同时发了600个call请求,每个muticall里有1~N个call,则至少要发送100个multicall rpc请求,在浏览器中很容易造成堵塞或者失败的情况

实现:100处muticall600个call,只用2个rpc进行请求

思路:

1、重写multicall.all方法

export function all<T extends any[] = any[]>(
  calls: ContractCall[],
  multicallAddress: string,
  provider: any,
): Promise<T> {
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 < queryCalls.length; i++) {
      const outputs = queryCalls[i].outputs;
      const returnData = response.returnData[i];
      const params = Abi.decode(outputs, returnData);
      const result = outputs.length === 1 ? params[0] : params;
      callResult.push(result);
    }
	// 处理返回值,前提在存call的时候,将call的长度也一并存入,返回值按顺序通过长度截取
    resolveList.map(resolve => {
      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<T extends any[] = any[]>(
  calls: ContractCall[],
  multicallAddress: string,
  provider: any,
): Promise<T> {
  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 < queryCalls.length; i++) {
      const outputs = queryCalls[i].outputs;
      const returnData = response.returnData[i];
      const params = Abi.decode(outputs, returnData);
      const result = outputs.length === 1 ? params[0] : params;
      callResult.push(result);
    }
    resolveList.map(resolve => {
      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,以上优化只在插件内部实现

JavaScript
  • 作者:零三(联系作者)
  • 最后更新时间:2021-10-27 15:12
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 转载声明:来源地址 https://web03.cn
  • 评论