From 5b7013a563854724c7da8150c29c8b11a789f1d1 Mon Sep 17 00:00:00 2001 From: edwin082 Date: Wed, 25 Jan 2023 22:19:07 +0900 Subject: [PATCH] Add Multicall page to the Misc menu --- src/routes.tsx | 8 + src/views/Miscellaneous/Multicall.tsx | 286 ++++++++++++++++ .../Miscellaneous/constants/exMulticallAbi.ts | 315 ++++++++++++++++++ .../constants/exMulticallData.ts | 20 ++ 4 files changed, 629 insertions(+) create mode 100644 src/views/Miscellaneous/Multicall.tsx create mode 100644 src/views/Miscellaneous/constants/exMulticallAbi.ts create mode 100644 src/views/Miscellaneous/constants/exMulticallData.ts diff --git a/src/routes.tsx b/src/routes.tsx index 920d2e5..79bc9fd 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -26,6 +26,7 @@ import KeccakFromString from './views/Miscellaneous/KeccakFromString' import LoadKeystore from './views/Miscellaneous/LoadKeystore' import SoulboundNFT from './views/Miscellaneous/SoulboundNFT' import WKLAY from './views/Miscellaneous/WKLAY' +import Multicall from './views/Miscellaneous/Multicall' import Web3modalExample from './views/Web3modal' import Web3modalNFT from './views/Web3modal/web3modalNFT' @@ -275,6 +276,13 @@ const routes: RouteType[] = [ description: 'Interact with canonical-WKLAY contract and tests functions: Deposit, Withdraw, Approve, and Transfer', }, + { + path: '/multicall', + name: 'Multicall', + component: Multicall, + description: + 'Aggregates results from multiple contract constant function calls', + }, ], }, ] diff --git a/src/views/Miscellaneous/Multicall.tsx b/src/views/Miscellaneous/Multicall.tsx new file mode 100644 index 0000000..704b65c --- /dev/null +++ b/src/views/Miscellaneous/Multicall.tsx @@ -0,0 +1,286 @@ +import { ReactElement, useMemo, useState } from 'react' +import Caver from 'caver-js' +import _ from 'lodash' + +import { URLMAP, COLOR } from 'consts' +import { + Button, + Card, + CardHeader, + CardBody, + Container, + Text, + CardSection, + CodeBlock, + View, + Label, + FormInput, + LinkA, +} from 'components' + +import { exWKLAYAbi } from './constants/exWKLAYAbi' +import { exMulticallAbi } from './constants/exMulticallAbi' +import { + exposureTime, + contractAddress, + wklayContractAddress, + exAddress1, + exAddress2, + exAddress3, + exAddress4, + exAddress5, +} from './constants/exMulticallData' + +type Res = { + success: boolean + returnData: string +} + +const Multicall = (): ReactElement => { + const caver = useMemo(() => new Caver(URLMAP.network['testnet']['rpc']), []) + + const [multicallMsg, setMulticallMsg] = useState('') + const [multicallButtonDisabled, setMulticallButtonDisabled] = useState(false) + const [multicallSuccess, setMulticallSuccess] = useState(false) + + const [address1, setAddress1] = useState(exAddress1) + const [address2, setAddress2] = useState(exAddress2) + const [address3, setAddress3] = useState(exAddress3) + const [address4, setAddress4] = useState(exAddress4) + const [address5, setAddress5] = useState(exAddress5) + + const [outputArray, setOuputArray] = useState(Array) + + const multicall = async (): Promise => { + try { + setMulticallButtonDisabled(true) + + const wklay = new caver.contract( + JSON.parse(JSON.stringify(exWKLAYAbi)), + wklayContractAddress + ) + + const multicall = new caver.contract( + JSON.parse(JSON.stringify(exMulticallAbi)), + contractAddress + ) + + const calls = [ + wklay.methods.balanceOf(address1), + wklay.methods.balanceOf(address2), + wklay.methods.balanceOf(address3), + wklay.methods.balanceOf(address4), + wklay.methods.balanceOf(address5), + ] + + const callRequests = calls.map((call) => ({ + target: call._parent._address, + callData: call.encodeABI(), + })) + + const { returnData } = await multicall.call( + 'tryBlockAndAggregate', + false, + callRequests + ) + let output = returnData.map((data: Res, index: number) => { + const types = calls[index]._method.outputs + const { __length__, ...result } = caver.abi.decodeParameters( + types, + data.returnData + ) + return Object.values(result) + }) + + output = output.map((elem: Array) => + caver.utils.fromPeb(elem[0], 'KLAY') + ) + setOuputArray(output) + + if (output.length === 5) { + setMulticallMsg('Multicall is successfully called.') + setMulticallButtonDisabled(false) + setMulticallSuccess(true) + } else { + throw Error('Multicall is failed') + } + } catch (err) { + setMulticallMsg(_.toString(err)) + setMulticallButtonDisabled(false) + setMulticallSuccess(false) + + setTimeout(() => { + setMulticallMsg('') + }, exposureTime) + } + } + + return ( + + + +

Multicall - Check the WKLAY Balance at Once

+ + Multicall aggregates result from multiple smart contract constant + function calls in a single call. It reduces several JSON RPC + requests while guaranteeing that all returned values are from the + same block, like an atomic read. The following example comes from + the 'Miscellaneous menu - Canonical WKLAY page'. You can deposit or + transfer the WKLAY to other addresses on that page. After it, change + the account addresses as you like and check the results here at + once. You are able to find more information about Multicall:{' '} + + Multicall Github Repository1 + {' '} + and{' '} + + Multicall Github Repository2 + + . + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + ({ + target: call._parent._address, + callData: call.encodeABI(), +})) + +const { returnData } = await multicall.call( + 'tryBlockAndAggregate', + false, + callRequests +) +let output = returnData.map((data: Res, index: number) => { + const types = calls[index]._method.outputs + const { __length__, ...result } = caver.abi.decodeParameters( + types, + data.returnData + ) + return Object.values(result) +}) + +output = output.map((elem: Array) => + caver.utils.fromPeb(elem[0], 'KLAY') +) +setOuputArray(output)`} + /> + + {!!multicallMsg && ( + + {multicallSuccess ? ( + <> + {multicallMsg} + + {'\n'} + Balance of {address1} + {`\n-> `} + {outputArray[0]} WKLAY + + + {'\n'} + Balance of {address2} + {`\n-> `} + {outputArray[1]} WKLAY + + + {'\n'} + Balance of {address3} + {`\n-> `} + {outputArray[2]} WKLAY + + + {'\n'} + Balance of {address4} + {`\n-> `} + {outputArray[3]} WKLAY + + + {'\n'} + Balance of {address5} + {`\n-> `} + {outputArray[4]} WKLAY + + + ) : ( + {multicallMsg} + )} + + )} + +
+
+ ) +} + +export default Multicall diff --git a/src/views/Miscellaneous/constants/exMulticallAbi.ts b/src/views/Miscellaneous/constants/exMulticallAbi.ts new file mode 100644 index 0000000..763c425 --- /dev/null +++ b/src/views/Miscellaneous/constants/exMulticallAbi.ts @@ -0,0 +1,315 @@ +const exMulticallAbi = [ + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'target', + type: 'address', + }, + { + internalType: 'bytes', + name: 'callData', + type: 'bytes', + }, + ], + internalType: 'struct Multicall2.Call[]', + name: 'calls', + type: 'tuple[]', + }, + ], + name: 'aggregate', + outputs: [ + { + internalType: 'uint256', + name: 'blockNumber', + type: 'uint256', + }, + { + internalType: 'bytes[]', + name: 'returnData', + type: 'bytes[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'target', + type: 'address', + }, + { + internalType: 'bytes', + name: 'callData', + type: 'bytes', + }, + ], + internalType: 'struct Multicall2.Call[]', + name: 'calls', + type: 'tuple[]', + }, + ], + name: 'blockAndAggregate', + outputs: [ + { + internalType: 'uint256', + name: 'blockNumber', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'blockHash', + type: 'bytes32', + }, + { + components: [ + { + internalType: 'bool', + name: 'success', + type: 'bool', + }, + { + internalType: 'bytes', + name: 'returnData', + type: 'bytes', + }, + ], + internalType: 'struct Multicall2.Result[]', + name: 'returnData', + type: 'tuple[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'blockNumber', + type: 'uint256', + }, + ], + name: 'getBlockHash', + outputs: [ + { + internalType: 'bytes32', + name: 'blockHash', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getBlockNumber', + outputs: [ + { + internalType: 'uint256', + name: 'blockNumber', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getCurrentBlockCoinbase', + outputs: [ + { + internalType: 'address', + name: 'coinbase', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getCurrentBlockDifficulty', + outputs: [ + { + internalType: 'uint256', + name: 'difficulty', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getCurrentBlockGasLimit', + outputs: [ + { + internalType: 'uint256', + name: 'gaslimit', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getCurrentBlockTimestamp', + outputs: [ + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + ], + name: 'getEthBalance', + outputs: [ + { + internalType: 'uint256', + name: 'balance', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getLastBlockHash', + outputs: [ + { + internalType: 'bytes32', + name: 'blockHash', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: 'requireSuccess', + type: 'bool', + }, + { + components: [ + { + internalType: 'address', + name: 'target', + type: 'address', + }, + { + internalType: 'bytes', + name: 'callData', + type: 'bytes', + }, + ], + internalType: 'struct Multicall2.Call[]', + name: 'calls', + type: 'tuple[]', + }, + ], + name: 'tryAggregate', + outputs: [ + { + components: [ + { + internalType: 'bool', + name: 'success', + type: 'bool', + }, + { + internalType: 'bytes', + name: 'returnData', + type: 'bytes', + }, + ], + internalType: 'struct Multicall2.Result[]', + name: 'returnData', + type: 'tuple[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: 'requireSuccess', + type: 'bool', + }, + { + components: [ + { + internalType: 'address', + name: 'target', + type: 'address', + }, + { + internalType: 'bytes', + name: 'callData', + type: 'bytes', + }, + ], + internalType: 'struct Multicall2.Call[]', + name: 'calls', + type: 'tuple[]', + }, + ], + name: 'tryBlockAndAggregate', + outputs: [ + { + internalType: 'uint256', + name: 'blockNumber', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'blockHash', + type: 'bytes32', + }, + { + components: [ + { + internalType: 'bool', + name: 'success', + type: 'bool', + }, + { + internalType: 'bytes', + name: 'returnData', + type: 'bytes', + }, + ], + internalType: 'struct Multicall2.Result[]', + name: 'returnData', + type: 'tuple[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] + +export { exMulticallAbi } diff --git a/src/views/Miscellaneous/constants/exMulticallData.ts b/src/views/Miscellaneous/constants/exMulticallData.ts new file mode 100644 index 0000000..d356aeb --- /dev/null +++ b/src/views/Miscellaneous/constants/exMulticallData.ts @@ -0,0 +1,20 @@ +const exposureTime = 5000 +const contractAddress = '0xc58580b20154462196021eac953c4e3d6e204ca4' +const wklayContractAddress = '0x043c471bEe060e00A56CcD02c0Ca286808a5A436' + +const exAddress1 = '0x7b4777923b6e659b1ea10f426ec91addd15f9506' +const exAddress2 = '0xe57329a1175a9ce04028c48d58a7963a5a16aa93' +const exAddress3 = '0x12183c2e478f031f343f141f14a797a007a6dbdc' +const exAddress4 = '0xe1cc95100397aab1f096f6130be7cb87ff76dcaf' +const exAddress5 = '0x13b723d90b20bd2be88929b8ec2dcb2bb2d6acc5' + +export { + exposureTime, + contractAddress, + wklayContractAddress, + exAddress1, + exAddress2, + exAddress3, + exAddress4, + exAddress5, +}