Skip to content

Instantly share code, notes, and snippets.

@itoonx
Created May 28, 2021 14:38
Show Gist options
  • Save itoonx/d13489733472d2f6085dc499f084e9da to your computer and use it in GitHub Desktop.
Save itoonx/d13489733472d2f6085dc499f084e9da to your computer and use it in GitHub Desktop.
multicall + web 3 multi provider
const axios = require("axios");
const { MultiCall } = require("eth-multicall");
const crypto = require("crypto");
const Web3 = require("web3");
const _ = require("lodash");
const { providers } = require("ethers");
const { MulticallProvider } = require("@0xsequence/multicall").providers;
const HOSTS = Object.freeze([
// Recommend
"https://bsc-dataseed.binance.org/",
"https://bsc-dataseed1.defibit.io/",
"https://bsc-dataseed1.ninicoin.io/",
// Backup
"https://bsc-dataseed2.defibit.io/",
"https://bsc-dataseed3.defibit.io/",
"https://bsc-dataseed4.defibit.io/",
"https://bsc-dataseed2.ninicoin.io/",
"https://bsc-dataseed3.ninicoin.io/",
"https://bsc-dataseed4.ninicoin.io/",
"https://bsc-dataseed1.binance.org/",
"https://bsc-dataseed2.binance.org/",
"https://bsc-dataseed3.binance.org/",
"https://bsc-dataseed4.binance.org/",
]);
const ENDPOINTS_MULTICALL = {};
const ENDPOINTS_RPC_WRAPPER = {};
HOSTS.forEach((url) => {
ENDPOINTS_MULTICALL[url] = new MulticallProvider(
new providers.StaticJsonRpcProvider({
url: url,
timeout: 10000,
}),
{
contract: "0xB94858b0bB5437498F5453A16039337e5Fdc269C",
batchSize: 50,
timeWindow: 50,
}
);
const f1 = new Web3.providers.HttpProvider(url, {
keepAlive: true,
timeout: 10000,
});
ENDPOINTS_RPC_WRAPPER[url] = new Web3(f1);
});
const ENDPOINTS = Object.freeze(Object.keys(ENDPOINTS_MULTICALL));
module.exports = {
MULTI_CALL_CONTRACT: "0xB94858b0bB5437498F5453A16039337e5Fdc269C",
getEndpoint: () => {
return ENDPOINTS;
},
getWeb3: () => {
return ENDPOINTS_RPC_WRAPPER[
_.shuffle(Object.keys(ENDPOINTS_RPC_WRAPPER))[0]
];
},
fetchContractABI: async (address) => {
const contractUrl =
"https://api.bscscan.com/api?module=contract&action=getabi&address=%address%&%apikey%";
const buildURL = contractUrl
.replace("%address%", address)
.replace("%apikey%", process.env.BSCSCAN_APY_KEY);
const response = await axios.get(buildURL);
return JSON.parse(response.data.result);
},
multiCall: async (vaultCalls) => {
if (vaultCalls.length === 0) {
return [];
}
const promises = [];
const endpoints = _.shuffle(ENDPOINTS.slice());
let i = 0;
const hash = crypto
.createHash("md5")
.update(new Date().getTime().toString())
.digest("hex");
// console.log('chunks', hash, vaultCalls.length, 'to ' + max);
for (const chunk of _.chunk(vaultCalls, 80)) {
const endpoint = endpoints[i];
promises.push(async () => {
let endpointInner = endpoint;
let existing = endpoints.slice();
const done = [];
let throwIt;
for (let i = 0; i < 2; i++) {
if (i > 0) {
existing = _.shuffle(
existing.slice().filter((item) => item !== endpointInner)
);
if (existing.length === 0) {
console.log("no one left", hash);
break;
}
endpointInner = existing[0];
}
try {
done.push(endpointInner);
const [foo] = await new MultiCall(
ENDPOINTS_RPC_WRAPPER[endpointInner],
module.exports.MULTI_CALL_CONTRACT
).all([chunk]);
return foo;
} catch (e) {
console.error(
"failed",
"multiCall",
endpointInner,
chunk.length,
e.message
);
throwIt = e;
}
}
throw new Error(
`final error: ${hash} ${endpointInner} ${
chunk.length
} ${JSON.stringify(done)} ${throwIt.message.substring(0, 100)}`
);
});
}
return (await Promise.all(promises.map((fn) => fn()))).flat(1);
},
multiCallIndexBy: async (index, vaultCalls) => {
const proms = await module.exports.multiCall(vaultCalls);
const results = {};
proms.forEach((c) => {
if (c[index]) {
results[c[index]] = c;
}
});
return results;
},
multiCallRpcIndex: async (calls) => {
const try1 = await module.exports.multiCallRpcIndexInner(calls);
if (try1 === false) {
console.error("multiCallRpcIndex retry");
const try2 = await module.exports.multiCallRpcIndexInner(calls);
if (try2 === false) {
console.error("multiCallRpcIndex final failed");
return [];
}
return try2;
}
return try1;
},
multiCallRpcIndexInner: async (calls) => {
const endpoints = _.shuffle(ENDPOINTS.slice());
const promises = [];
calls.forEach((group) => {
const contract = new ethers.Contract(
group.contractAddress,
group.abi,
ENDPOINTS_MULTICALL[endpoints[0]]
);
group.calls.forEach((call) => {
promises.push(
contract[call.method](...call.parameters).then((r) => {
const reference = call.reference ? call.reference : call.method;
return {
reference: group.reference,
call: [reference, r],
};
})
);
});
});
let results;
try {
results = await Promise.all([...promises]);
} catch (e) {
console.error("failed", "multiCallRpcIndex", e.message);
if (
e.message &&
e.message.includes("property 'toHexString' of undefined")
) {
return false;
}
return [];
}
// pcIndex Cannot read property 'toHexString' of undefined
const final = {};
results.forEach((call) => {
if (!final[call.reference]) {
final[call.reference] = {
id: call.reference,
};
}
final[call.reference][call.call[0]] = call.call[1];
});
return final;
},
multiCallRpc: async (calls) => {
return Object.values(await module.exports.multiCallRpcIndex(calls));
},
};
@lolosj
Copy link

lolosj commented Jun 3, 2021

Can you show an exemple of how to use it ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment