diff --git a/CHANGELOG.md b/CHANGELOG.md index 259de5b7f..d5255ff4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#1250](https://github.com/alleslabs/celatone-frontend/pull/1250) Add evm code snippet modal and fix input fields - [#1247](https://github.com/alleslabs/celatone-frontend/pull/1247) Support multi address creation txs - [#1245](https://github.com/alleslabs/celatone-frontend/pull/1245) Support assets from assetlist in chain config - [#1244](https://github.com/alleslabs/celatone-frontend/pull/1244) Add EVM contract verify alert info to both Solidity and Vyper upload file(s) and contract code diff --git a/src/lib/components/evm-abi/fields/BoolField.tsx b/src/lib/components/evm-abi/fields/BoolField.tsx index 2e3cef511..b36b10ef7 100644 --- a/src/lib/components/evm-abi/fields/BoolField.tsx +++ b/src/lib/components/evm-abi/fields/BoolField.tsx @@ -4,14 +4,14 @@ import type { SelectInputOption } from "lib/components/forms"; import { SelectInput } from "lib/components/forms"; import type { FieldProps } from "./types"; -const BOOL_FIELD_OPTIONS: SelectInputOption[] = [ +const BOOL_FIELD_OPTIONS: SelectInputOption[] = [ { label: "True", - value: "1", + value: true, }, { label: "False", - value: "0", + value: false, }, ]; diff --git a/src/lib/components/evm-abi/fields/FieldTemplate.tsx b/src/lib/components/evm-abi/fields/FieldTemplate.tsx index d3ae959e5..9f5f0e433 100644 --- a/src/lib/components/evm-abi/fields/FieldTemplate.tsx +++ b/src/lib/components/evm-abi/fields/FieldTemplate.tsx @@ -1,6 +1,6 @@ import { Button, Flex, Text } from "@chakra-ui/react"; import type { FieldPath, FieldValues } from "react-hook-form"; -import { useController } from "react-hook-form"; +import { useController, useWatch } from "react-hook-form"; import { CustomIcon } from "lib/components/icon"; import type { Option } from "lib/types"; import { Field } from "./Field"; @@ -19,12 +19,14 @@ export const FieldTemplate = ({ ...rest }: FieldTemplateProps) => { const { - field: { value, onChange }, + field: { onChange }, } = useController({ control, name, }); + const value = useWatch({ control, name }); + if (dimensions.length === 0) return ( diff --git a/src/lib/components/modal/EvmCodeSnippet.tsx b/src/lib/components/modal/EvmCodeSnippet.tsx index 2975ebce4..adac66ee3 100644 --- a/src/lib/components/modal/EvmCodeSnippet.tsx +++ b/src/lib/components/modal/EvmCodeSnippet.tsx @@ -1,6 +1,5 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import type { ButtonProps } from "@chakra-ui/react"; import { + Box, Button, Heading, Modal, @@ -9,76 +8,197 @@ import { ModalContent, ModalHeader, ModalOverlay, - Text, + TabList, + TabPanel, + TabPanels, + Tabs, useDisclosure, } from "@chakra-ui/react"; +import type { JsonFragment } from "ethers"; +import { useMemo } from "react"; +import AceEditor from "react-ace"; import { AmpEvent, track } from "lib/amplitude"; -import { CustomIcon } from "../icon"; +import { useCelatoneApp } from "lib/app-provider/contexts"; +import { useEvmConfig } from "lib/app-provider/hooks"; +import { CustomTab } from "lib/components/CustomTab"; +import type { HexAddr20, JsonDataType, Nullable } from "lib/types"; import "ace-builds/src-noconflict/ace"; import "ace-builds/src-noconflict/mode-javascript"; -import "ace-builds/src-noconflict/mode-python"; -import "ace-builds/src-noconflict/mode-sh"; import "ace-builds/src-noconflict/theme-monokai"; import "ace-builds/src-noconflict/theme-one_dark"; import "ace-builds/src-noconflict/theme-pastel_on_dark"; +import { formatEvmFunctionInputsArgs } from "lib/utils"; +import { CopyButton } from "../copy"; +import { CustomIcon } from "../icon"; + +type CodeSnippetType = "read" | "write"; + +interface CodeSnippet { + name: string; + mode: string; + snippet: string; +} interface EvmCodeSnippetProps { - ml?: ButtonProps["ml"]; + type: CodeSnippetType; + contractAddress: HexAddr20; + abiSection: JsonFragment; + inputs?: JsonDataType[]; } -// TODO: Implement this modal -const EvmCodeSnippet = ({ ml }: EvmCodeSnippetProps) => { +const EvmCodeSnippet = ({ + contractAddress, + abiSection, + type, + inputs, +}: EvmCodeSnippetProps) => { const { isOpen, onClose, onOpen } = useDisclosure(); + const { theme } = useCelatoneApp(); + const evm = useEvmConfig({ shouldRedirect: false }); + + const codeSnippets: Nullable> = + useMemo(() => { + if (!evm.enabled || !evm.jsonRpc || !abiSection.name) return null; + + const functionName = `"${abiSection.name}"`; + const inputsString = formatEvmFunctionInputsArgs(inputs); + + return { + read: [ + { + name: "Ethers", + mode: "javascript", + snippet: `import { ethers, Interface } from "ethers"; + +const provider = new ethers.JsonRpcProvider("${evm.jsonRpc}"); +const iface = new Interface([${JSON.stringify(abiSection)}]); + +const main = async () => { + const encodedData = iface.encodeFunctionData(${functionName}, ${inputsString}); + + const rawResult = await provider.call({ + to: "${contractAddress}", + data: encodedData, + }); + + const decodedResult = iface.decodeFunctionResult(${functionName}, rawResult); + console.log(decodedResult); +}; + +main();`, + }, + ], + write: [ + { + name: "Ethers", + mode: "javascript", + snippet: `import { ethers, Interface } from "ethers"; + +const provider = new ethers.JsonRpcProvider("${evm.jsonRpc}"); +const privateKey = "your-private-key"; +const wallet = new ethers.Wallet(privateKey, provider); + +const ABI = [${JSON.stringify(abiSection)}]; +const iface = new Interface(ABI); +const encodedData = iface.encodeFunctionData(${functionName}, ${inputsString}); + +const main = async () => { + const tx = await wallet.sendTransaction({ + to: "${contractAddress}", + data: encodedData, + }); + + console.log(tx); +}; + +main();`, + }, + ], + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(evm), JSON.stringify(inputs), abiSection]); + + if (!codeSnippets) return null; + + return ( + <> + - return null; - - // return ( - // <> - // - - // - // - // - // - // - // - // Code Snippet - // - // - // - // - // - // Lorem Ipsum is simply dummy text of the printing and typesetting - // industry. Lorem Ipsum has been the industry's standard dummy text - // ever since the 1500s, when an unknown printer took a galley of - // type and scrambled it to make a type specimen book. It has - // survived not only five centuries, but also the leap into - // electronic typesetting, remaining essentially unchanged. It was - // popularised in the 1960s with the release of Letraset sheets - // containing Lorem Ipsum passages, and more recently with desktop - // publishing software like Aldus PageMaker including versions of - // Lorem Ipsum. - // - // - // - // - // - // ); + + + + + + + Code Snippet + + + + + + + {codeSnippets[type].map((item) => ( + {item.name} + ))} + + + {codeSnippets[type].map((item) => ( + + + + + + + + + ))} + + + + + + + ); }; export default EvmCodeSnippet; diff --git a/src/lib/pages/evm-contract-details/components/interact-evm-contract/abi-read/ReadBox.tsx b/src/lib/pages/evm-contract-details/components/interact-evm-contract/abi-read/ReadBox.tsx index 51dc233dc..6d10a10a3 100644 --- a/src/lib/pages/evm-contract-details/components/interact-evm-contract/abi-read/ReadBox.tsx +++ b/src/lib/pages/evm-contract-details/components/interact-evm-contract/abi-read/ReadBox.tsx @@ -9,6 +9,7 @@ import { Flex, Grid, GridItem, + HStack, Text, } from "@chakra-ui/react"; import type { JsonFragment } from "ethers"; @@ -81,7 +82,7 @@ export const ReadBox = ({ [abiSection, inputs] ); const { refetch, isFetching } = useEthCall(contractAddress, data ?? "", { - enabled: !isUndefined(data) && opened, + enabled: opened && !isUndefined(data) && !inputRequired, retry: false, cacheTime: 0, onSuccess: (data) => { @@ -143,15 +144,22 @@ export const ReadBox = ({ gap={2} mt={6} > - - + + + +