From 016a7357813fe8e3f588ad3bbe780ae1895ed19b Mon Sep 17 00:00:00 2001 From: edwin082 Date: Thu, 19 Jan 2023 18:31:05 +0900 Subject: [PATCH 1/9] Add SBT page in Misc menu --- package.json | 4 +- src/routes.tsx | 8 + src/views/Miscellaneous/SoulboundNFT.tsx | 594 ++++++++++++++++++ src/views/Miscellaneous/constants/exSBTAbi.ts | 539 ++++++++++++++++ .../Miscellaneous/constants/exSBTBytecode.ts | 4 + .../constants/exSoulboundNFTdata.ts | 5 + 6 files changed, 1152 insertions(+), 2 deletions(-) create mode 100644 src/views/Miscellaneous/SoulboundNFT.tsx create mode 100644 src/views/Miscellaneous/constants/exSBTAbi.ts create mode 100644 src/views/Miscellaneous/constants/exSBTBytecode.ts create mode 100644 src/views/Miscellaneous/constants/exSoulboundNFTdata.ts diff --git a/package.json b/package.json index 464c2a7..bd600c7 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,8 @@ "react-app" ], "rules": { - "@typescript-eslint/explicit-function-return-type": "error", - "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-unused-vars": "off", "import/no-anonymous-default-export": "off", "react-hooks/exhaustive-deps": "off" } diff --git a/src/routes.tsx b/src/routes.tsx index 95f1194..7b24965 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -24,6 +24,7 @@ import CheckAccountKey from './views/Miscellaneous/CheckAccountKey' import GenerateKeystore from './views/Miscellaneous/GenerateKeystore' import KeccakFromString from './views/Miscellaneous/KeccakFromString' import LoadKeystore from './views/Miscellaneous/LoadKeystore' +import SoulboundNFT from './views/Miscellaneous/SoulboundNFT' import Web3modalExample from './views/Web3modal' import Web3modalNFT from './views/Web3modal/web3modalNFT' @@ -259,6 +260,13 @@ const routes: RouteType[] = [ component: CheckAccountKey, description: 'Returns account key type of the given address', }, + { + path: '/soulboundNFT', + name: 'Soulbound NFT', + component: SoulboundNFT, + description: + 'Deploys Soulbound NFT contract and tests functions: Mint, Transfer, and Burn', + }, ], }, ] diff --git a/src/views/Miscellaneous/SoulboundNFT.tsx b/src/views/Miscellaneous/SoulboundNFT.tsx new file mode 100644 index 0000000..4361435 --- /dev/null +++ b/src/views/Miscellaneous/SoulboundNFT.tsx @@ -0,0 +1,594 @@ +import { ReactElement, useMemo, useState } from 'react' +import Caver, { Keystore } from 'caver-js' +import _ from 'lodash' + +import { URLMAP, UTIL, COLOR } from 'consts' +import { + Button, + Card, + CardHeader, + CardBody, + Label, + Container, + Text, + FormInput, + FormRadio, + CardSection, + CardExample, + LinkA, + PrivateKeyWarning, + CodeBlock, + View, +} from 'components' +import FormFile from 'components/FormFile' + +import { + exposureTime, + exTokenURI, + exTokenReceiver, +} from './constants/exSoulboundNFTdata' + +import { exSBTAbi } from './constants/exSBTAbi' +import exSBTBytecode from './constants/exSBTBytecode' +import { ERROR_EVENT } from '@klaytn/web3modal' + +enum FunctionEnum { + BURN = 'Burn', + Transfer = 'Transfer', +} + +const SoulboundNFT = (): ReactElement => { + const caver = useMemo(() => new Caver(URLMAP.network['testnet']['rpc']), []) + + const [senderAddress, setSenderAddress] = useState('') + const [senderKeystoreJSON, setSenderKeystoreJSON] = useState() + const [senderKeystorePassword, setSenderKeystorePassword] = useState('') + const [senderDecryptMessage, setSenderDecryptMessage] = useState('') + + const [deployMsg, setDeployMsg] = useState('') + const [deployButtonDisabled, setDeployButtonDisabled] = useState(false) + const [deploySuccess, setDeploySuccess] = useState(false) + + const [tokenName, setTokenName] = useState('') + const [tokenSymbol, setTokenSymbol] = useState('') + const [tokenURI, setTokenURI] = useState('') + + const [contractAddress, setContractAddress] = useState('') + const [sbtReceiver, setSbtReceiver] = useState('') + + const [mintMsg, setMintMsg] = useState('') + const [mintButtonDisabled, setMintButtonDisabled] = useState(false) + const [mintSuccess, setMintSuccess] = useState(false) + + const [lastTokenId, setLastTokenId] = useState(0) + const [transferTokenId, setTransferTokenId] = useState('') + const [transferReceiver, setTransferReceiver] = useState('') + const [transferMsg, setTransferMsg] = useState('') + const [transferButtonDisabled, setTransferButtonDisabled] = useState(false) + const [transferSuccess, setTransferSuccess] = useState(false) + + const [burnTokenId, setBurnTokenId] = useState('') + const [burnMsg, setBurnMsg] = useState('') + const [burnButtonDisabled, setBurnButtonDisabled] = useState(false) + const [burnSuccess, setBurnSuccess] = useState(false) + + const [belowPage, setBelowPage] = useState(FunctionEnum.BURN) + + const handleSenderKeystoreChange = (files?: FileList): void => { + if (files && files.length > 0) { + const fileReader = new FileReader() + fileReader.readAsText(files[0], 'UTF-8') + fileReader.onload = (event): void => { + if (typeof event.target?.result === 'string') { + const json = UTIL.jsonTryParse(event.target.result) + setSenderKeystoreJSON(json) + } + } + } + } + + const decryptSenderKeystore = (): void => { + try { + if (senderKeystoreJSON) { + const keyring = caver.wallet.keyring.decrypt( + senderKeystoreJSON, + senderKeystorePassword + ) + + if (caver.wallet.isExisted(keyring.address)) { + caver.wallet.updateKeyring(keyring) + } else { + caver.wallet.add(keyring) + } + + setSenderDecryptMessage('Decryption succeeds!') + setSenderAddress(keyring.address) + } else { + throw Error('Sender Keystore is not uploaded!') + } + } catch (err) { + setSenderDecryptMessage(_.toString(err)) + setSenderAddress('') + + setTimeout(() => { + setSenderDecryptMessage('') + }, exposureTime) + } + } + + const deploy = async (): Promise => { + try { + if (senderAddress === '') { + throw Error('Sender Keystore is not uploaded!') + } + + setDeployButtonDisabled(true) + + const sbt = caver.contract.create(JSON.parse(JSON.stringify(exSBTAbi))) + const sbtInstance = await sbt.deploy( + { from: senderAddress, gas: 6000000 }, + exSBTBytecode, + tokenName, + tokenSymbol, + tokenURI + ) + + setDeployMsg('SBT smart contract is successfully deployed! ') + setDeployButtonDisabled(false) + setContractAddress(sbtInstance.options.address) + setDeploySuccess(true) + } catch (err) { + setDeployMsg(_.toString(err)) + setDeployButtonDisabled(false) + setContractAddress('') + setDeploySuccess(false) + + setTimeout(() => { + setDeployMsg('') + }, exposureTime) + } + } + + const mint = async (): Promise => { + try { + setMintButtonDisabled(true) + + const sbt = new caver.contract( + JSON.parse(JSON.stringify(exSBTAbi)), + contractAddress + ) + sbt.options.from = senderAddress + const currentTokenId = lastTokenId + let minted = await sbt.send( + { from: senderAddress, gas: 1000000 }, + 'safeMint', + sbtReceiver, + currentTokenId + ) + + const newMintMsg = `SBT(token ID: ${currentTokenId}) is successfully mintend!` + + if (minted) { + setMintMsg(newMintMsg) + setMintButtonDisabled(false) + setMintSuccess(true) + setLastTokenId(currentTokenId + 1) + } else { + throw Error('Minting is failed') + } + } catch (err) { + setMintMsg(_.toString(err)) + setMintButtonDisabled(false) + setSbtReceiver('') + setTokenURI('') + setMintSuccess(false) + + setTimeout(() => { + setMintMsg('') + }, exposureTime) + } + } + + const burn = async (): Promise => { + try { + setBurnButtonDisabled(true) + + const sbt = new caver.contract( + JSON.parse(JSON.stringify(exSBTAbi)), + contractAddress + ) + sbt.options.from = senderAddress + let burned = await sbt.send( + { from: senderAddress, gas: 1000000 }, + 'burn', + burnTokenId + ) + const newBurnMsg = `SBT(token ID: ${burnTokenId}) is successfully burned!` + + if (burned) { + setBurnMsg(newBurnMsg) + setBurnButtonDisabled(false) + setBurnSuccess(true) + } else { + throw Error('Burning is failed') + } + } catch (err) { + setBurnMsg(_.toString(err)) + setBurnButtonDisabled(false) + setBurnTokenId('') + setBurnSuccess(false) + + setTimeout(() => { + setBurnMsg('') + }, exposureTime) + } + } + + const transfer = async (): Promise => { + try { + setTransferButtonDisabled(true) + + const sbt = new caver.contract( + JSON.parse(JSON.stringify(exSBTAbi)), + contractAddress + ) + sbt.options.from = senderAddress + let transferred = await sbt.send( + { from: senderAddress, gas: 1000000 }, + 'safeTransferFrom', + senderAddress, + transferReceiver, + transferTokenId + ) + + const newTransferMsg = `SBT(token ID: ${transferTokenId}) is successfully transferred!` + + if (transferred) { + setTransferMsg(newTransferMsg) + setTransferButtonDisabled(false) + setTransferSuccess(true) + } else { + throw Error('Transferring is failed') + } + } catch (err) { + setTransferMsg(_.toString(err)) + setTransferButtonDisabled(false) + setTransferTokenId('') + setTransferReceiver('') + setTransferSuccess(false) + + setTimeout(() => { + setTransferMsg('') + }, exposureTime) + } + } + + return ( + + + +

Deploy Soulbound NFT (SBT)

+ + A soulbound token (SBT) is a non-fungible token bound to a single + account. You can easily deploy SBT contracts here. Through the + process below, you will find the difference between general NFTs and + SBTs. You are able to find more information about soulboundToken{' '} + + SBT Smart Contracts Github + {' '} + and{' '} + + Vitalik's blog: Soulbound + + . After successful deployment, you can find a contract account in + the block explorer. + + +
+ +

Upload Deployer Keystore File

+ + + Upload the Keystore file. This account must have enough KLAY to + deploy a smart contract. + + + + Testnet + + + + + + + + + + + + + + + + {senderDecryptMessage && + (!!senderAddress ? ( + + {senderDecryptMessage} + + ) : ( + + + {senderDecryptMessage} + + + ))} +
+
+ + +

Soulbound NFT (SBT) Information

+ + Enter the Soulbound NFT (SBT) information: the SBT's name, symbol, + and URI. + +
+ + + + + + + + + + + + + + + + + + + + {!!deployMsg && ( + + {deploySuccess ? ( + + {deployMsg}You can check it below link: +
+ + SBT Address + +
+ ) : ( + {deployMsg} + )} +
+ )} +
+
+ {deploySuccess && ( + + +

Mint the Soulbound NFT (SBT)

+ Enter the SBT's receiver. +
+ + + + + + + + + + + + + + + {!!mintMsg && ( + + {mintSuccess ? ( + + {mintMsg} You can check it below link: +
+ + SBT Inventory + +
+ ) : ( + {mintMsg} + )} +
+ )} +
+
+ )} + {mintSuccess && ( + + )} + {mintSuccess && belowPage === FunctionEnum.BURN && ( + + +

Burn the Soulbound Token (SBT)

+ Enter the token Id you own and want to burn. +
+ + + + + + + + + + + + {!!burnMsg && ( + + {burnSuccess ? ( + + {burnMsg} You can see that the SBT you just removed no + longer exists:{' '} + + SBT Inventory + + + ) : ( + {burnMsg} + )} + + )} + +
+ )} + {mintSuccess && belowPage === FunctionEnum.Transfer && ( + + +

Transfer the Soulbound NFT (SBT)

+ Enter the token Id you own and want to transfer. +
+ + + + + + + + + + + + + + + + + + + {!!transferMsg && ( + + {transferSuccess ? ( + + {transferMsg} You can see that the SBT you just sent:{' '} + + SBT Inventory + + + ) : ( + {transferMsg} + )} + + )} + +
+ )} +
+ ) +} + +export default SoulboundNFT diff --git a/src/views/Miscellaneous/constants/exSBTAbi.ts b/src/views/Miscellaneous/constants/exSBTAbi.ts new file mode 100644 index 0000000..2885fe9 --- /dev/null +++ b/src/views/Miscellaneous/constants/exSBTAbi.ts @@ -0,0 +1,539 @@ +const exSBTAbi = [ + { + inputs: [ + { + internalType: 'string', + name: 'name_', + type: 'string', + }, + { + internalType: 'string', + name: 'symbol_', + type: 'string', + }, + { + internalType: 'string', + name: 'baseURI_', + type: 'string', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'approved', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Locked', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Unlocked', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'burn', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getApproved', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + ], + name: 'isApprovedForAll', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'locked', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'ownerOf', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'safeMint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + ], + name: 'tokenByIndex', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + ], + name: 'tokenOfOwnerByIndex', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'tokenURI', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] + +export { exSBTAbi } diff --git a/src/views/Miscellaneous/constants/exSBTBytecode.ts b/src/views/Miscellaneous/constants/exSBTBytecode.ts new file mode 100644 index 0000000..17868ed --- /dev/null +++ b/src/views/Miscellaneous/constants/exSBTBytecode.ts @@ -0,0 +1,4 @@ +const exSBTBytecode = + '60806040523480156200001157600080fd5b5060405162003cf138038062003cf18339818101604052810190620000379190620002fb565b828281600090816200004a9190620005ff565b5080600190816200005c9190620005ff565b5050506200007f620000736200009a60201b60201c565b620000a260201b60201c565b80600b9081620000909190620005ff565b50505050620006e6565b600033905090565b6000600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081600a60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620001d18262000186565b810181811067ffffffffffffffff82111715620001f357620001f262000197565b5b80604052505050565b60006200020862000168565b9050620002168282620001c6565b919050565b600067ffffffffffffffff82111562000239576200023862000197565b5b620002448262000186565b9050602081019050919050565b60005b838110156200027157808201518184015260208101905062000254565b60008484015250505050565b6000620002946200028e846200021b565b620001fc565b905082815260208101848484011115620002b357620002b262000181565b5b620002c084828562000251565b509392505050565b600082601f830112620002e057620002df6200017c565b5b8151620002f28482602086016200027d565b91505092915050565b60008060006060848603121562000317576200031662000172565b5b600084015167ffffffffffffffff81111562000338576200033762000177565b5b6200034686828701620002c8565b935050602084015167ffffffffffffffff8111156200036a576200036962000177565b5b6200037886828701620002c8565b925050604084015167ffffffffffffffff8111156200039c576200039b62000177565b5b620003aa86828701620002c8565b9150509250925092565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200040757607f821691505b6020821081036200041d576200041c620003bf565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620004877fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000448565b62000493868362000448565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620004e0620004da620004d484620004ab565b620004b5565b620004ab565b9050919050565b6000819050919050565b620004fc83620004bf565b620005146200050b82620004e7565b84845462000455565b825550505050565b600090565b6200052b6200051c565b62000538818484620004f1565b505050565b5b8181101562000560576200055460008262000521565b6001810190506200053e565b5050565b601f821115620005af57620005798162000423565b620005848462000438565b8101602085101562000594578190505b620005ac620005a38562000438565b8301826200053d565b50505b505050565b600082821c905092915050565b6000620005d460001984600802620005b4565b1980831691505092915050565b6000620005ef8383620005c1565b9150826002028217905092915050565b6200060a82620003b4565b67ffffffffffffffff81111562000626576200062562000197565b5b620006328254620003ee565b6200063f82828562000564565b600060209050601f83116001811462000677576000841562000662578287015190505b6200066e8582620005e1565b865550620006de565b601f198416620006878662000423565b60005b82811015620006b1578489015182556001820191506020850194506020810190506200068a565b86831015620006d15784890151620006cd601f891682620005c1565b8355505b6001600288020188555050505b505050505050565b6135fb80620006f66000396000f3fe608060405234801561001057600080fd5b50600436106101425760003560e01c806370a08231116100b8578063a22cb4651161007c578063a22cb46514610375578063b45a3c0e14610391578063b88d4fde146103c1578063c87b56dd146103dd578063e985e9c51461040d578063f2fde38b1461043d57610142565b806370a08231146102e3578063715018a6146103135780638da5cb5b1461031d57806395d89b411461033b578063a14481941461035957610142565b806323b872dd1161010a57806323b872dd146101ff5780632f745c591461021b57806342842e0e1461024b57806342966c68146102675780634f6ccce7146102835780636352211e146102b357610142565b806301ffc9a71461014757806306fdde0314610177578063081812fc14610195578063095ea7b3146101c557806318160ddd146101e1575b600080fd5b610161600480360381019061015c91906123f2565b610459565b60405161016e919061243a565b60405180910390f35b61017f6104d3565b60405161018c91906124e5565b60405180910390f35b6101af60048036038101906101aa919061253d565b610565565b6040516101bc91906125ab565b60405180910390f35b6101df60048036038101906101da91906125f2565b6105ab565b005b6101e96106c2565b6040516101f69190612641565b60405180910390f35b6102196004803603810190610214919061265c565b6106cf565b005b610235600480360381019061023091906125f2565b61070c565b6040516102429190612641565b60405180910390f35b6102656004803603810190610260919061265c565b6107b1565b005b610281600480360381019061027c919061253d565b6107ee565b005b61029d6004803603810190610298919061253d565b610870565b6040516102aa9190612641565b60405180910390f35b6102cd60048036038101906102c8919061253d565b6108e1565b6040516102da91906125ab565b60405180910390f35b6102fd60048036038101906102f891906126af565b610992565b60405161030a9190612641565b60405180910390f35b61031b610a49565b005b610325610a5d565b60405161033291906125ab565b60405180910390f35b610343610a87565b60405161035091906124e5565b60405180910390f35b610373600480360381019061036e91906125f2565b610b19565b005b61038f600480360381019061038a9190612708565b610c44565b005b6103ab60048036038101906103a6919061253d565b610c5a565b6040516103b8919061243a565b60405180910390f35b6103db60048036038101906103d6919061287d565b610cc4565b005b6103f760048036038101906103f2919061253d565b610d03565b60405161040491906124e5565b60405180910390f35b61042760048036038101906104229190612900565b610d6b565b604051610434919061243a565b60405180910390f35b610457600480360381019061045291906126af565b610dff565b005b60007fb45a3c0e000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806104cc57506104cb82610e82565b5b9050919050565b6060600080546104e29061296f565b80601f016020809104026020016040519081016040528092919081815260200182805461050e9061296f565b801561055b5780601f106105305761010080835404028352916020019161055b565b820191906000526020600020905b81548152906001019060200180831161053e57829003601f168201915b5050505050905090565b600061057082610efc565b6004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b60006105b6826108e1565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610626576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161061d90612a12565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16610645610f47565b73ffffffffffffffffffffffffffffffffffffffff16148061067457506106738161066e610f47565b610d6b565b5b6106b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106aa90612aa4565b60405180910390fd5b6106bd8383610f4f565b505050565b6000600880549050905090565b80600c600082815260200190815260200160002060009054906101000a900460ff16156106fb57600080fd5b610706848484611008565b50505050565b600061071783610992565b8210610758576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161074f90612b36565b60405180910390fd5b600660008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600083815260200190815260200160002054905092915050565b80600c600082815260200190815260200160002060009054906101000a900460ff16156107dd57600080fd5b6107e8848484611008565b50505050565b6107f7816108e1565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610864576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161085b90612ba2565b60405180910390fd5b61086d81611028565b50565b600061087a6106c2565b82106108bb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108b290612c34565b60405180910390fd5b600882815481106108cf576108ce612c54565b5b90600052602060002001549050919050565b6000806002600084815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610989576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098090612ccf565b60405180910390fd5b80915050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109f990612d61565b60405180910390fd5b600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b610a51611145565b610a5b60006111c3565b565b6000600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606060018054610a969061296f565b80601f0160208091040260200160405190810160405280929190818152602001828054610ac29061296f565b8015610b0f5780601f10610ae457610100808354040283529160200191610b0f565b820191906000526020600020905b815481529060010190602001808311610af257829003601f168201915b5050505050905090565b610b21611145565b6000610b2c83610992565b14610b6c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b6390612dcd565b60405180910390fd5b60011515600c600083815260200190815260200160002060009054906101000a900460ff16151503610bd3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610bca90612e39565b60405180910390fd5b6001600c600083815260200190815260200160002060006101000a81548160ff0219169083151502179055507f032bc66be43dbccb7487781d168eb7bda224628a3b2c3388bdf69b532a3a161181604051610c2e9190612641565b60405180910390a1610c408282611289565b5050565b610c56610c4f610f47565b83836112a7565b5050565b60008073ffffffffffffffffffffffffffffffffffffffff16610c7c836108e1565b73ffffffffffffffffffffffffffffffffffffffff1603610c9c57600080fd5b600c600083815260200190815260200160002060009054906101000a900460ff169050919050565b81600c600082815260200190815260200160002060009054906101000a900460ff1615610cf057600080fd5b610cfc85858585611413565b5050505050565b6060610d0e82610efc565b6000610d18611475565b90506000815111610d385760405180602001604052806000815250610d63565b80610d4284611507565b604051602001610d53929190612e95565b6040516020818303038152906040525b915050919050565b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b610e07611145565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610e76576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e6d90612f2b565b60405180910390fd5b610e7f816111c3565b50565b60007f780e9d63000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161480610ef55750610ef482611667565b5b9050919050565b610f0581611749565b610f44576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f3b90612ccf565b60405180910390fd5b50565b600033905090565b816004600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16610fc2836108e1565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b61102383838360405180602001604052806000815250610cc4565b505050565b6000611033826108e1565b9050611041816000846117b5565b61104c600083610f4f565b6001600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461109c9190612f7a565b925050819055506002600083815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905581600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4611141816000846117c5565b5050565b61114d610f47565b73ffffffffffffffffffffffffffffffffffffffff1661116b610a5d565b73ffffffffffffffffffffffffffffffffffffffff16146111c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016111b890612ffa565b60405180910390fd5b565b6000600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081600a60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b6112a38282604051806020016040528060008152506117ca565b5050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603611315576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161130c90613066565b60405180910390fd5b80600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051611406919061243a565b60405180910390a3505050565b61142461141e610f47565b83611825565b611463576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161145a906130f8565b60405180910390fd5b61146f848484846118ba565b50505050565b6060600b80546114849061296f565b80601f01602080910402602001604051908101604052809291908181526020018280546114b09061296f565b80156114fd5780601f106114d2576101008083540402835291602001916114fd565b820191906000526020600020905b8154815290600101906020018083116114e057829003601f168201915b5050505050905090565b60606000820361154e576040518060400160405280600181526020017f30000000000000000000000000000000000000000000000000000000000000008152509050611662565b600082905060005b6000821461158057808061156990613118565b915050600a82611579919061318f565b9150611556565b60008167ffffffffffffffff81111561159c5761159b612752565b5b6040519080825280601f01601f1916602001820160405280156115ce5781602001600182028036833780820191505090505b5090505b6000851461165b576001826115e79190612f7a565b9150600a856115f691906131c0565b603061160291906131f1565b60f81b81838151811061161857611617612c54565b5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600a85611654919061318f565b94506115d2565b8093505050505b919050565b60007f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061173257507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b80611742575061174182611916565b5b9050919050565b60008073ffffffffffffffffffffffffffffffffffffffff166002600084815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614159050919050565b6117c0838383611980565b505050565b505050565b6117d48383611a92565b6117e16000848484611c6b565b611820576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161181790613297565b60405180910390fd5b505050565b600080611831836108e1565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16148061187357506118728185610d6b565b5b806118b157508373ffffffffffffffffffffffffffffffffffffffff1661189984610565565b73ffffffffffffffffffffffffffffffffffffffff16145b91505092915050565b6118c5848484611df2565b6118d184848484611c6b565b611910576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161190790613297565b60405180910390fd5b50505050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b61198b838383612058565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036119cd576119c88161205d565b611a0c565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614611a0b57611a0a83826120a6565b5b5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611a4e57611a4981612213565b611a8d565b8273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614611a8c57611a8b82826122e4565b5b5b505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611b01576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611af890613303565b60405180910390fd5b611b0a81611749565b15611b4a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b419061336f565b60405180910390fd5b611b56600083836117b5565b6001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611ba691906131f1565b92505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4611c67600083836117c5565b5050565b6000611c8c8473ffffffffffffffffffffffffffffffffffffffff16612363565b15611de5578373ffffffffffffffffffffffffffffffffffffffff1663150b7a02611cb5610f47565b8786866040518563ffffffff1660e01b8152600401611cd794939291906133e4565b6020604051808303816000875af1925050508015611d1357506040513d601f19601f82011682018060405250810190611d109190613445565b60015b611d95573d8060008114611d43576040519150601f19603f3d011682016040523d82523d6000602084013e611d48565b606091505b506000815103611d8d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d8490613297565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614915050611dea565b600190505b949350505050565b8273ffffffffffffffffffffffffffffffffffffffff16611e12826108e1565b73ffffffffffffffffffffffffffffffffffffffff1614611e68576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e5f906134e4565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611ed7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611ece90613576565b60405180910390fd5b611ee28383836117b5565b611eed600082610f4f565b6001600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611f3d9190612f7a565b925050819055506001600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611f9491906131f1565b92505081905550816002600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a46120538383836117c5565b505050565b505050565b6008805490506009600083815260200190815260200160002081905550600881908060018154018082558091505060019003906000526020600020016000909190919091505550565b600060016120b384610992565b6120bd9190612f7a565b90506000600760008481526020019081526020016000205490508181146121a2576000600660008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600084815260200190815260200160002054905080600660008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600084815260200190815260200160002081905550816007600083815260200190815260200160002081905550505b6007600084815260200190815260200160002060009055600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008381526020019081526020016000206000905550505050565b600060016008805490506122279190612f7a565b905060006009600084815260200190815260200160002054905060006008838154811061225757612256612c54565b5b90600052602060002001549050806008838154811061227957612278612c54565b5b9060005260206000200181905550816009600083815260200190815260200160002081905550600960008581526020019081526020016000206000905560088054806122c8576122c7613596565b5b6001900381819060005260206000200160009055905550505050565b60006122ef83610992565b905081600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600083815260200190815260200160002081905550806007600084815260200190815260200160002081905550505050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b6000604051905090565b600080fd5b600080fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6123cf8161239a565b81146123da57600080fd5b50565b6000813590506123ec816123c6565b92915050565b60006020828403121561240857612407612390565b5b6000612416848285016123dd565b91505092915050565b60008115159050919050565b6124348161241f565b82525050565b600060208201905061244f600083018461242b565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561248f578082015181840152602081019050612474565b60008484015250505050565b6000601f19601f8301169050919050565b60006124b782612455565b6124c18185612460565b93506124d1818560208601612471565b6124da8161249b565b840191505092915050565b600060208201905081810360008301526124ff81846124ac565b905092915050565b6000819050919050565b61251a81612507565b811461252557600080fd5b50565b60008135905061253781612511565b92915050565b60006020828403121561255357612552612390565b5b600061256184828501612528565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006125958261256a565b9050919050565b6125a58161258a565b82525050565b60006020820190506125c0600083018461259c565b92915050565b6125cf8161258a565b81146125da57600080fd5b50565b6000813590506125ec816125c6565b92915050565b6000806040838503121561260957612608612390565b5b6000612617858286016125dd565b925050602061262885828601612528565b9150509250929050565b61263b81612507565b82525050565b60006020820190506126566000830184612632565b92915050565b60008060006060848603121561267557612674612390565b5b6000612683868287016125dd565b9350506020612694868287016125dd565b92505060406126a586828701612528565b9150509250925092565b6000602082840312156126c5576126c4612390565b5b60006126d3848285016125dd565b91505092915050565b6126e58161241f565b81146126f057600080fd5b50565b600081359050612702816126dc565b92915050565b6000806040838503121561271f5761271e612390565b5b600061272d858286016125dd565b925050602061273e858286016126f3565b9150509250929050565b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61278a8261249b565b810181811067ffffffffffffffff821117156127a9576127a8612752565b5b80604052505050565b60006127bc612386565b90506127c88282612781565b919050565b600067ffffffffffffffff8211156127e8576127e7612752565b5b6127f18261249b565b9050602081019050919050565b82818337600083830152505050565b600061282061281b846127cd565b6127b2565b90508281526020810184848401111561283c5761283b61274d565b5b6128478482856127fe565b509392505050565b600082601f83011261286457612863612748565b5b813561287484826020860161280d565b91505092915050565b6000806000806080858703121561289757612896612390565b5b60006128a5878288016125dd565b94505060206128b6878288016125dd565b93505060406128c787828801612528565b925050606085013567ffffffffffffffff8111156128e8576128e7612395565b5b6128f48782880161284f565b91505092959194509250565b6000806040838503121561291757612916612390565b5b6000612925858286016125dd565b9250506020612936858286016125dd565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061298757607f821691505b60208210810361299a57612999612940565b5b50919050565b7f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560008201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b60006129fc602183612460565b9150612a07826129a0565b604082019050919050565b60006020820190508181036000830152612a2b816129ef565b9050919050565b7f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60008201527f6b656e206f776e6572206e6f7220617070726f76656420666f7220616c6c0000602082015250565b6000612a8e603e83612460565b9150612a9982612a32565b604082019050919050565b60006020820190508181036000830152612abd81612a81565b9050919050565b7f455243373231456e756d657261626c653a206f776e657220696e646578206f7560008201527f74206f6620626f756e6473000000000000000000000000000000000000000000602082015250565b6000612b20602b83612460565b9150612b2b82612ac4565b604082019050919050565b60006020820190508181036000830152612b4f81612b13565b9050919050565b7f42524e3031000000000000000000000000000000000000000000000000000000600082015250565b6000612b8c600583612460565b9150612b9782612b56565b602082019050919050565b60006020820190508181036000830152612bbb81612b7f565b9050919050565b7f455243373231456e756d657261626c653a20676c6f62616c20696e646578206f60008201527f7574206f6620626f756e64730000000000000000000000000000000000000000602082015250565b6000612c1e602c83612460565b9150612c2982612bc2565b604082019050919050565b60006020820190508181036000830152612c4d81612c11565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4552433732313a20696e76616c696420746f6b656e2049440000000000000000600082015250565b6000612cb9601883612460565b9150612cc482612c83565b602082019050919050565b60006020820190508181036000830152612ce881612cac565b9050919050565b7f4552433732313a2061646472657373207a65726f206973206e6f74206120766160008201527f6c6964206f776e65720000000000000000000000000000000000000000000000602082015250565b6000612d4b602983612460565b9150612d5682612cef565b604082019050919050565b60006020820190508181036000830152612d7a81612d3e565b9050919050565b7f4d4e543031000000000000000000000000000000000000000000000000000000600082015250565b6000612db7600583612460565b9150612dc282612d81565b602082019050919050565b60006020820190508181036000830152612de681612daa565b9050919050565b7f4d4e543032000000000000000000000000000000000000000000000000000000600082015250565b6000612e23600583612460565b9150612e2e82612ded565b602082019050919050565b60006020820190508181036000830152612e5281612e16565b9050919050565b600081905092915050565b6000612e6f82612455565b612e798185612e59565b9350612e89818560208601612471565b80840191505092915050565b6000612ea18285612e64565b9150612ead8284612e64565b91508190509392505050565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b6000612f15602683612460565b9150612f2082612eb9565b604082019050919050565b60006020820190508181036000830152612f4481612f08565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000612f8582612507565b9150612f9083612507565b9250828203905081811115612fa857612fa7612f4b565b5b92915050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b6000612fe4602083612460565b9150612fef82612fae565b602082019050919050565b6000602082019050818103600083015261301381612fd7565b9050919050565b7f4552433732313a20617070726f766520746f2063616c6c657200000000000000600082015250565b6000613050601983612460565b915061305b8261301a565b602082019050919050565b6000602082019050818103600083015261307f81613043565b9050919050565b7f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560008201527f72206e6f7220617070726f766564000000000000000000000000000000000000602082015250565b60006130e2602e83612460565b91506130ed82613086565b604082019050919050565b60006020820190508181036000830152613111816130d5565b9050919050565b600061312382612507565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361315557613154612f4b565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600061319a82612507565b91506131a583612507565b9250826131b5576131b4613160565b5b828204905092915050565b60006131cb82612507565b91506131d683612507565b9250826131e6576131e5613160565b5b828206905092915050565b60006131fc82612507565b915061320783612507565b925082820190508082111561321f5761321e612f4b565b5b92915050565b7f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560008201527f63656976657220696d706c656d656e7465720000000000000000000000000000602082015250565b6000613281603283612460565b915061328c82613225565b604082019050919050565b600060208201905081810360008301526132b081613274565b9050919050565b7f4552433732313a206d696e7420746f20746865207a65726f2061646472657373600082015250565b60006132ed602083612460565b91506132f8826132b7565b602082019050919050565b6000602082019050818103600083015261331c816132e0565b9050919050565b7f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000600082015250565b6000613359601c83612460565b915061336482613323565b602082019050919050565b600060208201905081810360008301526133888161334c565b9050919050565b600081519050919050565b600082825260208201905092915050565b60006133b68261338f565b6133c0818561339a565b93506133d0818560208601612471565b6133d98161249b565b840191505092915050565b60006080820190506133f9600083018761259c565b613406602083018661259c565b6134136040830185612632565b818103606083015261342581846133ab565b905095945050505050565b60008151905061343f816123c6565b92915050565b60006020828403121561345b5761345a612390565b5b600061346984828501613430565b91505092915050565b7f4552433732313a207472616e736665722066726f6d20696e636f72726563742060008201527f6f776e6572000000000000000000000000000000000000000000000000000000602082015250565b60006134ce602583612460565b91506134d982613472565b604082019050919050565b600060208201905081810360008301526134fd816134c1565b9050919050565b7f4552433732313a207472616e7366657220746f20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000613560602483612460565b915061356b82613504565b604082019050919050565b6000602082019050818103600083015261358f81613553565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea264697066735822122016514c231cd0ac5788f7ba5c98d33db5026fae4047550e41ae8fcdddfe9d65bd64736f6c63430008100033' + +export default exSBTBytecode diff --git a/src/views/Miscellaneous/constants/exSoulboundNFTdata.ts b/src/views/Miscellaneous/constants/exSoulboundNFTdata.ts new file mode 100644 index 0000000..c20defa --- /dev/null +++ b/src/views/Miscellaneous/constants/exSoulboundNFTdata.ts @@ -0,0 +1,5 @@ +const exTokenURI = 'https://cryptologos.cc/logos/klaytn-klay-logo.svg?v=023' +const exTokenReceiver = '0x6CEe3d8c038ab74E2854C158d7B1b55544E814C8' +const exposureTime = 5000 + +export { exTokenURI, exTokenReceiver, exposureTime } From cddeadc7b4fd8dcccaeb45b9dbcd645e7dfc8bf0 Mon Sep 17 00:00:00 2001 From: edwin082 Date: Thu, 19 Jan 2023 22:52:39 +0900 Subject: [PATCH 2/9] Update the explanations on SBT page --- src/views/Miscellaneous/SoulboundNFT.tsx | 84 +++++++++++++++++------- 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/src/views/Miscellaneous/SoulboundNFT.tsx b/src/views/Miscellaneous/SoulboundNFT.tsx index 4361435..80fa0ea 100644 --- a/src/views/Miscellaneous/SoulboundNFT.tsx +++ b/src/views/Miscellaneous/SoulboundNFT.tsx @@ -159,7 +159,7 @@ const SoulboundNFT = (): ReactElement => { ) sbt.options.from = senderAddress const currentTokenId = lastTokenId - let minted = await sbt.send( + const minted = await sbt.send( { from: senderAddress, gas: 1000000 }, 'safeMint', sbtReceiver, @@ -271,8 +271,9 @@ const SoulboundNFT = (): ReactElement => { A soulbound token (SBT) is a non-fungible token bound to a single account. You can easily deploy SBT contracts here. Through the - process below, you will find the difference between general NFTs and - SBTs. You are able to find more information about soulboundToken{' '} + process below, you will check the difference between general NFTs + and SBTs. You are able to find more information about + soulboundToken:{' '} SBT Smart Contracts Github {' '} @@ -280,8 +281,8 @@ const SoulboundNFT = (): ReactElement => { Vitalik's blog: Soulbound - . After successful deployment, you can find a contract account in - the block explorer. + . After successful deployment, you can verify a contract account + status in the block explorer. @@ -384,10 +385,15 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} {!!deployMsg && ( @@ -413,7 +419,10 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`}

Mint the Soulbound NFT (SBT)

- Enter the SBT's receiver. + + Enter the SBT's receiver. Token CANNOT be re-issued to users who + already own it. +
@@ -439,9 +448,18 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} {!!mintMsg && ( @@ -478,7 +496,10 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`}

Burn the Soulbound Token (SBT)

- Enter the token Id you own and want to burn. + + Enter the token Id you own and want to burn. You CANNOT burn + tokens that you do not own. +
@@ -498,10 +519,16 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} {!!burnMsg && ( @@ -528,7 +555,11 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`}

Transfer the Soulbound NFT (SBT)

- Enter the token Id you own and want to transfer. + + Enter the token Id you own and want to transfer. The most + significant feature of SBT is that it CANNOT be transferred to + others. +
@@ -563,9 +594,18 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} {!!transferMsg && ( From 824333d3d2d93c26f9cf462143cc9de5a1fbe1d1 Mon Sep 17 00:00:00 2001 From: edwin082 Date: Thu, 19 Jan 2023 22:56:39 +0900 Subject: [PATCH 3/9] Add example input on SBT page --- package.json | 4 ++-- src/views/Miscellaneous/SoulboundNFT.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index bd600c7..464c2a7 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,8 @@ "react-app" ], "rules": { - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/explicit-function-return-type": "error", + "@typescript-eslint/no-unused-vars": "error", "import/no-anonymous-default-export": "off", "react-hooks/exhaustive-deps": "off" } diff --git a/src/views/Miscellaneous/SoulboundNFT.tsx b/src/views/Miscellaneous/SoulboundNFT.tsx index 80fa0ea..8d1c621 100644 --- a/src/views/Miscellaneous/SoulboundNFT.tsx +++ b/src/views/Miscellaneous/SoulboundNFT.tsx @@ -30,7 +30,6 @@ import { import { exSBTAbi } from './constants/exSBTAbi' import exSBTBytecode from './constants/exSBTBytecode' -import { ERROR_EVENT } from '@klaytn/web3modal' enum FunctionEnum { BURN = 'Burn', @@ -369,6 +368,7 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} /> + Date: Fri, 20 Jan 2023 23:32:25 +0900 Subject: [PATCH 4/9] Add Canonical WKLAY page to the Misc menu --- src/routes.tsx | 8 + src/views/Miscellaneous/SoulboundNFT.tsx | 8 +- src/views/Miscellaneous/WKLAY.tsx | 363 ++++++++++++++++++ .../{exSoulboundNFTdata.ts => exSBTData.ts} | 0 .../Miscellaneous/constants/exWKLAYAbi.ts | 155 ++++++++ .../Miscellaneous/constants/exWKLAYAddress.ts | 3 + .../Miscellaneous/constants/exWKLAYData.ts | 4 + 7 files changed, 537 insertions(+), 4 deletions(-) create mode 100644 src/views/Miscellaneous/WKLAY.tsx rename src/views/Miscellaneous/constants/{exSoulboundNFTdata.ts => exSBTData.ts} (100%) create mode 100644 src/views/Miscellaneous/constants/exWKLAYAbi.ts create mode 100644 src/views/Miscellaneous/constants/exWKLAYAddress.ts create mode 100644 src/views/Miscellaneous/constants/exWKLAYData.ts diff --git a/src/routes.tsx b/src/routes.tsx index 7b24965..920d2e5 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -25,6 +25,7 @@ import GenerateKeystore from './views/Miscellaneous/GenerateKeystore' 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 Web3modalExample from './views/Web3modal' import Web3modalNFT from './views/Web3modal/web3modalNFT' @@ -267,6 +268,13 @@ const routes: RouteType[] = [ description: 'Deploys Soulbound NFT contract and tests functions: Mint, Transfer, and Burn', }, + { + path: '/WKLAY', + name: 'Canonical WKLAY', + component: WKLAY, + description: + 'Interact with canonical-WKLAY contract and tests functions: Deposit, Withdraw, Approve, and Transfer', + }, ], }, ] diff --git a/src/views/Miscellaneous/SoulboundNFT.tsx b/src/views/Miscellaneous/SoulboundNFT.tsx index 8d1c621..b0ff02f 100644 --- a/src/views/Miscellaneous/SoulboundNFT.tsx +++ b/src/views/Miscellaneous/SoulboundNFT.tsx @@ -26,14 +26,14 @@ import { exposureTime, exTokenURI, exTokenReceiver, -} from './constants/exSoulboundNFTdata' +} from './constants/exSBTData' import { exSBTAbi } from './constants/exSBTAbi' import exSBTBytecode from './constants/exSBTBytecode' enum FunctionEnum { BURN = 'Burn', - Transfer = 'Transfer', + TRANSFER = 'Transfer', } const SoulboundNFT = (): ReactElement => { @@ -486,7 +486,7 @@ const minted = await sbt.send(
)} - {mintSuccess && belowPage === FunctionEnum.Transfer && ( + {mintSuccess && belowPage === FunctionEnum.TRANSFER && (

Transfer the Soulbound NFT (SBT)

diff --git a/src/views/Miscellaneous/WKLAY.tsx b/src/views/Miscellaneous/WKLAY.tsx new file mode 100644 index 0000000..2349043 --- /dev/null +++ b/src/views/Miscellaneous/WKLAY.tsx @@ -0,0 +1,363 @@ +import { ReactElement, useMemo, useState } from 'react' +import Caver, { Keystore } from 'caver-js' +import _ from 'lodash' + +import { URLMAP, UTIL, COLOR } from 'consts' +import { + Button, + Card, + CardHeader, + CardBody, + Label, + Container, + Text, + FormInput, + CardSection, + LinkA, + PrivateKeyWarning, + CodeBlock, + View, + FormRadio, +} from 'components' +import FormFile from 'components/FormFile' + +import { exWKLAYAbi } from './constants/exWKLAYAbi' +import { exposureTime, contractAddress } from './constants/exWKLAYData' + +enum FunctionEnum { + BALANCE = 'Balance', + DEPOSIT = 'Deposit', + WITHDRAW = 'Withdraw', + TRANSFEROFOWNER = 'Transfer of Owner', + TRANSFEROFAPPROVER = 'Transfer of Approver', + GETEVENTS = 'Get Events', +} + +const WKLAY = (): ReactElement => { + const caver = useMemo(() => new Caver(URLMAP.network['testnet']['rpc']), []) + + const [ownerAddress, setOwnerAddress] = useState('') + const [ownerKeystoreJSON, setOwnerKeystoreJSON] = useState() + const [ownerKeystorePassword, setOwnerKeystorePassword] = useState('') + const [ownerDecryptMessage, setOwnerDecryptMessage] = useState('') + + const [approverAddress, setApproverAddress] = useState('') + const [approverKeystoreJSON, setApproverKeystoreJSON] = useState() + const [approverKeystorePassword, setApproverKeystorePassword] = useState('') + const [approverDecryptMessage, setApproverDecryptMessage] = useState('') + + const [balanceMsg, setBalanceMsg] = useState('') + const [balanceButtonDisabled, setBalanceButtonDisabled] = useState(false) + const [balanceSuccess, setBalanceSuccess] = useState(false) + + const [belowPage, setBelowPage] = useState(FunctionEnum.BALANCE) + + const handleOwnerKeystoreChange = (files?: FileList): void => { + if (files && files.length > 0) { + const fileReader = new FileReader() + fileReader.readAsText(files[0], 'UTF-8') + fileReader.onload = (event): void => { + if (typeof event.target?.result === 'string') { + const json = UTIL.jsonTryParse(event.target.result) + setOwnerKeystoreJSON(json) + } + } + } + } + + const decryptOwnerKeystore = (): void => { + try { + if (ownerKeystoreJSON) { + const keyring = caver.wallet.keyring.decrypt( + ownerKeystoreJSON, + ownerKeystorePassword + ) + + if (caver.wallet.isExisted(keyring.address)) { + caver.wallet.updateKeyring(keyring) + } else { + caver.wallet.add(keyring) + } + + setOwnerDecryptMessage('Decryption succeeds!') + setOwnerAddress(keyring.address) + } else { + throw Error('Owner Keystore is not uploaded!') + } + } catch (err) { + setOwnerDecryptMessage(_.toString(err)) + setOwnerAddress('') + + setTimeout(() => { + setOwnerDecryptMessage('') + }, exposureTime) + } + } + + const handleApproverKeystoreChange = (files?: FileList): void => { + if (files && files.length > 0) { + const fileReader = new FileReader() + fileReader.readAsText(files[0], 'UTF-8') + fileReader.onload = (event): void => { + if (typeof event.target?.result === 'string') { + const json = UTIL.jsonTryParse(event.target.result) + setApproverKeystoreJSON(json) + } + } + } + } + + const decryptApproverKeystore = (): void => { + try { + if (approverKeystoreJSON) { + const keyring = caver.wallet.keyring.decrypt( + approverKeystoreJSON, + approverKeystorePassword + ) + + if (caver.wallet.isExisted(keyring.address)) { + caver.wallet.updateKeyring(keyring) + } else { + caver.wallet.add(keyring) + } + + setApproverDecryptMessage('Decryption succeeds!') + setApproverAddress(keyring.address) + } else { + throw Error('Approver Keystore is not uploaded!') + } + } catch (err) { + setApproverDecryptMessage(_.toString(err)) + setApproverAddress('') + + setTimeout(() => { + setApproverDecryptMessage('') + }, exposureTime) + } + } + + const balance = async (): Promise => { + try { + setBalanceButtonDisabled(true) + + const wklay = new caver.contract( + JSON.parse(JSON.stringify(exWKLAYAbi)), + contractAddress + ) + wklay.options.from = ownerAddress + let balance = await wklay.call('balanceOf', ownerAddress) + const newDepositMsg = `${balance}` + + if (balance) { + setBalanceMsg(newDepositMsg) + setBalanceButtonDisabled(false) + setBalanceSuccess(true) + } else { + throw Error('Deposit is failed') + } + } catch (err) { + setBalanceMsg(_.toString(err)) + setBalanceButtonDisabled(false) + setBalanceSuccess(false) + + setTimeout(() => { + setBalanceMsg('') + }, exposureTime) + } + } + + return ( + + + +

+ Interact with Deployed Contracts - Canonical WKLAY +

+ + To make the user experience more efficient and reduce friction when + moving KLAY between DApps, we have released Canonical WKLAY, a + standard interface for using Wrapped Klay in dApps. For dApp + standardization and more secure smart contracts, we want our + partners to join us in using a standard implementation of WKLAY so + that end users can convert KLAY to WKLAY once and use the same WKLAY + in their dApps. You can easily test the Canonical WKLAY functions + before actually making the service. You are able to find more + information about Canonical WKLAY:{' '} + + Canonical WKLAY Contract Github + + {'\n\n'} + Deployed Contract Addresses{'\n'} + Mainnet:{' '} + + 0x19Aac5f612f524B754CA7e7c41cbFa2E981A4432 + + {'\n'} + Testnet:{' '} + + 0x043c471bEe060e00A56CcD02c0Ca286808a5A436 + {' '} + + +
+ +

+ {' '} + Upload Keystore File of Owner of the WKLAY Balance +

+ + + Upload the Keystore file. This account must have enough KLAY to + send transactions. + + + + Testnet + + + + + + + + + + + + + + + + {ownerDecryptMessage && + (!!ownerAddress ? ( + + {ownerDecryptMessage} + + ) : ( + + + {ownerDecryptMessage} + + + ))} +
+
+ + +

+ {' '} + Upload Keystore File of Approver of the WKLAY Balance +

+ + + Upload the Keystore file. This account must have enough KLAY to + send transactions. + + + + Testnet + + + + + + + + + + + + + + + + {approverDecryptMessage && + (!!approverAddress ? ( + + {approverDecryptMessage} + + ) : ( + + + {approverDecryptMessage} + + + ))} +
+
+ {ownerDecryptMessage && ( + + )} + {ownerDecryptMessage && belowPage === 'Balance' && ( + + +

Balance of WKLAY

+ Check the balance of WKLAY of the owner's address. +
+ + + + + + + + {!!balanceMsg && ( + + {balanceSuccess ? ( + {balanceMsg} + ) : ( + {balanceMsg} + )} + + )} + +
+ )} +
+ ) +} + +export default WKLAY diff --git a/src/views/Miscellaneous/constants/exSoulboundNFTdata.ts b/src/views/Miscellaneous/constants/exSBTData.ts similarity index 100% rename from src/views/Miscellaneous/constants/exSoulboundNFTdata.ts rename to src/views/Miscellaneous/constants/exSBTData.ts diff --git a/src/views/Miscellaneous/constants/exWKLAYAbi.ts b/src/views/Miscellaneous/constants/exWKLAYAbi.ts new file mode 100644 index 0000000..0477c9c --- /dev/null +++ b/src/views/Miscellaneous/constants/exWKLAYAbi.ts @@ -0,0 +1,155 @@ +const exWKLAYAbi = [ + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'src', type: 'address' }, + { indexed: true, internalType: 'address', name: 'guy', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'wad', type: 'uint256' }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'dst', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'wad', type: 'uint256' }, + ], + name: 'Deposit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'src', type: 'address' }, + { indexed: true, internalType: 'address', name: 'dst', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'wad', type: 'uint256' }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'src', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'wad', type: 'uint256' }, + ], + name: 'Withdrawal', + type: 'event', + }, + { payable: true, stateMutability: 'payable', type: 'fallback' }, + { + constant: true, + inputs: [ + { internalType: 'address', name: '', type: 'address' }, + { internalType: 'address', name: '', type: 'address' }, + ], + name: 'allowance', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { internalType: 'address', name: 'guy', type: 'address' }, + { internalType: 'uint256', name: 'wad', type: 'uint256' }, + ], + name: 'approve', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [{ internalType: 'address', name: '', type: 'address' }], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [], + name: 'deposit', + outputs: [], + payable: true, + stateMutability: 'payable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'totalSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { internalType: 'address', name: 'dst', type: 'address' }, + { internalType: 'uint256', name: 'wad', type: 'uint256' }, + ], + name: 'transfer', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { internalType: 'address', name: 'src', type: 'address' }, + { internalType: 'address', name: 'dst', type: 'address' }, + { internalType: 'uint256', name: 'wad', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [{ internalType: 'uint256', name: 'wad', type: 'uint256' }], + name: 'withdraw', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, +] + +export { exWKLAYAbi } diff --git a/src/views/Miscellaneous/constants/exWKLAYAddress.ts b/src/views/Miscellaneous/constants/exWKLAYAddress.ts new file mode 100644 index 0000000..883a4f4 --- /dev/null +++ b/src/views/Miscellaneous/constants/exWKLAYAddress.ts @@ -0,0 +1,3 @@ +const baobabWKLAYAddress = '0x043c471bEe060e00A56CcD02c0Ca286808a5A436' + +export { baobabWKLAYAddress } diff --git a/src/views/Miscellaneous/constants/exWKLAYData.ts b/src/views/Miscellaneous/constants/exWKLAYData.ts new file mode 100644 index 0000000..466e7f8 --- /dev/null +++ b/src/views/Miscellaneous/constants/exWKLAYData.ts @@ -0,0 +1,4 @@ +const exposureTime = 5000 +const contractAddress = '0x043c471bEe060e00A56CcD02c0Ca286808a5A436' + +export { exposureTime, contractAddress } From 8ae64131ff475cf55614623dd3fdf52a22cfb8bc Mon Sep 17 00:00:00 2001 From: edwin082 Date: Sat, 21 Jan 2023 23:57:49 +0900 Subject: [PATCH 5/9] Add deposit function on Canonical WKLAY page --- src/views/Miscellaneous/WKLAY.tsx | 96 ++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 7 deletions(-) diff --git a/src/views/Miscellaneous/WKLAY.tsx b/src/views/Miscellaneous/WKLAY.tsx index 2349043..261f17f 100644 --- a/src/views/Miscellaneous/WKLAY.tsx +++ b/src/views/Miscellaneous/WKLAY.tsx @@ -50,6 +50,11 @@ const WKLAY = (): ReactElement => { const [balanceButtonDisabled, setBalanceButtonDisabled] = useState(false) const [balanceSuccess, setBalanceSuccess] = useState(false) + const [depositKlayAmount, setDepositKlayAmount] = useState('') + const [depositMsg, setDepositMsg] = useState('') + const [depositButtonDisabled, setDepositButtonDisabled] = useState(false) + const [depositSuccess, setDepositSuccess] = useState(false) + const [belowPage, setBelowPage] = useState(FunctionEnum.BALANCE) const handleOwnerKeystoreChange = (files?: FileList): void => { @@ -145,15 +150,14 @@ const WKLAY = (): ReactElement => { contractAddress ) wklay.options.from = ownerAddress - let balance = await wklay.call('balanceOf', ownerAddress) - const newDepositMsg = `${balance}` + const newBalanceMsg = await wklay.call('balanceOf', ownerAddress) - if (balance) { - setBalanceMsg(newDepositMsg) + if (newBalanceMsg) { + setBalanceMsg(newBalanceMsg) setBalanceButtonDisabled(false) setBalanceSuccess(true) } else { - throw Error('Deposit is failed') + throw Error('Viewing balance is failed') } } catch (err) { setBalanceMsg(_.toString(err)) @@ -166,12 +170,48 @@ const WKLAY = (): ReactElement => { } } + const deposit = async (): Promise => { + try { + setDepositButtonDisabled(true) + + const tx = caver.transaction.valueTransfer.create({ + from: ownerAddress, + to: contractAddress, + value: caver.utils.toPeb(depositKlayAmount, 'KLAY'), + gas: 25000, + }) + + await tx.fillTransaction() + await caver.rpc.klay.sendRawTransaction( + JSON.parse(JSON.stringify(tx.getRawTransaction())) + ) + + const newDepositMsg = 'Deposit is successfully executed.' + + if (newDepositMsg) { + setDepositMsg(newDepositMsg) + setDepositButtonDisabled(false) + setDepositSuccess(true) + } else { + throw Error('Deposit is failed') + } + } catch (err) { + setDepositMsg(_.toString(err)) + setDepositButtonDisabled(false) + setDepositSuccess(false) + + setTimeout(() => { + setBalanceMsg('') + }, exposureTime) + } + } + return (

- Interact with Deployed Contracts - Canonical WKLAY + Interact with a Deployed Contract - Canonical WKLAY

To make the user experience more efficient and reduce friction when @@ -319,7 +359,10 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`}
{ownerDecryptMessage && ( @@ -356,6 +399,45 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`}
)} + {ownerDecryptMessage && belowPage === 'Deposit' && ( + + +

Deposit KLAY

+ Deposit the KLAY. +
+ + + + + + + + + + + + {!!depositMsg && ( + + {depositSuccess ? ( + {depositMsg} + ) : ( + {depositMsg} + )} + + )} + +
+ )} ) } From 381748913150f449cf63aa12a168462169643362 Mon Sep 17 00:00:00 2001 From: edwin082 Date: Sun, 22 Jan 2023 02:07:21 +0900 Subject: [PATCH 6/9] Update the balance and deposit functions on the WKLAY page --- src/consts/urlMap.ts | 2 + src/views/Miscellaneous/WKLAY.tsx | 71 ++++++++++++++++++++++--------- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/consts/urlMap.ts b/src/consts/urlMap.ts index 640fcc1..925a4db 100644 --- a/src/consts/urlMap.ts +++ b/src/consts/urlMap.ts @@ -1,12 +1,14 @@ const network = { testnet: { rpc: 'https://public-node-api.klaytnapi.com/v1/baobab', + scope: 'https://baobab.scope.klaytn.com/tx/', finder: 'https://baobab.klaytnfinder.io/tx/', finderToken: 'https://baobab.klaytnfinder.io/token/', finderNFT: 'https://baobab.klaytnfinder.io/nft/', }, mainnet: { rpc: 'https://public-node-api.klaytnapi.com/v1/cypress', + scope: 'https://scope.klaytn.com/tx/', finder: 'https://www.klaytnfinder.io/tx/', finderToken: 'https://www.klaytnfinder.io/token/', finderNFT: 'https://www.klaytnfinder.io/nft/', diff --git a/src/views/Miscellaneous/WKLAY.tsx b/src/views/Miscellaneous/WKLAY.tsx index 261f17f..eb4fa3b 100644 --- a/src/views/Miscellaneous/WKLAY.tsx +++ b/src/views/Miscellaneous/WKLAY.tsx @@ -55,6 +55,8 @@ const WKLAY = (): ReactElement => { const [depositButtonDisabled, setDepositButtonDisabled] = useState(false) const [depositSuccess, setDepositSuccess] = useState(false) + const [txHash, setTxHash] = useState('') + const [belowPage, setBelowPage] = useState(FunctionEnum.BALANCE) const handleOwnerKeystoreChange = (files?: FileList): void => { @@ -150,14 +152,16 @@ const WKLAY = (): ReactElement => { contractAddress ) wklay.options.from = ownerAddress - const newBalanceMsg = await wklay.call('balanceOf', ownerAddress) + const returnedBalance = await wklay.call('balanceOf', ownerAddress) - if (newBalanceMsg) { - setBalanceMsg(newBalanceMsg) + if (returnedBalance) { + setBalanceMsg( + `${caver.utils.convertFromPeb(returnedBalance, 'KLAY')} WKLAY` + ) setBalanceButtonDisabled(false) setBalanceSuccess(true) } else { - throw Error('Viewing balance is failed') + throw Error('Checking balance is failed') } } catch (err) { setBalanceMsg(_.toString(err)) @@ -174,19 +178,21 @@ const WKLAY = (): ReactElement => { try { setDepositButtonDisabled(true) - const tx = caver.transaction.valueTransfer.create({ + const tx = caver.transaction.legacyTransaction.create({ from: ownerAddress, to: contractAddress, value: caver.utils.toPeb(depositKlayAmount, 'KLAY'), - gas: 25000, + gas: 1000000, }) - await tx.fillTransaction() - await caver.rpc.klay.sendRawTransaction( - JSON.parse(JSON.stringify(tx.getRawTransaction())) + const signedTx = await caver.wallet.sign(ownerAddress, tx) + await signedTx.fillTransaction() + const receipt = await caver.rpc.klay.sendRawTransaction( + signedTx.getRawTransaction() ) + setTxHash(receipt.senderTxHash) - const newDepositMsg = 'Deposit is successfully executed.' + const newDepositMsg = `Deposit is successfully executed.` if (newDepositMsg) { setDepositMsg(newDepositMsg) @@ -370,21 +376,25 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} {ownerDecryptMessage && belowPage === 'Balance' && ( -

Balance of WKLAY

+

Check the Balance of WKLAY

Check the balance of WKLAY of the owner's address.
{!!balanceMsg && ( @@ -403,14 +413,16 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`}

Deposit KLAY

- Deposit the KLAY. + Deposit the KLAY and get the WKLAY.
- + @@ -421,15 +433,32 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} {!!depositMsg && ( {depositSuccess ? ( - {depositMsg} + + {depositMsg} You can check it below link: +
+ + Block Explorer + +
) : ( {depositMsg} )} From dc90f810e8eba9b03d85c49caf0c92214dded29e Mon Sep 17 00:00:00 2001 From: edwin082 Date: Tue, 24 Jan 2023 23:50:01 +0900 Subject: [PATCH 7/9] Add withdraw function on Canonical WKLAY page --- src/views/Miscellaneous/WKLAY.tsx | 102 +++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/src/views/Miscellaneous/WKLAY.tsx b/src/views/Miscellaneous/WKLAY.tsx index eb4fa3b..3c7dfa6 100644 --- a/src/views/Miscellaneous/WKLAY.tsx +++ b/src/views/Miscellaneous/WKLAY.tsx @@ -54,9 +54,14 @@ const WKLAY = (): ReactElement => { const [depositMsg, setDepositMsg] = useState('') const [depositButtonDisabled, setDepositButtonDisabled] = useState(false) const [depositSuccess, setDepositSuccess] = useState(false) - const [txHash, setTxHash] = useState('') + const [withdrawKlayAmount, setWithdrawKlayAmount] = useState('') + const [withdrawMsg, setWithdrawMsg] = useState('') + const [withdrawButtonDisabled, setWithdrawButtonDisabled] = useState(false) + const [withdrawSuccess, setWithdrawSuccess] = useState(false) + const [withdrawTxHash, setWithdrawTxHash] = useState('') + const [belowPage, setBelowPage] = useState(FunctionEnum.BALANCE) const handleOwnerKeystoreChange = (files?: FileList): void => { @@ -207,7 +212,43 @@ const WKLAY = (): ReactElement => { setDepositSuccess(false) setTimeout(() => { - setBalanceMsg('') + setDepositMsg('') + }, exposureTime) + } + } + + const withdraw = async (): Promise => { + try { + setWithdrawButtonDisabled(true) + + const wklay = new caver.contract( + JSON.parse(JSON.stringify(exWKLAYAbi)), + contractAddress + ) + wklay.options.from = ownerAddress + const receipt = await wklay.send( + { from: ownerAddress, gas: 1000000 }, + 'withdraw', + caver.utils.toPeb(withdrawKlayAmount, 'KLAY') + ) + + setWithdrawTxHash(receipt.transactionHash) + const newWithdrawMsg = `Withdraw is successfully executed.` + + if (newWithdrawMsg) { + setWithdrawMsg(newWithdrawMsg) + setWithdrawButtonDisabled(false) + setWithdrawSuccess(true) + } else { + throw Error('Withdraw is failed') + } + } catch (err) { + setWithdrawMsg(_.toString(err)) + setWithdrawButtonDisabled(false) + setWithdrawSuccess(false) + + setTimeout(() => { + setWithdrawMsg('') }, exposureTime) } } @@ -368,6 +409,7 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} itemList={[ { title: 'Balance', value: FunctionEnum.BALANCE }, { title: 'Deposit', value: FunctionEnum.DEPOSIT }, + { title: 'Withdraw', value: FunctionEnum.WITHDRAW }, ]} selectedValue={belowPage} onClick={setBelowPage} @@ -467,6 +509,62 @@ const receipt = await caver.rpc.klay.sendRawTransaction(
)} + {ownerDecryptMessage && belowPage === 'Withdraw' && ( + + +

Withdraw KLAY

+ Withdraw as much KLAY as you want from WKLAY. +
+ + + + + + + + + + + + {!!withdrawMsg && ( + + {withdrawSuccess ? ( + + {withdrawMsg} You can check it below link: +
+ + Block Explorer + +
+ ) : ( + {withdrawMsg} + )} +
+ )} +
+
+ )} ) } From 263497c78a0841783465e0d41c0b94582f62a70c Mon Sep 17 00:00:00 2001 From: edwin082 Date: Wed, 25 Jan 2023 13:49:06 +0900 Subject: [PATCH 8/9] Add Approved and Transfer Using Approved Account functions on the WKLAY page --- src/views/Miscellaneous/SoulboundNFT.tsx | 6 +- src/views/Miscellaneous/WKLAY.tsx | 518 +++++++++++++++++++++-- 2 files changed, 492 insertions(+), 32 deletions(-) diff --git a/src/views/Miscellaneous/SoulboundNFT.tsx b/src/views/Miscellaneous/SoulboundNFT.tsx index b0ff02f..b8e6763 100644 --- a/src/views/Miscellaneous/SoulboundNFT.tsx +++ b/src/views/Miscellaneous/SoulboundNFT.tsx @@ -275,10 +275,14 @@ const SoulboundNFT = (): ReactElement => { soulboundToken:{' '} SBT Smart Contracts Github + + ,{' '} + + Medium Article For Soulbound Tokens {' '} and{' '} - Vitalik's blog: Soulbound + Vitalik's Blog: Soulbound . After successful deployment, you can verify a contract account status in the block explorer. diff --git a/src/views/Miscellaneous/WKLAY.tsx b/src/views/Miscellaneous/WKLAY.tsx index 3c7dfa6..562a45b 100644 --- a/src/views/Miscellaneous/WKLAY.tsx +++ b/src/views/Miscellaneous/WKLAY.tsx @@ -28,8 +28,9 @@ enum FunctionEnum { BALANCE = 'Balance', DEPOSIT = 'Deposit', WITHDRAW = 'Withdraw', - TRANSFEROFOWNER = 'Transfer of Owner', - TRANSFEROFAPPROVER = 'Transfer of Approver', + APPROVE = 'Approve', + TRANSFEROFOWNER = `Transfer of Owner's WKLAY`, + TRANSFEROFAPPROVED = `Transfer of Owner's WKLAY Using Approved Account`, GETEVENTS = 'Get Events', } @@ -41,10 +42,10 @@ const WKLAY = (): ReactElement => { const [ownerKeystorePassword, setOwnerKeystorePassword] = useState('') const [ownerDecryptMessage, setOwnerDecryptMessage] = useState('') - const [approverAddress, setApproverAddress] = useState('') - const [approverKeystoreJSON, setApproverKeystoreJSON] = useState() - const [approverKeystorePassword, setApproverKeystorePassword] = useState('') - const [approverDecryptMessage, setApproverDecryptMessage] = useState('') + const [approvedAddress, setApprovedAddress] = useState('') + const [approvedKeystoreJSON, setApprovedKeystoreJSON] = useState() + const [approvedKeystorePassword, setApprovedKeystorePassword] = useState('') + const [approvedDecryptMessage, setApprovedDecryptMessage] = useState('') const [balanceMsg, setBalanceMsg] = useState('') const [balanceButtonDisabled, setBalanceButtonDisabled] = useState(false) @@ -54,7 +55,7 @@ const WKLAY = (): ReactElement => { const [depositMsg, setDepositMsg] = useState('') const [depositButtonDisabled, setDepositButtonDisabled] = useState(false) const [depositSuccess, setDepositSuccess] = useState(false) - const [txHash, setTxHash] = useState('') + const [depositTxHash, setDepositTxHash] = useState('') const [withdrawKlayAmount, setWithdrawKlayAmount] = useState('') const [withdrawMsg, setWithdrawMsg] = useState('') @@ -62,6 +63,32 @@ const WKLAY = (): ReactElement => { const [withdrawSuccess, setWithdrawSuccess] = useState(false) const [withdrawTxHash, setWithdrawTxHash] = useState('') + const [transferAddress, setTransferAddress] = useState('') + const [transferKlayAmount, setTransferKlayAmount] = useState('') + const [transferMsg, setTransferMsg] = useState('') + const [transferButtonDisabled, setTransferButtonDisabled] = useState(false) + const [transferSuccess, setTransferSuccess] = useState(false) + const [transferTxHash, setTransferTxHash] = useState('') + + const [approvedKlayAmount, setApprovedKlayAmount] = useState('') + const [approvedMsg, setApprovedMsg] = useState('') + const [approvedButtonDisabled, setApprovedButtonDisabled] = useState(false) + const [approvedSuccess, setApprovedSuccess] = useState(false) + const [approvedTxHash, setApprovedTxHash] = useState('') + + const [allowanceMsg, setAllowanceMsg] = useState('') + const [allowanceButtonDisabled, setAllowanceButtonDisabled] = useState(false) + const [allowanceSuccess, setAllowanceSuccess] = useState(false) + + const [approvedTransferAddress, setApprovedTransferAddress] = useState('') + const [approvedTransferKlayAmount, setApprovedTransferKlayAmount] = + useState('') + const [approvedTransferMsg, setApprovedTransferMsg] = useState('') + const [approvedTransferButtonDisabled, setApprovedTransferButtonDisabled] = + useState(false) + const [approvedTransferSuccess, setApprovedTransferSuccess] = useState(false) + const [approvedTransferTxHash, setApprovedTransferTxHash] = useState('') + const [belowPage, setBelowPage] = useState(FunctionEnum.BALANCE) const handleOwnerKeystoreChange = (files?: FileList): void => { @@ -106,25 +133,25 @@ const WKLAY = (): ReactElement => { } } - const handleApproverKeystoreChange = (files?: FileList): void => { + const handleApprovedKeystoreChange = (files?: FileList): void => { if (files && files.length > 0) { const fileReader = new FileReader() fileReader.readAsText(files[0], 'UTF-8') fileReader.onload = (event): void => { if (typeof event.target?.result === 'string') { const json = UTIL.jsonTryParse(event.target.result) - setApproverKeystoreJSON(json) + setApprovedKeystoreJSON(json) } } } } - const decryptApproverKeystore = (): void => { + const decryptApprovedKeystore = (): void => { try { - if (approverKeystoreJSON) { + if (approvedKeystoreJSON) { const keyring = caver.wallet.keyring.decrypt( - approverKeystoreJSON, - approverKeystorePassword + approvedKeystoreJSON, + approvedKeystorePassword ) if (caver.wallet.isExisted(keyring.address)) { @@ -133,17 +160,17 @@ const WKLAY = (): ReactElement => { caver.wallet.add(keyring) } - setApproverDecryptMessage('Decryption succeeds!') - setApproverAddress(keyring.address) + setApprovedDecryptMessage('Decryption succeeds!') + setApprovedAddress(keyring.address) } else { - throw Error('Approver Keystore is not uploaded!') + throw Error(`Approved account's Keystore is not uploaded!`) } } catch (err) { - setApproverDecryptMessage(_.toString(err)) - setApproverAddress('') + setApprovedDecryptMessage(_.toString(err)) + setApprovedAddress('') setTimeout(() => { - setApproverDecryptMessage('') + setApprovedDecryptMessage('') }, exposureTime) } } @@ -195,7 +222,7 @@ const WKLAY = (): ReactElement => { const receipt = await caver.rpc.klay.sendRawTransaction( signedTx.getRawTransaction() ) - setTxHash(receipt.senderTxHash) + setDepositTxHash(receipt.transactionHash) const newDepositMsg = `Deposit is successfully executed.` @@ -252,6 +279,149 @@ const WKLAY = (): ReactElement => { }, exposureTime) } } + const transfer = async (): Promise => { + try { + setTransferButtonDisabled(true) + + const wklay = new caver.contract( + JSON.parse(JSON.stringify(exWKLAYAbi)), + contractAddress + ) + wklay.options.from = ownerAddress + const receipt = await wklay.send( + { from: ownerAddress, gas: 1000000 }, + 'transfer', + transferAddress, + caver.utils.toPeb(transferKlayAmount, 'KLAY') + ) + + setTransferTxHash(receipt.transactionHash) + const newTransferMsg = `Transfer of Owner's WKLAY is successfully executed.` + + if (newTransferMsg) { + setTransferMsg(newTransferMsg) + setTransferButtonDisabled(false) + setTransferSuccess(true) + } else { + throw Error('Transfer is failed') + } + } catch (err) { + setTransferMsg(_.toString(err)) + setTransferButtonDisabled(false) + setTransferSuccess(false) + + setTimeout(() => { + setTransferMsg('') + }, exposureTime) + } + } + const approve = async (): Promise => { + try { + setApprovedButtonDisabled(true) + + const wklay = new caver.contract( + JSON.parse(JSON.stringify(exWKLAYAbi)), + contractAddress + ) + wklay.options.from = ownerAddress + const receipt = await wklay.send( + { from: ownerAddress, gas: 1000000 }, + 'approve', + approvedAddress, + caver.utils.toPeb(approvedKlayAmount, 'KLAY') + ) + + setApprovedTxHash(receipt.transactionHash) + const newApprovedMsg = `Approve is successfully executed.` + + if (newApprovedMsg) { + setApprovedMsg(newApprovedMsg) + setApprovedButtonDisabled(false) + setApprovedSuccess(true) + } else { + throw Error('Approve is failed') + } + } catch (err) { + setApprovedMsg(_.toString(err)) + setApprovedButtonDisabled(false) + setApprovedSuccess(false) + + setTimeout(() => { + setApprovedMsg('') + }, exposureTime) + } + } + const allowance = async (): Promise => { + try { + setAllowanceButtonDisabled(true) + + const wklay = new caver.contract( + JSON.parse(JSON.stringify(exWKLAYAbi)), + contractAddress + ) + wklay.options.from = approvedAddress + const returnedAllowance = await wklay.call( + 'allowance', + ownerAddress, + approvedAddress + ) + + if (returnedAllowance) { + setAllowanceMsg( + `${caver.utils.convertFromPeb(returnedAllowance, 'KLAY')} WKLAY` + ) + setAllowanceButtonDisabled(false) + setAllowanceSuccess(true) + } else { + throw Error('Checking allowance is failed') + } + } catch (err) { + setAllowanceMsg(_.toString(err)) + setAllowanceButtonDisabled(false) + setAllowanceSuccess(false) + + setTimeout(() => { + setAllowanceMsg('') + }, exposureTime) + } + } + const approvedTransfer = async (): Promise => { + try { + setApprovedTransferButtonDisabled(true) + + const wklay = new caver.contract( + JSON.parse(JSON.stringify(exWKLAYAbi)), + contractAddress + ) + wklay.options.from = approvedAddress + const receipt = await wklay.send( + { from: approvedAddress, gas: 1000000 }, + 'transferFrom', + ownerAddress, + approvedTransferAddress, + caver.utils.toPeb(approvedTransferKlayAmount, 'KLAY') + ) + + setApprovedTransferTxHash(receipt.transactionHash) + const newApprovedTransferMsg = `Transfer of Owner's WKLAY Using Approved Account is successfully executed.` + + if (newApprovedTransferMsg) { + setApprovedTransferMsg(newApprovedTransferMsg) + setApprovedTransferButtonDisabled(false) + setApprovedTransferSuccess(true) + } else { + throw Error('Transfer Using Approved Account is failed') + } + } catch (err) { + setApprovedTransferMsg(_.toString(err)) + setApprovedTransferButtonDisabled(false) + setApprovedTransferSuccess(false) + + setTimeout(() => { + setApprovedTransferMsg('') + }, exposureTime) + } + } return ( @@ -272,8 +442,12 @@ const WKLAY = (): ReactElement => { information about Canonical WKLAY:{' '} Canonical WKLAY Contract Github + {' '} + and{' '} + + Medium Article For Announcing Canonical WKLAY - {'\n\n'} + .{'\n\n'} Deployed Contract Addresses{'\n'} Mainnet:{' '} @@ -349,7 +523,7 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`}

{' '} - Upload Keystore File of Approver of the WKLAY Balance + Upload Keystore File of Approved Account of the WKLAY Balance

@@ -365,7 +539,7 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} @@ -373,13 +547,13 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} - + - {approverDecryptMessage && - (!!approverAddress ? ( + {approvedDecryptMessage && + (!!approvedAddress ? ( - {approverDecryptMessage} + {approvedDecryptMessage} ) : ( - {approverDecryptMessage} + {approvedDecryptMessage} ))} @@ -410,6 +584,12 @@ const keyring = caver.wallet.keyring.decrypt(keystoreJSON, password)`} { title: 'Balance', value: FunctionEnum.BALANCE }, { title: 'Deposit', value: FunctionEnum.DEPOSIT }, { title: 'Withdraw', value: FunctionEnum.WITHDRAW }, + { title: 'Transfer', value: FunctionEnum.TRANSFEROFOWNER }, + { title: 'Approve', value: FunctionEnum.APPROVE }, + { + title: 'Transfer Using Approved Account', + value: FunctionEnum.TRANSFEROFAPPROVED, + }, ]} selectedValue={belowPage} onClick={setBelowPage} @@ -496,7 +676,7 @@ const receipt = await caver.rpc.klay.sendRawTransaction( {depositMsg} You can check it below link:
Block Explorer @@ -565,6 +745,282 @@ const receipt = await wklay.send(
)} + {ownerDecryptMessage && belowPage === `Transfer of Owner's WKLAY` && ( + + +

Transfer of Owner's WKLAY

+ Transfer the WKLAY you own to another address. +
+ + + + + + + + + + + + + + {!!transferMsg && ( + + {transferSuccess ? ( + + {transferMsg} You can check it below link: +
+ + Block Explorer + +
+ ) : ( + {transferMsg} + )} +
+ )} +
+
+ )} + {ownerDecryptMessage && belowPage === 'Approve' && ( + + +

Approve Using WKLAY

+ Approve using WKLAY at another account address. +
+ + {approvedAddress ? ( + <> + + + + + {}} + /> + + + + + + + + {!!approvedMsg && ( + + {approvedSuccess ? ( + + {approvedMsg} You can check it below link: +
+ + Block Explorer + +
+ ) : ( + + {' '} + {approvedMsg}{' '} + + )} +
+ )} + + ) : ( + + + Please upload the keystore of the account to be approved + first. + + + )} +
+
+ )} + {ownerDecryptMessage && + belowPage === `Transfer of Owner's WKLAY Using Approved Account` && ( + <> + + +

Check the Allowance of WKLAY

+ + Check the allowance of WKLAY of the approved account. + +
+ + {approvedAddress ? ( + <> + + + + + + + {!!allowanceMsg && ( + + {allowanceSuccess ? ( + {allowanceMsg} + ) : ( + + {' '} + {allowanceMsg}{' '} + + )} + + )} + + ) : ( + + + Please upload the keystore of the account to be approved + first. + + + )} + +
+ {allowanceSuccess && ( + + +

+ Transfer of Owner's WKLAY Using Approved Account +

+ Send the WKLAY allowed to use. +
+ + + + + + + + + + + + + + {!!approvedTransferMsg && ( + + {approvedTransferSuccess ? ( + + {approvedTransferMsg} You can check it below link: +
+ + Block Explorer + +
+ ) : ( + + {' '} + {approvedTransferMsg}{' '} + + )} +
+ )} +
+
+ )} + + )} ) } From 5b7013a563854724c7da8150c29c8b11a789f1d1 Mon Sep 17 00:00:00 2001 From: edwin082 Date: Wed, 25 Jan 2023 22:19:07 +0900 Subject: [PATCH 9/9] 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, +}