diff --git a/CHANGELOG.md b/CHANGELOG.md index 98f78caa4..699531ee7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Bug fixes +- [#1243](https://github.com/alleslabs/celatone-frontend/pull/1243) Fix EVM Create checked by null To instead - [#1241](https://github.com/alleslabs/celatone-frontend/pull/1241) Fix EVM contract details interaction filter - [#1239](https://github.com/alleslabs/celatone-frontend/pull/1239) Fix check verified EVM contract and minor EVM contract details - [#1238](https://github.com/alleslabs/celatone-frontend/pull/1238) Fix EVM verify external docs link and EVM contract details mobile diff --git a/src/lib/components/EvmMethodChip.tsx b/src/lib/components/EvmMethodChip.tsx index ddeeb0550..53e530cb9 100644 --- a/src/lib/components/EvmMethodChip.tsx +++ b/src/lib/components/EvmMethodChip.tsx @@ -1,18 +1,21 @@ import { Tag } from "@chakra-ui/react"; import type { TagProps } from "@chakra-ui/react"; +import type { HexAddr20, Nullable } from "lib/types"; import { getEvmMethod } from "lib/utils"; interface EvmMethodChipProps { txInput: string; + txTo: Nullable; width?: TagProps["width"]; } export const EvmMethodChip = ({ txInput, + txTo, width = "144px", }: EvmMethodChipProps) => ( - {getEvmMethod(txInput)} + {getEvmMethod(txInput, txTo)} ); diff --git a/src/lib/components/EvmToCell.tsx b/src/lib/components/EvmToCell.tsx index e84784954..afa798a02 100644 --- a/src/lib/components/EvmToCell.tsx +++ b/src/lib/components/EvmToCell.tsx @@ -1,7 +1,7 @@ import { Flex, Text } from "@chakra-ui/react"; -import type { Option } from "lib/types"; -import type { EvmToAddress } from "lib/utils"; +import type { EvmToAddress, HexAddr20, Option } from "lib/types"; +import { EvmMethodName } from "lib/types"; import { ExplorerLink } from "./ExplorerLink"; import { CustomIcon } from "./icon"; @@ -10,40 +10,52 @@ interface EvmToCellProps { toAddress: Option; } +const EvmToCellCreate = ({ + address, +}: { + address: HexAddr20; + evmTxHash?: string; +}) => ( + + + Created Contract + + {/* TODO: fix contract addresses */} + + + + + +); + export const EvmToCell = ({ toAddress }: EvmToCellProps) => { - if (toAddress?.isCreatedContract) + if (!toAddress) return ( - - - Created Contract - - - - - - + + - + ); - if (toAddress) + if (toAddress.toType === EvmMethodName.Create) return ( - ); + if (toAddress.toType === EvmMethodName.CallErc20Factory) + return ; + return ( - - - - + ); }; diff --git a/src/lib/components/table/evm-transactions/EvmTransactionsTableMobileCard.tsx b/src/lib/components/table/evm-transactions/EvmTransactionsTableMobileCard.tsx index 2a830fc54..aa98bf70c 100644 --- a/src/lib/components/table/evm-transactions/EvmTransactionsTableMobileCard.tsx +++ b/src/lib/components/table/evm-transactions/EvmTransactionsTableMobileCard.tsx @@ -66,7 +66,10 @@ export const EvmTransactionsTableMobileCard = ({ - + } diff --git a/src/lib/components/table/evm-transactions/EvmTransactionsTableRow.tsx b/src/lib/components/table/evm-transactions/EvmTransactionsTableRow.tsx index a20d1159e..56cfd07e0 100644 --- a/src/lib/components/table/evm-transactions/EvmTransactionsTableRow.tsx +++ b/src/lib/components/table/evm-transactions/EvmTransactionsTableRow.tsx @@ -72,7 +72,10 @@ export const EvmTransactionsTableRow = ({ )} - + { data: newEvmTxsData, isFetching: isNewEvmTxsDataFetching, isError: isNewEvmTxsDataError, - } = useTxsDataJsonRpc( + } = useEvmTxsDataJsonRpc( newEvmTxHashes?.filter((tx) => tx !== null) as string[], !isCosmosTxsFetching && !isNewEvmHashesFetching ); diff --git a/src/lib/pages/evm-tx-details/components/EvmInputData.tsx b/src/lib/pages/evm-tx-details/components/EvmInputData.tsx index fe0059525..8e26afdcc 100644 --- a/src/lib/pages/evm-tx-details/components/EvmInputData.tsx +++ b/src/lib/pages/evm-tx-details/components/EvmInputData.tsx @@ -2,17 +2,19 @@ import { Flex, Heading } from "@chakra-ui/react"; import { useMemo, useState } from "react"; import { TextReadOnly } from "lib/components/json/TextReadOnly"; +import type { HexAddr20, Nullable } from "lib/types"; import { EvmMethodName } from "lib/types"; import { getEvmMethod } from "lib/utils"; import { EvmDataFormatSwitch, EvmDataFormatTabs } from "./EvmDataFormatSwitch"; interface EvmInputDataProps { - inputData: string; + txInput: string; + txTo: Nullable; } -export const EvmInputData = ({ inputData }: EvmInputDataProps) => { - const evmMethod = getEvmMethod(inputData); +export const EvmInputData = ({ txInput, txTo }: EvmInputDataProps) => { + const evmMethod = getEvmMethod(txInput, txTo); const showFormatOption = evmMethod !== EvmMethodName.Transfer && evmMethod !== EvmMethodName.Create; const [tab, setTab] = useState( @@ -25,8 +27,8 @@ export const EvmInputData = ({ inputData }: EvmInputDataProps) => { const chunkSize = 64; const formattedData: string[] = []; - const methodId = inputData.slice(0, 10); - const remainingData = inputData.slice(10); + const methodId = txInput.slice(0, 10); + const remainingData = txInput.slice(10); formattedData.push(`MethodID: ${methodId}`); @@ -39,12 +41,12 @@ export const EvmInputData = ({ inputData }: EvmInputDataProps) => { return formattedData.join("\n"); } case EvmDataFormatTabs.UTF8: - return Buffer.from(inputData.slice(2), "hex").toString("binary"); + return Buffer.from(txInput.slice(2), "hex").toString("binary"); case EvmDataFormatTabs.Raw: default: - return inputData; + return txInput; } - }, [inputData, tab]); + }, [txInput, tab]); return ( <> diff --git a/src/lib/pages/evm-tx-details/components/EvmTxMsgDetails.tsx b/src/lib/pages/evm-tx-details/components/EvmTxMsgDetails.tsx index 7ec093ea1..4aaa370e3 100644 --- a/src/lib/pages/evm-tx-details/components/EvmTxMsgDetails.tsx +++ b/src/lib/pages/evm-tx-details/components/EvmTxMsgDetails.tsx @@ -34,6 +34,6 @@ export const EvmTxMsgDetails = ({ )} - + ); diff --git a/src/lib/pages/evm-tx-details/components/EvmTxMsgDetailsBody.tsx b/src/lib/pages/evm-tx-details/components/EvmTxMsgDetailsBody.tsx index a3f496658..414176ad5 100644 --- a/src/lib/pages/evm-tx-details/components/EvmTxMsgDetailsBody.tsx +++ b/src/lib/pages/evm-tx-details/components/EvmTxMsgDetailsBody.tsx @@ -21,7 +21,7 @@ export const EvmTxMsgDetailsBody = ({ evmTxData, evmDenom, }: EvmTxMsgDetailsBodyProps) => { - const method = getEvmMethod(evmTxData.tx.input); + const method = getEvmMethod(evmTxData.tx.input, evmTxData.tx.to); const { data: assetInfos } = useAssetInfos({ withPrices: true, }); diff --git a/src/lib/pages/evm-tx-details/components/evm-tx-method/EvmTxCreateContract.tsx b/src/lib/pages/evm-tx-details/components/evm-tx-method/EvmTxCreateContract.tsx index 3b232b1c9..ddea7d075 100644 --- a/src/lib/pages/evm-tx-details/components/evm-tx-method/EvmTxCreateContract.tsx +++ b/src/lib/pages/evm-tx-details/components/evm-tx-method/EvmTxCreateContract.tsx @@ -17,6 +17,8 @@ export const EvmTxCreateContract = ({ const { from } = evmTxData.tx; const { contractAddress } = evmTxData.txReceipt; + // TODO: fix contract addresses + return ( {" "} - {" "} + {" "} {formatTokenWithValue(amount)} to{" "} {to ? ( { const { from, input, to: erc20Contract } = evmTxData.tx; - const toAddress = getEvmToAddress(evmTxData); - const amountBig = hexToBig(evmTxData.tx.input.slice(74, 138)); + const { address, amount } = extractErc20TransferInput(input); - const amount = coinToTokenWithValue( + const amountToken = coinToTokenWithValue( erc20Contract ? convertToEvmDenom(erc20Contract) : "", - amountBig.toString(), + amount.toString(), assetInfos ); @@ -50,20 +48,14 @@ export const EvmTxTransferErc20 = ({ textVariant="body1" ampCopierSection="tx_page_message_header_send_address" />{" "} - {" "} - {formatTokenWithValue(amount)} to{" "} - {toAddress?.address ? ( - - ) : ( - - - - - )} + {" "} + {formatTokenWithValue(amountToken)} to{" "} + } > @@ -82,19 +74,13 @@ export const EvmTxTransferErc20 = ({ - ) : ( - - - - - ) + } /> + isSupportedToken(amountToken) ? ( + ) : ( ) diff --git a/src/lib/pages/evm-tx-details/data.ts b/src/lib/pages/evm-tx-details/data.ts index b2208bd1c..c06300050 100644 --- a/src/lib/pages/evm-tx-details/data.ts +++ b/src/lib/pages/evm-tx-details/data.ts @@ -6,8 +6,8 @@ import { useBlockDataJsonRpc } from "lib/services/block"; import { useEvmParams } from "lib/services/evm"; import { useCosmosTxHashByEvmTxHash, + useEvmTxDataJsonRpc, useTxData, - useTxDataJsonRpc, } from "lib/services/tx"; import type { TxData, TxDataJsonRpc } from "lib/services/types"; import { type Option, type Ratio, type TokenWithValue } from "lib/types"; @@ -41,7 +41,7 @@ export const useEvmTxDetailsData = (evmTxHash: string): EvmTxDetailsData => { withPrices: true, }); const { data: evmTxData, isLoading: isLoadingEvmTxData } = - useTxDataJsonRpc(evmTxHash); + useEvmTxDataJsonRpc(evmTxHash); const { data: cosmosTxHash } = useCosmosTxHashByEvmTxHash(evmTxHash); const { data: cosmosTxData, isLoading: isLoadingCosmosTxData } = useTxData( cosmosTxHash, diff --git a/src/lib/pages/tx-details/components/evm-related-tx-section/index.tsx b/src/lib/pages/tx-details/components/evm-related-tx-section/index.tsx index 51d2f8177..6d0ff76a0 100644 --- a/src/lib/pages/tx-details/components/evm-related-tx-section/index.tsx +++ b/src/lib/pages/tx-details/components/evm-related-tx-section/index.tsx @@ -7,7 +7,7 @@ import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; import { Loading } from "lib/components/Loading"; import { EmptyState } from "lib/components/state"; -import { useTxDataJsonRpc } from "lib/services/tx"; +import { useEvmTxDataJsonRpc } from "lib/services/tx"; import { formatEvmTxHash, getEvmToAddress } from "lib/utils"; import { EvmRelatedField } from "./EvmRelatedField"; @@ -18,7 +18,7 @@ interface EvmRelatedTxSectionProps { const EvmRelatedTxSectionBody = ({ evmTxHash }: EvmRelatedTxSectionProps) => { const isMobile = useMobile(); - const { data, isLoading } = useTxDataJsonRpc(evmTxHash); + const { data, isLoading } = useEvmTxDataJsonRpc(evmTxHash); if (isLoading) return ; if (!data) @@ -41,7 +41,7 @@ const EvmRelatedTxSectionBody = ({ evmTxHash }: EvmRelatedTxSectionProps) => { /> - + ) => { return getEvmTxHashByCosmosTxHash(evm.jsonRpc, cosmosTxHash); }, { + enabled: evm.enabled && !!evm.jsonRpc && !!cosmosTxHash, retry: false, refetchOnWindowFocus: false, - enabled: evm.enabled && !!evm.jsonRpc && !!cosmosTxHash, + staleTime: Infinity, } ); }; @@ -701,7 +702,7 @@ export const useEvmTxHashesByCosmosTxHashes = ( ); }; -export const useTxDataJsonRpc = (evmTxHash: string, enabled = true) => { +export const useEvmTxDataJsonRpc = (evmTxHash: string, enabled = true) => { const evm = useEvmConfig({ shouldRedirect: false }); return useQuery( @@ -712,7 +713,7 @@ export const useTxDataJsonRpc = (evmTxHash: string, enabled = true) => { ], async () => { if (!evm.enabled) - throw new Error("EVM is not enabled (useTxDataJsonRpc)"); + throw new Error("EVM is not enabled (useEvmTxDataJsonRpc)"); return getTxDataJsonRpc(evm.jsonRpc, evmTxHash); }, @@ -740,14 +741,15 @@ export const useCosmosTxHashByEvmTxHash = (evmTxHash: string) => { return getCosmosTxHashByEvmTxHash(evm.jsonRpc, evmTxHash); }, { + enabled: evm.enabled && !!evm.jsonRpc, retry: false, refetchOnWindowFocus: false, - enabled: evm.enabled && !!evm.jsonRpc, + staleTime: Infinity, } ); }; -export const useTxsDataJsonRpc = ( +export const useEvmTxsDataJsonRpc = ( evmTxHashes: Option, enabled = true ) => { @@ -761,9 +763,9 @@ export const useTxsDataJsonRpc = ( ], async () => { if (!evm.enabled) - throw new Error("EVM is not enabled (useTxsDataJsonRpc)"); + throw new Error("EVM is not enabled (useEvmTxsDataJsonRpc)"); if (!evmTxHashes) - throw new Error("evmTxHashes is undefined (useTxsDataJsonRpc)"); + throw new Error("evmTxHashes is undefined (useEvmTxsDataJsonRpc)"); if (!evmTxHashes.length) return []; return getTxsDataJsonRpc(evm.jsonRpc, evmTxHashes); diff --git a/src/lib/services/tx/jsonRpc.ts b/src/lib/services/tx/jsonRpc.ts index daea4377c..8c8ca5116 100644 --- a/src/lib/services/tx/jsonRpc.ts +++ b/src/lib/services/tx/jsonRpc.ts @@ -78,6 +78,7 @@ export const getTxsDataJsonRpc = async ( return parseWithError(zTxsDataJsonRpc, parsedResults); }); }; + export const getCosmosTxHashByEvmTxHash = ( endpoint: string, evmTxHash: string diff --git a/src/lib/types/evm.ts b/src/lib/types/evm.ts index 0368128a9..986e0a5dc 100644 --- a/src/lib/types/evm.ts +++ b/src/lib/types/evm.ts @@ -1,14 +1,19 @@ import type { JsonFragment } from "ethers"; -import { z, ZodIssueCode } from "zod"; import { snakeToCamel } from "lib/utils/formatter/snakeToCamel"; import { isHex20Bytes } from "lib/utils/validate"; +import { z, ZodIssueCode } from "zod"; +import type { HexAddr20 } from "./addrs"; import { zHexAddr20 } from "./addrs"; +import type { Option } from "./common"; import { zUtcDate } from "./time"; export enum EvmMethodId { Transfer = "0x", TransferErc20 = "0xa9059cbb", - Create = "0x60806040", + // Note: Create multiple contracts + // 2 -> 0x60A06040 + // 3 -> 0x60C06040 + SingleCreate = "0x60806040", CallErc20Factory = "0x06ef1a86", } @@ -19,6 +24,21 @@ export enum EvmMethodName { CallErc20Factory = "Call ERC20 Factory", } +export type EvmToAddress = + | { + toType: EvmMethodName.Create; + address: HexAddr20; + evmTxHash: Option; + } + | { + toType: EvmMethodName.CallErc20Factory; + address: HexAddr20; + } + | { + toType: null; + address: HexAddr20; + }; + // MARK: Verification export enum EvmProgrammingLanguage { Solidity = "Solidity", diff --git a/src/lib/utils/evm.ts b/src/lib/utils/evm.ts index bd9deb566..0c123c3fc 100644 --- a/src/lib/utils/evm.ts +++ b/src/lib/utils/evm.ts @@ -1,5 +1,11 @@ import type { TxDataJsonRpc } from "lib/services/types"; -import type { Coin, HexAddr20, Option } from "lib/types"; +import type { + Coin, + EvmToAddress, + HexAddr20, + Nullable, + Option, +} from "lib/types"; import { big, EvmMethodId, EvmMethodName, zHexAddr20 } from "lib/types"; import { keccak256 } from "@initia/initia.js"; @@ -8,48 +14,37 @@ import { Interface } from "ethers"; import { toChecksumAddress } from "./address"; import { hexToBig } from "./number"; -export const getEvmMethod = (txInput: string) => { +export const getEvmMethod = (txInput: string, txTo: Nullable) => { + if (txTo === null && txInput !== "0x") return EvmMethodName.Create; if (txInput === EvmMethodId.Transfer) return EvmMethodName.Transfer; if (txInput.startsWith(EvmMethodId.TransferErc20)) return EvmMethodName.TransferErc20; - if (txInput.startsWith(EvmMethodId.Create)) return EvmMethodName.Create; if (txInput.startsWith(EvmMethodId.CallErc20Factory)) return EvmMethodName.CallErc20Factory; return txInput.slice(0, 10); }; -export const convertToEvmDenom = (contractAddress: HexAddr20) => - toChecksumAddress(contractAddress).replace("0x", "evm/"); +export const isEvmSingleCreate = (txInput: string, txTo: Nullable) => + txTo === null && txInput.startsWith(EvmMethodId.SingleCreate); -export interface EvmToAddress { - address: HexAddr20; - type: "user_address" | "evm_contract_address"; - isCreatedContract: boolean; -} +export const extractErc20TransferInput = (input: string) => ({ + address: toChecksumAddress(zHexAddr20.parse(`0x${input.slice(34, 74)}`)), + amount: hexToBig(input.slice(74, 138)).toString(), +}); export const getEvmToAddress = ( - evmTxData: Option + evmTxData: TxDataJsonRpc ): Option => { - if (!evmTxData) return undefined; - const { to, input } = evmTxData.tx; - const method = getEvmMethod(input); - - if (method === EvmMethodName.TransferErc20) { - return { - address: toChecksumAddress(zHexAddr20.parse(`0x${input.slice(34, 74)}`)), - type: "user_address", - isCreatedContract: false, - }; - } + const method = getEvmMethod(input, to); if (method === EvmMethodName.Create) { const { contractAddress } = evmTxData.txReceipt; if (!contractAddress) return undefined; return { + toType: EvmMethodName.Create, address: toChecksumAddress(contractAddress), - type: "evm_contract_address", - isCreatedContract: true, + evmTxHash: !isEvmSingleCreate(input, to) ? evmTxData.tx.hash : undefined, }; } @@ -58,32 +53,40 @@ export const getEvmToAddress = ( const contractAddress = logs[0]?.address; if (!contractAddress) return undefined; return { + toType: EvmMethodName.CallErc20Factory, address: toChecksumAddress(zHexAddr20.parse(contractAddress)), - type: "evm_contract_address", - isCreatedContract: true, + }; + } + + if (method === EvmMethodName.TransferErc20) { + return { + toType: null, + address: extractErc20TransferInput(input).address, }; } if (to) { return { + toType: null, address: toChecksumAddress(to), - type: "user_address", - isCreatedContract: false, }; } return undefined; }; +export const convertToEvmDenom = (contractAddress: HexAddr20) => + toChecksumAddress(contractAddress).replace("0x", "evm/"); + export const getEvmAmount = ( evmTxData: TxDataJsonRpc, evmDenom: Option ): Coin => { - const method = getEvmMethod(evmTxData.tx.input); + const method = getEvmMethod(evmTxData.tx.input, evmTxData.tx.to); if (method === EvmMethodName.TransferErc20) { return { - amount: hexToBig(evmTxData.tx.input.slice(74, 138)).toString(), + amount: extractErc20TransferInput(evmTxData.tx.input).amount, denom: evmTxData.tx.to ? convertToEvmDenom(evmTxData.tx.to) : "", }; }