diff --git a/.github/workflows/create-asana-attachment.yaml b/.github/workflows/create-asana-attachment.yaml index 8c53831..7b1eee8 100644 --- a/.github/workflows/create-asana-attachment.yaml +++ b/.github/workflows/create-asana-attachment.yaml @@ -13,6 +13,8 @@ jobs: with: asana-secret: ${{ secrets.ASANA_SECRET }} allowed-projects: | - 1201812548509877 + 1207145524635866 + 1207160002492686 + 1204161964711931 - name: Log output status - run: echo "Status is ${{ steps.postAttachment.outputs.status }}" \ No newline at end of file + run: echo "Status is ${{ steps.postAttachment.outputs.status }}" diff --git a/.gitignore b/.gitignore index 532eddc..2ac3efa 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.env \ No newline at end of file +.env + +# IntelijIDEA +.idea \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..07d6a3b --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,18 @@ +FROM node:16-alpine + +WORKDIR /app + +COPY package*.json yarn.lock ./ +RUN apk add --no-cache yarn + +RUN apk add --no-cache python3 +ENV PYTHON=/usr/bin/python3 + +RUN apk add --no-cache make g++ + +RUN yarn install +COPY . ./ + +EXPOSE 3001 + +CMD ["yarn", "start"] diff --git a/package.json b/package.json index ad9083c..29a270f 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "dependencies": { "@blocto/dappauth": "^2.1.0", "@blocto/fcl": "^1.0.0-alpha.1", - "@blocto/sdk": "^0.9.0-beta.3", + "@blocto/sdk": "^0.10.2", "@chakra-ui/icons": "^1.1.1", "@chakra-ui/react": "^1.7.4", "@emotion/react": "^11", diff --git a/src/components/EvmChainSelect.tsx b/src/components/EvmChainSelect.tsx index 2c78e76..b128eb6 100644 --- a/src/components/EvmChainSelect.tsx +++ b/src/components/EvmChainSelect.tsx @@ -1,14 +1,5 @@ -import React, { useEffect, useState } from "react"; -import { - Button, - Menu, - MenuButton, - MenuList, - MenuItem, - MenuGroup, - MenuDivider, -} from "@chakra-ui/react"; -import { ChevronDownIcon } from "@chakra-ui/icons"; +import React from "react"; +import { Select } from "@chakra-ui/react"; import { ReactJSXElement } from "@emotion/react/types/jsx-namespace"; import { supportedChains, bloctoSDK, useEthereum } from "../services/evm"; @@ -21,70 +12,31 @@ const supportedTestnetChains = supportedChains.filter( const EvmChainSelect: React.FC = ({}): ReactJSXElement => { const { chainId: currentChainId } = useEthereum(); - const [chainName, setChainName] = useState( - supportedChains.find(({ chainId }) => chainId === currentChainId)?.name || - "Ethereum Goerli" - ); - useEffect(() => { - const chainName = supportedChains.find( - ({ chainId }) => chainId === currentChainId - )?.name; - if (chainName) { - setChainName(chainName); - } - }, [currentChainId]); return ( - - } width="200px"> - {chainName} - - - - {supportedTestnetChains.map(({ name, chainId }) => ( - { - bloctoSDK.ethereum - .request({ - method: "wallet_switchEthereumChain", - params: [{ chainId }], - }) - .then(() => { - setChainName(name); - }); - }} - > - {name} - - ))} - - - - {supportedMainnetChains.map(({ name, chainId }) => ( - { - bloctoSDK.ethereum - .request({ - method: "wallet_switchEthereumChain", - params: [{ chainId }], - }) - .then(() => { - setChainName(name); - }); - }} - > - {name} - - ))} - - - + ); }; diff --git a/src/components/EvmEditor.tsx b/src/components/EvmEditor.tsx index bb64b7d..d5b2389 100644 --- a/src/components/EvmEditor.tsx +++ b/src/components/EvmEditor.tsx @@ -28,6 +28,7 @@ import EvmSignEditor from "./EvmEditors/EvmSignEditor"; import EvmUserOpEditor from "./EvmEditors/EvmUserOpEditor"; import EvmSendEditor from "./EvmEditors/EvmSendEditor"; import EvmContractEditor from "./EvmEditors/EvmContractEditor"; +import EvmBatchTxEditor from "./EvmEditors/EvmBatchTxEditor"; import type { EthereumTypes } from "@blocto/sdk"; import { bloctoSDK, useEthereum, supportedChains, web3 } from "../services/evm"; import ReactJson from "react-json-view"; @@ -233,6 +234,7 @@ const EvmEditor = (): ReactJSXElement => { User Operation Send Contract + Batch Transaction @@ -254,12 +256,7 @@ const EvmEditor = (): ReactJSXElement => { - setRequestObject({ - method: "eth_sendTransaction", - params, - }) - } + setRequestObject={setRequestObject} account={account} /> @@ -271,6 +268,13 @@ const EvmEditor = (): ReactJSXElement => { chainId={chainId} /> + + + diff --git a/src/components/EvmEditors/EvmBatchTxEditor.tsx b/src/components/EvmEditors/EvmBatchTxEditor.tsx new file mode 100644 index 0000000..7c9ad85 --- /dev/null +++ b/src/components/EvmEditors/EvmBatchTxEditor.tsx @@ -0,0 +1,183 @@ +import React, { useEffect, useState, Dispatch, SetStateAction } from "react"; +import { + Box, + Text, + Grid, + Flex, + IconButton, + Radio, + RadioGroup, + Button, +} from "@chakra-ui/react"; +import { CloseIcon } from "@chakra-ui/icons"; +import { ReactJSXElement } from "@emotion/react/types/jsx-namespace"; +import type { EthereumTypes } from "@blocto/sdk"; +import EvmTxForm from "./EvmTxForm"; +import { web3 } from "../../services/evm"; +import erc721Abi from "../../contracts/abi/ERC721.json"; + +interface EvmBatchTxEditorProps { + setRequestObject: Dispatch< + SetStateAction + >; + account: string | null; + chainId: string | null; +} + +const RevertOptionMap: Record = { + false: false, + true: true, + unset: undefined, +}; + +const EvmBatchTxEditor = ({ + setRequestObject, + account, + chainId, +}: EvmBatchTxEditorProps): ReactJSXElement => { + const [revert, setRevert] = useState("false"); + const [txs, setTxs] = useState(); + + useEffect(() => { + if (account) { + setRequestObject({ + method: "wallet_sendMultiCallTransaction", + params: [ + txs, + ...(RevertOptionMap[revert] !== undefined + ? [RevertOptionMap[revert]] + : []), + ], + }); + } + }, [account, setRequestObject, revert, txs]); + + const addTransfer = () => { + const obj = { + value: "0x1", + to: "0x85fD692D2a075908079261F5E351e7fE0267dB02", + from: account, + }; + setTxs((state) => { + return [...(state || []), obj]; + }); + }; + + const mintNFT = () => { + // TODO: wait backend provide all evm chain contract address + const contractAddr = "0x6bDa27BB78833658D19049118b73eC7b07815C8A"; // only scroll testnet + const contract = new web3.eth.Contract(erc721Abi as any, contractAddr); + const obj = { + from: account, + to: contractAddr, + data: contract.methods.mint(account).encodeABI(), + }; + setTxs((state) => { + return [...(state || []), obj]; + }); + }; + + const SwapToken = () => { + const obj = { + from: account, + to: "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD", + data: "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000066053af000000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000038d7ea4c680000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000038d7ea4c680000000000000000000000000000000000000000000000000000000b99e289c676500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bfff9976782d46cc05630d1f6ebab18b2324d6b140001f41f9840a85d5af5bf1d1762f925bdaddc4201f984000000000000000000000000000000000000000000", + }; + setTxs((state) => { + return [...(state || []), obj]; + }); + }; + + const removeTransfer = (index: number) => { + setTxs((state) => { + if (Array.isArray(state) && state.length === 1) { + return []; + } + const newParam = [...(state || [])]; + return newParam.splice(index, 1); + }); + }; + return ( + <> + + Revert + { + setRevert(e); + }} + > + + {Object.keys(RevertOptionMap).map((key) => ( + + {key} + + ))} + + + + + Transaction + + {chainId === "0x8274f" && ( + + )} + {chainId === "0xaa36a7" && ( + + )} + + + {Array.isArray(txs) && + txs?.length > 0 && + txs?.map((value, i: number) => ( + + + + Transaction {i + 1} + + } + size="xs" + colorScheme="red" + onClick={() => removeTransfer(i)} + /> + + + { + if (updatedTxs) + setTxs((prev) => { + const newTxs = [...(prev || [])]; + newTxs[i] = updatedTxs[0]; + return newTxs; + }); + }} + isCustom={true} + account={account} + customParams={value} + /> + + + ))} + + + + ); +}; + +export default EvmBatchTxEditor; diff --git a/src/components/EvmEditors/EvmSendEditor.tsx b/src/components/EvmEditors/EvmSendEditor.tsx index 65564b6..577d5a6 100644 --- a/src/components/EvmEditors/EvmSendEditor.tsx +++ b/src/components/EvmEditors/EvmSendEditor.tsx @@ -1,84 +1,27 @@ -import React, { useEffect, useState, Dispatch } from "react"; -import { Box, Textarea, Grid } from "@chakra-ui/react"; +import React, { Dispatch, useCallback } from "react"; import { ReactJSXElement } from "@emotion/react/types/jsx-namespace"; import type { EthereumTypes } from "@blocto/sdk"; -import { web3 } from "../../services/evm"; +import EvmTxForm from "./EvmTxForm"; const EvmSendEditor = ({ setRequestObject, account, }: { - setRequestObject: Dispatch< - EthereumTypes.EIP1193RequestPayload["params"] | undefined - >; + setRequestObject: Dispatch; account: string | null; }): ReactJSXElement => { - const [fromString, setFrom] = useState(account || ""); - const [toString, setTo] = useState(""); - const [valueString, setValue] = useState(""); - const [dataString, setData] = useState(""); - useEffect(() => { - if (account) { - const sendObj: { - from: string; - to?: string; - value?: string; - data?: string; - } = { - from: fromString, - }; - if (toString) { - sendObj.to = toString; - } - if (valueString) { - sendObj.value = web3.utils.toHex(valueString); - } - if (dataString) { - sendObj.data = dataString; - } - setRequestObject([sendObj]); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [account, fromString, toString, dataString, valueString]); - useEffect(() => { - setFrom(account || ""); - }, [account]); + const setTransactionObject = useCallback( + (params: EthereumTypes.EIP1193RequestPayload["params"] | undefined) => { + setRequestObject({ + method: "eth_sendTransaction", + params, + }); + }, + [setRequestObject] + ); return ( - - From: -