From efa78e18a26c4c0550fb5c2e088e03cd337dde8e Mon Sep 17 00:00:00 2001 From: "Chiaki.C" Date: Tue, 11 Jul 2023 14:42:34 +0800 Subject: [PATCH 01/56] chore: add prettierrc --- .prettierrc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..59111f6 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": true, + "trailingComma": "es5", + "printWidth": 80 +} From 8ced3d7d6e8c64b5bf74fcc685d76502f234fdd3 Mon Sep 17 00:00:00 2001 From: "Chiaki.C" Date: Mon, 17 Jul 2023 17:39:49 +0800 Subject: [PATCH 02/56] refactor: evm editor --- package.json | 3 +- src/components/EvmChainSelect.tsx | 33 +- src/components/EvmEditor.tsx | 366 +++++++----------- .../EvmEditors/EvmRequestEditor.tsx | 97 +++++ src/components/EvmEditors/EvmSignEditor.tsx | 291 ++++++++++++++ src/components/EvmEditors/ParamEditor.tsx | 83 ++++ src/components/LoginButton.tsx | 7 +- src/scripts/evm/Request.ts | 4 + src/services/evm.ts | 136 +++++++ yarn.lock | 147 ++++++- 10 files changed, 931 insertions(+), 236 deletions(-) create mode 100644 src/components/EvmEditors/EvmRequestEditor.tsx create mode 100644 src/components/EvmEditors/EvmSignEditor.tsx create mode 100644 src/components/EvmEditors/ParamEditor.tsx create mode 100644 src/scripts/evm/Request.ts create mode 100644 src/services/evm.ts diff --git a/package.json b/package.json index 76bd581..7cbba6d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@blocto/fcl": "^1.0.0-alpha.1", - "@blocto/sdk": "^0.5.0", + "@blocto/sdk": "^0.5.1", "@chakra-ui/icons": "^1.1.1", "@chakra-ui/react": "^1.7.4", "@emotion/react": "^11", @@ -23,6 +23,7 @@ "framer-motion": "^4", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-json-view": "^1.21.3", "react-scripts": "4.0.3", "sha3": "^2.1.4", "typescript": "^4.5.4", diff --git a/src/components/EvmChainSelect.tsx b/src/components/EvmChainSelect.tsx index 3831a6a..e02f268 100644 --- a/src/components/EvmChainSelect.tsx +++ b/src/components/EvmChainSelect.tsx @@ -1,31 +1,38 @@ -import React, { useContext } from "react"; +import React, { useState } from "react"; import { Button, Menu, MenuButton, MenuList, MenuItem } from "@chakra-ui/react"; import { ChevronDownIcon } from "@chakra-ui/icons"; -import { startCase } from "lodash"; import { ReactJSXElement } from "@emotion/react/types/jsx-namespace"; -import { Context } from "../context/Context"; -import { EvmChain } from "../types/ChainTypes"; +import { supportedChains, bloctoSDK, useEthereum } from "../services/evm"; const EvmChainSelect: React.FC = ({}): ReactJSXElement => { - const { chain, switchChain } = useContext(Context); + const { chainId: currentChainId } = useEthereum(); + const [chainName, setChainName] = useState( + supportedChains.find(({ chainId }) => chainId === currentChainId)?.name || + "Ethereum Goerli" + ); return ( - } width="130px"> - {startCase(chain)} + } width="200px"> + {chainName} - {Object.values(EvmChain).map((chain) => ( + {supportedChains.map(({ name, chainId }) => ( { - if (switchChain) { - switchChain(chain); - } + bloctoSDK.ethereum + .request({ + method: "wallet_switchEthereumChain", + params: [{ chainId }], + }) + .then(() => { + setChainName(name); + }); }} > - {startCase(chain)} + {name} ))} diff --git a/src/components/EvmEditor.tsx b/src/components/EvmEditor.tsx index 6a02b8c..8ec3dd3 100644 --- a/src/components/EvmEditor.tsx +++ b/src/components/EvmEditor.tsx @@ -1,17 +1,28 @@ -import React, { useCallback, useContext } from "react"; -import { useToast } from "@chakra-ui/react"; +import React, { useCallback, useState } from "react"; +import { + useToast, + Box, + Button, + Flex, + Tab, + TabList, + Tabs, + TabPanels, + TabPanel, + Stat, + StatLabel, + StatNumber, + StatHelpText, +} from "@chakra-ui/react"; +import { CopyIcon } from "@chakra-ui/icons"; import { ReactJSXElement } from "@emotion/react/types/jsx-namespace"; -import { Context } from "../context/Context"; -import * as ContractTemplates from "../scripts/evm/Contract"; -import * as SignMessageTemplates from "../scripts/evm/SignMessage"; -import * as TransactionsTemplates from "../scripts/evm/Transactions"; -import * as UserOperationsTemplates from "../scripts/evm/UserOperations"; import { EvmChain } from "../types/ChainTypes"; -import ScriptTypes, { Arg, ArgTypes, PerInfo } from "../types/ScriptTypes"; -import Editor from "./Editor"; import EvmChainSelect from "./EvmChainSelect"; - -const typeKeys = Object.values(ArgTypes); +import EvmRequestEditor from "./EvmEditors/EvmRequestEditor"; +import EvmSignEditor from "./EvmEditors/EvmSignEditor"; +import { EthereumTypes } from "@blocto/sdk"; +import { bloctoSDK, useEthereum } from "../services/evm"; +import ReactJson from "react-json-view"; const FaucetUrls = { [EvmChain.Ethereum]: "https://goerlifaucet.com/", @@ -22,218 +33,141 @@ const FaucetUrls = { [EvmChain.Optimism]: "https://faucet.paradigm.xyz/", }; -const MenuGroups = [ - { title: "Transactions", templates: TransactionsTemplates }, - { title: "Sign Message", templates: SignMessageTemplates }, - { title: "Contract", templates: ContractTemplates }, - { title: "User Operations", templates: UserOperationsTemplates }, -]; - -const formatTransactionArgs = (args: Arg[] | undefined) => { - return args?.reduce((initial: { [key: string]: any }, currentValue: Arg) => { - if (currentValue.name) { - initial[currentValue.name] = - currentValue.type === ArgTypes.Number - ? +currentValue.value - : currentValue.value; - } - return initial; - }, {}); -}; - -const formatContractArgs = (args: Arg[] | undefined) => { - return args - ?.map((arg) => { - return arg.type === ArgTypes.Number ? +arg.value : arg.value; - }) - .filter((arg): arg is string | number => arg); -}; - const EvmEditor = (): ReactJSXElement => { - const { chain, address, login } = useContext(Context); + const { account, chainId, connect, disconnect } = useEthereum(); const toast = useToast(); - const handleSignMessage = useCallback( - async (args, method) => { - return new Promise(async (resolve, reject) => { - try { - let evmAddress = address; - if (!evmAddress && login) { - evmAddress = await login(); - } - method(args?.[0]?.value ?? "", evmAddress, chain) - .then(resolve) - .catch(reject); - } catch (error) { - reject(error); - } - }); - }, - [address, login, chain] - ); - - const checkArgumentsAndAddress = useCallback( - async (args: Arg[] | undefined) => { - return new Promise(async (resolve, reject) => { - const noArgsProvided = args?.every((arg) => arg.value === undefined); - if (args?.length !== 0 && noArgsProvided) { - throw new Error("Error: Transaction arguments are missing."); - } - - try { - let evmAddress = address; - if (!evmAddress && login) { - evmAddress = await login(); - } - - resolve(evmAddress); - } catch (error) { - reject(error); - } - }); - }, - [address, login] - ); - - const handleSendTransactions = useCallback( - ( - args: Arg[] | undefined, - shouldSign: boolean | undefined, - signers: Array<{ privateKey: string; address: string }> | undefined, - script: string, - method?: (...param: any[]) => Promise, - isUserOperation?: boolean - ): Promise<{ - transactionId?: string; - transaction?: any; - userOpHash?: string; - }> => { - return new Promise(async (resolve, reject) => { - if (!method) { - return reject(new Error("Error: Transaction method is missing.")); - } - - const address = await checkArgumentsAndAddress(args); - const formattedArgs = formatTransactionArgs(args); - - !isUserOperation - ? method(address, formattedArgs, chain) - .then((transaction) => { - resolve({ - transactionId: transaction.transactionHash, - transaction, - }); - toast({ - title: "Transaction is Sealed", - status: "success", - isClosable: true, - duration: 1000, - }); - }) - .catch((error) => { - reject(error); - toast({ - title: "Transaction failed", - status: "error", - isClosable: true, - duration: 1000, - }); - }) - : method(address, formattedArgs, chain) - .then((userOpHash) => { - resolve({ userOpHash }); - toast({ - title: "Transaction is Sealed", - status: "success", - isClosable: true, - duration: 1000, - }); - }) - .catch((error) => { - reject(error); - toast({ - title: "Transaction failed", - status: "error", - isClosable: true, - duration: 1000, - }); - }); - }); - }, - [toast, checkArgumentsAndAddress, chain] - ); - - const handleInteractWithContract = useCallback( - async ( - contractInfo: Record, - args: Arg[] | undefined, - method?: (...param: any[]) => Promise - ): Promise => { - return new Promise(async (resolve, reject) => { - if (!method) { - return reject(new Error("Error: Transaction method is missing.")); - } - - const address = await checkArgumentsAndAddress(args); - const formattedArgs = formatContractArgs(args); + const [requestObject, setRequestObject] = + useState(); + const sendRequest = useCallback(async () => { + if (!requestObject) return; + console.log(requestObject); + try { + const response = await bloctoSDK.ethereum.request(requestObject); + console.log(response); + } catch (e) { + console.log(e); + } + }, [requestObject]); - method({ - account: address, - args: formattedArgs, - chain, - contractAbi: contractInfo?.contractAbi?.value, - contractAddress: contractInfo?.contractAddress?.value, - methodName: contractInfo?.methodName?.value, - }) - .then((result) => { - resolve( - typeof result === "string" - ? result - : { - transactionId: result.transactionHash, - transaction: result, - } - ); - toast({ - title: "Contract Method is Executed", - status: "success", - isClosable: true, - duration: 1000, - }); - }) - .catch((error) => { - reject(error); - toast({ - title: "Contract Method failed", - status: "error", - isClosable: true, - duration: 1000, - }); - }); - }); - }, - [toast, checkArgumentsAndAddress, chain] - ); + const copyAddress = useCallback(() => { + window.navigator.clipboard + .writeText(account || "") + .then(() => + toast({ title: "Address copied!", status: "success", duration: 2000 }) + ); + }, [account, toast]); return ( - - - + + + + Current ChainId + + {chainId} + + {parseInt(chainId || "5", 16)} + + + + {account ? ( + + + {account} + + + + ) : ( + "disconnected" + )} + + + + + + + + + + + + Sign + Request + Contract + User Operation + + + + + + + + + +

