Created
May 28, 2021 14:38
-
-
Save itoonx/d13489733472d2f6085dc499f084e9da to your computer and use it in GitHub Desktop.
multicall + web 3 multi provider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
}, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you show an exemple of how to use it ?