Skip to content

Commit

Permalink
Merge pull request #1250 from alleslabs/feat/evm-code-snippet
Browse files Browse the repository at this point in the history
feat: add evm code snippet, fix fields bugs
  • Loading branch information
evilpeach authored Feb 27, 2025
2 parents 138ff1d + 216dbad commit 21e95a1
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 83 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/lib/components/evm-abi/fields/BoolField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>[] = [
const BOOL_FIELD_OPTIONS: SelectInputOption<boolean>[] = [
{
label: "True",
value: "1",
value: true,
},
{
label: "False",
value: "0",
value: false,
},
];

Expand Down
6 changes: 4 additions & 2 deletions src/lib/components/evm-abi/fields/FieldTemplate.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -19,12 +19,14 @@ export const FieldTemplate = <T extends FieldValues>({
...rest
}: FieldTemplateProps<T>) => {
const {
field: { value, onChange },
field: { onChange },
} = useController<T>({
control,
name,
});

const value = useWatch({ control, name });

if (dimensions.length === 0)
return (
<Field control={control} name={name} components={components} {...rest} />
Expand Down
232 changes: 176 additions & 56 deletions src/lib/components/modal/EvmCodeSnippet.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { ButtonProps } from "@chakra-ui/react";
import {
Box,
Button,
Heading,
Modal,
Expand All @@ -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<Record<CodeSnippetType, CodeSnippet[]>> =
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 (
<>
<Button
variant="outline-secondary"
minW="128px"
size="sm"
gap={1}
onClick={() => {
track(AmpEvent.USE_CONTRACT_SNIPPET, {});
onOpen();
}}
>
<CustomIcon name="code" />
Code Snippet
</Button>

return null;

// return (
// <>
// <Button
// variant="outline-secondary"
// minW="128px"
// size="sm"
// ml={ml}
// gap={1}
// onClick={() => {
// track(AmpEvent.USE_CONTRACT_SNIPPET, {});
// onOpen();
// }}
// >
// <CustomIcon name="code" />
// Code Snippet
// </Button>

// <Modal isOpen={isOpen} onClose={onClose} isCentered size="4xl">
// <ModalOverlay />
// <ModalContent w="840px">
// <ModalHeader>
// <CustomIcon name="code" boxSize={6} color="gray.600" />
// <Heading as="h5" variant="h5">
// Code Snippet
// </Heading>
// </ModalHeader>
// <ModalCloseButton color="gray.600" />
// <ModalBody px={4} maxH="640px" overflow="scroll">
// <Text wordBreak="break-word">
// 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.
// </Text>
// </ModalBody>
// </ModalContent>
// </Modal>
// </>
// );
<Modal isOpen={isOpen} onClose={onClose} isCentered size="4xl">
<ModalOverlay />
<ModalContent w="840px">
<ModalHeader>
<CustomIcon name="code" boxSize={6} color="gray.600" />
<Heading as="h5" variant="h5">
Code Snippet
</Heading>
</ModalHeader>
<ModalCloseButton color="gray.600" />
<ModalBody px={4} maxH="640px" overflow="scroll">
<Tabs>
<TabList borderBottom="1px solid" borderColor="gray.700">
{codeSnippets[type].map((item) => (
<CustomTab key={`menu-${item.name}`}>{item.name}</CustomTab>
))}
</TabList>
<TabPanels>
{codeSnippets[type].map((item) => (
<TabPanel key={item.name} px={2} py={4}>
<Box
bgColor="background.main"
p={4}
borderRadius="8px"
position="relative"
>
<AceEditor
readOnly
mode={item.mode}
theme={theme.jsonTheme}
fontSize="14px"
style={{
width: "100%",
background: "transparent",
}}
value={item.snippet}
setOptions={{
showGutter: false,
useWorker: false,
printMargin: false,
wrap: true,
}}
/>
<Box position="absolute" top={4} right={4}>
<CopyButton
value={item.snippet}
amptrackSection="code_snippet"
amptrackSubSection={item.name}
amptrackInfo={type}
/>
</Box>
</Box>
</TabPanel>
))}
</TabPanels>
</Tabs>
</ModalBody>
</ModalContent>
</Modal>
</>
);
};

export default EvmCodeSnippet;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Flex,
Grid,
GridItem,
HStack,
Text,
} from "@chakra-ui/react";
import type { JsonFragment } from "ethers";
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -143,15 +144,22 @@ export const ReadBox = ({
gap={2}
mt={6}
>
<CopyButton
variant="outline-secondary"
isDisable={isUndefined(data)}
value={data ?? ""}
amptrackSection="read_inputs"
buttonText="Copy Encoded Inputs"
w="100%"
/>
<EvmCodeSnippet />
<HStack gap={2}>
<CopyButton
variant="outline-secondary"
isDisable={isUndefined(data)}
value={data ?? ""}
amptrackSection="read_inputs"
buttonText="Copy Encoded Inputs"
w="100%"
/>
<EvmCodeSnippet
abiSection={abiSection}
contractAddress={contractAddress}
type="read"
inputs={inputs}
/>
</HStack>
<Button
variant="primary"
size="sm"
Expand Down Expand Up @@ -195,14 +203,21 @@ export const ReadBox = ({
justifyContent="space-between"
>
<Grid gridTemplateColumns="1fr 1fr" gap={2}>
<CopyButton
variant="outline-secondary"
isDisable={isUndefined(data)}
value={data ?? ""}
amptrackSection="read_inputs"
buttonText="Copy Encoded Inputs"
w="100%"
/>
<HStack gap={2}>
<CopyButton
variant="outline-secondary"
isDisable={isUndefined(data)}
value={data ?? ""}
amptrackSection="read_inputs"
buttonText="Copy Encoded Inputs"
w="100%"
/>
<EvmCodeSnippet
abiSection={abiSection}
contractAddress={contractAddress}
type="read"
/>
</HStack>
<CopyButton
variant="outline-secondary"
isDisable={res === "" || Boolean(queryError)}
Expand All @@ -213,7 +228,6 @@ export const ReadBox = ({
w="100%"
/>
</Grid>
<EvmCodeSnippet />
<Flex
gap={{
md: 2,
Expand Down
Loading

0 comments on commit 21e95a1

Please sign in to comment.