Contract

+
+ +

User Operation

+
+
+
+
+ + + + + + + + + + + ); }; diff --git a/src/components/EvmEditors/EvmRequestEditor.tsx b/src/components/EvmEditors/EvmRequestEditor.tsx new file mode 100644 index 0000000..c87ac98 --- /dev/null +++ b/src/components/EvmEditors/EvmRequestEditor.tsx @@ -0,0 +1,97 @@ +import React, { + useCallback, + useEffect, + useState, + Dispatch, + SetStateAction, +} from "react"; +import { + Box, + Button, + Flex, + Textarea, + Menu, + MenuButton, + MenuList, + MenuItem, + MenuGroup, +} from "@chakra-ui/react"; +import { ChevronDownIcon } from "@chakra-ui/icons"; +import { ReactJSXElement } from "@emotion/react/types/jsx-namespace"; +import * as RequestTemplate from "../../scripts/evm/Request"; +import { EthereumTypes } from "@blocto/sdk"; +import ParamEditor from "./ParamEditor"; + +const MenuGroups = [{ title: "Request", templates: RequestTemplate }]; + +const EvmRequestEditor = ({ + setRequestObject, +}: { + setRequestObject: Dispatch< + SetStateAction + >; +}): ReactJSXElement => { + const [method, setMethod] = useState(""); + const [param, setParam] = useState([["", ""]]); + const importTemplate = useCallback( + (template: EthereumTypes.EIP1193RequestPayload) => { + setMethod(template.method); + setParam(Object.entries(template?.params?.[0] || {})); + }, + [] + ); + useEffect(() => { + setRequestObject({ + method, + params: [Object.fromEntries(param)], + }); + }, [method, param, setRequestObject]); + + return ( + + + } width="130px"> + Templates + + + {MenuGroups.map((menuGroup) => ( + + {Object.entries(menuGroup.templates).map(([name, template]) => ( + { + importTemplate(template); + }} + > + {name} + + ))} + + ))} + + + + + + Method + +