From fb5257b1cb303dd5cc16fc73bfcfc2408418093e Mon Sep 17 00:00:00 2001 From: poomthiti Date: Thu, 9 Mar 2023 19:09:01 +0700 Subject: [PATCH 01/19] feat(pages): initial tx details page --- CHANGELOG.md | 2 + package.json | 2 + src/lib/app-fns/explorer/index.ts | 22 +- src/lib/app-provider/hooks/useAddress.ts | 15 +- src/lib/components/ExplorerLink.tsx | 21 +- src/lib/components/PermissionChip.tsx | 1 + .../components/ViewPermissionAddresses.tsx | 66 + src/lib/components/copy/CopyButton.tsx | 31 +- src/lib/components/copy/CopyTemplate.tsx | 5 +- .../modal/UnsupportedTokensModal.tsx | 44 +- src/lib/components/tx/TxReceiptRender.tsx | 46 +- src/lib/components/tx/modal/ButtonSection.tsx | 8 +- src/lib/layout/Searchbar.tsx | 4 +- .../components/CodeInfoSection.tsx | 53 +- src/lib/pages/faucet/index.tsx | 35 +- .../tx-details/components/MessageSection.tsx | 50 + .../pages/tx-details/components/TxHeader.tsx | 90 + .../pages/tx-details/components/TxInfo.tsx | 49 + src/lib/pages/tx-details/components/index.ts | 3 + .../components/tx-message/EventBox.tsx | 147 ++ .../components/tx-message/TxMsgDetails.tsx | 56 + .../components/tx-message/TxMsgExpand.tsx | 310 +++ .../components/tx-message/index.tsx | 29 + .../tx-message/msg-receipts/CoinComponent.tsx | 132 ++ .../tx-message/msg-receipts/index.tsx | 1815 +++++++++++++++++ .../tx-message/msg-receipts/mapping.ts | 7 + .../tx-message/msg-receipts/renderUtils.tsx | 162 ++ src/lib/pages/tx-details/index.tsx | 50 + src/lib/services/amplitude.tsx | 1 + src/lib/services/contract.ts | 6 +- src/lib/services/tx.ts | 97 + src/lib/services/txQuery/useTxQuery.ts | 2 + src/lib/services/txService.ts | 43 +- src/lib/styles/theme/components/alert.ts | 1 + src/lib/styles/theme/components/badge.ts | 1 + src/lib/types/addrs.ts | 1 + src/lib/types/code.ts | 2 +- src/lib/types/currency/balance.ts | 7 +- src/lib/types/tx/common.ts | 5 + src/lib/types/tx/index.ts | 1 + src/lib/types/tx/model.ts | 2 +- src/lib/utils/formatter/camelToTitle.ts | 4 + src/lib/utils/formatter/denom.ts | 4 +- src/lib/utils/formatter/index.ts | 1 + src/lib/utils/formatter/token.ts | 5 + src/pages/[network]/tx/[txHash].tsx | 3 + src/pages/_app.tsx | 1 - src/pages/tx/[txHash].tsx | 3 + yarn.lock | 84 +- 49 files changed, 3371 insertions(+), 158 deletions(-) create mode 100644 src/lib/components/ViewPermissionAddresses.tsx create mode 100644 src/lib/pages/tx-details/components/MessageSection.tsx create mode 100644 src/lib/pages/tx-details/components/TxHeader.tsx create mode 100644 src/lib/pages/tx-details/components/TxInfo.tsx create mode 100644 src/lib/pages/tx-details/components/index.ts create mode 100644 src/lib/pages/tx-details/components/tx-message/EventBox.tsx create mode 100644 src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx create mode 100644 src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx create mode 100644 src/lib/pages/tx-details/components/tx-message/index.tsx create mode 100644 src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx create mode 100644 src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx create mode 100644 src/lib/pages/tx-details/components/tx-message/msg-receipts/mapping.ts create mode 100644 src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx create mode 100644 src/lib/pages/tx-details/index.tsx create mode 100644 src/lib/services/tx.ts create mode 100644 src/lib/types/tx/common.ts create mode 100644 src/lib/utils/formatter/camelToTitle.ts create mode 100644 src/pages/[network]/tx/[txHash].tsx create mode 100644 src/pages/tx/[txHash].tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index bb74f483c..9647b3bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#224](https://github.com/alleslabs/celatone-frontend/pull/224) Support search by tx and internal tx link +- [#226](https://github.com/alleslabs/celatone-frontend/pull/226) Add fully functional transaction details page - [#254](https://github.com/alleslabs/celatone-frontend/pull/254) Add GiHub link to public code and contract detail pages - [#230](https://github.com/alleslabs/celatone-frontend/pull/230) Add cw2info to code table diff --git a/package.json b/package.json index 3698ed4e4..9458a0db1 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,8 @@ "mobx-react-lite": "^3.4.0", "next": "^13.0.0", "next-seo": "^5.8.0", + "osmojs": "^14.0.0-rc.0", + "protobufjs": "^7.2.2", "react": "^18.2.0", "react-ace": "^10.1.0", "react-dom": "^18.2.0", diff --git a/src/lib/app-fns/explorer/index.ts b/src/lib/app-fns/explorer/index.ts index bb367e179..a41b2b8ae 100644 --- a/src/lib/app-fns/explorer/index.ts +++ b/src/lib/app-fns/explorer/index.ts @@ -5,16 +5,14 @@ export const explorerMap: Record = { osmosistestnet: "https://testnet.mintscan.io/osmosis-testnet", }; -export const getExplorerTxUrl = (chainName: string) => { +export const getExplorerBlockUrl = (chainName: string) => { let pathSuffix = ""; switch (chainName) { case "osmosis": case "osmosistestnet": - pathSuffix = "txs"; - break; case "terra2": case "terra2testnet": - pathSuffix = "tx"; + pathSuffix = "blocks"; break; default: break; @@ -22,32 +20,34 @@ export const getExplorerTxUrl = (chainName: string) => { return `${explorerMap[chainName]}/${pathSuffix}`; }; -export const getExplorerBlockUrl = (chainName: string) => { +export const getProposalUrl = (chainName: string) => { let pathSuffix = ""; switch (chainName) { case "osmosis": case "osmosistestnet": + pathSuffix = "proposals"; + break; case "terra2": + return `https://station.terra.money/proposal/phoenix-1`; case "terra2testnet": - pathSuffix = "blocks"; - break; + return `https://station.terra.money/proposal/pisco-1`; default: break; } return `${explorerMap[chainName]}/${pathSuffix}`; }; -export const getProposalUrl = (chainName: string) => { +export const getExplorerValidatorUrl = (chainName: string) => { let pathSuffix = ""; switch (chainName) { case "osmosis": case "osmosistestnet": - pathSuffix = "proposals"; + pathSuffix = "validators"; break; case "terra2": - return `https://station.terra.money/proposal/phoenix-1`; case "terra2testnet": - return `https://station.terra.money/proposal/pisco-1`; + pathSuffix = "validator"; + break; default: break; } diff --git a/src/lib/app-provider/hooks/useAddress.ts b/src/lib/app-provider/hooks/useAddress.ts index 7b3785db7..418e137a0 100644 --- a/src/lib/app-provider/hooks/useAddress.ts +++ b/src/lib/app-provider/hooks/useAddress.ts @@ -3,9 +3,12 @@ import type { ChainRecord } from "@cosmos-kit/core"; import { useWallet } from "@cosmos-kit/react"; import { useCallback } from "react"; +import type { Option } from "lib/types"; + export type AddressReturnType = | "user_address" | "contract_address" + | "validator_address" | "invalid_address"; const addressLengthMap: { @@ -13,32 +16,38 @@ const addressLengthMap: { } = { osmosis: { 43: "user_address", + 50: "validator_address", 63: "contract_address", }, osmosistestnet: { 43: "user_address", + 50: "validator_address", 63: "contract_address", }, terra2: { 44: "user_address", + 51: "validator_address", 64: "contract_address", }, terra2testnet: { 44: "user_address", + 51: "validator_address", 64: "contract_address", }, }; export const getAddressTypeByLength = ( chainName: string, - address: string + address: Option ): AddressReturnType => - addressLengthMap[chainName]?.[address.length] ?? "invalid_address"; + address + ? addressLengthMap[chainName]?.[address.length] ?? "invalid_address" + : "invalid_address"; export const useGetAddressType = () => { const { currentChainName } = useWallet(); return useCallback( - (address: string): AddressReturnType => + (address: Option): AddressReturnType => getAddressTypeByLength(currentChainName, address), [currentChainName] ); diff --git a/src/lib/components/ExplorerLink.tsx b/src/lib/components/ExplorerLink.tsx index 6f8428513..c41820e3c 100644 --- a/src/lib/components/ExplorerLink.tsx +++ b/src/lib/components/ExplorerLink.tsx @@ -1,11 +1,11 @@ -import type { BoxProps } from "@chakra-ui/react"; +import type { BoxProps, TextProps } from "@chakra-ui/react"; import { Box, Text } from "@chakra-ui/react"; import { useWallet } from "@cosmos-kit/react"; import { getExplorerBlockUrl, - getExplorerTxUrl, getProposalUrl, + getExplorerValidatorUrl, } from "lib/app-fns/explorer"; import type { AddressReturnType } from "lib/app-provider"; import { AmpTrackMintscan } from "lib/services/amplitude"; @@ -29,6 +29,7 @@ interface ExplorerLinkProps extends BoxProps { isReadOnly?: boolean; textFormat?: "truncate" | "ellipsis" | "normal"; maxWidth?: string; + textVariant?: TextProps["variant"]; } const getNavigationUrl = ( @@ -39,7 +40,7 @@ const getNavigationUrl = ( let url = ""; switch (type) { case "tx_hash": - url = getExplorerTxUrl(currentChainName); + url = "/tx"; break; case "contract_address": url = "/contract"; @@ -47,6 +48,9 @@ const getNavigationUrl = ( case "user_address": url = "/account"; break; + case "validator_address": + url = getExplorerValidatorUrl(currentChainName); + break; case "code_id": url = "/code"; break; @@ -82,6 +86,7 @@ const LinkRender = ({ textValue, isEllipsis, maxWidth, + textVariant, }: { type: string; isInternal: boolean; @@ -89,10 +94,11 @@ const LinkRender = ({ textValue: string; isEllipsis: boolean; maxWidth: ExplorerLinkProps["maxWidth"]; + textVariant: TextProps["variant"]; }) => { const textElement = ( { const { address, currentChainName } = useWallet(); const isInternal = type === "code_id" || type === "contract_address" || - type === "user_address"; + type === "user_address" || + type === "tx_hash"; const [hrefLink, textValue] = [ getNavigationUrl(type, currentChainName, copyValue || value), @@ -172,6 +180,7 @@ export const ExplorerLink = ({ textValue={textValue} isEllipsis={textFormat === "ellipsis"} maxWidth={maxWidth} + textVariant={textVariant} /> { + const [viewAll, setViewAll] = useState(false); + const getAddressType = useGetAddressType(); + const showAddressses = + viewAll || + typeof permissionAddresses === "string" || + (typeof permissionAddresses === "object" && + permissionAddresses.length === 1); + + return ( + <> + {showAddressses && + (typeof permissionAddresses === "string" ? ( + + ) : ( + permissionAddresses.map((addr) => { + return ( + + ); + }) + ))} + {typeof permissionAddresses === "object" && + permissionAddresses.length > 1 && ( + + )} + + ); +}; diff --git a/src/lib/components/copy/CopyButton.tsx b/src/lib/components/copy/CopyButton.tsx index ced0a7fb4..ee0f65752 100644 --- a/src/lib/components/copy/CopyButton.tsx +++ b/src/lib/components/copy/CopyButton.tsx @@ -1,16 +1,18 @@ import { Button } from "@chakra-ui/react"; -import type { ButtonProps } from "@chakra-ui/react"; +import type { ButtonProps, TooltipProps } from "@chakra-ui/react"; import { CustomIcon } from "../icon"; import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { CopyTemplate } from "./CopyTemplate"; -interface CopyButtonProps { +interface CopyButtonProps extends ButtonProps { isDisable?: boolean; value: string; - size?: ButtonProps["size"]; copyLabel?: string; + hasIcon?: boolean; + buttonText?: string; + tooltipBgColor?: TooltipProps["bgColor"]; } export const CopyButton = ({ @@ -18,26 +20,35 @@ export const CopyButton = ({ value, size = "sm", copyLabel, + hasIcon = true, + variant = "outline-info", + buttonText = "Copy", + tooltipBgColor, + ...buttonProps }: CopyButtonProps) => ( AmpTrack(AmpEvent.USE_COPY_BUTTON)} leftIcon={ - + hasIcon ? ( + + ) : undefined } + {...buttonProps} > - Copy + {buttonText} } /> diff --git a/src/lib/components/copy/CopyTemplate.tsx b/src/lib/components/copy/CopyTemplate.tsx index 7eebca494..f54b8f852 100644 --- a/src/lib/components/copy/CopyTemplate.tsx +++ b/src/lib/components/copy/CopyTemplate.tsx @@ -1,3 +1,4 @@ +import type { TooltipProps } from "@chakra-ui/react"; import { Box, Tooltip, useClipboard } from "@chakra-ui/react"; import { useEffect } from "react"; @@ -5,12 +6,14 @@ interface CopyTemplateProps { value: string; copyLabel?: string; triggerElement: JSX.Element; + tooltipBgColor?: TooltipProps["bgColor"]; } export const CopyTemplate = ({ value, copyLabel = "Copied!", triggerElement, + tooltipBgColor = "honeydew.darker", }: CopyTemplateProps) => { const { onCopy, hasCopied, setValue } = useClipboard(value); useEffect(() => setValue(value), [value, setValue]); @@ -22,7 +25,7 @@ export const CopyTemplate = ({ label={copyLabel} placement="top" arrowSize={8} - bg="honeydew.darker" + bgColor={tooltipBgColor} > { diff --git a/src/lib/components/modal/UnsupportedTokensModal.tsx b/src/lib/components/modal/UnsupportedTokensModal.tsx index bccfbb315..bc24d6d59 100644 --- a/src/lib/components/modal/UnsupportedTokensModal.tsx +++ b/src/lib/components/modal/UnsupportedTokensModal.tsx @@ -1,3 +1,4 @@ +import type { ButtonProps } from "@chakra-ui/react"; import { Modal, ModalHeader, @@ -18,7 +19,7 @@ import { useMemo } from "react"; import { ExplorerLink } from "../ExplorerLink"; import type { IconKeys } from "../icon"; import { CustomIcon } from "../icon"; -import { getAddressTypeByLength } from "lib/app-provider"; +import { useGetAddressType, getAddressTypeByLength } from "lib/app-provider"; import type { AddressReturnType } from "lib/app-provider"; import { Copier } from "lib/components/copy"; import type { BalanceWithAssetInfo, Balance, Token, U, Addr } from "lib/types"; @@ -30,22 +31,34 @@ import { interface UnsupportedTokensModalProps { unsupportedAssets: BalanceWithAssetInfo[]; - address: Addr; + address?: Addr; + buttonProps?: ButtonProps; } interface UnsupportedTokenProps { balance: Balance; } +const getTokenTypeWithAddress = ( + type: Balance["type"], + addrType: AddressReturnType +) => { + if (type) return getTokenType(type); + return addrType === "contract_address" + ? getTokenType("cw20") + : getTokenType("native"); +}; + const UnsupportedToken = ({ balance }: UnsupportedTokenProps) => { + const getAddressType = useGetAddressType(); // TODO - Move this to utils const [tokenLabel, tokenType] = useMemo(() => { const label = getTokenLabel(balance.id); const type = !balance.id.includes("/") - ? getTokenType(balance.type) + ? getTokenTypeWithAddress(balance.type, getAddressType(balance.id)) : getTokenType(balance.id.split("/")[0]); return [label, type]; - }, [balance]); + }, [balance, getAddressType]); return ( { const { currentChainName } = useWallet(); const { isOpen, onOpen, onClose } = useDisclosure(); @@ -145,7 +159,13 @@ export const UnsupportedTokensModal = ({ return ( <> - @@ -164,12 +184,14 @@ export const UnsupportedTokensModal = ({ - - - {content.header} - - - + {address && ( + + + {content.header} + + + + )} {unsupportedAssets.map((asset) => ( = { full: { - w: "100%", + w: "full", "> div": { justifyContent: "space-between", + alignItems: "center", }, }, packed: { w: "50%", + "> div": { + alignItems: "center", + }, "> div > p:first-of-type": { w: "140px", + fontWeight: 700, + }, + }, + "tx-page": { + w: "full", + "> div": { + alignItems: "flex-start", + }, + "> div > p:first-of-type": { + minW: "180px", + w: "180px", + mr: 4, + color: "text.dark", + fontWeight: 500, }, }, }; const ReceiptRow = ({ title, value, html }: TxReceipt) => ( - - - {title} - - {html || {value}} + + {title} + {html || ( + + {value} + + )} ); export const TxReceiptRender = ({ receipts, variant = "packed", + gap = 2, + keyPrefix = "", }: TxReceiptRenderProps) => ( - - {receipts.map((receipt) => ( - + + {receipts.map((receipt, idx) => ( + ))} ); diff --git a/src/lib/components/tx/modal/ButtonSection.tsx b/src/lib/components/tx/modal/ButtonSection.tsx index 3ce5d1db8..ad21b2c8a 100644 --- a/src/lib/components/tx/modal/ButtonSection.tsx +++ b/src/lib/components/tx/modal/ButtonSection.tsx @@ -3,10 +3,9 @@ import { useWallet } from "@cosmos-kit/react"; import { useRouter } from "next/router"; import { useCallback } from "react"; -import { getExplorerTxUrl } from "lib/app-fns/explorer"; import { useInternalNavigate } from "lib/app-provider"; import { CustomIcon } from "lib/components/icon"; -import { AmpTrackMintscan } from "lib/services/amplitude"; +import { getNetworkByChainName } from "lib/data"; import type { ActionVariant, TxReceipt } from "lib/types"; // TODO: refactor props to pass param in txResultRendering instead of receipt @@ -26,15 +25,14 @@ export const ButtonSection = ({ const { currentChainName } = useWallet(); const openExplorer = useCallback(() => { - AmpTrackMintscan("tx_hash"); const txHash = receipts.find((r) => r.title === "Tx Hash")?.value; window.open( - `${getExplorerTxUrl(currentChainName)}/${txHash}`, + `/${getNetworkByChainName(currentChainName)}/tx/${txHash}`, "_blank", "noopener,noreferrer" ); onClose?.(); - }, [receipts, onClose, currentChainName]); + }, [receipts, currentChainName, onClose]); switch (actionVariant) { case "sending": diff --git a/src/lib/layout/Searchbar.tsx b/src/lib/layout/Searchbar.tsx index 06486bc58..2b00e0249 100644 --- a/src/lib/layout/Searchbar.tsx +++ b/src/lib/layout/Searchbar.tsx @@ -42,6 +42,8 @@ const getRoute = (type: SearchResultType) => { return "/contract"; case "Wallet Address": return "/account"; + case "Transaction Hash": + return "/tx"; default: return null; } @@ -122,7 +124,7 @@ const Searchbar = () => { value={keyword} h="36px" onChange={handleSearchChange} - placeholder="Search by Wallet Address / Contract Address / Code ID" + placeholder="Search by Wallet Address / Contract Address / Code ID / Tx Hash" focusBorderColor="lilac.main" onFocus={() => setDisplayResults(keyword.length > 0)} onKeyDown={handleOnKeyEnter} diff --git a/src/lib/pages/code-details/components/CodeInfoSection.tsx b/src/lib/pages/code-details/components/CodeInfoSection.tsx index f5cec0eba..0c87a903e 100644 --- a/src/lib/pages/code-details/components/CodeInfoSection.tsx +++ b/src/lib/pages/code-details/components/CodeInfoSection.tsx @@ -1,12 +1,11 @@ -import { Heading, Flex, Text, Box, Grid, Button } from "@chakra-ui/react"; -import { useState } from "react"; +import { Heading, Flex, Text, Box, Grid } from "@chakra-ui/react"; import { useGetAddressType } from "lib/app-provider"; import { ExplorerLink } from "lib/components/ExplorerLink"; -import { CustomIcon } from "lib/components/icon"; import { LabelText } from "lib/components/LabelText"; import { PermissionChip } from "lib/components/PermissionChip"; -import type { CodeData, PermissionAddresses } from "lib/types"; +import { ViewPermissionAddresses } from "lib/components/ViewPermissionAddresses"; +import type { CodeData } from "lib/types"; import { dateFromNow, formatUTC, getAddressTypeText } from "lib/utils"; interface CodeInfoSectionProps { @@ -89,48 +88,6 @@ const getMethodSpecificRender = ( }; }; -const ViewAddresses = ({ - permissionAddresses, -}: { - permissionAddresses: PermissionAddresses; -}) => { - const [viewAll, setViewAll] = useState(false); - const getAddressType = useGetAddressType(); - return ( - <> - {(viewAll || permissionAddresses.length === 1) && - permissionAddresses.map((addr) => { - return ( - - ); - })} - {permissionAddresses.length > 1 && ( - - )} - - ); -}; - export const CodeInfoSection = ({ codeData, chainId, @@ -181,7 +138,9 @@ export const CodeInfoSection = ({ instantiatePermission={instantiatePermission} permissionAddresses={permissionAddresses} /> - + diff --git a/src/lib/pages/faucet/index.tsx b/src/lib/pages/faucet/index.tsx index f2b35d598..b2e38db15 100644 --- a/src/lib/pages/faucet/index.tsx +++ b/src/lib/pages/faucet/index.tsx @@ -12,7 +12,6 @@ import axios from "axios"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { getExplorerTxUrl } from "lib/app-fns/explorer"; import { useCurrentNetwork, useInternalNavigate, @@ -24,6 +23,7 @@ import { TextInput } from "lib/components/forms"; import { CustomIcon } from "lib/components/icon"; import type { IconKeys } from "lib/components/icon"; import WasmPageContainer from "lib/components/WasmPageContainer"; +import { getNetworkByChainName } from "lib/data"; import { AmpEvent, AmpTrack } from "lib/services/amplitude"; type ResultStatus = "success" | "error" | "warning"; @@ -55,8 +55,6 @@ const Faucet = () => { const router = useRouter(); const faucetUrl = process.env.NEXT_PUBLIC_FAUCET_URL; - // TODO: navigate to Celatone tx page - const txLinkUrl = getExplorerTxUrl(currentChainName); useEffect(() => { if (!isTestnet) navigate({ pathname: "/" }); @@ -198,21 +196,24 @@ const Faucet = () => { {result.message || "Something went wrong"} {result.txHash && ( - + window.open( + `/${getNetworkByChainName(currentChainName)}/tx/${ + result.txHash + }`, + "_blank", + "noopener,noreferrer" + ) + } > - - + View Transaction + )} )} diff --git a/src/lib/pages/tx-details/components/MessageSection.tsx b/src/lib/pages/tx-details/components/MessageSection.tsx new file mode 100644 index 000000000..bfea8091e --- /dev/null +++ b/src/lib/pages/tx-details/components/MessageSection.tsx @@ -0,0 +1,50 @@ +import { + Alert, + AlertDescription, + AlertIcon, + Badge, + Flex, + Text, +} from "@chakra-ui/react"; + +import type { TxData } from "lib/services/txService"; + +import { TxMessage } from "./tx-message"; + +interface MessageSectionProps { + txData: TxData; +} + +export const MessageSection = ({ txData }: MessageSectionProps) => { + const isTxFailed = Boolean(txData.code); + const msgs = txData.tx.body.messages; + return ( + + {isTxFailed && ( + + + {txData.raw_log} + + )} + + + Messages + + + {msgs.length} + + + {msgs.map((msg, idx) => { + const msgLog = txData.logs.find((log) => log.msg_index === idx); + return ( + + ); + })} + + ); +}; diff --git a/src/lib/pages/tx-details/components/TxHeader.tsx b/src/lib/pages/tx-details/components/TxHeader.tsx new file mode 100644 index 000000000..ca1157168 --- /dev/null +++ b/src/lib/pages/tx-details/components/TxHeader.tsx @@ -0,0 +1,90 @@ +import type { FlexProps } from "@chakra-ui/react"; +import { Button, Box, Flex, Heading, Icon, Text } from "@chakra-ui/react"; +import { useWallet } from "@cosmos-kit/react"; +import { IoIosWarning } from "react-icons/io"; +import { MdCheckCircle, MdLaunch } from "react-icons/md"; + +import { CELATONE_API_ENDPOINT, getChainApiPath } from "env"; +import { useChainId } from "lib/app-provider"; +import { ExplorerLink } from "lib/components/ExplorerLink"; +import type { TxData } from "lib/services/txService"; +import { dateFromNow, formatUTC } from "lib/utils"; + +interface TxHeaderProps extends FlexProps { + txData: TxData; +} + +const DotSeparator = () => ( + +); + +export const TxHeader = ({ txData, ...flexProps }: TxHeaderProps) => { + const { currentChainName } = useWallet(); + const chainId = useChainId(); + const isTxFailed = Boolean(txData.code); + const openLcdPage = () => { + window.open( + `${CELATONE_API_ENDPOINT}/txs/${getChainApiPath( + currentChainName + )}/${chainId}/${txData.txhash}`, + "_blank", + "noopener,noreferrer" + ); + }; + return ( + + + + Transaction Details + + + + + + Transaction Hash: + + + + + + {isTxFailed ? ( + <> + +

Failed

+ + ) : ( + <> + +

Success

+ + )} +
+ + {txData.timestamp ? ( + <> +

{dateFromNow(txData.timestamp)}

+ +

{formatUTC(txData.timestamp)}

+ + ) : ( +

N/A

+ )} +
+
+ ); +}; diff --git a/src/lib/pages/tx-details/components/TxInfo.tsx b/src/lib/pages/tx-details/components/TxInfo.tsx new file mode 100644 index 000000000..2c0d89616 --- /dev/null +++ b/src/lib/pages/tx-details/components/TxInfo.tsx @@ -0,0 +1,49 @@ +import type { FlexProps } from "@chakra-ui/react"; +import { Text, chakra, Flex } from "@chakra-ui/react"; + +import { ExplorerLink } from "lib/components/ExplorerLink"; +import { LabelText } from "lib/components/LabelText"; +import type { TxData } from "lib/services/txService"; +import { formatInteger } from "lib/utils"; + +interface TxInfoProps extends FlexProps { + txData: TxData; +} + +const Container = chakra(Flex, { + baseStyle: { + flexDir: "column", + gap: 6, + w: "250px", + }, +}); + +export const TxInfo = ({ txData, ...flexProps }: TxInfoProps) => ( + + {txData.chainId} + + + + + {txData.formattedFee || ( + + No Fee + + )} + + + {`${formatInteger(txData.gas_used)}/${formatInteger(txData.gas_wanted)}`} + + + {txData.tx.body.memo || ( + + No Memo + + )} + + +); diff --git a/src/lib/pages/tx-details/components/index.ts b/src/lib/pages/tx-details/components/index.ts new file mode 100644 index 000000000..c36abca20 --- /dev/null +++ b/src/lib/pages/tx-details/components/index.ts @@ -0,0 +1,3 @@ +export * from "./TxHeader"; +export * from "./TxInfo"; +export * from "./tx-message"; diff --git a/src/lib/pages/tx-details/components/tx-message/EventBox.tsx b/src/lib/pages/tx-details/components/tx-message/EventBox.tsx new file mode 100644 index 000000000..a7b9bcd10 --- /dev/null +++ b/src/lib/pages/tx-details/components/tx-message/EventBox.tsx @@ -0,0 +1,147 @@ +import { Box, Divider, Flex, Icon } from "@chakra-ui/react"; +import type { Event } from "@cosmjs/stargate"; +import type { ReactNode } from "react"; +import { useState } from "react"; +import { FiChevronDown } from "react-icons/fi"; +import { MdList } from "react-icons/md"; + +import { useGetAddressType } from "lib/app-provider"; +import { ExplorerLink } from "lib/components/ExplorerLink"; +import JsonReadOnly from "lib/components/json/JsonReadOnly"; +import { TxReceiptRender } from "lib/components/tx"; +import type { TxReceipt } from "lib/types"; +import { jsonPrettify, jsonValidate } from "lib/utils"; + +interface EventBoxProps { + event: Event; + msgIndex: number; +} + +export const EventBox = ({ event, msgIndex }: EventBoxProps) => { + const getAddressType = useGetAddressType(); + const [expand, setExpand] = useState(true); + + const receipts = event.attributes.map(({ key, value }) => { + const addrType = getAddressType(value); + let valueComponent: ReactNode; + + switch (true) { + case addrType !== "invalid_address": + valueComponent = ( + + ); + break; + case key === "code_id": + valueComponent = ( + + ); + break; + case key === "_contract_address": + valueComponent = ( + + ); + break; + case key === "proposal_id": + valueComponent = ( + + ); + break; + case jsonValidate(value) === null: + if (typeof JSON.parse(value) === "object") + valueComponent = ( + + ); + else valueComponent = value; + break; + default: + valueComponent = value; + break; + } + + return { + title: key, + ...(typeof valueComponent === "string" + ? { value } + : // Value is included to avoid receipt row key duplicate + { html: valueComponent, value }), + }; + }); + + return ( + + setExpand((prev) => !prev)} + p={4} + > + + + {`[${msgIndex}] ${event.type}`} + + + + {expand && ( + + + + + )} + + ); +}; diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx new file mode 100644 index 000000000..acd35786f --- /dev/null +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx @@ -0,0 +1,56 @@ +import { Flex } from "@chakra-ui/react"; + +import { useGetAddressType } from "lib/app-provider"; +import { TxReceiptRender } from "lib/components/tx"; +import { useAssetInfos } from "lib/services/assetService"; +import type { TxReceipt } from "lib/types"; + +import type { TxMsgData } from "."; +import { EventBox } from "./EventBox"; +import { generateReceipts } from "./msg-receipts"; + +interface TxMsgDetailsProps extends TxMsgData { + isExpand: boolean; +} + +export const TxMsgDetails = ({ isExpand, ...txMsgData }: TxMsgDetailsProps) => { + const getAddressType = useGetAddressType(); + const assetInfos = useAssetInfos(); + const receipts = generateReceipts(txMsgData, getAddressType, assetInfos) + .filter(Boolean) + .concat( + txMsgData.log + ? { + title: "Event Logs", + html: ( + + {txMsgData.log.events.map((event, idx) => ( + + ))} + + ), + } + : [] + ) as TxReceipt[]; + + return ( + + + + ); +}; diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx new file mode 100644 index 000000000..ff77ca1f3 --- /dev/null +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx @@ -0,0 +1,310 @@ +import { Badge, Flex, Icon } from "@chakra-ui/react"; +import { findAttribute } from "@cosmjs/stargate/build/logs"; +import type { ReactNode } from "react"; +import type { IconType } from "react-icons"; +import { FiChevronDown } from "react-icons/fi"; +import { + MdInfo, + MdUpload, + MdAdd, + MdMessage, + MdSend, + MdMail, + MdOutlineHowToVote, + MdDonutLarge, + MdFormatIndentIncrease, + MdPersonRemove, + MdManageAccounts, +} from "react-icons/md"; + +import { useGetAddressType } from "lib/app-provider"; +import { ExplorerLink } from "lib/components/ExplorerLink"; +import type { Addr } from "lib/types"; + +import type { TxMsgData } from "."; +import { voteOption } from "./msg-receipts/mapping"; + +interface TxMsgExpandProps extends TxMsgData { + isExpand: boolean; + onClick: () => void; +} + +export const TxMsgExpand = ({ + msgBody, + log, + isExpand, + onClick, +}: TxMsgExpandProps) => { + const getAddressType = useGetAddressType(); + let msgIcon: IconType = MdInfo; + let content: ReactNode; + const isIBC = Boolean( + log?.events?.find((event) => event.type === "send_packet") + ); + const { "@type": type, ...body } = msgBody; + + switch (type) { + case "/cosmwasm.wasm.v1.MsgStoreCode": + msgIcon = MdUpload; + content = ( + <> + Upload Wasm + {log && ( + <> + {" "} + and stored as + + + )} + + ); + break; + case "/cosmwasm.wasm.v1.MsgInstantiateContract": + msgIcon = MdAdd; + content = ( + <> + Instantiate + {log && ( + + )} +

from

+ + + ); + break; + case "/cosmwasm.wasm.v1.MsgInstantiateContract2": + msgIcon = MdAdd; + content = ( + <> + Instantiate2 + {log && ( + + )} +

from

+ + + ); + break; + case "/cosmwasm.wasm.v1.MsgExecuteContract": + msgIcon = MdMessage; + content = ( + <> + Execute + {Object.keys(body.msg).at(0)} + on + + + ); + break; + case "/cosmwasm.wasm.v1.MsgMigrateContract": + msgIcon = MdFormatIndentIncrease; + content = ( + <> + Migrate{" "} + {" "} + to Code ID{" "} + + + ); + break; + case "/cosmwasm.wasm.v1.MsgUpdateAdmin": + msgIcon = MdManageAccounts; + content = ( + <> + Update admin on{" "} + {" "} + to{" "} + + + ); + break; + case "/cosmwasm.wasm.v1.MsgClearAdmin": + msgIcon = MdPersonRemove; + content = ( + <> + Clear admin on{" "} + + + ); + break; + case "/cosmos.bank.v1beta1.MsgSend": + { + const toAddress = body.to_address as Addr; + msgIcon = MdSend; + content = ( + <> + Send assets to + + + ); + } + break; + case "/cosmos.gov.v1beta1.MsgSubmitProposal": + msgIcon = MdMail; + content = ( + <> + Submit Proposal + {log && ( + <> +

ID

+ + + )} + + ); + break; + case "/cosmos.gov.v1beta1.MsgVote": + msgIcon = MdOutlineHowToVote; + content = ( + <> + Vote{" "} + + {voteOption[body.option as keyof typeof voteOption]} + {" "} + on proposal ID{" "} + + + ); + break; + case "/cosmos.staking.v1beta1.MsgDelegate": + msgIcon = MdDonutLarge; + content = ( + <> + Delegate by{" "} + {" "} + to{" "} + + + ); + break; + default: { + const msgType = type.split("."); + content = msgType[msgType.length - 1]; + break; + } + } + + return ( + + + + {content} + {isIBC && IBC} + + + + ); +}; diff --git a/src/lib/pages/tx-details/components/tx-message/index.tsx b/src/lib/pages/tx-details/components/tx-message/index.tsx new file mode 100644 index 000000000..f96abbb33 --- /dev/null +++ b/src/lib/pages/tx-details/components/tx-message/index.tsx @@ -0,0 +1,29 @@ +import { Flex } from "@chakra-ui/react"; +import type { Log } from "@cosmjs/stargate/build/logs"; +import { useState } from "react"; + +import type { MsgBody } from "lib/services/tx"; +import type { Option } from "lib/types"; + +import { TxMsgDetails } from "./TxMsgDetails"; +import { TxMsgExpand } from "./TxMsgExpand"; + +export interface TxMsgData { + msgBody: MsgBody; + log: Option; + isSingleMsg?: boolean; +} + +export const TxMessage = ({ isSingleMsg, ...txMsgData }: TxMsgData) => { + const [expand, setExpand] = useState(!!isSingleMsg); + return ( + + setExpand((prev) => !prev)} + {...txMsgData} + /> + + + ); +}; diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx new file mode 100644 index 000000000..6dba2e96d --- /dev/null +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx @@ -0,0 +1,132 @@ +import { Flex, Grid } from "@chakra-ui/react"; +import type { Coin } from "@cosmjs/stargate"; +import { useState } from "react"; + +import { ShowMoreButton } from "lib/components/button"; +import { UnsupportedTokensModal } from "lib/components/modal/UnsupportedTokensModal"; +import { TokenCard } from "lib/components/TokenCard"; +import type { AssetInfo, Option } from "lib/types"; + +type AssetObject = { [key: string]: AssetInfo }; + +interface CoinComponentProps< + T extends Coin | Coin[], + A extends Option | AssetObject +> { + amount: T; + assetInfos: A; +} + +const MultiCoin = ({ + amount, + assetInfos, +}: CoinComponentProps) => { + const [supportedCoins, unsupportedCoins] = [ + amount.filter((coin) => Boolean(assetInfos[coin.denom])), + amount.filter((coin) => !assetInfos[coin.denom]), + ]; + const [showMore, setShowMore] = useState(false); + + const hasSupportedCoins = supportedCoins.length > 0; + return ( + + {hasSupportedCoins && ( + + {supportedCoins.slice(0, showMore ? undefined : 2).map((coin) => { + const assetInfo = assetInfos[coin.denom]; + return ( + + ); + })} + + )} + + {supportedCoins.length > 2 && ( + setShowMore(!showMore)} + /> + )} + {unsupportedCoins && ( + { + const assetInfo = assetInfos[coin.denom]; + return { + balance: { + amount: coin.amount, + id: coin.denom, + precision: 0, + }, + assetInfo, + }; + })} + buttonProps={{ fontSize: "12px", mb: 0 }} + /> + )} + + + ); +}; +const SingleCoin = ({ + amount, + assetInfos, +}: CoinComponentProps) => { + const assetInfo = assetInfos[amount.denom]; + return assetInfo ? ( + + ) : ( + + ); +}; + +export const CoinComponent = ({ + amount, + assetInfos, +}: CoinComponentProps>) => { + if (!assetInfos) return <>{JSON.stringify(amount)}; + return Array.isArray(amount) ? ( + + ) : ( + + ); +}; diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx new file mode 100644 index 000000000..54cc03d11 --- /dev/null +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx @@ -0,0 +1,1815 @@ +/* eslint-disable sonarjs/max-switch-cases */ +/* eslint-disable complexity */ +import { Flex } from "@chakra-ui/react"; +import { findAttribute } from "@cosmjs/stargate/build/logs"; +import big from "big.js"; + +import type { TxMsgData } from ".."; +import type { AddressReturnType } from "lib/app-provider"; +import { CopyButton } from "lib/components/copy"; +import { PermissionChip } from "lib/components/PermissionChip"; +import { ViewPermissionAddresses } from "lib/components/ViewPermissionAddresses"; +import type { TxReceipt, Option, AssetInfo } from "lib/types"; +import { formatUTC, parseDate } from "lib/utils"; + +import { voteOption } from "./mapping"; +import { + attachFundsReceipt, + channelIdReceipt, + clientStateReceipt, + delegatorAddrReceipt, + getCommonReceiptHtml, + proofHeightReceipt, + proofInitReceipt, + proposalIdReceipt, + validatorAddrReceipt, + getGenericValueEntry, + getCoinComponent, +} from "./renderUtils"; + +export const generateReceipts = ( + { msgBody, log }: TxMsgData, + getAddressType: (address: string) => AddressReturnType, + assetInfos: Option<{ [key: string]: AssetInfo }> +): Option[] => { + const { "@type": type, ...body } = msgBody; + switch (type) { + // cosmwasm/wasm + case "/cosmwasm.wasm.v1.MsgStoreCode": + return [ + log && { + title: "Stored Code ID", + html: getCommonReceiptHtml({ + type: "explorer", + value: findAttribute([log], "store_code", "code_id").value, + linkType: "code_id", + }), + }, + { + title: "Uploader", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + body.instantiate_permission && { + title: "Instantiate Permission", + html: ( + + + + + ), + }, + { + title: "Wasm Byte Code", + html: ( + + Size:{" "} + {big(Buffer.from(body.wasm_byte_code).byteLength) + .div(1024) + .toFixed(1)}{" "} + KB + + + ), + }, + ]; + case "/cosmwasm.wasm.v1.MsgInstantiateContract": + return [ + log && { + title: "Contract Instance", + html: getCommonReceiptHtml({ + type: "explorer", + value: findAttribute([log], "instantiate", "_contract_address") + .value, + linkType: "contract_address", + }), + }, + + { + title: "From Code ID", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.code_id, + linkType: "code_id", + }), + }, + { + title: "Instantiated by", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Contract Admin", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.admin, + linkType: getAddressType(body.admin), + fallback: "No Admin", + }), + }, + { + title: "Label", + value: body.label, + }, + attachFundsReceipt(body.funds, assetInfos), + { + title: "Instantiate Message", + html: getCommonReceiptHtml({ + type: "json", + value: body.msg, + }), + }, + ]; + case "/cosmwasm.wasm.v1.MsgInstantiateContract2": + return [ + log && { + title: "Contract Instance", + html: getCommonReceiptHtml({ + type: "explorer", + value: findAttribute([log], "instantiate", "_contract_address") + .value, + linkType: "contract_address", + }), + }, + { + title: "From Code ID", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.code_id, + linkType: "code_id", + }), + }, + { + title: "Instantiated by", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Contract Admin", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.admin, + linkType: getAddressType(body.admin), + fallback: "No Admin", + }), + }, + { + title: "Label", + value: body.label, + }, + attachFundsReceipt(body.funds, assetInfos), + { + title: "Instantiate Message", + html: getCommonReceiptHtml({ + type: "json", + value: body.msg, + }), + }, + { + title: "Salt", + html: body.salt, + }, + body.fix_msg && { + title: "Fix Msg", + value: body.fix_msg, + }, + ]; + case "/cosmwasm.wasm.v1.MsgExecuteContract": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Contract", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.contract, + linkType: "contract_address", + }), + }, + attachFundsReceipt(body.funds, assetInfos), + { + title: "Execute Message", + html: getCommonReceiptHtml({ + type: "json", + value: body.msg, + }), + }, + ]; + case "/cosmwasm.wasm.v1.MsgMigrateContract": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Contract", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.contract, + linkType: "contract_address", + }), + }, + { + title: "Code ID", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.code_id, + linkType: "code_id", + }), + }, + { + title: "Msg", + html: getCommonReceiptHtml({ + type: "json", + value: body.msg, + }), + }, + ]; + case "/cosmwasm.wasm.v1.MsgUpdateAdmin": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "New Admin", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.new_admin, + linkType: getAddressType(body.new_admin), + }), + }, + { + title: "Contract", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.contract, + linkType: "contract_address", + }), + }, + ]; + case "/cosmwasm.wasm.v1.MsgClearAdmin": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Contract", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.contract, + linkType: "contract_address", + }), + }, + ]; + // x/bank + case "/cosmos.bank.v1beta1.MsgSend": + return [ + { + title: "From Address", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.from_address, + linkType: getAddressType(body.from_address), + }), + }, + { + title: "To Address", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.to_address, + linkType: getAddressType(body.to_address), + }), + }, + { + title: "Amount", + html: getCoinComponent(body.amount, assetInfos), + }, + ]; + case "/cosmos.bank.v1beta1.MsgMultiSend": + return [ + { + title: "Inputs", + html: getCommonReceiptHtml({ + type: "json", + value: body.inputs, + }), + }, + { + title: "Outputs", + html: getCommonReceiptHtml({ + type: "json", + value: body.outputs, + }), + }, + ]; + // x/authz + case "/cosmos.authz.v1beta1.MsgGrant": + return [ + { + title: "Granter", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.granter, + linkType: getAddressType(body.granter), + }), + }, + { + title: "Grantee", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.grantee, + linkType: getAddressType(body.grantee), + }), + }, + { + title: "Grant", + html: getCommonReceiptHtml({ + type: "json", + value: body.grant, + }), + }, + ]; + case "/cosmos.authz.v1beta1.MsgRevoke": + return [ + { + title: "Granter", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.granter, + linkType: getAddressType(body.granter), + }), + }, + { + title: "Grantee", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.grantee, + linkType: getAddressType(body.grantee), + }), + }, + { + title: "MsgTypeUrl", + html: getCommonReceiptHtml({ + type: "json", + value: body.msg_type_url, + }), + }, + ]; + case "/cosmos.authz.v1beta1.MsgExec": + return [ + { + title: "Grantee", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.grantee, + linkType: getAddressType(body.grantee), + }), + }, + { + title: "Msgs", + html: getCommonReceiptHtml({ + type: "json", + value: body.msgs, + }), + }, + ]; + // x/crisis + case "/cosmos.crisis.v1beta1.MsgVerifyInvariant": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { title: "Invariant Module Name", value: body.invariant_module_name }, + { title: "Invariant Route", value: body.invariant_route }, + ]; + // x/distribution + case "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress": + return [ + delegatorAddrReceipt( + body.delegator_address, + getAddressType(body.delegator_address) + ), + { + title: "Withdraw Address", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.withdraw_address, + linkType: getAddressType(body.withdraw_address), + }), + }, + ]; + case "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward": + return [ + delegatorAddrReceipt( + body.delegator_address, + getAddressType(body.delegator_address) + ), + validatorAddrReceipt(body.validator_address), + ]; + case "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission": + return [validatorAddrReceipt(body.validator_address)]; + case "/cosmos.distribution.v1beta1.MsgFundCommunityPool": + return [ + { + title: "Amount", + html: getCoinComponent(body.amount, assetInfos), + }, + { + title: "Depositor", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.depositor, + linkType: getAddressType(body.depositor), + }), + }, + ]; + // x/evidence + case "/cosmos.evidence.v1beta1.MsgSubmitEvidence": + return [ + { + title: "Submitter", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.submitter, + linkType: getAddressType(body.submitter), + }), + }, + { + title: "Evidence", + html: getCommonReceiptHtml({ + type: "json", + value: body.evidence, + }), + }, + ]; + // x/feegrant + case "/cosmos.feegrant.v1beta1.MsgGrantAllowance": + return [ + { + title: "Granter", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.granter, + linkType: getAddressType(body.granter), + }), + }, + { + title: "Grantee", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.grantee, + linkType: getAddressType(body.grantee), + }), + }, + { + title: "Allowance", + html: getCommonReceiptHtml({ + type: "json", + value: body.allowance, + }), + }, + ]; + case "/cosmos.feegrant.v1beta1.MsgRevokeAllowance": + return [ + { + title: "Granter", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.granter, + linkType: getAddressType(body.granter), + }), + }, + { + title: "Grantee", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.grantee, + linkType: getAddressType(body.grantee), + }), + }, + ]; + // x/gov + case "/cosmos.gov.v1beta1.MsgSubmitProposal": + return [ + { + title: "Initial Deposit", + html: getCoinComponent(body.initial_deposit, assetInfos), + }, + { + title: "Proposer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.proposer, + linkType: getAddressType(body.proposer), + }), + }, + ...(log + ? [ + proposalIdReceipt( + findAttribute([log], "submit_proposal", "proposal_id").value + ), + { + title: "Proposal Type", + value: findAttribute([log], "submit_proposal", "proposal_type") + .value, + }, + ] + : []), + { title: "Title", value: body.content.title }, + ]; + case "/cosmos.gov.v1beta1.MsgVote": + return [ + proposalIdReceipt(body.proposal_id), + { + title: "Voter", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.voter, + linkType: getAddressType(body.voter), + }), + }, + { + title: "Option", + value: voteOption[body.option as keyof typeof voteOption], + }, + ]; + case "/cosmos.gov.v1beta1.MsgVoteWeighted": + return [ + proposalIdReceipt(body.proposal_id), + { + title: "Voter", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.voter, + linkType: getAddressType(body.voter), + }), + }, + { + title: "Options", + html: getCommonReceiptHtml({ + type: "json", + value: body.options, + }), + }, + ]; + case "/cosmos.gov.v1beta1.MsgDeposit": + return [ + proposalIdReceipt(body.proposal_id), + { + title: "Depositor", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.depositor, + linkType: getAddressType(body.depositor), + }), + }, + { + title: "Amount", + html: getCoinComponent(body.amount, assetInfos), + }, + ]; + // x/slashing + case "/cosmos.slashing.v1beta1.MsgUnjail": + return [validatorAddrReceipt(body.validator_addr)]; + // x/staking + case "/cosmos.staking.v1beta1.MsgCreateValidator": + return [ + { + title: "Description", + html: getCommonReceiptHtml({ + type: "json", + value: body.description, + }), + }, + { + title: "Commission", + html: getCommonReceiptHtml({ + type: "json", + value: body.commission, + }), + }, + { + title: "Min Self Delegation", + value: body.min_self_delegation, + }, + delegatorAddrReceipt( + body.delegator_address, + getAddressType(body.delegator_address) + ), + validatorAddrReceipt(body.validator_address), + { + title: "Public Key", + html: getCommonReceiptHtml({ + type: "json", + value: body.pubkey, + }), + }, + { + title: "Value", + html: getCoinComponent(body.value, assetInfos), + }, + ]; + case "/cosmos.staking.v1beta1.MsgEditValidator": + return [ + { + title: "Description", + html: getCommonReceiptHtml({ + type: "json", + value: body.description, + }), + }, + validatorAddrReceipt(body.validator_address), + { + title: "Commission Rate", + value: body.commission_rate, + }, + { + title: "Min Self Delegation", + value: body.min_self_delegation, + }, + ]; + case "/cosmos.staking.v1beta1.MsgDelegate": + case "/cosmos.staking.v1beta1.MsgUndelegate": + return [ + delegatorAddrReceipt( + body.delegator_address, + getAddressType(body.delegator_address) + ), + validatorAddrReceipt(body.validator_address), + { + title: "Amount", + html: getCoinComponent(body.amount, assetInfos), + }, + ]; + case "/cosmos.staking.v1beta1.MsgBeginRedelegate": + return [ + delegatorAddrReceipt( + body.delegator_address, + getAddressType(body.delegator_address) + ), + { + title: "Source Validator Address", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.validator_src_address, + linkType: "validator_address", + }), + }, + { + title: "Destination Validator Address", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.validator_dst_address, + linkType: "validator_address", + }), + }, + { + title: "Amount", + html: getCoinComponent(body.amount, assetInfos), + }, + ]; + // ibc/applications + case "/ibc.applications.transfer.v1.MsgTransfer": + return [ + { + title: "Source Port", + value: body.source_port, + }, + { + title: "Source Channel", + value: body.source_channel, + }, + { + title: "Token", + html: getCoinComponent(body.token, assetInfos), + }, + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Receiver", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.receiver, + linkType: getAddressType(body.receiver), + }), + }, + body.timeout_height && { + title: "Timeout Height", + html: getCommonReceiptHtml({ + type: "json", + value: body.timeout_height, + }), + }, + body.timeout_timestamp && { + title: "Timeout Timestamp", + value: formatUTC(parseDate(body.timeout_timestamp)), + }, + { + title: "Memo", + value: body.memo, + }, + ]; + // ibc/core + case "/ibc.core.client.v1.MsgCreateClient": + return [ + clientStateReceipt(body.client_state), + { + title: "Consensus State", + html: getCommonReceiptHtml({ + type: "json", + value: body.consensus_state, + }), + }, + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.client.v1.MsgUpdateClient": + return [ + { + title: "Client ID", + value: body.client_id, + }, + { + title: "Header", + html: getCommonReceiptHtml({ + type: "json", + value: body.header, + }), + }, + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.client.v1.MsgUpgradeClient": + return [ + { + title: "Client ID", + value: body.client_id, + }, + clientStateReceipt(body.client_state), + { + title: "Consensus State", + html: getCommonReceiptHtml({ + type: "json", + value: body.consensus_state, + }), + }, + { + title: "Proof Upgrade Client", + value: body.proof_upgrade_client, + }, + { + title: "Proof Upgrade Consensus State", + value: body.proof_upgrade_consensus_state, + }, + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.client.v1.MsgSubmitMisbehaviour": + return [ + { + title: "Client ID", + value: body.client_id, + }, + { + title: "Misbehaviour", + html: getCommonReceiptHtml({ + type: "json", + value: body.misbehaviour, + }), + }, + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.connection.v1.MsgConnectionOpenInit": + return [ + { + title: "Client ID", + value: body.client_id, + }, + { + title: "Counterparty", + html: getCommonReceiptHtml({ + type: "json", + value: body.counterparty, + }), + }, + { + title: "Version", + html: getCommonReceiptHtml({ + type: "json", + value: body.version, + }), + }, + { + title: "Delay Period", + value: body.delay_period, + }, + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.connection.v1.MsgConnectionOpenTry": + return [ + { + title: "Client ID", + value: body.client_id, + }, + { + title: "Previous Connection ID", + value: body.previous_connection_id, + }, + clientStateReceipt(body.client_state), + { + title: "Counterparty", + html: getCommonReceiptHtml({ + type: "json", + value: body.counterparty, + }), + }, + { + title: "Delay Period", + value: body.delay_period, + }, + { + title: "Counterparty Versions", + html: getCommonReceiptHtml({ + type: "json", + value: body.counterparty_versions, + }), + }, + proofHeightReceipt(body.proof_height), + proofInitReceipt(body.proof_init), + { + title: "Proof Client", + value: body.proof_client, + }, + { + title: "Proof Consensus", + value: body.proof_consensus, + }, + { + title: "Consensus Height", + html: getCommonReceiptHtml({ + type: "json", + value: body.consensus_height, + }), + }, + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.connection.v1.MsgConnectionOpenAck": + return [ + { + title: "Connection ID", + value: body.connection_id, + }, + { + title: "Counterparty Connection ID", + value: body.counterparty_connection_id, + }, + { + title: "Version", + html: getCommonReceiptHtml({ + type: "json", + value: body.version, + }), + }, + clientStateReceipt(body.client_state), + proofHeightReceipt(body.proof_height), + { + title: "Proof Try", + value: body.proof_try, + }, + { + title: "Proof Client", + value: body.proof_client, + }, + { + title: "Proof Consensus", + value: body.proof_consensus, + }, + { + title: "Consensus Height", + html: getCommonReceiptHtml({ + type: "json", + value: body.consensus_height, + }), + }, + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.connection.v1.MsgConnectionOpenConfirm": + return [ + { + title: "Connection ID", + value: body.connection_id, + }, + { + title: "Proof Ack", + value: body.proof_ack, + }, + proofHeightReceipt(body.proof_height), + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.channel.v1.MsgChannelOpenInit": + return [ + { + title: "Port ID", + value: body.port_id, + }, + { + title: "Channel", + html: getCommonReceiptHtml({ + type: "json", + value: body.channel, + }), + }, + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.channel.v1.MsgChannelOpenTry": + return [ + { + title: "Port ID", + value: body.port_id, + }, + { + title: "Previous Channel ID", + value: body.previous_channel_id, + }, + { + title: "Channel", + html: getCommonReceiptHtml({ + type: "json", + value: body.channel, + }), + }, + { + title: "Counterparty Version", + value: body.counterparty_version, + }, + proofInitReceipt(body.proof_init), + proofHeightReceipt(body.proof_height), + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.channel.v1.MsgChannelOpenAck": + return [ + { + title: "Port ID", + value: body.port_id, + }, + channelIdReceipt(body.channel_id), + { + title: "Counterparty Channel ID", + value: body.counterparty_channel_id, + }, + { + title: "Counterparty Version", + value: body.counterparty_version, + }, + { + title: "Proof Try", + value: body.proof_try, + }, + proofHeightReceipt(body.proof_height), + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.channel.v1.MsgChannelOpenConfirm": + return [ + { + title: "Port ID", + value: body.port_id, + }, + channelIdReceipt(body.channel_id), + { + title: "Proof Ack", + value: body.proofAck, + }, + proofHeightReceipt(body.proof_height), + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.channel.v1.MsgChannelCloseInit": + return [ + { + title: "Port ID", + value: body.port_id, + }, + channelIdReceipt(body.channel_id), + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.channel.v1.MsgChannelCloseConfirm": + return [ + { + title: "Port ID", + value: body.port_id, + }, + channelIdReceipt(body.channel_id), + proofInitReceipt(body.proof_init), + proofHeightReceipt(body.proof_height), + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.channel.v1.MsgRecvPacket": + return [ + { + title: "Packet", + html: getCommonReceiptHtml({ + type: "json", + value: body.packet, + }), + }, + { + title: "Proof Commitment", + value: body.proof_commitment, + }, + proofHeightReceipt(body.proof_height), + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.channel.v1.MsgTimeout": + return [ + { + title: "Packet", + html: getCommonReceiptHtml({ + type: "json", + value: body.packet, + }), + }, + { + title: "Proof Unreceived", + value: body.proof_unreceived, + }, + proofHeightReceipt(body.proof_height), + { + title: "Next Sequence Recv", + value: body.next_sequence_recv, + }, + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.channel.v1.MsgTimeoutOnClose": + return [ + { + title: "Packet", + html: getCommonReceiptHtml({ + type: "json", + value: body.packet, + }), + }, + { + title: "Proof Unreceived", + value: body.proof_unreceived, + }, + { + title: "Proof Close", + value: body.proof_close, + }, + proofHeightReceipt(body.proof_height), + { + title: "Next Sequence Recv", + value: body.next_sequence_recv, + }, + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + case "/ibc.core.channel.v1.MsgAcknowledgement": + return [ + { + title: "Packet", + html: getCommonReceiptHtml({ + type: "json", + value: body.packet, + }), + }, + { + title: "Acknowledgement", + value: body.acknowledgement, + }, + { + title: "Proof Acked", + value: body.proof_acked, + }, + proofHeightReceipt(body.proof_height), + { + title: "Signer", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.signer, + linkType: getAddressType(body.signer), + }), + }, + ]; + // osmosis/gamm + case "/osmosis.gamm.poolmodels.balancer.v1beta1.MsgCreateBalancerPool": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Pool Params", + html: getCommonReceiptHtml({ + type: "json", + value: body.pool_params, + }), + }, + { + title: "Pool Assets", + html: getCommonReceiptHtml({ + type: "json", + value: body.pool_assets, + }), + }, + { + title: "Future Pool Governor", + value: body.future_pool_governor, + }, + ]; + case "/osmosis.gamm.poolmodels.stableswap.v1beta1.MsgCreateStableswapPool": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Pool Params", + html: getCommonReceiptHtml({ + type: "json", + value: body.pool_params, + }), + }, + { + title: "Initial Pool Liquidity", + html: getCoinComponent(body.initial_pool_liquidity, assetInfos), + }, + { + title: "Scaling Factors", + value: JSON.stringify(body.scaling_factors), + }, + { + title: "Future Pool Governor", + value: body.future_pool_governor, + }, + body.scaling_factor_controller && { + title: "Scaling Factor Controller", + value: body.scaling_factor_controller, + }, + ]; + case "/osmosis.gamm.poolmodels.stableswap.v1beta1.MsgStableSwapAdjustScalingFactors": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Pool ID", + value: body.pool_id, + }, + { + title: "Scaling Factors", + value: JSON.stringify(body.scaling_factors), + }, + ]; + case "/osmosis.gamm.v1beta1.MsgJoinPool": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Pool ID", + value: body.pool_id, + }, + { + title: "Share Out Amount", + value: body.share_out_amount, + }, + { + title: "Token In Maxs", + html: getCoinComponent(body.token_in_maxs, assetInfos), + }, + ]; + case "/osmosis.gamm.v1beta1.MsgExitPool": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Pool ID", + value: body.pool_id, + }, + { + title: "Share In Amount", + value: body.share_in_amount, + }, + { + title: "Token Out Mins", + html: getCoinComponent(body.token_out_mins, assetInfos), + }, + ]; + case "/osmosis.gamm.v1beta1.MsgSwapExactAmountIn": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Routes", + html: getCommonReceiptHtml({ + type: "json", + value: body.routes, + }), + }, + { + title: "Token In", + html: getCoinComponent(body.token_in, assetInfos), + }, + { + title: "Token Out Min Amount", + value: body.token_out_min_amount, + }, + ]; + case "/osmosis.gamm.v1beta1.MsgSwapExactAmountOut": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Routes", + html: getCommonReceiptHtml({ + type: "json", + value: body.routes, + }), + }, + { + title: "Token In Max Amount", + value: body.token_in_max_amount, + }, + { + title: "Token Out", + html: getCoinComponent(body.token_out, assetInfos), + }, + ]; + case "/osmosis.gamm.v1beta1.MsgJoinSwapExternAmountIn": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Pool ID", + value: body.pool_id, + }, + { + title: "Token In", + html: getCoinComponent(body.token_in, assetInfos), + }, + { + title: "Share Out Min Amount", + value: body.share_out_min_amount, + }, + ]; + case "/osmosis.gamm.v1beta1.MsgJoinSwapShareAmountOut": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Pool ID", + value: body.pool_id, + }, + { + title: "Token In Denom", + value: body.token_in_denom, + }, + { + title: "Share Out Amount", + value: body.share_out_amount, + }, + { + title: "Token In Max Amount", + value: body.token_in_max_amount, + }, + ]; + case "/osmosis.gamm.v1beta1.MsgExitSwapShareAmountIn": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Pool ID", + value: body.pool_id, + }, + { + title: "Token Out Denom", + value: body.token_out_denom, + }, + { + title: "Share In Amount", + value: body.share_in_amount, + }, + { + title: "Token Out Min Amount", + value: body.token_out_min_amount, + }, + ]; + case "/osmosis.gamm.v1beta1.MsgExitSwapExternAmountOut": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Pool ID", + value: body.pool_id, + }, + { + title: "Token Out", + html: getCoinComponent(body.token_out, assetInfos), + }, + { + title: "Share In Max Amount", + value: body.share_in_max_amount, + }, + ]; + // osmosis/incentives + case "/osmosis.incentives.MsgCreateGauge": + return [ + { + title: "Is Perpetual", + value: String(body.is_perpetual), + }, + { + title: "Owner", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.owner, + linkType: getAddressType(body.owner), + }), + }, + { + title: "Distribute To", + html: getCommonReceiptHtml({ + type: "json", + value: body.distribute_to, + }), + }, + { + title: "Coins", + html: getCoinComponent(body.coins, assetInfos), + }, + { + title: "Start Time", + value: formatUTC(parseDate(body.start_time)), + }, + { + title: "Num Epochs Paid Over", + value: body.num_epochs_paid_over, + }, + ]; + case "/osmosis.incentives.MsgAddToGauge": + return [ + { + title: "Owner", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.owner, + linkType: getAddressType(body.owner), + }), + }, + { + title: "Gauge ID", + value: body.gauge_id, + }, + { + title: "Rewards", + html: getCoinComponent(body.rewards, assetInfos), + }, + ]; + // osmosis/lockup + case "/osmosis.lockup.MsgLockTokens": + return [ + { + title: "Owner", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.owner, + linkType: getAddressType(body.owner), + }), + }, + { + title: "Duration", + value: body.duration, + }, + { + title: "Coins", + html: getCoinComponent(body.coins, assetInfos), + }, + ]; + case "/osmosis.lockup.MsgBeginUnlockingAll": + return [ + { + title: "Owner", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.owner, + linkType: getAddressType(body.owner), + }), + }, + ]; + case "/osmosis.lockup.MsgBeginUnlocking": + case "/osmosis.lockup.MsgForceUnlock": + return [ + { + title: "Owner", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.owner, + linkType: getAddressType(body.owner), + }), + }, + { + title: "ID", + value: body.ID, + }, + { + title: "Coins", + html: getCoinComponent(body.coins, assetInfos), + }, + ]; + // ** No example + case "/osmosis.lockup.MsgExtendLockup": + return [ + { + title: "Owner", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.owner, + linkType: getAddressType(body.owner), + }), + }, + { + title: "ID", + value: body.ID, + }, + { + title: "Duration", + value: formatUTC(parseDate(body.duration)), + }, + ]; + // osmosis/superfluid + case "/osmosis.superfluid.MsgSuperfluidDelegate": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Lock ID", + value: body.lock_id, + }, + validatorAddrReceipt(body.val_addr), + ]; + case "/osmosis.superfluid.MsgSuperfluidUndelegate": + case "/osmosis.superfluid.MsgSuperfluidUnbondLock": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Lock ID", + value: body.lock_id, + }, + ]; + case "/osmosis.superfluid.MsgLockAndSuperfluidDelegate": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Coins", + html: getCoinComponent(body.coins, assetInfos), + }, + validatorAddrReceipt(body.val_addr), + ]; + case "/osmosis.superfluid.MsgUnPoolWhitelistedPool": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Pool ID", + value: body.pool_id, + }, + ]; + // osmosis/tokenfactory + case "/osmosis.tokenfactory.v1beta1.MsgCreateDenom": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Subdenom", + value: body.subdenom, + }, + ]; + case "/osmosis.tokenfactory.v1beta1.MsgMint": + case "/osmosis.tokenfactory.v1beta1.MsgBurn": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Amount", + html: getCoinComponent(body.amount, assetInfos), + }, + ]; + case "/osmosis.tokenfactory.v1beta1.MsgChangeAdmin": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Denom", + value: body.denom, + }, + { + title: "New Admin", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.new_admin, + linkType: getAddressType(body.new_admin), + }), + }, + ]; + // **No example + case "/osmosis.tokenfactory.v1beta1.MsgSetDenomMetadata": + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: body.sender, + linkType: getAddressType(body.sender), + }), + }, + { + title: "Metadata", + html: getCommonReceiptHtml({ + type: "json", + value: body.metadata, + }), + }, + ]; + default: + return Object.entries(body).map((entry) => + getGenericValueEntry(entry, getAddressType) + ); + } +}; diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/mapping.ts b/src/lib/pages/tx-details/components/tx-message/msg-receipts/mapping.ts new file mode 100644 index 000000000..5d1d8920e --- /dev/null +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/mapping.ts @@ -0,0 +1,7 @@ +export const voteOption = { + VOTE_OPTION_UNSPECIFIED: "Empty", + VOTE_OPTION_YES: "Yes", + VOTE_OPTION_NO: "No", + VOTE_OPTION_NO_WITH_VETO: "NoWithVeto", + VOTE_OPTION_ABSTAIN: "Abstain", +}; diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx new file mode 100644 index 000000000..d24c0db74 --- /dev/null +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx @@ -0,0 +1,162 @@ +import { Text } from "@chakra-ui/react"; +import type { Coin } from "@cosmjs/stargate"; + +import type { AddressReturnType } from "lib/app-provider"; +import type { LinkType } from "lib/components/ExplorerLink"; +import { ExplorerLink } from "lib/components/ExplorerLink"; +import JsonReadOnly from "lib/components/json/JsonReadOnly"; +import type { + Addr, + AssetInfo, + Option, + TxReceipt, + ValidatorAddr, +} from "lib/types"; +import { camelToTitle } from "lib/utils"; + +import { CoinComponent } from "./CoinComponent"; + +interface CommonReceiptHtmlArgs { + type: "json" | "explorer"; + value: Option; + linkType?: LinkType; + fallback?: string; +} + +// Util Functions +export const getCommonReceiptHtml = ({ + type, + value, + linkType = "invalid_address", + fallback, +}: CommonReceiptHtmlArgs) => { + if (!value) + return ( + + {fallback} + + ); + + return type === "json" ? ( + + ) : ( + + ); +}; + +export const getCoinComponent = ( + amount: Coin | Coin[], + assetInfos: Option<{ [key: string]: AssetInfo }> +) => ; + +export const getGenericValueEntry = ( + entry: [string, string | object], + getAddressType: (address: string) => AddressReturnType +): TxReceipt => { + const [title, value] = entry; + let valueObj: Omit; + switch (typeof value) { + case "object": + valueObj = { + html: getCommonReceiptHtml({ + type: "json", + value: JSON.stringify(value, null, 2), + }), + }; + break; + case "string": + if (getAddressType(value) !== "invalid_address") + valueObj = { + html: getCommonReceiptHtml({ + type: "explorer", + value, + linkType: getAddressType(value), + }), + }; + else valueObj = { value }; + break; + default: + valueObj = { value }; + } + + return { title: camelToTitle(title), ...valueObj }; +}; + +// Duplicated Receipt +export const attachFundsReceipt = ( + value: Option, + assetInfos: Option<{ [key: string]: AssetInfo }> +): TxReceipt => ({ + title: "Attached Funds", + html: value?.length ? ( + getCoinComponent(value, assetInfos) + ) : ( + + No Attached Funds + + ), +}); + +export const delegatorAddrReceipt = ( + value: Addr, + addrType: LinkType +): TxReceipt => ({ + title: "Delegator Address", + html: getCommonReceiptHtml({ + type: "explorer", + value, + linkType: addrType, + }), +}); + +export const validatorAddrReceipt = (value: ValidatorAddr): TxReceipt => ({ + title: "Validator Address", + html: getCommonReceiptHtml({ + type: "explorer", + value, + linkType: "validator_address", + }), +}); + +export const proposalIdReceipt = (value: string): TxReceipt => ({ + title: "Proposal ID", + html: getCommonReceiptHtml({ + type: "explorer", + value, + linkType: "proposal_id", + }), +}); + +export const clientStateReceipt = (value: string): TxReceipt => ({ + title: "Client State", + html: getCommonReceiptHtml({ type: "json", value }), +}); + +export const proofInitReceipt = (value: string): TxReceipt => ({ + title: "Proof Init", + value, +}); + +export const proofHeightReceipt = (value: string): TxReceipt => ({ + title: "Proof Height", + html: getCommonReceiptHtml({ type: "json", value }), +}); + +export const channelIdReceipt = (value: string): TxReceipt => ({ + title: "Channel ID", + value, +}); diff --git a/src/lib/pages/tx-details/index.tsx b/src/lib/pages/tx-details/index.tsx new file mode 100644 index 000000000..f11f8e9f5 --- /dev/null +++ b/src/lib/pages/tx-details/index.tsx @@ -0,0 +1,50 @@ +import { Flex } from "@chakra-ui/react"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; + +import { BackButton } from "lib/components/button"; +import { Loading } from "lib/components/Loading"; +import PageContainer from "lib/components/PageContainer"; +import { EmptyState } from "lib/components/state/EmptyState"; +import { AmpEvent, AmpTrack } from "lib/services/amplitude"; +import { useTxData } from "lib/services/txService"; +import { getFirstQueryParam } from "lib/utils"; + +import { TxHeader, TxInfo } from "./components"; +import { MessageSection } from "./components/MessageSection"; + +const TxDetails = () => { + const router = useRouter(); + const hashParam = getFirstQueryParam(router.query.txHash); + + const { data: txData, isLoading } = useTxData(hashParam); + + useEffect(() => { + if (router.isReady) AmpTrack(AmpEvent.TO_TRANSACTION_DETAIL); + }, [router.isReady]); + + if (isLoading) return ; + + return ( + + + {txData ? ( + <> + + + + + + + ) : ( + + )} + + ); +}; + +export default TxDetails; diff --git a/src/lib/services/amplitude.tsx b/src/lib/services/amplitude.tsx index b50e45edf..93326e161 100644 --- a/src/lib/services/amplitude.tsx +++ b/src/lib/services/amplitude.tsx @@ -46,6 +46,7 @@ export enum AmpEvent { TO_CONTRACT_DETAIL = "To Contract Detail", TO_CODE_DETAIL = "To Code Detail", TO_PROJECT_DETAIL = "To Public Project Detail", + TO_TRANSACTION_DETAIL = "To Transaction Detail", TO_NOT_FOUND = "To 404 Not Found", TO_FAUCET = "To Faucet", // ACTIONS diff --git a/src/lib/services/contract.ts b/src/lib/services/contract.ts index 2b7a77bb0..b77c59d50 100644 --- a/src/lib/services/contract.ts +++ b/src/lib/services/contract.ts @@ -10,7 +10,7 @@ import type { Option, PublicInfo, } from "lib/types"; -import { encode, libDecode, parseDateDefault } from "lib/utils"; +import { encode, libDecode, parseDateOpt } from "lib/utils"; export interface ContractCw2InfoRaw { data: string; @@ -106,9 +106,7 @@ export const queryInstantiateInfo = async ( height: createdHeight, }) .then(({ blocks_by_pk }) => { - createdTime = blocks_by_pk - ? parseDateDefault(blocks_by_pk?.timestamp) - : undefined; + createdTime = parseDateOpt(blocks_by_pk?.timestamp); }) .catch(() => {}); } diff --git a/src/lib/services/tx.ts b/src/lib/services/tx.ts new file mode 100644 index 000000000..7e0085647 --- /dev/null +++ b/src/lib/services/tx.ts @@ -0,0 +1,97 @@ +import type { Event } from "@cosmjs/stargate"; +import type { Log } from "@cosmjs/stargate/build/logs"; +import axios from "axios"; +import type { CompactBitArray } from "cosmjs-types/cosmos/crypto/multisig/v1beta1/multisig"; +import type { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; +import type { Any } from "cosmjs-types/google/protobuf/any"; + +import { CELATONE_API_ENDPOINT, getChainApiPath } from "env"; +import type { Option, Fee } from "lib/types"; +import { parseDateOpt } from "lib/utils"; + +// ---------------------------------------- +// --------------AuthInfo------------------ +// ---------------------------------------- +interface AuthInfo { + signer_infos: SignerInfo[]; + fee?: Fee; +} + +interface SignerInfo { + public_key?: { "@type": string; key: string }; + mode_info?: ModeInfo; + sequence: string; +} + +interface ModeInfo { + single?: ModeInfoSingle | undefined; + multi?: ModeInfoMulti | undefined; +} + +interface ModeInfoSingle { + mode: keyof typeof SignMode; +} + +// Revisit Multisig Info +interface ModeInfoMulti { + bitarray?: CompactBitArray; + modeInfos: ModeInfo[]; +} + +// ---------------------------------------- +// -----------------Tx--------------------- +// ---------------------------------------- +export interface MsgBody { + "@type": string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +} + +export interface TxBody { + messages: MsgBody[]; + memo: string; + timeout_height: string; + // Revisit extension options + extension_options: Any[]; + non_critical_extension_options: Any[]; +} + +export interface Tx { + "@type": string; + body: TxBody; + auth_info: AuthInfo; + signatures: string[]; +} + +export interface TxResponse { + height: string; + txhash: string; + codespace: string; + code: number; + data: string; + raw_log: string; + logs: Log[]; + info: string; + gas_wanted: string; + gas_used: string; + tx: Tx; + timestamp: Option; + events: Event[]; +} + +export const queryTxData = async ( + chainName: string, + chainId: string, + txHash: string +): Promise => { + const { data } = await axios.get( + `${CELATONE_API_ENDPOINT}/txs/${getChainApiPath( + chainName + )}/${chainId}/${txHash}` + ); + + return { + ...data.tx_response, + timestamp: parseDateOpt(data.tx_response.timestamp), + } as TxResponse; +}; diff --git a/src/lib/services/txQuery/useTxQuery.ts b/src/lib/services/txQuery/useTxQuery.ts index 6e5e1b324..3c1de26a0 100644 --- a/src/lib/services/txQuery/useTxQuery.ts +++ b/src/lib/services/txQuery/useTxQuery.ts @@ -207,6 +207,7 @@ export const useTxQuery = ( indexerGraphClient, ], queryFn, + enabled: !!userAddress, }); }; @@ -257,5 +258,6 @@ export const useTxQueryCount = ( indexerGraphClient, ], queryFn, + enabled: !!userAddress, }); }; diff --git a/src/lib/services/txService.ts b/src/lib/services/txService.ts index 10c99f633..6d55eef65 100644 --- a/src/lib/services/txService.ts +++ b/src/lib/services/txService.ts @@ -1,8 +1,12 @@ -import type { UseQueryResult } from "@tanstack/react-query"; +import { useWallet } from "@cosmos-kit/react"; +import type { + QueryFunctionContext, + UseQueryResult, +} from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query"; import { useCallback } from "react"; -import { useCelatoneApp } from "lib/app-provider"; +import { useCelatoneApp, useChainId } from "lib/app-provider"; import { getExecuteTxsByContractAddressPagination, getExecuteTxsCountByContractAddress, @@ -12,6 +16,7 @@ import { import { MsgFurtherAction } from "lib/types"; import type { Addr, ContractAddr, Option, Transaction } from "lib/types"; import { + formatStdFee, getActionMsgType, parseDateOpt, parseTxHash, @@ -19,6 +24,40 @@ import { unwrapAll, } from "lib/utils"; +import type { TxResponse } from "./tx"; +import { queryTxData } from "./tx"; + +export interface TxData extends TxResponse { + chainId: string; + formattedFee: Option; +} + +export const useTxData = (txHash: Option): UseQueryResult => { + const { currentChainName } = useWallet(); + const chainId = useChainId(); + const queryFn = useCallback( + async ({ queryKey }: QueryFunctionContext) => { + const txData = await queryTxData(queryKey[1], queryKey[2], queryKey[3]); + return { + ...txData, + chainId, + formattedFee: txData.tx.auth_info.fee?.amount.length + ? formatStdFee(txData.tx.auth_info.fee) + : undefined, + }; + }, + [chainId] + ); + + return useQuery({ + queryKey: ["tx_data", currentChainName, chainId, txHash] as string[], + queryFn, + enabled: !!txHash, + refetchOnWindowFocus: false, + retry: false, + }); +}; + export const useExecuteTxsByContractAddressPagination = ( contractAddress: ContractAddr, offset: number, diff --git a/src/lib/styles/theme/components/alert.ts b/src/lib/styles/theme/components/alert.ts index 018ef6c58..c144fec30 100644 --- a/src/lib/styles/theme/components/alert.ts +++ b/src/lib/styles/theme/components/alert.ts @@ -16,6 +16,7 @@ const generateVariantStyle = ( borderColor = `${variant}.dark`; break; case "honeydew": + case "error": default: mainColor = `${variant}.main`; borderColor = `${variant}.main`; diff --git a/src/lib/styles/theme/components/badge.ts b/src/lib/styles/theme/components/badge.ts index 31296f630..26f9b7710 100644 --- a/src/lib/styles/theme/components/badge.ts +++ b/src/lib/styles/theme/components/badge.ts @@ -15,5 +15,6 @@ export const Badge: ComponentStyleConfig = { background: "pebble.700", color: "text.dark", }, + honeydew: { background: "honeydew.dark", color: "background.main" }, }, }; diff --git a/src/lib/types/addrs.ts b/src/lib/types/addrs.ts index 17a25b84b..3d1deb162 100644 --- a/src/lib/types/addrs.ts +++ b/src/lib/types/addrs.ts @@ -2,5 +2,6 @@ import type { NominalType } from "./currency/common"; export type HumanAddr = string & NominalType<"HumanAddr">; export type ContractAddr = string & NominalType<"ContractAddr">; +export type ValidatorAddr = string & NominalType<"ValidatorAddr">; export type Addr = HumanAddr | ContractAddr; diff --git a/src/lib/types/code.ts b/src/lib/types/code.ts index 846f57484..9ba9760eb 100644 --- a/src/lib/types/code.ts +++ b/src/lib/types/code.ts @@ -10,7 +10,7 @@ export enum InstantiatePermission { UNKNOWN = "Unknown", } -export type PermissionAddresses = Addr[]; +export type PermissionAddresses = Addr | Addr[]; export interface CodeInfo extends CodeLocalInfo { contractCount: Option; diff --git a/src/lib/types/currency/balance.ts b/src/lib/types/currency/balance.ts index ba29e2147..6b0bede51 100644 --- a/src/lib/types/currency/balance.ts +++ b/src/lib/types/currency/balance.ts @@ -3,10 +3,10 @@ import type { Option } from "lib/types"; export interface Balance { amount: string; id: string; - name: Option; + name?: Option; precision: number; - symbol: Option; - type: string; + symbol?: Option; + type?: string; price?: number; } @@ -21,6 +21,7 @@ export interface AssetInfo { slugs: string[]; symbol: string; type: string; + price: number; } export interface BalanceWithAssetInfo { diff --git a/src/lib/types/tx/common.ts b/src/lib/types/tx/common.ts new file mode 100644 index 000000000..373385e58 --- /dev/null +++ b/src/lib/types/tx/common.ts @@ -0,0 +1,5 @@ +import type { StdFee } from "@cosmjs/stargate"; + +export interface Fee extends Omit { + gas_limit: string; +} diff --git a/src/lib/types/tx/index.ts b/src/lib/types/tx/index.ts index a9430e003..24ce88206 100644 --- a/src/lib/types/tx/index.ts +++ b/src/lib/types/tx/index.ts @@ -1,3 +1,4 @@ export * from "./msg"; export * from "./model"; export * from "./transaction"; +export * from "./common"; diff --git a/src/lib/types/tx/model.ts b/src/lib/types/tx/model.ts index eee5266f7..1a63c750b 100644 --- a/src/lib/types/tx/model.ts +++ b/src/lib/types/tx/model.ts @@ -13,7 +13,7 @@ export interface TxErrorRendering { export interface TxReceipt { title: string; - value?: string; + value?: string | number; html?: ReactNode; } diff --git a/src/lib/utils/formatter/camelToTitle.ts b/src/lib/utils/formatter/camelToTitle.ts new file mode 100644 index 000000000..2597bf4f4 --- /dev/null +++ b/src/lib/utils/formatter/camelToTitle.ts @@ -0,0 +1,4 @@ +export const camelToTitle = (text: string) => { + const result = text.replace(/([A-Z])/g, " $1"); + return result.charAt(0).toUpperCase() + result.slice(1); +}; diff --git a/src/lib/utils/formatter/denom.ts b/src/lib/utils/formatter/denom.ts index ea12d29f4..a904b921b 100644 --- a/src/lib/utils/formatter/denom.ts +++ b/src/lib/utils/formatter/denom.ts @@ -1,6 +1,6 @@ import type { StdFee } from "@cosmjs/stargate"; -import type { Token, U } from "lib/types"; +import type { Fee, Token, U } from "lib/types"; import { formatToken } from "./currency.format"; import { getTokenLabel } from "./tokenType"; @@ -11,7 +11,7 @@ export const formatUFee = (uFee: string) => { return `${formatToken(fee as U, denom)} ${getTokenLabel(denom)}`; }; -export const formatStdFee = (fee: StdFee) => { +export const formatStdFee = (fee: StdFee | Fee) => { return `${formatToken( fee.amount[0].amount as U, fee.amount[0].denom diff --git a/src/lib/utils/formatter/index.ts b/src/lib/utils/formatter/index.ts index 307a42162..bc129542d 100644 --- a/src/lib/utils/formatter/index.ts +++ b/src/lib/utils/formatter/index.ts @@ -1,6 +1,7 @@ export * from "./currency"; export * from "./currency.format"; export * from "./denom"; +export * from "./camelToTitle"; export * from "./camelToSnake"; export * from "./snakeToCamel"; export * from "./formatBalanceWithDenom"; diff --git a/src/lib/utils/formatter/token.ts b/src/lib/utils/formatter/token.ts index 97d37e74f..962c344b9 100644 --- a/src/lib/utils/formatter/token.ts +++ b/src/lib/utils/formatter/token.ts @@ -87,3 +87,8 @@ export const formatPrice = (value: USD): string => { } return `$${d6}`; }; + +export const formatInteger = (n: BigSource): string => { + const formatter = formatDemimal({ decimalPoints: 0, delimiter: true }); + return formatter(n, "FormatError"); +}; diff --git a/src/pages/[network]/tx/[txHash].tsx b/src/pages/[network]/tx/[txHash].tsx new file mode 100644 index 000000000..fd2aa4abb --- /dev/null +++ b/src/pages/[network]/tx/[txHash].tsx @@ -0,0 +1,3 @@ +import TxDetails from "lib/pages/tx-details"; + +export default TxDetails; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 4da01dc57..949201de8 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { wallets } from "@cosmos-kit/keplr"; import { WalletProvider } from "@cosmos-kit/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; diff --git a/src/pages/tx/[txHash].tsx b/src/pages/tx/[txHash].tsx new file mode 100644 index 000000000..fd2aa4abb --- /dev/null +++ b/src/pages/tx/[txHash].tsx @@ -0,0 +1,3 @@ +import TxDetails from "lib/pages/tx-details"; + +export default TxDetails; diff --git a/yarn.lock b/yarn.lock index c05fa5eac..e9d747558 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1608,7 +1608,7 @@ resolved "https://registry.npmjs.org/@corex/deepmerge/-/deepmerge-4.0.29.tgz" integrity sha512-q/yVUnqckA8Do+EvAfpy7RLdumnBy9ZsducMUtZTvpdbJC7azEf1hGtnYYxm0QfphYxjwggv6XtH64prvS1W+A== -"@cosmjs/amino@^0.29.3": +"@cosmjs/amino@0.29.3", "@cosmjs/amino@^0.29.3": version "0.29.3" resolved "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.29.3.tgz" integrity sha512-BFz1++ERerIggiFc7iGHhGe1CeV3rCv8BvkoBQTBN/ZwzHOaKvqQj8smDlRGlQxX3HWlTwgiLN2A+OB5yX4ZRw== @@ -1773,16 +1773,7 @@ dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@^0.24.0-alpha.25": - version "0.24.1" - resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.24.1.tgz" - integrity sha512-/rnyNx+FlG6b6O+igsb42eMN1/RXY+pTrNnAE8/YZaRloP9A6MXiTMO5JdYSTcjaD0mEVhejiy96bcyflKYXBg== - dependencies: - "@cosmjs/launchpad" "^0.24.1" - long "^4.0.0" - protobufjs "~6.10.2" - -"@cosmjs/proto-signing@^0.29.3": +"@cosmjs/proto-signing@0.29.3", "@cosmjs/proto-signing@^0.29.3": version "0.29.3" resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.29.3.tgz" integrity sha512-Ai3l9THjMOrLJ4Ebn1Dgptwg6W5ZIRJqtnJjijHhGwTVC1WT0WdYU3aMZ7+PwubcA/cA1rH4ZTK7jrfYbra63g== @@ -1795,6 +1786,15 @@ cosmjs-types "^0.5.2" long "^4.0.0" +"@cosmjs/proto-signing@^0.24.0-alpha.25": + version "0.24.1" + resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.24.1.tgz" + integrity sha512-/rnyNx+FlG6b6O+igsb42eMN1/RXY+pTrNnAE8/YZaRloP9A6MXiTMO5JdYSTcjaD0mEVhejiy96bcyflKYXBg== + dependencies: + "@cosmjs/launchpad" "^0.24.1" + long "^4.0.0" + protobufjs "~6.10.2" + "@cosmjs/proto-signing@^0.29.5": version "0.29.5" resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.5.tgz#af3b62a46c2c2f1d2327d678b13b7262db1fe87c" @@ -1818,7 +1818,7 @@ ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@^0.29.3": +"@cosmjs/stargate@0.29.3", "@cosmjs/stargate@^0.29.3": version "0.29.3" resolved "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.29.3.tgz" integrity sha512-455TgXStCi6E8KDjnhDAM8wt6aLSjobH4Dixvd7Up1DfCH6UB9NkC/G0fMJANNcNXMaM4wSX14niTXwD1d31BA== @@ -3099,6 +3099,14 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@osmonauts/lcd@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@osmonauts/lcd/-/lcd-0.8.0.tgz#fcabba93edadd23f73b2046a5cad897b420a9c84" + integrity sha512-k7m2gAVnXc0H4m/eTq4z/8A6hFrr3MPS9wnLV4Xu9/K/WYltCnp2PpiObZm+feZUPK/svES6hxIQeO1bODLx8g== + dependencies: + "@babel/runtime" "^7.19.0" + axios "0.27.2" + "@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.3.0": version "2.3.3" resolved "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.3.tgz" @@ -4092,14 +4100,7 @@ axios@0.21.1: dependencies: follow-redirects "^1.10.0" -axios@^0.21.1, axios@^0.21.2: - version "0.21.4" - resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== - dependencies: - follow-redirects "^1.14.0" - -axios@^0.27.2: +axios@0.27.2, axios@^0.27.2: version "0.27.2" resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz" integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== @@ -4107,6 +4108,13 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" +axios@^0.21.1, axios@^0.21.2: + version "0.21.4" + resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + axios@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz" @@ -7254,7 +7262,7 @@ long@^4.0.0: resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -long@^5.2.1: +long@^5.0.0, long@^5.2.0, long@^5.2.1: version "5.2.1" resolved "https://registry.npmjs.org/long/-/long-5.2.1.tgz" integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== @@ -7804,6 +7812,20 @@ os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +osmojs@^14.0.0-rc.0: + version "14.0.0-rc.0" + resolved "https://registry.yarnpkg.com/osmojs/-/osmojs-14.0.0-rc.0.tgz#c49847b667ee86973f2260c9ca71cc2a76bbedbc" + integrity sha512-YXtg5mxaACWc0N8vKqWvdV3ZO9y/NgtJeUQPvjin+bkveEMkJA4RSN0nl0kRTKQIY6Tjt8YWgae+/98VF/2pzA== + dependencies: + "@babel/runtime" "^7.19.0" + "@cosmjs/amino" "0.29.3" + "@cosmjs/proto-signing" "0.29.3" + "@cosmjs/stargate" "0.29.3" + "@cosmjs/tendermint-rpc" "^0.29.3" + "@osmonauts/lcd" "^0.8.0" + long "^5.2.0" + protobufjs "^6.11.3" + p-limit@3.1.0, p-limit@^3.0.2: version "3.1.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" @@ -8100,7 +8122,7 @@ prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -protobufjs@6.11.3, protobufjs@^6.11.2, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: +protobufjs@6.11.3, protobufjs@^6.11.2, protobufjs@^6.11.3, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: version "6.11.3" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz" integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== @@ -8119,6 +8141,24 @@ protobufjs@6.11.3, protobufjs@^6.11.2, protobufjs@^6.8.8, protobufjs@~6.11.2, pr "@types/node" ">=13.7.0" long "^4.0.0" +protobufjs@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.2.tgz#2af401d8c547b9476fb37ffc65782cf302342ca3" + integrity sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + protobufjs@~6.10.2: version "6.10.3" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz" From 83dc56866b75bb6791b5e3ea493c90fc746aa46f Mon Sep 17 00:00:00 2001 From: poomthiti Date: Wed, 15 Mar 2023 08:10:27 +0700 Subject: [PATCH 02/19] fix: handle old protobuf field case inconsistency --- src/lib/components/PermissionChip.tsx | 1 - .../components/ViewPermissionAddresses.tsx | 67 +- .../pages/tx-details/components/TxHeader.tsx | 2 +- .../components/tx-message/TxMsgDetails.tsx | 40 +- .../components/tx-message/TxMsgExpand.tsx | 18 +- .../tx-message/msg-receipts/CoinComponent.tsx | 3 +- .../tx-message/msg-receipts/index.tsx | 1080 ++++++++++------- .../tx-message/msg-receipts/renderUtils.tsx | 25 +- src/lib/services/tx.ts | 84 +- src/lib/types/code.ts | 2 +- src/lib/utils/formatter/camelToTitle.ts | 4 - src/lib/utils/formatter/convertTitle.ts | 14 + src/lib/utils/formatter/index.ts | 2 +- src/lib/utils/tx/extractTxDetails.ts | 72 ++ src/lib/utils/tx/findAttr.ts | 17 + src/lib/utils/tx/index.ts | 3 + src/lib/utils/tx/mapping.ts | 9 + src/lib/utils/tx/types.ts | 666 ++++++++++ 18 files changed, 1556 insertions(+), 553 deletions(-) delete mode 100644 src/lib/utils/formatter/camelToTitle.ts create mode 100644 src/lib/utils/formatter/convertTitle.ts create mode 100644 src/lib/utils/tx/extractTxDetails.ts create mode 100644 src/lib/utils/tx/findAttr.ts create mode 100644 src/lib/utils/tx/mapping.ts create mode 100644 src/lib/utils/tx/types.ts diff --git a/src/lib/components/PermissionChip.tsx b/src/lib/components/PermissionChip.tsx index ccaf5f81a..eaf85da57 100644 --- a/src/lib/components/PermissionChip.tsx +++ b/src/lib/components/PermissionChip.tsx @@ -28,7 +28,6 @@ export const PermissionChip = ({ const { address } = useWallet(); const isAllowed = - permissionAddresses === (address as HumanAddr) || permissionAddresses.includes(address as HumanAddr) || instantiatePermission === InstantiatePermission.EVERYBODY; diff --git a/src/lib/components/ViewPermissionAddresses.tsx b/src/lib/components/ViewPermissionAddresses.tsx index 44b06c4d9..615e5eef3 100644 --- a/src/lib/components/ViewPermissionAddresses.tsx +++ b/src/lib/components/ViewPermissionAddresses.tsx @@ -16,51 +16,40 @@ export const ViewPermissionAddresses = ({ const getAddressType = useGetAddressType(); const showAddressses = viewAll || - typeof permissionAddresses === "string" || (typeof permissionAddresses === "object" && permissionAddresses.length === 1); return ( <> {showAddressses && - (typeof permissionAddresses === "string" ? ( - - ) : ( - permissionAddresses.map((addr) => { - return ( - - ); - }) - ))} - {typeof permissionAddresses === "object" && - permissionAddresses.length > 1 && ( - - )} + permissionAddresses.map((addr) => { + return ( + + ); + })} + {permissionAddresses.length > 1 && ( + + )} ); }; diff --git a/src/lib/pages/tx-details/components/TxHeader.tsx b/src/lib/pages/tx-details/components/TxHeader.tsx index ca1157168..4a814f78a 100644 --- a/src/lib/pages/tx-details/components/TxHeader.tsx +++ b/src/lib/pages/tx-details/components/TxHeader.tsx @@ -15,7 +15,7 @@ interface TxHeaderProps extends FlexProps { } const DotSeparator = () => ( - + ); export const TxHeader = ({ txData, ...flexProps }: TxHeaderProps) => { diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx index acd35786f..44923d8fa 100644 --- a/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx @@ -17,29 +17,25 @@ export const TxMsgDetails = ({ isExpand, ...txMsgData }: TxMsgDetailsProps) => { const getAddressType = useGetAddressType(); const assetInfos = useAssetInfos(); const receipts = generateReceipts(txMsgData, getAddressType, assetInfos) - .filter(Boolean) .concat( - txMsgData.log - ? { - title: "Event Logs", - html: ( - - {txMsgData.log.events.map((event, idx) => ( - - ))} - - ), - } - : [] - ) as TxReceipt[]; + txMsgData.log && { + title: "Event Logs", + html: ( + + {txMsgData.log.events.map((event, idx) => ( + + ))} + + ), + } + ) + .filter(Boolean) as TxReceipt[]; return ( { const getAddressType = useGetAddressType(); + const assetInfos = useAssetInfos(); let msgIcon: IconType = MdInfo; let content: ReactNode; const isIBC = Boolean( @@ -190,10 +195,19 @@ export const TxMsgExpand = ({ case "/cosmos.bank.v1beta1.MsgSend": { const toAddress = body.to_address as Addr; + const singleCoin = body.amount.at(0) as Coin; + const assetText = + body.amount.length > 1 + ? "assets" + : formatBalanceWithDenom({ + coin: { denom: singleCoin.denom, amount: singleCoin.amount }, + symbol: assetInfos?.[singleCoin.denom]?.symbol, + precision: assetInfos?.[singleCoin.denom]?.precision, + }); msgIcon = MdSend; content = ( <> - Send assets to + Send {assetText} to Vote{" "} - {voteOption[body.option as keyof typeof voteOption]} + {voteOption[body.option as VoteOption]} {" "} on proposal ID{" "} >) => { - if (!assetInfos) return <>{JSON.stringify(amount)}; + if (!assetInfos || (Array.isArray(amount) && !amount.length)) + return <>{JSON.stringify(amount)}; return Array.isArray(amount) ? ( ) : ( diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx index 54cc03d11..6f1a45d23 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx @@ -1,7 +1,6 @@ /* eslint-disable sonarjs/max-switch-cases */ /* eslint-disable complexity */ import { Flex } from "@chakra-ui/react"; -import { findAttribute } from "@cosmjs/stargate/build/logs"; import big from "big.js"; import type { TxMsgData } from ".."; @@ -10,7 +9,8 @@ import { CopyButton } from "lib/components/copy"; import { PermissionChip } from "lib/components/PermissionChip"; import { ViewPermissionAddresses } from "lib/components/ViewPermissionAddresses"; import type { TxReceipt, Option, AssetInfo } from "lib/types"; -import { formatUTC, parseDate } from "lib/utils"; +import type { VoteOption } from "lib/utils"; +import { extractTxDetails, formatUTC, parseDate } from "lib/utils"; import { voteOption } from "./mapping"; import { @@ -31,17 +31,19 @@ export const generateReceipts = ( { msgBody, log }: TxMsgData, getAddressType: (address: string) => AddressReturnType, assetInfos: Option<{ [key: string]: AssetInfo }> -): Option[] => { +): Option[] => { const { "@type": type, ...body } = msgBody; + switch (type) { // cosmwasm/wasm - case "/cosmwasm.wasm.v1.MsgStoreCode": + case "/cosmwasm.wasm.v1.MsgStoreCode": { + const details = extractTxDetails(type, body, log); return [ - log && { + !!details.code_id && { title: "Stored Code ID", html: getCommonReceiptHtml({ type: "explorer", - value: findAttribute([log], "store_code", "code_id").value, + value: details.code_id, linkType: "code_id", }), }, @@ -49,25 +51,29 @@ export const generateReceipts = ( title: "Uploader", html: getCommonReceiptHtml({ type: "explorer", - value: body.sender, - linkType: getAddressType(body.sender), + value: details.sender, + linkType: getAddressType(details.sender), }), }, - body.instantiate_permission && { + details.instantiate_permission && { title: "Instantiate Permission", html: ( @@ -78,12 +84,12 @@ export const generateReceipts = ( html: ( Size:{" "} - {big(Buffer.from(body.wasm_byte_code).byteLength) + {big(Buffer.from(details.wasm_byte_code).byteLength) .div(1024) .toFixed(1)}{" "} KB (body).map((entry) => getGenericValueEntry(entry, getAddressType) diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx index d24c0db74..a9489f626 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx @@ -12,24 +12,26 @@ import type { TxReceipt, ValidatorAddr, } from "lib/types"; -import { camelToTitle } from "lib/utils"; +import { convertToTitle } from "lib/utils"; import { CoinComponent } from "./CoinComponent"; -interface CommonReceiptHtmlArgs { - type: "json" | "explorer"; - value: Option; +type HtmlType = "json" | "explorer"; + +interface CommonReceiptHtmlArgs { + type: T; + value: Option; linkType?: LinkType; fallback?: string; } // Util Functions -export const getCommonReceiptHtml = ({ +export const getCommonReceiptHtml = ({ type, value, linkType = "invalid_address", fallback, -}: CommonReceiptHtmlArgs) => { +}: CommonReceiptHtmlArgs) => { if (!value) return ( @@ -37,7 +39,8 @@ export const getCommonReceiptHtml = ({ ); - return type === "json" ? ( + // TODO: Find a solution, TS doesn't know that type === 'json' would make typeof value === 'object + return type === "json" || typeof value === "object" ? ( ({ }), }); -export const clientStateReceipt = (value: string): TxReceipt => ({ +export const clientStateReceipt = (value: object): TxReceipt => ({ title: "Client State", html: getCommonReceiptHtml({ type: "json", value }), }); @@ -151,7 +154,7 @@ export const proofInitReceipt = (value: string): TxReceipt => ({ value, }); -export const proofHeightReceipt = (value: string): TxReceipt => ({ +export const proofHeightReceipt = (value: object): TxReceipt => ({ title: "Proof Height", html: getCommonReceiptHtml({ type: "json", value }), }); diff --git a/src/lib/services/tx.ts b/src/lib/services/tx.ts index 7e0085647..74ba40af7 100644 --- a/src/lib/services/tx.ts +++ b/src/lib/services/tx.ts @@ -41,8 +41,90 @@ interface ModeInfoMulti { // ---------------------------------------- // -----------------Tx--------------------- // ---------------------------------------- +export type TypeUrl = + // cosmwasm + | "/cosmwasm.wasm.v1.MsgStoreCode" + | "/cosmwasm.wasm.v1.MsgInstantiateContract" + | "/cosmwasm.wasm.v1.MsgInstantiateContract2" + | "/cosmwasm.wasm.v1.MsgExecuteContract" + | "/cosmwasm.wasm.v1.MsgMigrateContract" + | "/cosmwasm.wasm.v1.MsgUpdateAdmin" + | "/cosmwasm.wasm.v1.MsgClearAdmin" + // cosmos-sdk + | "/cosmos.bank.v1beta1.MsgSend" + | "/cosmos.bank.v1beta1.MsgMultiSend" + | "/cosmos.authz.v1beta1.MsgGrant" + | "/cosmos.authz.v1beta1.MsgRevoke" + | "/cosmos.authz.v1beta1.MsgExec" + | "/cosmos.crisis.v1beta1.MsgVerifyInvariant" + | "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress" + | "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward" + | "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission" + | "/cosmos.distribution.v1beta1.MsgFundCommunityPool" + | "/cosmos.evidence.v1beta1.MsgSubmitEvidence" + | "/cosmos.feegrant.v1beta1.MsgGrantAllowance" + | "/cosmos.feegrant.v1beta1.MsgRevokeAllowance" + | "/cosmos.gov.v1beta1.MsgSubmitProposal" + | "/cosmos.gov.v1beta1.MsgVote" + | "/cosmos.gov.v1beta1.MsgVoteWeighted" + | "/cosmos.gov.v1beta1.MsgDeposit" + | "/cosmos.slashing.v1beta1.MsgUnjail" + | "/cosmos.staking.v1beta1.MsgCreateValidator" + | "/cosmos.staking.v1beta1.MsgEditValidator" + | "/cosmos.staking.v1beta1.MsgDelegate" + | "/cosmos.staking.v1beta1.MsgUndelegate" + | "/cosmos.staking.v1beta1.MsgBeginRedelegate" + | "/ibc.applications.transfer.v1.MsgTransfer" + | "/ibc.core.client.v1.MsgCreateClient" + | "/ibc.core.client.v1.MsgUpdateClient" + | "/ibc.core.client.v1.MsgUpgradeClient" + | "/ibc.core.client.v1.MsgSubmitMisbehaviour" + | "/ibc.core.connection.v1.MsgConnectionOpenInit" + | "/ibc.core.connection.v1.MsgConnectionOpenTry" + | "/ibc.core.connection.v1.MsgConnectionOpenAck" + | "/ibc.core.connection.v1.MsgConnectionOpenConfirm" + | "/ibc.core.channel.v1.MsgChannelOpenInit" + | "/ibc.core.channel.v1.MsgChannelOpenTry" + | "/ibc.core.channel.v1.MsgChannelOpenAck" + | "/ibc.core.channel.v1.MsgChannelOpenConfirm" + | "/ibc.core.channel.v1.MsgChannelCloseInit" + | "/ibc.core.channel.v1.MsgChannelCloseConfirm" + | "/ibc.core.channel.v1.MsgRecvPacket" + | "/ibc.core.channel.v1.MsgTimeout" + | "/ibc.core.channel.v1.MsgTimeoutOnClose" + | "/ibc.core.channel.v1.MsgAcknowledgement" + // osmosis + | "/osmosis.gamm.poolmodels.balancer.v1beta1.MsgCreateBalancerPool" + | "/osmosis.gamm.poolmodels.stableswap.v1beta1.MsgCreateStableswapPool" + | "/osmosis.gamm.poolmodels.stableswap.v1beta1.MsgStableSwapAdjustScalingFactors" + | "/osmosis.gamm.v1beta1.MsgJoinPool" + | "/osmosis.gamm.v1beta1.MsgExitPool" + | "/osmosis.gamm.v1beta1.MsgSwapExactAmountIn" + | "/osmosis.gamm.v1beta1.MsgSwapExactAmountOut" + | "/osmosis.gamm.v1beta1.MsgJoinSwapExternAmountIn" + | "/osmosis.gamm.v1beta1.MsgJoinSwapShareAmountOut" + | "/osmosis.gamm.v1beta1.MsgExitSwapShareAmountIn" + | "/osmosis.gamm.v1beta1.MsgExitSwapExternAmountOut" + | "/osmosis.incentives.MsgCreateGauge" + | "/osmosis.incentives.MsgAddToGauge" + | "/osmosis.lockup.MsgLockTokens" + | "/osmosis.lockup.MsgBeginUnlockingAll" + | "/osmosis.lockup.MsgBeginUnlocking" + | "/osmosis.lockup.MsgForceUnlock" + | "/osmosis.lockup.MsgExtendLockup" + | "/osmosis.superfluid.MsgSuperfluidDelegate" + | "/osmosis.superfluid.MsgSuperfluidUndelegate" + | "/osmosis.superfluid.MsgSuperfluidUnbondLock" + | "/osmosis.superfluid.MsgLockAndSuperfluidDelegate" + | "/osmosis.superfluid.MsgUnPoolWhitelistedPool" + | "/osmosis.tokenfactory.v1beta1.MsgCreateDenom" + | "/osmosis.tokenfactory.v1beta1.MsgMint" + | "/osmosis.tokenfactory.v1beta1.MsgBurn" + | "/osmosis.tokenfactory.v1beta1.MsgChangeAdmin" + | "/osmosis.tokenfactory.v1beta1.MsgSetDenomMetadata"; + export interface MsgBody { - "@type": string; + "@type": TypeUrl; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } diff --git a/src/lib/types/code.ts b/src/lib/types/code.ts index 9ba9760eb..846f57484 100644 --- a/src/lib/types/code.ts +++ b/src/lib/types/code.ts @@ -10,7 +10,7 @@ export enum InstantiatePermission { UNKNOWN = "Unknown", } -export type PermissionAddresses = Addr | Addr[]; +export type PermissionAddresses = Addr[]; export interface CodeInfo extends CodeLocalInfo { contractCount: Option; diff --git a/src/lib/utils/formatter/camelToTitle.ts b/src/lib/utils/formatter/camelToTitle.ts deleted file mode 100644 index 2597bf4f4..000000000 --- a/src/lib/utils/formatter/camelToTitle.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const camelToTitle = (text: string) => { - const result = text.replace(/([A-Z])/g, " $1"); - return result.charAt(0).toUpperCase() + result.slice(1); -}; diff --git a/src/lib/utils/formatter/convertTitle.ts b/src/lib/utils/formatter/convertTitle.ts new file mode 100644 index 000000000..6ee3c9ef2 --- /dev/null +++ b/src/lib/utils/formatter/convertTitle.ts @@ -0,0 +1,14 @@ +export const camelToTitle = (text: string) => { + const result = text.replace(/([A-Z])/g, " $1"); + return result.charAt(0).toUpperCase() + result.slice(1); +}; + +export const snakeToTitle = (text: string) => { + return text + .replace(/^[-_]*(.)/, (_, c) => c.toUpperCase()) + .replace(/[-_]+(.)/g, (_, c) => ` ${c.toUpperCase()}`); +}; + +export const convertToTitle = (text: string) => { + return text.includes("_") ? snakeToTitle(text) : camelToTitle(text); +}; diff --git a/src/lib/utils/formatter/index.ts b/src/lib/utils/formatter/index.ts index bc129542d..ad0a3ee3b 100644 --- a/src/lib/utils/formatter/index.ts +++ b/src/lib/utils/formatter/index.ts @@ -1,7 +1,7 @@ export * from "./currency"; export * from "./currency.format"; export * from "./denom"; -export * from "./camelToTitle"; +export * from "./convertTitle"; export * from "./camelToSnake"; export * from "./snakeToCamel"; export * from "./formatBalanceWithDenom"; diff --git a/src/lib/utils/tx/extractTxDetails.ts b/src/lib/utils/tx/extractTxDetails.ts new file mode 100644 index 000000000..74f55e08a --- /dev/null +++ b/src/lib/utils/tx/extractTxDetails.ts @@ -0,0 +1,72 @@ +import type { Log } from "@cosmjs/stargate/build/logs"; +import { snakeCase } from "snake-case"; + +import type { MsgBody, TypeUrl } from "lib/services/tx"; +import type { Option } from "lib/types"; + +import { findAttr } from "./findAttr"; +import type { + MsgReturnType, + MsgStoreCodeDetails, + MsgInstantiateDetails, + MsgInstantiate2Details, + MsgSubmitProposalDetails, +} from "./types"; + +type MsgBodyWithoutType = Omit; + +const transformKeyToSnake = (obj: MsgBodyWithoutType): MsgBodyWithoutType => { + return Object.entries(obj).reduce((acc, entry) => { + const [k, v] = entry; + // check if the key is camelCase + if (/^([a-z]+)(([A-Z]([a-z]+))+)$/.test(k)) { + acc[snakeCase(k)] = v; + } else { + acc[k] = v; + } + return acc; + }, {} as MsgBodyWithoutType); +}; + +export const extractTxDetails = ( + type: T, + body: MsgBodyWithoutType, + log: Option +): MsgReturnType => { + /** + * @remarks Some keys are in camelCase due to old protobuf decoding + */ + const msgBody = transformKeyToSnake(body); + switch (type) { + case "/cosmwasm.wasm.v1.MsgStoreCode": + return { + type, + ...(msgBody as Omit), + code_id: findAttr(log, "store_code", "code_id"), + } as MsgReturnType; + case "/cosmwasm.wasm.v1.MsgInstantiateContract": + return { + type, + ...(msgBody as Omit), + contract_address: findAttr(log, "instantiate", "_contract_address"), + } as MsgReturnType; + case "/cosmwasm.wasm.v1.MsgInstantiateContract2": + return { + type, + ...(msgBody as Omit), + contract_address: findAttr(log, "instantiate", "_contract_address"), + } as MsgReturnType; + case "/cosmos.gov.v1beta1.MsgSubmitProposal": + return { + type, + ...(msgBody as Omit< + MsgSubmitProposalDetails, + "proposal_id" | "proposal_type" + >), + proposal_id: findAttr(log, "submit_proposal", "proposal_id"), + proposal_type: findAttr(log, "submit_proposal", "proposal_type"), + } as MsgReturnType; + default: + return { type, ...msgBody } as MsgReturnType; + } +}; diff --git a/src/lib/utils/tx/findAttr.ts b/src/lib/utils/tx/findAttr.ts new file mode 100644 index 000000000..e8de1f0be --- /dev/null +++ b/src/lib/utils/tx/findAttr.ts @@ -0,0 +1,17 @@ +import type { Log } from "@cosmjs/stargate/build/logs"; +import { findAttribute } from "@cosmjs/stargate/build/logs"; + +import type { Option } from "lib/types"; + +export const findAttr = ( + log: Option, + eventType: string, + attrKey: string +): Option => { + if (!log) return undefined; + try { + return findAttribute([log], eventType, attrKey).value; + } catch { + return undefined; + } +}; diff --git a/src/lib/utils/tx/index.ts b/src/lib/utils/tx/index.ts index e73762035..e0b26945f 100644 --- a/src/lib/utils/tx/index.ts +++ b/src/lib/utils/tx/index.ts @@ -1 +1,4 @@ export * from "./composeMsg"; +export * from "./findAttr"; +export * from "./extractTxDetails"; +export * from "./mapping"; diff --git a/src/lib/utils/tx/mapping.ts b/src/lib/utils/tx/mapping.ts new file mode 100644 index 000000000..4a9dd38e7 --- /dev/null +++ b/src/lib/utils/tx/mapping.ts @@ -0,0 +1,9 @@ +export const voteOption = { + VOTE_OPTION_UNSPECIFIED: "Empty", + VOTE_OPTION_YES: "Yes", + VOTE_OPTION_NO: "No", + VOTE_OPTION_NO_WITH_VETO: "NoWithVeto", + VOTE_OPTION_ABSTAIN: "Abstain", +}; + +export type VoteOption = keyof typeof voteOption; diff --git a/src/lib/utils/tx/types.ts b/src/lib/utils/tx/types.ts new file mode 100644 index 000000000..412ce318a --- /dev/null +++ b/src/lib/utils/tx/types.ts @@ -0,0 +1,666 @@ +import type { Coin } from "@cosmjs/stargate"; + +import type { TypeUrl } from "lib/services/tx"; +import type { + Addr, + ContractAddr, + InstantiatePermission, + Option, + ValidatorAddr, +} from "lib/types"; + +import type { VoteOption } from "./mapping"; + +export interface MsgUnknownDetails { + type: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +} + +interface InstantiatePermissionResponse { + permission: InstantiatePermission; + address: Addr; + addresses: Addr[]; +} + +// cosmwasm/wasm +export interface MsgStoreCodeDetails extends MsgUnknownDetails { + code_id: Option; + sender: Addr; + wasm_byte_code: string; // base64 + instantiate_permission: InstantiatePermissionResponse | null; +} + +export interface MsgInstantiateDetails extends MsgUnknownDetails { + contract_address: Option; + sender: Addr; + admin: Addr; + code_id: string; + label: string; + msg: object; + funds: Coin[]; +} + +export interface MsgInstantiate2Details extends MsgInstantiateDetails { + salt: string; + fix_msg: boolean; +} + +export interface MsgExecuteDetails extends MsgUnknownDetails { + sender: Addr; + contract: ContractAddr; + msg: object; + funds: Coin[]; +} + +export interface MsgMigrateDetails extends MsgUnknownDetails { + sender: Addr; + contract: ContractAddr; + code_id: string; + msg: object; +} + +export interface MsgUpdateAdminDetails extends MsgUnknownDetails { + sender: Addr; + new_admin: Addr; + contract: ContractAddr; +} + +export interface MsgClearAdminDetails extends MsgUnknownDetails { + sender: Addr; + contract: ContractAddr; +} + +// x/authz +export interface MsgGrantDetails extends MsgUnknownDetails { + granter: Addr; + grantee: Addr; + grant: object; +} +export interface MsgRevokeDetails extends MsgUnknownDetails { + granter: Addr; + grantee: Addr; + msg_type_url: string; +} +export interface MsgExecDetails extends MsgUnknownDetails { + grantee: Addr; + msgs: object[]; + msg_type_url: string; +} + +// x/bank +export interface MsgSendDetails extends MsgUnknownDetails { + from_address: Addr; + to_address: Addr; + amount: Coin[]; +} +export interface MsgMultiSendDetails extends MsgUnknownDetails { + inputs: object; + outputs: object; +} + +// x/crisis +export interface MsgVerifyInvariantDetails extends MsgUnknownDetails { + sender: Addr; + invariant_module_name: string; + invariant_route: string; +} + +// x/distribution +export interface MsgSetWithdrawAddressDetails extends MsgUnknownDetails { + delegator_address: Addr; + withdraw_address: Addr; +} +export interface MsgWithdrawDelegatorRewardDetails extends MsgUnknownDetails { + delegator_address: Addr; + validator_address: ValidatorAddr; +} +export interface MsgWithdrawValidatorCommissionDetails + extends MsgUnknownDetails { + validator_address: ValidatorAddr; +} +export interface MsgFundCommunityPoolDetails extends MsgUnknownDetails { + amount: Coin[]; + depositor: Addr; +} + +// x/evidence +export interface MsgSubmitEvidenceDetails extends MsgUnknownDetails { + submitter: Addr; + evidence: object; +} + +// x/feegrant +export interface MsgGrantAllowanceDetails extends MsgUnknownDetails { + granter: Addr; + grantee: Addr; + allowance: object; +} +export interface MsgRevokeAllowanceDetails extends MsgUnknownDetails { + granter: Addr; + grantee: Addr; +} + +// x/gov +export interface MsgSubmitProposalDetails extends MsgUnknownDetails { + initial_deposit: Coin[]; + proposer: Addr; + proposal_id: Option; + proposal_type: Option; + title: string; +} +export interface MsgVoteDetails extends MsgUnknownDetails { + proposal_id: string; + voter: Addr; + option: VoteOption; +} +export interface MsgVoteWeightedDetails extends MsgUnknownDetails { + proposal_id: string; + voter: Addr; + options: { option: VoteOption; weight: string }[]; +} +export interface MsgDepositDetails extends MsgUnknownDetails { + proposal_id: string; + depositor: Addr; + amount: Coin[]; +} + +// x/slashing +export interface MsgUnjailDetails extends MsgUnknownDetails { + validator_addr: ValidatorAddr; +} + +// x/staking +export interface MsgCreateValidatorDetails extends MsgUnknownDetails { + description: object; + commission: object; + min_self_delegation: string; + delegator_address: Addr; + validator_address: ValidatorAddr; + pubkey: object; + value: Coin; +} +export interface MsgEditValidatorDetails extends MsgUnknownDetails { + description: object; + validator_address: ValidatorAddr; + commission_rate: string; + min_self_delegation: string; +} +export interface MsgDelegateDetails extends MsgUnknownDetails { + delegator_address: Addr; + validator_address: ValidatorAddr; + amount: Coin; +} +export interface MsgBeginRedelegateDetails extends MsgUnknownDetails { + delegator_address: Addr; + validator_src_address: ValidatorAddr; + validator_dst_address: ValidatorAddr; + amount: Coin; +} +export interface MsgUndelegateDetails extends MsgUnknownDetails { + delegator_address: Addr; + validator_address: ValidatorAddr; + amount: Coin; +} + +// ibc/applications +export interface MsgTransferDetails extends MsgUnknownDetails { + source_port: string; + source_channel: string; + token: Coin; + sender: Addr; + receiver: Addr; + timeout_height: object; + timeout_timestamp: string; + memo: string; +} + +// ibc/core +export interface MsgCreateClientDetails extends MsgUnknownDetails { + client_state: object; + consensus_state: object; + signer: Addr; +} +export interface MsgUpdateClientDetails extends MsgUnknownDetails { + client_id: string; + header: object; + signer: Addr; +} +export interface MsgUpgradeClientDetails extends MsgUnknownDetails { + client_id: string; + client_state: object; + consensus_state: object; + proof_upgrade_client: string; + proof_upgrade_consensus_state: string; + signer: Addr; +} +export interface MsgSubmitMisbehaviourDetails extends MsgUnknownDetails { + client_id: string; + misbehaviour: object; + signer: Addr; +} +export interface MsgConnectionOpenInitDetails extends MsgUnknownDetails { + client_id: string; + counterparty: object; + version: object; + delay_period: number; + signer: Addr; +} +export interface MsgConnectionOpenTryDetails extends MsgUnknownDetails { + client_id: string; + previous_connection_id: string; + client_state: object; + counterparty: object; + delay_period: number; + counterparty_versions: object; + proof_height: object; + proof_init: string; + proof_client: string; + proof_consensus: string; + consensus_height: object; + signer: Addr; +} +export interface MsgConnectionOpenAckDetails extends MsgUnknownDetails { + connection_id: string; + counterparty_connection_id: string; + version: object; + client_state: object; + proof_height: object; + proof_try: string; + proof_client: string; + proof_consensus: string; + consensus_height: object; + signer: Addr; +} +export interface MsgConnectionOpenConfirmDetails extends MsgUnknownDetails { + connection_id: string; + proof_ack: string; + proof_height: object; + signer: Addr; +} +export interface MsgChannelOpenInitDetails extends MsgUnknownDetails { + port_id: string; + channel: object; + signer: Addr; +} +export interface MsgChannelOpenTryDetails extends MsgUnknownDetails { + port_id: string; + previous_channel_id: string; + channel: object; + counterparty_version: string; + proof_init: string; + proof_height: object; + signer: Addr; +} +export interface MsgChannelOpenAckDetails extends MsgUnknownDetails { + port_id: string; + channel_id: string; + counterparty_channel_id: string; + counterparty_version: string; + proof_try: string; + proof_height: object; + signer: Addr; +} +export interface MsgChannelOpenConfirmDetails extends MsgUnknownDetails { + port_id: string; + channel_id: string; + proof_ack: string; + proof_height: object; + signer: Addr; +} +export interface MsgChannelCloseInitDetails extends MsgUnknownDetails { + port_id: string; + channel_id: string; + signer: Addr; +} +export interface MsgChannelCloseConfirmDetails extends MsgUnknownDetails { + port_id: string; + channel_id: string; + proof_init: string; + proof_height: object; + signer: Addr; +} +export interface MsgRecvPacketDetails extends MsgUnknownDetails { + packet: object; + proof_commitment: string; + proof_height: object; + signer: Addr; +} +export interface MsgTimeoutDetails extends MsgUnknownDetails { + packet: object; + proof_unreceived: string; + proof_height: object; + next_sequence_recv: number; + signer: Addr; +} +export interface MsgTimeoutOnCloseDetails extends MsgUnknownDetails { + packet: object; + proof_unreceived: string; + proof_close: string; + proof_height: object; + next_sequence_recv: number; + signer: Addr; +} +export interface MsgAcknowledgementDetails extends MsgUnknownDetails { + packet: object; + acknowledgement: string; + proof_acked: string; + proof_height: object; + signer: Addr; +} + +// osmosis/gamm +export interface MsgCreateBalancerPoolDetails extends MsgUnknownDetails { + sender: Addr; + pool_params: object; + pool_assets: object; + future_pool_governor: string; +} +export interface MsgCreateStableswapPoolDetails extends MsgUnknownDetails { + sender: Addr; + pool_params: object; + initial_pool_liquidity: Coin[]; + scaling_factors: string[]; + future_pool_governor: string; + scaling_factor_controller: string; +} +export interface MsgStableSwapAdjustScalingFactorsDetails + extends MsgUnknownDetails { + sender: Addr; + pool_id: string; + scaling_factors: string[]; +} +export interface MsgJoinPoolDetails extends MsgUnknownDetails { + sender: Addr; + pool_id: string; + share_out_amount: string; + token_in_maxs: Coin[]; +} +export interface MsgExitPoolDetails extends MsgUnknownDetails { + sender: Addr; + pool_id: string; + share_in_amount: string; + token_out_mins: Coin[]; +} +export interface MsgSwapExactAmountInDetails extends MsgUnknownDetails { + sender: Addr; + routes: object; + token_in: Coin; + token_out_min_amount: string; +} +export interface MsgSwapExactAmountOutDetails extends MsgUnknownDetails { + sender: Addr; + routes: object; + token_in_max_amount: string; + token_out: Coin; +} +export interface MsgJoinSwapExternAmountInDetails extends MsgUnknownDetails { + sender: Addr; + pool_id: string; + token_in: Coin; + share_out_min_amount: string; +} +export interface MsgJoinSwapShareAmountOutDetails extends MsgUnknownDetails { + sender: Addr; + pool_id: string; + token_in_denom: string; + share_out_amount: string; + token_in_max_amount: string; +} +export interface MsgExitSwapShareAmountInDetails extends MsgUnknownDetails { + sender: Addr; + pool_id: string; + token_out_denom: string; + share_in_amount: string; + token_out_min_amount: string; +} +export interface MsgExitSwapExternAmountOutDetails extends MsgUnknownDetails { + sender: Addr; + pool_id: string; + token_out: Coin; + share_in_max_amount: string; +} + +// osmosis/incentives +export interface MsgCreateGaugeDetails extends MsgUnknownDetails { + is_perpetual: boolean; + owner: Addr; + distribute_to: object; + coins: Coin[]; + start_time: string; + num_epochs_paid_over: string; +} +export interface MsgAddToGaugeDetails extends MsgUnknownDetails { + owner: Addr; + gauge_id: string; + rewards: Coin[]; +} + +// osmosis/lockup +export interface MsgLockTokensDetails extends MsgUnknownDetails { + owner: Addr; + duration: string; + coins: Coin[]; +} +export interface MsgBeginUnlockingAllDetails extends MsgUnknownDetails { + owner: Addr; +} +export interface MsgBeginUnlockingDetails extends MsgUnknownDetails { + owner: Addr; + ID: string; + coins: Coin[]; +} +export interface MsgExtendLockupDetails extends MsgUnknownDetails { + owner: Addr; + ID: string; + duration: string; +} +export interface MsgForceUnlockDetails extends MsgUnknownDetails { + owner: Addr; + ID: string; + coins: Coin[]; +} + +// osmosis/superfluid +export interface MsgSuperfluidDelegateDetails extends MsgUnknownDetails { + sender: Addr; + lock_id: string; + val_addr: ValidatorAddr; +} +export interface MsgSuperfluidUndelegateDetails extends MsgUnknownDetails { + sender: Addr; + lock_id: string; +} +export interface MsgSuperfluidUnbondLockDetails extends MsgUnknownDetails { + sender: Addr; + lock_id: string; +} +export interface MsgLockAndSuperfluidDelegateDetails extends MsgUnknownDetails { + sender: Addr; + coins: Coin[]; + val_addr: ValidatorAddr; +} +export interface MsgUnPoolWhitelistedPoolDetails extends MsgUnknownDetails { + sender: Addr; + pool_id: string; +} + +// osmosis/tokenfactory +export interface MsgCreateDenomDetails extends MsgUnknownDetails { + sender: Addr; + subdenom: string; +} +export interface MsgMintDetails extends MsgUnknownDetails { + sender: Addr; + amount: Coin; +} +export interface MsgBurnDetails extends MsgUnknownDetails { + sender: Addr; + amount: Coin; +} +export interface MsgChangeAdminDetails extends MsgUnknownDetails { + sender: Addr; + denom: string; + new_admin: Addr; +} +export interface MsgSetDenomMetadataDetails extends MsgUnknownDetails { + sender: Addr; + metadata: object; +} + +export type MsgReturnType = + T extends "/cosmwasm.wasm.v1.MsgStoreCode" + ? MsgStoreCodeDetails + : T extends "/cosmwasm.wasm.v1.MsgInstantiateContract" + ? MsgInstantiateDetails + : T extends "/cosmwasm.wasm.v1.MsgInstantiateContract2" + ? MsgInstantiate2Details + : T extends "/cosmwasm.wasm.v1.MsgExecuteContract" + ? MsgExecuteDetails + : T extends "/cosmwasm.wasm.v1.MsgMigrateContract" + ? MsgMigrateDetails + : T extends "/cosmwasm.wasm.v1.MsgUpdateAdmin" + ? MsgUpdateAdminDetails + : T extends "/cosmwasm.wasm.v1.MsgClearAdmin" + ? MsgClearAdminDetails + : T extends "/cosmos.bank.v1beta1.MsgSend" + ? MsgSendDetails + : T extends "/cosmos.bank.v1beta1.MsgMultiSend" + ? MsgMultiSendDetails + : T extends "/cosmos.authz.v1beta1.MsgGrant" + ? MsgGrantDetails + : T extends "/cosmos.authz.v1beta1.MsgRevoke" + ? MsgRevokeDetails + : T extends "/cosmos.authz.v1beta1.MsgExec" + ? MsgExecDetails + : T extends "/cosmos.crisis.v1beta1.MsgVerifyInvariant" + ? MsgVerifyInvariantDetails + : T extends "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress" + ? MsgSetWithdrawAddressDetails + : T extends "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward" + ? MsgWithdrawDelegatorRewardDetails + : T extends "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission" + ? MsgWithdrawValidatorCommissionDetails + : T extends "/cosmos.distribution.v1beta1.MsgFundCommunityPool" + ? MsgFundCommunityPoolDetails + : T extends "/cosmos.evidence.v1beta1.MsgSubmitEvidence" + ? MsgSubmitEvidenceDetails + : T extends "/cosmos.feegrant.v1beta1.MsgGrantAllowance" + ? MsgGrantAllowanceDetails + : T extends "/cosmos.feegrant.v1beta1.MsgRevokeAllowance" + ? MsgRevokeAllowanceDetails + : T extends "/cosmos.gov.v1beta1.MsgSubmitProposal" + ? MsgSubmitProposalDetails + : T extends "/cosmos.gov.v1beta1.MsgVote" + ? MsgVoteDetails + : T extends "/cosmos.gov.v1beta1.MsgVoteWeighted" + ? MsgVoteWeightedDetails + : T extends "/cosmos.gov.v1beta1.MsgDeposit" + ? MsgDepositDetails + : T extends "/cosmos.slashing.v1beta1.MsgUnjail" + ? MsgUnjailDetails + : T extends "/cosmos.staking.v1beta1.MsgCreateValidator" + ? MsgCreateValidatorDetails + : T extends "/cosmos.staking.v1beta1.MsgEditValidator" + ? MsgEditValidatorDetails + : T extends "/cosmos.staking.v1beta1.MsgDelegate" + ? MsgDelegateDetails + : T extends "/cosmos.staking.v1beta1.MsgUndelegate" + ? MsgUndelegateDetails + : T extends "/cosmos.staking.v1beta1.MsgBeginRedelegate" + ? MsgBeginRedelegateDetails + : T extends "/ibc.applications.transfer.v1.MsgTransfer" + ? MsgTransferDetails + : T extends "/ibc.core.client.v1.MsgCreateClient" + ? MsgCreateClientDetails + : T extends "/ibc.core.client.v1.MsgUpdateClient" + ? MsgUpdateClientDetails + : T extends "/ibc.core.client.v1.MsgUpgradeClient" + ? MsgUpgradeClientDetails + : T extends "/ibc.core.client.v1.MsgSubmitMisbehaviour" + ? MsgSubmitMisbehaviourDetails + : T extends "/ibc.core.connection.v1.MsgConnectionOpenInit" + ? MsgConnectionOpenInitDetails + : T extends "/ibc.core.connection.v1.MsgConnectionOpenTry" + ? MsgConnectionOpenTryDetails + : T extends "/ibc.core.connection.v1.MsgConnectionOpenAck" + ? MsgConnectionOpenAckDetails + : T extends "/ibc.core.connection.v1.MsgConnectionOpenConfirm" + ? MsgConnectionOpenConfirmDetails + : T extends "/ibc.core.channel.v1.MsgChannelOpenInit" + ? MsgChannelOpenInitDetails + : T extends "/ibc.core.channel.v1.MsgChannelOpenTry" + ? MsgChannelOpenTryDetails + : T extends "/ibc.core.channel.v1.MsgChannelOpenAck" + ? MsgChannelOpenAckDetails + : T extends "/ibc.core.channel.v1.MsgChannelOpenConfirm" + ? MsgChannelOpenConfirmDetails + : T extends "/ibc.core.channel.v1.MsgChannelCloseInit" + ? MsgChannelCloseInitDetails + : T extends "/ibc.core.channel.v1.MsgChannelCloseConfirm" + ? MsgChannelCloseConfirmDetails + : T extends "/ibc.core.channel.v1.MsgRecvPacket" + ? MsgRecvPacketDetails + : T extends "/ibc.core.channel.v1.MsgTimeout" + ? MsgTimeoutDetails + : T extends "/ibc.core.channel.v1.MsgTimeoutOnClose" + ? MsgTimeoutOnCloseDetails + : T extends "/ibc.core.channel.v1.MsgAcknowledgement" + ? MsgAcknowledgementDetails + : T extends "/osmosis.gamm.poolmodels.balancer.v1beta1.MsgCreateBalancerPool" + ? MsgCreateBalancerPoolDetails + : T extends "/osmosis.gamm.poolmodels.stableswap.v1beta1.MsgCreateStableswapPool" + ? MsgCreateStableswapPoolDetails + : T extends "/osmosis.gamm.poolmodels.stableswap.v1beta1.MsgStableSwapAdjustScalingFactors" + ? MsgStableSwapAdjustScalingFactorsDetails + : T extends "/osmosis.gamm.v1beta1.MsgJoinPool" + ? MsgJoinPoolDetails + : T extends "/osmosis.gamm.v1beta1.MsgExitPool" + ? MsgExitPoolDetails + : T extends "/osmosis.gamm.v1beta1.MsgSwapExactAmountIn" + ? MsgSwapExactAmountInDetails + : T extends "/osmosis.gamm.v1beta1.MsgSwapExactAmountOut" + ? MsgSwapExactAmountOutDetails + : T extends "/osmosis.gamm.v1beta1.MsgJoinSwapExternAmountIn" + ? MsgJoinSwapExternAmountInDetails + : T extends "/osmosis.gamm.v1beta1.MsgJoinSwapShareAmountOut" + ? MsgJoinSwapShareAmountOutDetails + : T extends "/osmosis.gamm.v1beta1.MsgExitSwapShareAmountIn" + ? MsgExitSwapShareAmountInDetails + : T extends "/osmosis.gamm.v1beta1.MsgExitSwapExternAmountOut" + ? MsgExitSwapExternAmountOutDetails + : T extends "/osmosis.incentives.MsgCreateGauge" + ? MsgCreateGaugeDetails + : T extends "/osmosis.incentives.MsgAddToGauge" + ? MsgAddToGaugeDetails + : T extends "/osmosis.lockup.MsgLockTokens" + ? MsgLockTokensDetails + : T extends "/osmosis.lockup.MsgBeginUnlockingAll" + ? MsgBeginUnlockingAllDetails + : T extends "/osmosis.lockup.MsgBeginUnlocking" + ? MsgBeginUnlockingDetails + : T extends "/osmosis.lockup.MsgForceUnlock" + ? MsgForceUnlockDetails + : T extends "/osmosis.lockup.MsgExtendLockup" + ? MsgExtendLockupDetails + : T extends "/osmosis.superfluid.MsgSuperfluidDelegate" + ? MsgSuperfluidDelegateDetails + : T extends "/osmosis.superfluid.MsgSuperfluidUndelegate" + ? MsgSuperfluidUndelegateDetails + : T extends "/osmosis.superfluid.MsgSuperfluidUnbondLock" + ? MsgSuperfluidUnbondLockDetails + : T extends "/osmosis.superfluid.MsgLockAndSuperfluidDelegate" + ? MsgLockAndSuperfluidDelegateDetails + : T extends "/osmosis.superfluid.MsgUnPoolWhitelistedPool" + ? MsgUnPoolWhitelistedPoolDetails + : T extends "/osmosis.tokenfactory.v1beta1.MsgCreateDenom" + ? MsgCreateDenomDetails + : T extends "/osmosis.tokenfactory.v1beta1.MsgMint" + ? MsgMintDetails + : T extends "/osmosis.tokenfactory.v1beta1.MsgBurn" + ? MsgBurnDetails + : T extends "/osmosis.tokenfactory.v1beta1.MsgChangeAdmin" + ? MsgChangeAdminDetails + : T extends "/osmosis.tokenfactory.v1beta1.MsgSetDenomMetadata" + ? MsgSetDenomMetadataDetails + : MsgUnknownDetails; From 5abbcd9e5bf454020375e8df79c99036f01cd7e5 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Thu, 16 Mar 2023 19:25:13 +0700 Subject: [PATCH 03/19] fix(components): update json readonly component --- src/lib/components/json/JsonReadOnly.tsx | 3 +++ .../pages/tx-details/components/tx-message/EventBox.tsx | 7 ++----- .../components/tx-message/msg-receipts/renderUtils.tsx | 7 ++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/lib/components/json/JsonReadOnly.tsx b/src/lib/components/json/JsonReadOnly.tsx index c12dee1ec..f31196eb5 100644 --- a/src/lib/components/json/JsonReadOnly.tsx +++ b/src/lib/components/json/JsonReadOnly.tsx @@ -16,6 +16,7 @@ interface JsonReadOnlyProps { text: string; canCopy?: boolean; isExpandable?: boolean; + fullWidth?: boolean; } const THRESHOLD_LINES = 16; @@ -25,6 +26,7 @@ const JsonReadOnly = ({ text, canCopy, isExpandable, + fullWidth, }: JsonReadOnlyProps) => { const [viewFull, setViewFull] = useState(false); @@ -57,6 +59,7 @@ const JsonReadOnly = ({ borderColor: isJsonValid && "pebble.600", "& .copy-button-box": { display: "block" }, }} + w={fullWidth ? "full" : undefined} > { valueComponent = ( ); else valueComponent = value; diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx index a9489f626..51e44c9bc 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx @@ -43,12 +43,9 @@ export const getCommonReceiptHtml = ({ return type === "json" || typeof value === "object" ? ( ) : ( Date: Fri, 17 Mar 2023 15:31:27 +0700 Subject: [PATCH 04/19] chore: update icon --- src/lib/components/icon/CustomIcon.tsx | 4 +- .../pages/tx-details/components/TxHeader.tsx | 29 ++++++---- .../components/tx-message/EventBox.tsx | 21 ++++--- .../components/tx-message/TxMsgExpand.tsx | 55 +++++++------------ 4 files changed, 54 insertions(+), 55 deletions(-) diff --git a/src/lib/components/icon/CustomIcon.tsx b/src/lib/components/icon/CustomIcon.tsx index a19b6ba8a..83d2723a2 100644 --- a/src/lib/components/icon/CustomIcon.tsx +++ b/src/lib/components/icon/CustomIcon.tsx @@ -564,7 +564,7 @@ export const ICONS = { ), viewBox: viewBox101616, }, - deligate: { + delegate: { svg: ( { diff --git a/src/lib/pages/tx-details/components/MessageSection.tsx b/src/lib/pages/tx-details/components/MessageSection.tsx index bfea8091e..2eddf3b48 100644 --- a/src/lib/pages/tx-details/components/MessageSection.tsx +++ b/src/lib/pages/tx-details/components/MessageSection.tsx @@ -16,11 +16,10 @@ interface MessageSectionProps { } export const MessageSection = ({ txData }: MessageSectionProps) => { - const isTxFailed = Boolean(txData.code); const msgs = txData.tx.body.messages; return ( - - {isTxFailed && ( + + {txData.isTxFailed && ( {txData.raw_log} diff --git a/src/lib/pages/tx-details/components/TxHeader.tsx b/src/lib/pages/tx-details/components/TxHeader.tsx index d80693fbb..e9c959822 100644 --- a/src/lib/pages/tx-details/components/TxHeader.tsx +++ b/src/lib/pages/tx-details/components/TxHeader.tsx @@ -1,11 +1,9 @@ import type { FlexProps } from "@chakra-ui/react"; import { Button, Box, Flex, Heading, Text } from "@chakra-ui/react"; -import { useWallet } from "@cosmos-kit/react"; -import { CELATONE_API_ENDPOINT, getChainApiPath } from "env"; -import { useChainId } from "lib/app-provider"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; +import { useOpenTxTab } from "lib/hooks"; import type { TxData } from "lib/services/txService"; import { dateFromNow, formatUTC } from "lib/utils"; @@ -18,18 +16,7 @@ const DotSeparator = () => ( ); export const TxHeader = ({ txData, ...flexProps }: TxHeaderProps) => { - const { currentChainName } = useWallet(); - const chainId = useChainId(); - const isTxFailed = Boolean(txData.code); - const openLcdPage = () => { - window.open( - `${CELATONE_API_ENDPOINT}/txs/${getChainApiPath( - currentChainName - )}/${chainId}/${txData.txhash}`, - "_blank", - "noopener,noreferrer" - ); - }; + const openLcdTab = useOpenTxTab("lcd"); return ( @@ -41,7 +28,7 @@ export const TxHeader = ({ txData, ...flexProps }: TxHeaderProps) => { rightIcon={ } - onClick={openLcdPage} + onClick={() => openLcdTab(txData.txhash)} > View in JSON @@ -59,7 +46,7 @@ export const TxHeader = ({ txData, ...flexProps }: TxHeaderProps) => { - {isTxFailed ? ( + {txData.isTxFailed ? ( <> ( {txData.chainId} - {txData.formattedFee || ( + {txData.formattedFee ?? ( No Fee diff --git a/src/lib/pages/tx-details/components/tx-message/EventBox.tsx b/src/lib/pages/tx-details/components/tx-message/EventBox.tsx index 6acc5f43d..b474331b2 100644 --- a/src/lib/pages/tx-details/components/tx-message/EventBox.tsx +++ b/src/lib/pages/tx-details/components/tx-message/EventBox.tsx @@ -4,6 +4,7 @@ import type { ReactNode } from "react"; import { useState } from "react"; import { useGetAddressType } from "lib/app-provider"; +import type { LinkType } from "lib/components/ExplorerLink"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; import JsonReadOnly from "lib/components/json/JsonReadOnly"; @@ -37,9 +38,10 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { ); break; case key === "code_id": + case key === "proposal_id": valueComponent = ( { /> ); break; - case key === "proposal_id": - valueComponent = ( - - ); - break; case jsonValidate(value) === null: if (typeof JSON.parse(value) === "object") valueComponent = ( diff --git a/src/lib/pages/tx-details/components/tx-message/index.tsx b/src/lib/pages/tx-details/components/tx-message/index.tsx index f96abbb33..38aaf5044 100644 --- a/src/lib/pages/tx-details/components/tx-message/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/index.tsx @@ -1,5 +1,5 @@ import { Flex } from "@chakra-ui/react"; -import type { Log } from "@cosmjs/stargate/build/logs"; +import type { logs } from "@cosmjs/stargate"; import { useState } from "react"; import type { MsgBody } from "lib/services/tx"; @@ -10,7 +10,7 @@ import { TxMsgExpand } from "./TxMsgExpand"; export interface TxMsgData { msgBody: MsgBody; - log: Option; + log: Option; isSingleMsg?: boolean; } diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx index 6f1a45d23..276a0b4cc 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx @@ -10,9 +10,8 @@ import { PermissionChip } from "lib/components/PermissionChip"; import { ViewPermissionAddresses } from "lib/components/ViewPermissionAddresses"; import type { TxReceipt, Option, AssetInfo } from "lib/types"; import type { VoteOption } from "lib/utils"; -import { extractTxDetails, formatUTC, parseDate } from "lib/utils"; +import { voteOption, extractTxDetails, formatUTC, parseDate } from "lib/utils"; -import { voteOption } from "./mapping"; import { attachFundsReceipt, channelIdReceipt, @@ -91,7 +90,6 @@ export const generateReceipts = ( ({ type, value, linkType = "invalid_address", - fallback, + fallback = "-", }: CommonReceiptHtmlArgs) => { if (!value) return ( @@ -64,10 +64,9 @@ export const getCoinComponent = ( ) => ; export const getGenericValueEntry = ( - entry: [string, string | object], + [title, value]: [string, string | object], getAddressType: (address: string) => AddressReturnType ): TxReceipt => { - const [title, value] = entry; let valueObj: Omit; switch (typeof value) { case "object": @@ -96,7 +95,6 @@ export const getGenericValueEntry = ( return { title: convertToTitle(title), ...valueObj }; }; -// Duplicated Receipt export const attachFundsReceipt = ( value: Option, assetInfos: Option<{ [key: string]: AssetInfo }> diff --git a/src/lib/pages/tx-details/index.tsx b/src/lib/pages/tx-details/index.tsx index f11f8e9f5..b22a8a930 100644 --- a/src/lib/pages/tx-details/index.tsx +++ b/src/lib/pages/tx-details/index.tsx @@ -17,13 +17,13 @@ const TxDetails = () => { const router = useRouter(); const hashParam = getFirstQueryParam(router.query.txHash); - const { data: txData, isLoading } = useTxData(hashParam); + const { data: txData, isLoading, isFetching } = useTxData(hashParam); useEffect(() => { if (router.isReady) AmpTrack(AmpEvent.TO_TRANSACTION_DETAIL); }, [router.isReady]); - if (isLoading) return ; + if ((isLoading && isFetching) || !hashParam) return ; return ( diff --git a/src/lib/services/tx.ts b/src/lib/services/tx.ts index 74ba40af7..0d157da09 100644 --- a/src/lib/services/tx.ts +++ b/src/lib/services/tx.ts @@ -1,11 +1,11 @@ -import type { Event } from "@cosmjs/stargate"; -import type { Log } from "@cosmjs/stargate/build/logs"; +import type { Event, logs } from "@cosmjs/stargate"; import axios from "axios"; import type { CompactBitArray } from "cosmjs-types/cosmos/crypto/multisig/v1beta1/multisig"; import type { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; import type { Any } from "cosmjs-types/google/protobuf/any"; import { CELATONE_API_ENDPOINT, getChainApiPath } from "env"; +import type { TypeUrl } from "lib/data"; import type { Option, Fee } from "lib/types"; import { parseDateOpt } from "lib/utils"; @@ -41,88 +41,6 @@ interface ModeInfoMulti { // ---------------------------------------- // -----------------Tx--------------------- // ---------------------------------------- -export type TypeUrl = - // cosmwasm - | "/cosmwasm.wasm.v1.MsgStoreCode" - | "/cosmwasm.wasm.v1.MsgInstantiateContract" - | "/cosmwasm.wasm.v1.MsgInstantiateContract2" - | "/cosmwasm.wasm.v1.MsgExecuteContract" - | "/cosmwasm.wasm.v1.MsgMigrateContract" - | "/cosmwasm.wasm.v1.MsgUpdateAdmin" - | "/cosmwasm.wasm.v1.MsgClearAdmin" - // cosmos-sdk - | "/cosmos.bank.v1beta1.MsgSend" - | "/cosmos.bank.v1beta1.MsgMultiSend" - | "/cosmos.authz.v1beta1.MsgGrant" - | "/cosmos.authz.v1beta1.MsgRevoke" - | "/cosmos.authz.v1beta1.MsgExec" - | "/cosmos.crisis.v1beta1.MsgVerifyInvariant" - | "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress" - | "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward" - | "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission" - | "/cosmos.distribution.v1beta1.MsgFundCommunityPool" - | "/cosmos.evidence.v1beta1.MsgSubmitEvidence" - | "/cosmos.feegrant.v1beta1.MsgGrantAllowance" - | "/cosmos.feegrant.v1beta1.MsgRevokeAllowance" - | "/cosmos.gov.v1beta1.MsgSubmitProposal" - | "/cosmos.gov.v1beta1.MsgVote" - | "/cosmos.gov.v1beta1.MsgVoteWeighted" - | "/cosmos.gov.v1beta1.MsgDeposit" - | "/cosmos.slashing.v1beta1.MsgUnjail" - | "/cosmos.staking.v1beta1.MsgCreateValidator" - | "/cosmos.staking.v1beta1.MsgEditValidator" - | "/cosmos.staking.v1beta1.MsgDelegate" - | "/cosmos.staking.v1beta1.MsgUndelegate" - | "/cosmos.staking.v1beta1.MsgBeginRedelegate" - | "/ibc.applications.transfer.v1.MsgTransfer" - | "/ibc.core.client.v1.MsgCreateClient" - | "/ibc.core.client.v1.MsgUpdateClient" - | "/ibc.core.client.v1.MsgUpgradeClient" - | "/ibc.core.client.v1.MsgSubmitMisbehaviour" - | "/ibc.core.connection.v1.MsgConnectionOpenInit" - | "/ibc.core.connection.v1.MsgConnectionOpenTry" - | "/ibc.core.connection.v1.MsgConnectionOpenAck" - | "/ibc.core.connection.v1.MsgConnectionOpenConfirm" - | "/ibc.core.channel.v1.MsgChannelOpenInit" - | "/ibc.core.channel.v1.MsgChannelOpenTry" - | "/ibc.core.channel.v1.MsgChannelOpenAck" - | "/ibc.core.channel.v1.MsgChannelOpenConfirm" - | "/ibc.core.channel.v1.MsgChannelCloseInit" - | "/ibc.core.channel.v1.MsgChannelCloseConfirm" - | "/ibc.core.channel.v1.MsgRecvPacket" - | "/ibc.core.channel.v1.MsgTimeout" - | "/ibc.core.channel.v1.MsgTimeoutOnClose" - | "/ibc.core.channel.v1.MsgAcknowledgement" - // osmosis - | "/osmosis.gamm.poolmodels.balancer.v1beta1.MsgCreateBalancerPool" - | "/osmosis.gamm.poolmodels.stableswap.v1beta1.MsgCreateStableswapPool" - | "/osmosis.gamm.poolmodels.stableswap.v1beta1.MsgStableSwapAdjustScalingFactors" - | "/osmosis.gamm.v1beta1.MsgJoinPool" - | "/osmosis.gamm.v1beta1.MsgExitPool" - | "/osmosis.gamm.v1beta1.MsgSwapExactAmountIn" - | "/osmosis.gamm.v1beta1.MsgSwapExactAmountOut" - | "/osmosis.gamm.v1beta1.MsgJoinSwapExternAmountIn" - | "/osmosis.gamm.v1beta1.MsgJoinSwapShareAmountOut" - | "/osmosis.gamm.v1beta1.MsgExitSwapShareAmountIn" - | "/osmosis.gamm.v1beta1.MsgExitSwapExternAmountOut" - | "/osmosis.incentives.MsgCreateGauge" - | "/osmosis.incentives.MsgAddToGauge" - | "/osmosis.lockup.MsgLockTokens" - | "/osmosis.lockup.MsgBeginUnlockingAll" - | "/osmosis.lockup.MsgBeginUnlocking" - | "/osmosis.lockup.MsgForceUnlock" - | "/osmosis.lockup.MsgExtendLockup" - | "/osmosis.superfluid.MsgSuperfluidDelegate" - | "/osmosis.superfluid.MsgSuperfluidUndelegate" - | "/osmosis.superfluid.MsgSuperfluidUnbondLock" - | "/osmosis.superfluid.MsgLockAndSuperfluidDelegate" - | "/osmosis.superfluid.MsgUnPoolWhitelistedPool" - | "/osmosis.tokenfactory.v1beta1.MsgCreateDenom" - | "/osmosis.tokenfactory.v1beta1.MsgMint" - | "/osmosis.tokenfactory.v1beta1.MsgBurn" - | "/osmosis.tokenfactory.v1beta1.MsgChangeAdmin" - | "/osmosis.tokenfactory.v1beta1.MsgSetDenomMetadata"; - export interface MsgBody { "@type": TypeUrl; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -152,7 +70,7 @@ export interface TxResponse { code: number; data: string; raw_log: string; - logs: Log[]; + logs: logs.Log[]; info: string; gas_wanted: string; gas_used: string; diff --git a/src/lib/services/txService.ts b/src/lib/services/txService.ts index 6d55eef65..e2cb0f022 100644 --- a/src/lib/services/txService.ts +++ b/src/lib/services/txService.ts @@ -18,6 +18,7 @@ import type { Addr, ContractAddr, Option, Transaction } from "lib/types"; import { formatStdFee, getActionMsgType, + isTxHash, parseDateOpt, parseTxHash, snakeToCamel, @@ -30,13 +31,14 @@ import { queryTxData } from "./tx"; export interface TxData extends TxResponse { chainId: string; formattedFee: Option; + isTxFailed: boolean; } export const useTxData = (txHash: Option): UseQueryResult => { const { currentChainName } = useWallet(); const chainId = useChainId(); const queryFn = useCallback( - async ({ queryKey }: QueryFunctionContext) => { + async ({ queryKey }: QueryFunctionContext): Promise => { const txData = await queryTxData(queryKey[1], queryKey[2], queryKey[3]); return { ...txData, @@ -44,6 +46,7 @@ export const useTxData = (txHash: Option): UseQueryResult => { formattedFee: txData.tx.auth_info.fee?.amount.length ? formatStdFee(txData.tx.auth_info.fee) : undefined, + isTxFailed: Boolean(txData.code), }; }, [chainId] @@ -52,7 +55,7 @@ export const useTxData = (txHash: Option): UseQueryResult => { return useQuery({ queryKey: ["tx_data", currentChainName, chainId, txHash] as string[], queryFn, - enabled: !!txHash, + enabled: Boolean(txHash && isTxHash(txHash)), refetchOnWindowFocus: false, retry: false, }); diff --git a/src/lib/types/currency/balance.ts b/src/lib/types/currency/balance.ts index 6b0bede51..e66a136af 100644 --- a/src/lib/types/currency/balance.ts +++ b/src/lib/types/currency/balance.ts @@ -3,9 +3,9 @@ import type { Option } from "lib/types"; export interface Balance { amount: string; id: string; - name?: Option; + name?: string; precision: number; - symbol?: Option; + symbol?: string; type?: string; price?: number; } @@ -21,7 +21,7 @@ export interface AssetInfo { slugs: string[]; symbol: string; type: string; - price: number; + price: Option; } export interface BalanceWithAssetInfo { diff --git a/src/lib/utils/address.ts b/src/lib/utils/address.ts index 22b4d441c..dc8c06472 100644 --- a/src/lib/utils/address.ts +++ b/src/lib/utils/address.ts @@ -6,6 +6,8 @@ export const getAddressTypeText = (addressType: AddressReturnType) => { return "(Contract Address)"; case "user_address": return "(Wallet Address)"; + case "validator_address": + return "(Validator Address)"; default: return "(Invalid Address)"; } diff --git a/src/lib/utils/formatter/token.ts b/src/lib/utils/formatter/token.ts index 962c344b9..ea6950009 100644 --- a/src/lib/utils/formatter/token.ts +++ b/src/lib/utils/formatter/token.ts @@ -90,5 +90,5 @@ export const formatPrice = (value: USD): string => { export const formatInteger = (n: BigSource): string => { const formatter = formatDemimal({ decimalPoints: 0, delimiter: true }); - return formatter(n, "FormatError"); + return formatter(n, "Not Available"); }; diff --git a/src/lib/utils/tx/extractTxDetails.ts b/src/lib/utils/tx/extractTxDetails.ts index 74f55e08a..7b37fcdc0 100644 --- a/src/lib/utils/tx/extractTxDetails.ts +++ b/src/lib/utils/tx/extractTxDetails.ts @@ -1,17 +1,12 @@ import type { Log } from "@cosmjs/stargate/build/logs"; import { snakeCase } from "snake-case"; -import type { MsgBody, TypeUrl } from "lib/services/tx"; +import type { TypeUrl } from "lib/data"; +import type { MsgBody } from "lib/services/tx"; import type { Option } from "lib/types"; import { findAttr } from "./findAttr"; -import type { - MsgReturnType, - MsgStoreCodeDetails, - MsgInstantiateDetails, - MsgInstantiate2Details, - MsgSubmitProposalDetails, -} from "./types"; +import type { MsgReturnType } from "./types"; type MsgBodyWithoutType = Omit; @@ -41,28 +36,20 @@ export const extractTxDetails = ( case "/cosmwasm.wasm.v1.MsgStoreCode": return { type, - ...(msgBody as Omit), + ...msgBody, code_id: findAttr(log, "store_code", "code_id"), } as MsgReturnType; case "/cosmwasm.wasm.v1.MsgInstantiateContract": - return { - type, - ...(msgBody as Omit), - contract_address: findAttr(log, "instantiate", "_contract_address"), - } as MsgReturnType; case "/cosmwasm.wasm.v1.MsgInstantiateContract2": return { type, - ...(msgBody as Omit), + ...msgBody, contract_address: findAttr(log, "instantiate", "_contract_address"), } as MsgReturnType; case "/cosmos.gov.v1beta1.MsgSubmitProposal": return { type, - ...(msgBody as Omit< - MsgSubmitProposalDetails, - "proposal_id" | "proposal_type" - >), + ...msgBody, proposal_id: findAttr(log, "submit_proposal", "proposal_id"), proposal_type: findAttr(log, "submit_proposal", "proposal_type"), } as MsgReturnType; diff --git a/src/lib/utils/tx/findAttr.ts b/src/lib/utils/tx/findAttr.ts index e8de1f0be..132c579ba 100644 --- a/src/lib/utils/tx/findAttr.ts +++ b/src/lib/utils/tx/findAttr.ts @@ -1,16 +1,15 @@ -import type { Log } from "@cosmjs/stargate/build/logs"; -import { findAttribute } from "@cosmjs/stargate/build/logs"; +import { logs } from "@cosmjs/stargate"; import type { Option } from "lib/types"; export const findAttr = ( - log: Option, + log: Option, eventType: string, attrKey: string ): Option => { if (!log) return undefined; try { - return findAttribute([log], eventType, attrKey).value; + return logs.findAttribute([log], eventType, attrKey).value; } catch { return undefined; } diff --git a/src/lib/utils/tx/types.ts b/src/lib/utils/tx/types.ts index 412ce318a..61c3fbe22 100644 --- a/src/lib/utils/tx/types.ts +++ b/src/lib/utils/tx/types.ts @@ -1,6 +1,6 @@ import type { Coin } from "@cosmjs/stargate"; -import type { TypeUrl } from "lib/services/tx"; +import type { TypeUrl } from "lib/data"; import type { Addr, ContractAddr, diff --git a/yarn.lock b/yarn.lock index e9d747558..c05fa5eac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1608,7 +1608,7 @@ resolved "https://registry.npmjs.org/@corex/deepmerge/-/deepmerge-4.0.29.tgz" integrity sha512-q/yVUnqckA8Do+EvAfpy7RLdumnBy9ZsducMUtZTvpdbJC7azEf1hGtnYYxm0QfphYxjwggv6XtH64prvS1W+A== -"@cosmjs/amino@0.29.3", "@cosmjs/amino@^0.29.3": +"@cosmjs/amino@^0.29.3": version "0.29.3" resolved "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.29.3.tgz" integrity sha512-BFz1++ERerIggiFc7iGHhGe1CeV3rCv8BvkoBQTBN/ZwzHOaKvqQj8smDlRGlQxX3HWlTwgiLN2A+OB5yX4ZRw== @@ -1773,7 +1773,16 @@ dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@0.29.3", "@cosmjs/proto-signing@^0.29.3": +"@cosmjs/proto-signing@^0.24.0-alpha.25": + version "0.24.1" + resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.24.1.tgz" + integrity sha512-/rnyNx+FlG6b6O+igsb42eMN1/RXY+pTrNnAE8/YZaRloP9A6MXiTMO5JdYSTcjaD0mEVhejiy96bcyflKYXBg== + dependencies: + "@cosmjs/launchpad" "^0.24.1" + long "^4.0.0" + protobufjs "~6.10.2" + +"@cosmjs/proto-signing@^0.29.3": version "0.29.3" resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.29.3.tgz" integrity sha512-Ai3l9THjMOrLJ4Ebn1Dgptwg6W5ZIRJqtnJjijHhGwTVC1WT0WdYU3aMZ7+PwubcA/cA1rH4ZTK7jrfYbra63g== @@ -1786,15 +1795,6 @@ cosmjs-types "^0.5.2" long "^4.0.0" -"@cosmjs/proto-signing@^0.24.0-alpha.25": - version "0.24.1" - resolved "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.24.1.tgz" - integrity sha512-/rnyNx+FlG6b6O+igsb42eMN1/RXY+pTrNnAE8/YZaRloP9A6MXiTMO5JdYSTcjaD0mEVhejiy96bcyflKYXBg== - dependencies: - "@cosmjs/launchpad" "^0.24.1" - long "^4.0.0" - protobufjs "~6.10.2" - "@cosmjs/proto-signing@^0.29.5": version "0.29.5" resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.5.tgz#af3b62a46c2c2f1d2327d678b13b7262db1fe87c" @@ -1818,7 +1818,7 @@ ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@0.29.3", "@cosmjs/stargate@^0.29.3": +"@cosmjs/stargate@^0.29.3": version "0.29.3" resolved "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.29.3.tgz" integrity sha512-455TgXStCi6E8KDjnhDAM8wt6aLSjobH4Dixvd7Up1DfCH6UB9NkC/G0fMJANNcNXMaM4wSX14niTXwD1d31BA== @@ -3099,14 +3099,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@osmonauts/lcd@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@osmonauts/lcd/-/lcd-0.8.0.tgz#fcabba93edadd23f73b2046a5cad897b420a9c84" - integrity sha512-k7m2gAVnXc0H4m/eTq4z/8A6hFrr3MPS9wnLV4Xu9/K/WYltCnp2PpiObZm+feZUPK/svES6hxIQeO1bODLx8g== - dependencies: - "@babel/runtime" "^7.19.0" - axios "0.27.2" - "@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.3.0": version "2.3.3" resolved "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.3.tgz" @@ -4100,14 +4092,6 @@ axios@0.21.1: dependencies: follow-redirects "^1.10.0" -axios@0.27.2, axios@^0.27.2: - version "0.27.2" - resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - axios@^0.21.1, axios@^0.21.2: version "0.21.4" resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" @@ -4115,6 +4099,14 @@ axios@^0.21.1, axios@^0.21.2: dependencies: follow-redirects "^1.14.0" +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + axios@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz" @@ -7262,7 +7254,7 @@ long@^4.0.0: resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -long@^5.0.0, long@^5.2.0, long@^5.2.1: +long@^5.2.1: version "5.2.1" resolved "https://registry.npmjs.org/long/-/long-5.2.1.tgz" integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== @@ -7812,20 +7804,6 @@ os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osmojs@^14.0.0-rc.0: - version "14.0.0-rc.0" - resolved "https://registry.yarnpkg.com/osmojs/-/osmojs-14.0.0-rc.0.tgz#c49847b667ee86973f2260c9ca71cc2a76bbedbc" - integrity sha512-YXtg5mxaACWc0N8vKqWvdV3ZO9y/NgtJeUQPvjin+bkveEMkJA4RSN0nl0kRTKQIY6Tjt8YWgae+/98VF/2pzA== - dependencies: - "@babel/runtime" "^7.19.0" - "@cosmjs/amino" "0.29.3" - "@cosmjs/proto-signing" "0.29.3" - "@cosmjs/stargate" "0.29.3" - "@cosmjs/tendermint-rpc" "^0.29.3" - "@osmonauts/lcd" "^0.8.0" - long "^5.2.0" - protobufjs "^6.11.3" - p-limit@3.1.0, p-limit@^3.0.2: version "3.1.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" @@ -8122,7 +8100,7 @@ prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -protobufjs@6.11.3, protobufjs@^6.11.2, protobufjs@^6.11.3, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: +protobufjs@6.11.3, protobufjs@^6.11.2, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: version "6.11.3" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz" integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== @@ -8141,24 +8119,6 @@ protobufjs@6.11.3, protobufjs@^6.11.2, protobufjs@^6.11.3, protobufjs@^6.8.8, pr "@types/node" ">=13.7.0" long "^4.0.0" -protobufjs@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.2.tgz#2af401d8c547b9476fb37ffc65782cf302342ca3" - integrity sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/node" ">=13.7.0" - long "^5.0.0" - protobufjs@~6.10.2: version "6.10.3" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz" From a0a85569abd9d68f4ec5e4e81cda735a131c602d Mon Sep 17 00:00:00 2001 From: poomthiti Date: Tue, 21 Mar 2023 11:32:06 +0700 Subject: [PATCH 06/19] fix: as commented --- src/lib/components/copy/CopyButton.tsx | 4 +--- src/lib/types/currency/balance.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/components/copy/CopyButton.tsx b/src/lib/components/copy/CopyButton.tsx index 0e0212bdb..efefbc83a 100644 --- a/src/lib/components/copy/CopyButton.tsx +++ b/src/lib/components/copy/CopyButton.tsx @@ -1,5 +1,5 @@ import { Button } from "@chakra-ui/react"; -import type { ButtonProps, TooltipProps } from "@chakra-ui/react"; +import type { ButtonProps } from "@chakra-ui/react"; import { CustomIcon } from "../icon"; import { AmpEvent, AmpTrack } from "lib/services/amplitude"; @@ -12,7 +12,6 @@ interface CopyButtonProps extends ButtonProps { copyLabel?: string; hasIcon?: boolean; buttonText?: string; - tooltipBgColor?: TooltipProps["bgColor"]; } export const CopyButton = ({ @@ -23,7 +22,6 @@ export const CopyButton = ({ hasIcon = true, variant = "outline-info", buttonText = "Copy", - tooltipBgColor, ...buttonProps }: CopyButtonProps) => ( ; + price?: number; } export interface BalanceWithAssetInfo { From 8830645bd2ba0dc7e1513a98b889c0e0f9c81dfe Mon Sep 17 00:00:00 2001 From: poomthiti Date: Tue, 21 Mar 2023 14:44:57 +0700 Subject: [PATCH 07/19] fix: update explorerlink props --- .../components/ViewPermissionAddresses.tsx | 2 +- .../pages/tx-details/components/TxInfo.tsx | 6 +--- .../components/tx-message/EventBox.tsx | 6 ++-- .../components/tx-message/TxMsgExpand.tsx | 32 +++++++++---------- .../tx-message/msg-receipts/renderUtils.tsx | 2 +- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/lib/components/ViewPermissionAddresses.tsx b/src/lib/components/ViewPermissionAddresses.tsx index ee612849b..f33d9b356 100644 --- a/src/lib/components/ViewPermissionAddresses.tsx +++ b/src/lib/components/ViewPermissionAddresses.tsx @@ -27,7 +27,7 @@ export const ViewPermissionAddresses = ({ key={addr} type={getAddressType(addr)} value={addr} - canCopyWithHover + showCopyOnHover /> ))} {permissionAddresses.length > 1 && ( diff --git a/src/lib/pages/tx-details/components/TxInfo.tsx b/src/lib/pages/tx-details/components/TxInfo.tsx index b3882cee9..732846d13 100644 --- a/src/lib/pages/tx-details/components/TxInfo.tsx +++ b/src/lib/pages/tx-details/components/TxInfo.tsx @@ -23,11 +23,7 @@ export const TxInfo = ({ txData, ...flexProps }: TxInfoProps) => ( {txData.chainId} - + {txData.formattedFee ?? ( diff --git a/src/lib/pages/tx-details/components/tx-message/EventBox.tsx b/src/lib/pages/tx-details/components/tx-message/EventBox.tsx index b474331b2..f15ead387 100644 --- a/src/lib/pages/tx-details/components/tx-message/EventBox.tsx +++ b/src/lib/pages/tx-details/components/tx-message/EventBox.tsx @@ -31,7 +31,7 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { @@ -43,7 +43,7 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { @@ -54,7 +54,7 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx index b743d0409..8c3dfcee1 100644 --- a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx @@ -47,7 +47,7 @@ export const TxMsgExpand = ({ @@ -67,7 +67,7 @@ export const TxMsgExpand = ({ value={ findAttribute([log], "instantiate", "_contract_address").value } - canCopyWithHover + showCopyOnHover textVariant="body1" /> )} @@ -75,7 +75,7 @@ export const TxMsgExpand = ({ @@ -92,7 +92,7 @@ export const TxMsgExpand = ({ value={ findAttribute([log], "instantiate", "_contract_address").value } - canCopyWithHover + showCopyOnHover textVariant="body1" /> )} @@ -100,7 +100,7 @@ export const TxMsgExpand = ({ @@ -116,7 +116,7 @@ export const TxMsgExpand = ({ @@ -130,14 +130,14 @@ export const TxMsgExpand = ({ {" "} to Code ID{" "} @@ -151,14 +151,14 @@ export const TxMsgExpand = ({ {" "} to{" "} @@ -172,7 +172,7 @@ export const TxMsgExpand = ({ @@ -197,7 +197,7 @@ export const TxMsgExpand = ({ @@ -217,7 +217,7 @@ export const TxMsgExpand = ({ value={ findAttribute([log], "submit_proposal", "proposal_id").value } - canCopyWithHover + showCopyOnHover textVariant="body1" /> @@ -237,7 +237,7 @@ export const TxMsgExpand = ({ @@ -251,14 +251,14 @@ export const TxMsgExpand = ({ {" "} to{" "} diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx index 727706c0d..329611f50 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx @@ -51,7 +51,7 @@ export const getCommonReceiptHtml = ({ From c7ef31baa2ceba61a9173da5fadf10734d3f27d1 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Wed, 22 Mar 2023 11:08:18 +0700 Subject: [PATCH 08/19] fix: change height and gas used field label --- src/lib/pages/tx-details/components/TxInfo.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/pages/tx-details/components/TxInfo.tsx b/src/lib/pages/tx-details/components/TxInfo.tsx index 732846d13..efff14cb4 100644 --- a/src/lib/pages/tx-details/components/TxInfo.tsx +++ b/src/lib/pages/tx-details/components/TxInfo.tsx @@ -22,7 +22,7 @@ const Container = chakra(Flex, { export const TxInfo = ({ txData, ...flexProps }: TxInfoProps) => ( {txData.chainId} - + @@ -32,7 +32,7 @@ export const TxInfo = ({ txData, ...flexProps }: TxInfoProps) => (
)} - + {`${formatInteger(txData.gas_used)}/${formatInteger(txData.gas_wanted)}`} From 29291263f9b07b0c0b44093e021c4ec0d7d7ac20 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Tue, 28 Mar 2023 12:24:28 +0700 Subject: [PATCH 09/19] feat: add new osmosis messages --- src/lib/data/tx.ts | 15 +- .../tx-message/msg-receipts/index.tsx | 212 ++++++++++++++- src/lib/utils/tx/types.ts | 253 ++++++++++++------ 3 files changed, 397 insertions(+), 83 deletions(-) diff --git a/src/lib/data/tx.ts b/src/lib/data/tx.ts index 91db1e89f..fbad20b3c 100644 --- a/src/lib/data/tx.ts +++ b/src/lib/data/tx.ts @@ -74,8 +74,21 @@ export type TypeUrl = | "/osmosis.superfluid.MsgSuperfluidUnbondLock" | "/osmosis.superfluid.MsgLockAndSuperfluidDelegate" | "/osmosis.superfluid.MsgUnPoolWhitelistedPool" + | "/osmosis.superfluid.MsgSuperfluidUndelegateAndUnbondLock" | "/osmosis.tokenfactory.v1beta1.MsgCreateDenom" | "/osmosis.tokenfactory.v1beta1.MsgMint" | "/osmosis.tokenfactory.v1beta1.MsgBurn" | "/osmosis.tokenfactory.v1beta1.MsgChangeAdmin" - | "/osmosis.tokenfactory.v1beta1.MsgSetDenomMetadata"; + | "/osmosis.tokenfactory.v1beta1.MsgSetDenomMetadata" + | "/osmosis.protorev.v1beta1.MsgSetHotRoutes" + | "/osmosis.protorev.v1beta1.MsgSetBaseDenoms" + | "/osmosis.protorev.v1beta1.MsgSetDeveloperAccount" + | "/osmosis.protorev.v1beta1.MsgSetPoolWeights" + | "/osmosis.protorev.v1beta1.MsgSetMaxPoolPointsPerTx" + | "/osmosis.protorev.v1beta1.MsgSetMaxPoolPointsPerBlock" + | "/osmosis.valsetpref.v1beta1.MsgDelegateToValidatorSet" + | "/osmosis.valsetpref.v1beta1.MsgUndelegateFromValidatorSet" + | "/osmosis.valsetpref.v1beta1.MsgRedelegateValidatorSet" + | "/osmosis.valsetpref.v1beta1.MsgWithdrawDelegationRewards" + | "/osmosis.valsetpref.v1beta1.MsgDelegateBondedTokens" + | "/osmosis.valsetpref.v1beta1.MsgSetValidatorSetPreference"; diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx index 276a0b4cc..04e585c58 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx @@ -1186,7 +1186,7 @@ export const generateReceipts = ( channelIdReceipt(details.channel_id), { title: "Proof Ack", - value: details.proofAck, + value: details.proof_ack, }, proofHeightReceipt(details.proof_height), { @@ -1865,6 +1865,27 @@ export const generateReceipts = ( }, ]; } + case "/osmosis.superfluid.MsgSuperfluidUndelegateAndUnbondLock": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Sender", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.sender, + linkType: getAddressType(details.sender), + }), + }, + { + title: "Lock ID", + value: details.lock_id, + }, + { + title: "Coin", + html: getCoinComponent(details.coin, assetInfos), + }, + ]; + } // osmosis/tokenfactory case "/osmosis.tokenfactory.v1beta1.MsgCreateDenom": { const details = extractTxDetails(type, body, log); @@ -1947,6 +1968,195 @@ export const generateReceipts = ( }, ]; } + // osmosis/protorev + case "/osmosis.protorev.v1beta1.MsgSetHotRoutes": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Admin", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.admin, + linkType: getAddressType(details.admin), + }), + }, + { + title: "Hot Routes", + html: getCommonReceiptHtml({ + type: "json", + value: details.hot_routes, + }), + }, + ]; + } + case "/osmosis.protorev.v1beta1.MsgSetBaseDenoms": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Admin", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.admin, + linkType: getAddressType(details.admin), + }), + }, + { + title: "Hot Routes", + html: getCommonReceiptHtml({ + type: "json", + value: details.base_denoms, + }), + }, + ]; + } + case "/osmosis.protorev.v1beta1.MsgSetDeveloperAccount": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Admin", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.admin, + linkType: getAddressType(details.admin), + }), + }, + { + title: "Developer Account", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.developer_account, + linkType: getAddressType(details.developer_account), + }), + }, + ]; + } + case "/osmosis.protorev.v1beta1.MsgSetPoolWeights": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Admin", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.admin, + linkType: getAddressType(details.admin), + }), + }, + { + title: "Pool Weights", + html: getCommonReceiptHtml({ + type: "json", + value: details.pool_weights, + }), + }, + ]; + } + case "/osmosis.protorev.v1beta1.MsgSetMaxPoolPointsPerTx": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Admin", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.admin, + linkType: getAddressType(details.admin), + }), + }, + { + title: "Max Pool Points Per Tx", + value: details.max_pool_points_per_tx, + }, + ]; + } + case "/osmosis.protorev.v1beta1.MsgSetMaxPoolPointsPerBlock": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Admin", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.admin, + linkType: getAddressType(details.admin), + }), + }, + { + title: "Max Pool Points Per Block", + value: details.max_pool_points_per_block, + }, + ]; + } + // osmosis/valsetpref + case "/osmosis.valsetpref.v1beta1.MsgDelegateToValidatorSet": + case "/osmosis.valsetpref.v1beta1.MsgUndelegateFromValidatorSet": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Delegator", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.delegator, + linkType: getAddressType(details.delegator), + }), + }, + { + title: "Coin", + html: getCommonReceiptHtml({ + type: "json", + value: details.coin, + }), + }, + ]; + } + case "/osmosis.valsetpref.v1beta1.MsgRedelegateValidatorSet": + case "/osmosis.valsetpref.v1beta1.MsgSetValidatorSetPreference": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Delegator", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.delegator, + linkType: getAddressType(details.delegator), + }), + }, + { + title: "Preferences", + html: getCommonReceiptHtml({ + type: "json", + value: details.preferences, + }), + }, + ]; + } + case "/osmosis.valsetpref.v1beta1.MsgWithdrawDelegationRewards": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Delegator", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.delegator, + linkType: getAddressType(details.delegator), + }), + }, + ]; + } + case "/osmosis.valsetpref.v1beta1.MsgDelegateBondedTokens": { + const details = extractTxDetails(type, body, log); + return [ + { + title: "Delegator", + html: getCommonReceiptHtml({ + type: "explorer", + value: details.delegator, + linkType: getAddressType(details.delegator), + }), + }, + { + title: "Lock ID", + value: details.lockID, + }, + ]; + } default: return Object.entries(body).map((entry) => getGenericValueEntry(entry, getAddressType) diff --git a/src/lib/utils/tx/types.ts b/src/lib/utils/tx/types.ts index 61c3fbe22..5ccffe30a 100644 --- a/src/lib/utils/tx/types.ts +++ b/src/lib/utils/tx/types.ts @@ -11,8 +11,11 @@ import type { import type { VoteOption } from "./mapping"; -export interface MsgUnknownDetails { +export interface MsgBaseDetails { type: string; +} + +export interface MsgUnknownDetails extends MsgBaseDetails { // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } @@ -24,14 +27,14 @@ interface InstantiatePermissionResponse { } // cosmwasm/wasm -export interface MsgStoreCodeDetails extends MsgUnknownDetails { +export interface MsgStoreCodeDetails extends MsgBaseDetails { code_id: Option; sender: Addr; wasm_byte_code: string; // base64 instantiate_permission: InstantiatePermissionResponse | null; } -export interface MsgInstantiateDetails extends MsgUnknownDetails { +export interface MsgInstantiateDetails extends MsgBaseDetails { contract_address: Option; sender: Addr; admin: Addr; @@ -46,132 +49,137 @@ export interface MsgInstantiate2Details extends MsgInstantiateDetails { fix_msg: boolean; } -export interface MsgExecuteDetails extends MsgUnknownDetails { +export interface MsgExecuteDetails extends MsgBaseDetails { sender: Addr; contract: ContractAddr; msg: object; funds: Coin[]; } -export interface MsgMigrateDetails extends MsgUnknownDetails { +export interface MsgMigrateDetails extends MsgBaseDetails { sender: Addr; contract: ContractAddr; code_id: string; msg: object; } -export interface MsgUpdateAdminDetails extends MsgUnknownDetails { +export interface MsgUpdateAdminDetails extends MsgBaseDetails { sender: Addr; new_admin: Addr; contract: ContractAddr; } -export interface MsgClearAdminDetails extends MsgUnknownDetails { +export interface MsgClearAdminDetails extends MsgBaseDetails { sender: Addr; contract: ContractAddr; } // x/authz -export interface MsgGrantDetails extends MsgUnknownDetails { +export interface MsgGrantDetails extends MsgBaseDetails { granter: Addr; grantee: Addr; grant: object; } -export interface MsgRevokeDetails extends MsgUnknownDetails { +export interface MsgRevokeDetails extends MsgBaseDetails { granter: Addr; grantee: Addr; msg_type_url: string; } -export interface MsgExecDetails extends MsgUnknownDetails { +export interface MsgExecDetails extends MsgBaseDetails { grantee: Addr; msgs: object[]; msg_type_url: string; } // x/bank -export interface MsgSendDetails extends MsgUnknownDetails { +export interface MsgSendDetails extends MsgBaseDetails { from_address: Addr; to_address: Addr; amount: Coin[]; } -export interface MsgMultiSendDetails extends MsgUnknownDetails { +export interface MsgMultiSendDetails extends MsgBaseDetails { inputs: object; outputs: object; } // x/crisis -export interface MsgVerifyInvariantDetails extends MsgUnknownDetails { +export interface MsgVerifyInvariantDetails extends MsgBaseDetails { sender: Addr; invariant_module_name: string; invariant_route: string; } // x/distribution -export interface MsgSetWithdrawAddressDetails extends MsgUnknownDetails { +export interface MsgSetWithdrawAddressDetails extends MsgBaseDetails { delegator_address: Addr; withdraw_address: Addr; } -export interface MsgWithdrawDelegatorRewardDetails extends MsgUnknownDetails { +export interface MsgWithdrawDelegatorRewardDetails extends MsgBaseDetails { delegator_address: Addr; validator_address: ValidatorAddr; } -export interface MsgWithdrawValidatorCommissionDetails - extends MsgUnknownDetails { +export interface MsgWithdrawValidatorCommissionDetails extends MsgBaseDetails { validator_address: ValidatorAddr; } -export interface MsgFundCommunityPoolDetails extends MsgUnknownDetails { +export interface MsgFundCommunityPoolDetails extends MsgBaseDetails { amount: Coin[]; depositor: Addr; } // x/evidence -export interface MsgSubmitEvidenceDetails extends MsgUnknownDetails { +export interface MsgSubmitEvidenceDetails extends MsgBaseDetails { submitter: Addr; evidence: object; } // x/feegrant -export interface MsgGrantAllowanceDetails extends MsgUnknownDetails { +export interface MsgGrantAllowanceDetails extends MsgBaseDetails { granter: Addr; grantee: Addr; allowance: object; } -export interface MsgRevokeAllowanceDetails extends MsgUnknownDetails { +export interface MsgRevokeAllowanceDetails extends MsgBaseDetails { granter: Addr; grantee: Addr; } // x/gov -export interface MsgSubmitProposalDetails extends MsgUnknownDetails { +export interface MsgSubmitProposalDetails extends MsgBaseDetails { initial_deposit: Coin[]; proposer: Addr; proposal_id: Option; proposal_type: Option; - title: string; -} -export interface MsgVoteDetails extends MsgUnknownDetails { + content: { + "@type": string; + description: string; + subject_client_id: string; + substitute_client_id: string; + title: string; + }; +} +export interface MsgVoteDetails extends MsgBaseDetails { proposal_id: string; voter: Addr; option: VoteOption; } -export interface MsgVoteWeightedDetails extends MsgUnknownDetails { +export interface MsgVoteWeightedDetails extends MsgBaseDetails { proposal_id: string; voter: Addr; options: { option: VoteOption; weight: string }[]; } -export interface MsgDepositDetails extends MsgUnknownDetails { +export interface MsgDepositDetails extends MsgBaseDetails { proposal_id: string; depositor: Addr; amount: Coin[]; } // x/slashing -export interface MsgUnjailDetails extends MsgUnknownDetails { +export interface MsgUnjailDetails extends MsgBaseDetails { validator_addr: ValidatorAddr; } // x/staking -export interface MsgCreateValidatorDetails extends MsgUnknownDetails { +export interface MsgCreateValidatorDetails extends MsgBaseDetails { description: object; commission: object; min_self_delegation: string; @@ -180,31 +188,31 @@ export interface MsgCreateValidatorDetails extends MsgUnknownDetails { pubkey: object; value: Coin; } -export interface MsgEditValidatorDetails extends MsgUnknownDetails { +export interface MsgEditValidatorDetails extends MsgBaseDetails { description: object; validator_address: ValidatorAddr; commission_rate: string; min_self_delegation: string; } -export interface MsgDelegateDetails extends MsgUnknownDetails { +export interface MsgDelegateDetails extends MsgBaseDetails { delegator_address: Addr; validator_address: ValidatorAddr; amount: Coin; } -export interface MsgBeginRedelegateDetails extends MsgUnknownDetails { +export interface MsgBeginRedelegateDetails extends MsgBaseDetails { delegator_address: Addr; validator_src_address: ValidatorAddr; validator_dst_address: ValidatorAddr; amount: Coin; } -export interface MsgUndelegateDetails extends MsgUnknownDetails { +export interface MsgUndelegateDetails extends MsgBaseDetails { delegator_address: Addr; validator_address: ValidatorAddr; amount: Coin; } // ibc/applications -export interface MsgTransferDetails extends MsgUnknownDetails { +export interface MsgTransferDetails extends MsgBaseDetails { source_port: string; source_channel: string; token: Coin; @@ -216,17 +224,17 @@ export interface MsgTransferDetails extends MsgUnknownDetails { } // ibc/core -export interface MsgCreateClientDetails extends MsgUnknownDetails { +export interface MsgCreateClientDetails extends MsgBaseDetails { client_state: object; consensus_state: object; signer: Addr; } -export interface MsgUpdateClientDetails extends MsgUnknownDetails { +export interface MsgUpdateClientDetails extends MsgBaseDetails { client_id: string; header: object; signer: Addr; } -export interface MsgUpgradeClientDetails extends MsgUnknownDetails { +export interface MsgUpgradeClientDetails extends MsgBaseDetails { client_id: string; client_state: object; consensus_state: object; @@ -234,19 +242,19 @@ export interface MsgUpgradeClientDetails extends MsgUnknownDetails { proof_upgrade_consensus_state: string; signer: Addr; } -export interface MsgSubmitMisbehaviourDetails extends MsgUnknownDetails { +export interface MsgSubmitMisbehaviourDetails extends MsgBaseDetails { client_id: string; misbehaviour: object; signer: Addr; } -export interface MsgConnectionOpenInitDetails extends MsgUnknownDetails { +export interface MsgConnectionOpenInitDetails extends MsgBaseDetails { client_id: string; counterparty: object; version: object; delay_period: number; signer: Addr; } -export interface MsgConnectionOpenTryDetails extends MsgUnknownDetails { +export interface MsgConnectionOpenTryDetails extends MsgBaseDetails { client_id: string; previous_connection_id: string; client_state: object; @@ -260,7 +268,7 @@ export interface MsgConnectionOpenTryDetails extends MsgUnknownDetails { consensus_height: object; signer: Addr; } -export interface MsgConnectionOpenAckDetails extends MsgUnknownDetails { +export interface MsgConnectionOpenAckDetails extends MsgBaseDetails { connection_id: string; counterparty_connection_id: string; version: object; @@ -272,18 +280,18 @@ export interface MsgConnectionOpenAckDetails extends MsgUnknownDetails { consensus_height: object; signer: Addr; } -export interface MsgConnectionOpenConfirmDetails extends MsgUnknownDetails { +export interface MsgConnectionOpenConfirmDetails extends MsgBaseDetails { connection_id: string; proof_ack: string; proof_height: object; signer: Addr; } -export interface MsgChannelOpenInitDetails extends MsgUnknownDetails { +export interface MsgChannelOpenInitDetails extends MsgBaseDetails { port_id: string; channel: object; signer: Addr; } -export interface MsgChannelOpenTryDetails extends MsgUnknownDetails { +export interface MsgChannelOpenTryDetails extends MsgBaseDetails { port_id: string; previous_channel_id: string; channel: object; @@ -292,7 +300,7 @@ export interface MsgChannelOpenTryDetails extends MsgUnknownDetails { proof_height: object; signer: Addr; } -export interface MsgChannelOpenAckDetails extends MsgUnknownDetails { +export interface MsgChannelOpenAckDetails extends MsgBaseDetails { port_id: string; channel_id: string; counterparty_channel_id: string; @@ -301,39 +309,39 @@ export interface MsgChannelOpenAckDetails extends MsgUnknownDetails { proof_height: object; signer: Addr; } -export interface MsgChannelOpenConfirmDetails extends MsgUnknownDetails { +export interface MsgChannelOpenConfirmDetails extends MsgBaseDetails { port_id: string; channel_id: string; proof_ack: string; proof_height: object; signer: Addr; } -export interface MsgChannelCloseInitDetails extends MsgUnknownDetails { +export interface MsgChannelCloseInitDetails extends MsgBaseDetails { port_id: string; channel_id: string; signer: Addr; } -export interface MsgChannelCloseConfirmDetails extends MsgUnknownDetails { +export interface MsgChannelCloseConfirmDetails extends MsgBaseDetails { port_id: string; channel_id: string; proof_init: string; proof_height: object; signer: Addr; } -export interface MsgRecvPacketDetails extends MsgUnknownDetails { +export interface MsgRecvPacketDetails extends MsgBaseDetails { packet: object; proof_commitment: string; proof_height: object; signer: Addr; } -export interface MsgTimeoutDetails extends MsgUnknownDetails { +export interface MsgTimeoutDetails extends MsgBaseDetails { packet: object; proof_unreceived: string; proof_height: object; next_sequence_recv: number; signer: Addr; } -export interface MsgTimeoutOnCloseDetails extends MsgUnknownDetails { +export interface MsgTimeoutOnCloseDetails extends MsgBaseDetails { packet: object; proof_unreceived: string; proof_close: string; @@ -341,7 +349,7 @@ export interface MsgTimeoutOnCloseDetails extends MsgUnknownDetails { next_sequence_recv: number; signer: Addr; } -export interface MsgAcknowledgementDetails extends MsgUnknownDetails { +export interface MsgAcknowledgementDetails extends MsgBaseDetails { packet: object; acknowledgement: string; proof_acked: string; @@ -350,13 +358,13 @@ export interface MsgAcknowledgementDetails extends MsgUnknownDetails { } // osmosis/gamm -export interface MsgCreateBalancerPoolDetails extends MsgUnknownDetails { +export interface MsgCreateBalancerPoolDetails extends MsgBaseDetails { sender: Addr; pool_params: object; pool_assets: object; future_pool_governor: string; } -export interface MsgCreateStableswapPoolDetails extends MsgUnknownDetails { +export interface MsgCreateStableswapPoolDetails extends MsgBaseDetails { sender: Addr; pool_params: object; initial_pool_liquidity: Coin[]; @@ -365,56 +373,56 @@ export interface MsgCreateStableswapPoolDetails extends MsgUnknownDetails { scaling_factor_controller: string; } export interface MsgStableSwapAdjustScalingFactorsDetails - extends MsgUnknownDetails { + extends MsgBaseDetails { sender: Addr; pool_id: string; scaling_factors: string[]; } -export interface MsgJoinPoolDetails extends MsgUnknownDetails { +export interface MsgJoinPoolDetails extends MsgBaseDetails { sender: Addr; pool_id: string; share_out_amount: string; token_in_maxs: Coin[]; } -export interface MsgExitPoolDetails extends MsgUnknownDetails { +export interface MsgExitPoolDetails extends MsgBaseDetails { sender: Addr; pool_id: string; share_in_amount: string; token_out_mins: Coin[]; } -export interface MsgSwapExactAmountInDetails extends MsgUnknownDetails { +export interface MsgSwapExactAmountInDetails extends MsgBaseDetails { sender: Addr; routes: object; token_in: Coin; token_out_min_amount: string; } -export interface MsgSwapExactAmountOutDetails extends MsgUnknownDetails { +export interface MsgSwapExactAmountOutDetails extends MsgBaseDetails { sender: Addr; routes: object; token_in_max_amount: string; token_out: Coin; } -export interface MsgJoinSwapExternAmountInDetails extends MsgUnknownDetails { +export interface MsgJoinSwapExternAmountInDetails extends MsgBaseDetails { sender: Addr; pool_id: string; token_in: Coin; share_out_min_amount: string; } -export interface MsgJoinSwapShareAmountOutDetails extends MsgUnknownDetails { +export interface MsgJoinSwapShareAmountOutDetails extends MsgBaseDetails { sender: Addr; pool_id: string; token_in_denom: string; share_out_amount: string; token_in_max_amount: string; } -export interface MsgExitSwapShareAmountInDetails extends MsgUnknownDetails { +export interface MsgExitSwapShareAmountInDetails extends MsgBaseDetails { sender: Addr; pool_id: string; token_out_denom: string; share_in_amount: string; token_out_min_amount: string; } -export interface MsgExitSwapExternAmountOutDetails extends MsgUnknownDetails { +export interface MsgExitSwapExternAmountOutDetails extends MsgBaseDetails { sender: Addr; pool_id: string; token_out: Coin; @@ -422,7 +430,7 @@ export interface MsgExitSwapExternAmountOutDetails extends MsgUnknownDetails { } // osmosis/incentives -export interface MsgCreateGaugeDetails extends MsgUnknownDetails { +export interface MsgCreateGaugeDetails extends MsgBaseDetails { is_perpetual: boolean; owner: Addr; distribute_to: object; @@ -430,84 +438,141 @@ export interface MsgCreateGaugeDetails extends MsgUnknownDetails { start_time: string; num_epochs_paid_over: string; } -export interface MsgAddToGaugeDetails extends MsgUnknownDetails { +export interface MsgAddToGaugeDetails extends MsgBaseDetails { owner: Addr; gauge_id: string; rewards: Coin[]; } // osmosis/lockup -export interface MsgLockTokensDetails extends MsgUnknownDetails { +export interface MsgLockTokensDetails extends MsgBaseDetails { owner: Addr; duration: string; coins: Coin[]; } -export interface MsgBeginUnlockingAllDetails extends MsgUnknownDetails { +export interface MsgBeginUnlockingAllDetails extends MsgBaseDetails { owner: Addr; } -export interface MsgBeginUnlockingDetails extends MsgUnknownDetails { +export interface MsgBeginUnlockingDetails extends MsgBaseDetails { owner: Addr; ID: string; coins: Coin[]; } -export interface MsgExtendLockupDetails extends MsgUnknownDetails { +export interface MsgExtendLockupDetails extends MsgBaseDetails { owner: Addr; ID: string; duration: string; } -export interface MsgForceUnlockDetails extends MsgUnknownDetails { +export interface MsgForceUnlockDetails extends MsgBaseDetails { owner: Addr; ID: string; coins: Coin[]; } // osmosis/superfluid -export interface MsgSuperfluidDelegateDetails extends MsgUnknownDetails { +export interface MsgSuperfluidDelegateDetails extends MsgBaseDetails { sender: Addr; lock_id: string; val_addr: ValidatorAddr; } -export interface MsgSuperfluidUndelegateDetails extends MsgUnknownDetails { +export interface MsgSuperfluidUndelegateDetails extends MsgBaseDetails { sender: Addr; lock_id: string; } -export interface MsgSuperfluidUnbondLockDetails extends MsgUnknownDetails { +export interface MsgSuperfluidUnbondLockDetails extends MsgBaseDetails { sender: Addr; lock_id: string; } -export interface MsgLockAndSuperfluidDelegateDetails extends MsgUnknownDetails { +export interface MsgLockAndSuperfluidDelegateDetails extends MsgBaseDetails { sender: Addr; coins: Coin[]; val_addr: ValidatorAddr; } -export interface MsgUnPoolWhitelistedPoolDetails extends MsgUnknownDetails { +export interface MsgUnPoolWhitelistedPoolDetails extends MsgBaseDetails { sender: Addr; pool_id: string; } +export interface MsgSuperfluidUndelegateAndUnbondLockDetails + extends MsgBaseDetails { + sender: Addr; + lock_id: string; + coin: Coin; +} // osmosis/tokenfactory -export interface MsgCreateDenomDetails extends MsgUnknownDetails { +export interface MsgCreateDenomDetails extends MsgBaseDetails { sender: Addr; subdenom: string; } -export interface MsgMintDetails extends MsgUnknownDetails { +export interface MsgMintDetails extends MsgBaseDetails { sender: Addr; amount: Coin; } -export interface MsgBurnDetails extends MsgUnknownDetails { +export interface MsgBurnDetails extends MsgBaseDetails { sender: Addr; amount: Coin; } -export interface MsgChangeAdminDetails extends MsgUnknownDetails { +export interface MsgChangeAdminDetails extends MsgBaseDetails { sender: Addr; denom: string; new_admin: Addr; } -export interface MsgSetDenomMetadataDetails extends MsgUnknownDetails { +export interface MsgSetDenomMetadataDetails extends MsgBaseDetails { sender: Addr; metadata: object; } +// osmosis/protorev +export interface MsgSetHotRoutesDetails extends MsgBaseDetails { + admin: Addr; + hot_routes: object[]; +} +export interface MsgSetBaseDenomsDetails extends MsgBaseDetails { + admin: Addr; + base_denoms: object[]; +} +export interface MsgSetDeveloperAccountDetails extends MsgBaseDetails { + admin: Addr; + developer_account: Addr; +} +export interface MsgSetPoolWeightsDetails extends MsgBaseDetails { + admin: Addr; + pool_weights: object; +} +export interface MsgSetMaxPoolPointsPerTxDetails extends MsgBaseDetails { + admin: Addr; + max_pool_points_per_tx: string; +} +export interface MsgSetMaxPoolPointsPerBlockDetails extends MsgBaseDetails { + admin: Addr; + max_pool_points_per_block: string; +} + +// osmosis/valset-pref +export interface MsgDelegateToValidatorSetDetails extends MsgBaseDetails { + delegator: Addr; + coin: Coin; +} +export interface MsgUndelegateFromValidatorSetDetails extends MsgBaseDetails { + delegator: Addr; + coin: Coin; +} +export interface MsgRedelegateValidatorSetDetails extends MsgBaseDetails { + delegator: Addr; + preferences: object[]; +} +export interface MsgWithdrawDelegationRewardsDetails extends MsgBaseDetails { + delegator: Addr; +} +export interface MsgDelegateBondedTokensDetails extends MsgBaseDetails { + delegator: Addr; + lockID: string; +} +export interface MsgSetValidatorSetPreferenceDetails extends MsgBaseDetails { + delegator: Addr; + preferences: object[]; +} + export type MsgReturnType = T extends "/cosmwasm.wasm.v1.MsgStoreCode" ? MsgStoreCodeDetails @@ -653,6 +718,8 @@ export type MsgReturnType = ? MsgLockAndSuperfluidDelegateDetails : T extends "/osmosis.superfluid.MsgUnPoolWhitelistedPool" ? MsgUnPoolWhitelistedPoolDetails + : T extends "/osmosis.superfluid.MsgSuperfluidUndelegateAndUnbondLock" + ? MsgSuperfluidUndelegateAndUnbondLockDetails : T extends "/osmosis.tokenfactory.v1beta1.MsgCreateDenom" ? MsgCreateDenomDetails : T extends "/osmosis.tokenfactory.v1beta1.MsgMint" @@ -663,4 +730,28 @@ export type MsgReturnType = ? MsgChangeAdminDetails : T extends "/osmosis.tokenfactory.v1beta1.MsgSetDenomMetadata" ? MsgSetDenomMetadataDetails - : MsgUnknownDetails; + : T extends "/osmosis.protorev.v1beta1.MsgSetHotRoutes" + ? MsgSetHotRoutesDetails + : T extends "/osmosis.protorev.v1beta1.MsgSetBaseDenoms" + ? MsgSetBaseDenomsDetails + : T extends "/osmosis.protorev.v1beta1.MsgSetDeveloperAccount" + ? MsgSetDeveloperAccountDetails + : T extends "/osmosis.protorev.v1beta1.MsgSetPoolWeights" + ? MsgSetPoolWeightsDetails + : T extends "/osmosis.protorev.v1beta1.MsgSetMaxPoolPointsPerTx" + ? MsgSetMaxPoolPointsPerTxDetails + : T extends "/osmosis.protorev.v1beta1.MsgSetMaxPoolPointsPerBlock" + ? MsgSetMaxPoolPointsPerBlockDetails + : T extends "/osmosis.valsetpref.v1beta1.MsgDelegateToValidatorSet" + ? MsgDelegateToValidatorSetDetails + : T extends "/osmosis.valsetpref.v1beta1.MsgUndelegateFromValidatorSet" + ? MsgUndelegateFromValidatorSetDetails + : T extends "/osmosis.valsetpref.v1beta1.MsgRedelegateValidatorSet" + ? MsgRedelegateValidatorSetDetails + : T extends "/osmosis.valsetpref.v1beta1.MsgWithdrawDelegationRewards" + ? MsgWithdrawDelegationRewardsDetails + : T extends "/osmosis.valsetpref.v1beta1.MsgDelegateBondedTokens" + ? MsgDelegateBondedTokensDetails + : T extends "/osmosis.valsetpref.v1beta1.MsgSetValidatorSetPreference" + ? MsgSetValidatorSetPreferenceDetails + : MsgBaseDetails; From abdda44c334787ff030b0545ae4b9b3e661a79b4 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Tue, 28 Mar 2023 15:06:59 +0700 Subject: [PATCH 10/19] feat: map supported assets to tx fee --- src/lib/hooks/useSingleMessageProps.ts | 2 +- src/lib/model/contract.ts | 2 +- src/lib/pages/account-details/data.ts | 2 +- .../tx-details/components/MessageSection.tsx | 5 +- .../pages/tx-details/components/TxInfo.tsx | 70 ++++++++++++------- .../components/tx-message/TxMsgDetails.tsx | 8 ++- .../components/tx-message/TxMsgExpand.tsx | 10 +-- .../components/tx-message/index.tsx | 2 + .../tx-message/msg-receipts/index.tsx | 2 +- src/lib/pages/tx-details/index.tsx | 15 ++-- src/lib/services/asset.ts | 8 ++- src/lib/services/assetService.ts | 21 ++++-- src/lib/services/txService.ts | 5 -- 13 files changed, 96 insertions(+), 56 deletions(-) diff --git a/src/lib/hooks/useSingleMessageProps.ts b/src/lib/hooks/useSingleMessageProps.ts index c97b6f17b..821066399 100644 --- a/src/lib/hooks/useSingleMessageProps.ts +++ b/src/lib/hooks/useSingleMessageProps.ts @@ -599,7 +599,7 @@ export const useSingleActionMsgProps = ( ): SingleMsgProps => { const { currentChainName } = useWallet(); const { getContractLocalInfo } = useContractStore(); - const assetInfos = useAssetInfos(); + const { assetInfos } = useAssetInfos(); switch (type) { case "MsgExecuteContract": diff --git a/src/lib/model/contract.ts b/src/lib/model/contract.ts index 8b4c6cce3..302fcbca2 100644 --- a/src/lib/model/contract.ts +++ b/src/lib/model/contract.ts @@ -96,7 +96,7 @@ export const useContractData = ( const { getCodeLocalInfo } = useCodeStore(); const { getContractLocalInfo } = useContractStore(); const endpoint = useLCDEndpoint(); - const assetInfos = useAssetInfos(); + const { assetInfos } = useAssetInfos(); const { data: publicInfo } = usePublicProjectByContractAddress(contractAddress); const { data: publicInfoBySlug } = usePublicProjectBySlug(publicInfo?.slug); diff --git a/src/lib/pages/account-details/data.ts b/src/lib/pages/account-details/data.ts index a63ace350..ecd4e71f9 100644 --- a/src/lib/pages/account-details/data.ts +++ b/src/lib/pages/account-details/data.ts @@ -120,7 +120,7 @@ export const useUserAssetInfos = ( isLoading, error, } = useAccountBalances(walletAddress); - const assetInfos = useAssetInfos(); + const { assetInfos } = useAssetInfos(); const contractBalancesWithAssetInfos = balances?.map( (balance): BalanceWithAssetInfo => ({ diff --git a/src/lib/pages/tx-details/components/MessageSection.tsx b/src/lib/pages/tx-details/components/MessageSection.tsx index 2eddf3b48..b08ade012 100644 --- a/src/lib/pages/tx-details/components/MessageSection.tsx +++ b/src/lib/pages/tx-details/components/MessageSection.tsx @@ -7,15 +7,17 @@ import { Text, } from "@chakra-ui/react"; +import type { AssetInfosReturn } from "lib/services/assetService"; import type { TxData } from "lib/services/txService"; import { TxMessage } from "./tx-message"; interface MessageSectionProps { txData: TxData; + assetInfos: AssetInfosReturn; } -export const MessageSection = ({ txData }: MessageSectionProps) => { +export const MessageSection = ({ txData, assetInfos }: MessageSectionProps) => { const msgs = txData.tx.body.messages; return ( @@ -41,6 +43,7 @@ export const MessageSection = ({ txData }: MessageSectionProps) => { msgBody={msg} log={msgLog} isSingleMsg={msgs.length === 1} + assetInfos={assetInfos} /> ); })} diff --git a/src/lib/pages/tx-details/components/TxInfo.tsx b/src/lib/pages/tx-details/components/TxInfo.tsx index efff14cb4..6eccf6cc4 100644 --- a/src/lib/pages/tx-details/components/TxInfo.tsx +++ b/src/lib/pages/tx-details/components/TxInfo.tsx @@ -3,11 +3,13 @@ import { Text, chakra, Flex } from "@chakra-ui/react"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { LabelText } from "lib/components/LabelText"; +import type { AssetInfosReturn } from "lib/services/assetService"; import type { TxData } from "lib/services/txService"; -import { formatInteger } from "lib/utils"; +import { formatBalanceWithDenom, formatInteger } from "lib/utils"; interface TxInfoProps extends FlexProps { txData: TxData; + assetInfos: AssetInfosReturn; } const Container = chakra(Flex, { @@ -19,28 +21,44 @@ const Container = chakra(Flex, { }, }); -export const TxInfo = ({ txData, ...flexProps }: TxInfoProps) => ( - - {txData.chainId} - - - - - {txData.formattedFee ?? ( - - No Fee - - )} - - - {`${formatInteger(txData.gas_used)}/${formatInteger(txData.gas_wanted)}`} - - - {txData.tx.body.memo || ( - - No Memo - - )} - - -); +export const TxInfo = ({ txData, assetInfos, ...flexProps }: TxInfoProps) => { + const feeCoin = txData.tx.auth_info.fee?.amount.at(0); + const assetInfo = feeCoin ? assetInfos?.[feeCoin.denom] : undefined; + return ( + + {txData.chainId} + + + + + {feeCoin ? ( + formatBalanceWithDenom({ + coin: feeCoin, + symbol: assetInfo?.symbol, + precision: assetInfo?.precision, + }) + ) : ( + + No Fee + + )} + + + {`${formatInteger(txData.gas_used)}/${formatInteger( + txData.gas_wanted + )}`} + + + {txData.tx.body.memo || ( + + No Memo + + )} + + + ); +}; diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx index 44923d8fa..3343b87db 100644 --- a/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx @@ -2,7 +2,6 @@ import { Flex } from "@chakra-ui/react"; import { useGetAddressType } from "lib/app-provider"; import { TxReceiptRender } from "lib/components/tx"; -import { useAssetInfos } from "lib/services/assetService"; import type { TxReceipt } from "lib/types"; import type { TxMsgData } from "."; @@ -13,9 +12,12 @@ interface TxMsgDetailsProps extends TxMsgData { isExpand: boolean; } -export const TxMsgDetails = ({ isExpand, ...txMsgData }: TxMsgDetailsProps) => { +export const TxMsgDetails = ({ + isExpand, + assetInfos, + ...txMsgData +}: TxMsgDetailsProps) => { const getAddressType = useGetAddressType(); - const assetInfos = useAssetInfos(); const receipts = generateReceipts(txMsgData, getAddressType, assetInfos) .concat( txMsgData.log && { diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx index 8c3dfcee1..c8a61084e 100644 --- a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx @@ -7,7 +7,6 @@ import { useGetAddressType } from "lib/app-provider"; import { ExplorerLink } from "lib/components/ExplorerLink"; import type { IconKeys } from "lib/components/icon"; import { CustomIcon } from "lib/components/icon"; -import { useAssetInfos } from "lib/services/assetService"; import type { Addr } from "lib/types"; import type { VoteOption } from "lib/utils"; import { formatBalanceWithDenom, voteOption } from "lib/utils"; @@ -23,10 +22,10 @@ export const TxMsgExpand = ({ msgBody, log, isExpand, + assetInfos, onClick, }: TxMsgExpandProps) => { const getAddressType = useGetAddressType(); - const assetInfos = useAssetInfos(); let msgIcon: IconKeys = "info-circle"; let content: ReactNode; const isIBC = Boolean( @@ -182,13 +181,14 @@ export const TxMsgExpand = ({ { const toAddress = body.to_address as Addr; const singleCoin = body.amount.at(0) as Coin; + const assetInfo = assetInfos?.[singleCoin.denom]; const assetText = body.amount.length > 1 ? "assets" : formatBalanceWithDenom({ - coin: { denom: singleCoin.denom, amount: singleCoin.amount }, - symbol: assetInfos?.[singleCoin.denom]?.symbol, - precision: assetInfos?.[singleCoin.denom]?.precision, + coin: singleCoin, + symbol: assetInfo?.symbol, + precision: assetInfo?.precision, }); msgIcon = "send"; content = ( diff --git a/src/lib/pages/tx-details/components/tx-message/index.tsx b/src/lib/pages/tx-details/components/tx-message/index.tsx index 38aaf5044..6495e3bdc 100644 --- a/src/lib/pages/tx-details/components/tx-message/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/index.tsx @@ -2,6 +2,7 @@ import { Flex } from "@chakra-ui/react"; import type { logs } from "@cosmjs/stargate"; import { useState } from "react"; +import type { AssetInfosReturn } from "lib/services/assetService"; import type { MsgBody } from "lib/services/tx"; import type { Option } from "lib/types"; @@ -12,6 +13,7 @@ export interface TxMsgData { msgBody: MsgBody; log: Option; isSingleMsg?: boolean; + assetInfos: AssetInfosReturn; } export const TxMessage = ({ isSingleMsg, ...txMsgData }: TxMsgData) => { diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx index 04e585c58..899e1511b 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx @@ -27,7 +27,7 @@ import { } from "./renderUtils"; export const generateReceipts = ( - { msgBody, log }: TxMsgData, + { msgBody, log }: Omit, getAddressType: (address: string) => AddressReturnType, assetInfos: Option<{ [key: string]: AssetInfo }> ): Option[] => { diff --git a/src/lib/pages/tx-details/index.tsx b/src/lib/pages/tx-details/index.tsx index b22a8a930..0e2780aa5 100644 --- a/src/lib/pages/tx-details/index.tsx +++ b/src/lib/pages/tx-details/index.tsx @@ -7,6 +7,7 @@ import { Loading } from "lib/components/Loading"; import PageContainer from "lib/components/PageContainer"; import { EmptyState } from "lib/components/state/EmptyState"; import { AmpEvent, AmpTrack } from "lib/services/amplitude"; +import { useAssetInfos } from "lib/services/assetService"; import { useTxData } from "lib/services/txService"; import { getFirstQueryParam } from "lib/utils"; @@ -17,13 +18,19 @@ const TxDetails = () => { const router = useRouter(); const hashParam = getFirstQueryParam(router.query.txHash); - const { data: txData, isLoading, isFetching } = useTxData(hashParam); + const { + data: txData, + isLoading: txLoading, + isFetching: txFetching, + } = useTxData(hashParam); + const { assetInfos, isLoading: assetLoading } = useAssetInfos(); useEffect(() => { if (router.isReady) AmpTrack(AmpEvent.TO_TRANSACTION_DETAIL); }, [router.isReady]); - if ((isLoading && isFetching) || !hashParam) return ; + if ((txLoading && txFetching) || assetLoading || !hashParam) + return ; return ( @@ -32,8 +39,8 @@ const TxDetails = () => { <> - - + + ) : ( diff --git a/src/lib/services/asset.ts b/src/lib/services/asset.ts index 005111d8d..4e28dee51 100644 --- a/src/lib/services/asset.ts +++ b/src/lib/services/asset.ts @@ -3,13 +3,15 @@ import axios from "axios"; import { CELATONE_API_ENDPOINT, getChainApiPath } from "env"; import type { AssetInfo, Option } from "lib/types"; -export const getAssetInfo = async ( +export const getAssetInfos = async ( chainName: Option, chainId: Option ): Promise> => { - if (!chainName || !chainId) throw new Error("Invalid chain (getAssetInfo)"); + if (!chainName || !chainId) throw new Error("Invalid chain (getAssetInfos)"); const { data } = await axios.get( - `${CELATONE_API_ENDPOINT}/assets/${getChainApiPath(chainName)}/${chainId}` + `${CELATONE_API_ENDPOINT}/assets/${getChainApiPath( + chainName + )}/${chainId}/prices` ); return data; }; diff --git a/src/lib/services/assetService.ts b/src/lib/services/assetService.ts index 79caa600e..af8be67f9 100644 --- a/src/lib/services/assetService.ts +++ b/src/lib/services/assetService.ts @@ -1,13 +1,16 @@ import { useWallet } from "@cosmos-kit/react"; import { useQuery } from "@tanstack/react-query"; -import { getAssetInfo } from "lib/services/asset"; +import { getAssetInfos } from "lib/services/asset"; import type { AssetInfo, Option } from "lib/types"; -export const useAssetInfos = (): Option<{ [key: string]: AssetInfo }> => { +export const useAssetInfos = (): { + assetInfos: Option<{ [key: string]: AssetInfo }>; + isLoading: boolean; +} => { const { currentChainRecord } = useWallet(); - const { data: assets } = useQuery( + const { data: assets, isLoading } = useQuery( [ "query", "assetInfos", @@ -15,12 +18,20 @@ export const useAssetInfos = (): Option<{ [key: string]: AssetInfo }> => { currentChainRecord?.chain.chain_id, ], async () => - getAssetInfo( + getAssetInfos( currentChainRecord?.name, currentChainRecord?.chain.chain_id ), { enabled: !!currentChainRecord } ); - return assets?.reduce((acc, asset) => ({ ...acc, [asset.id]: asset }), {}); + return { + assetInfos: assets?.reduce( + (acc, asset) => ({ ...acc, [asset.id]: asset }), + {} + ), + isLoading, + }; }; + +export type AssetInfosReturn = ReturnType["assetInfos"]; diff --git a/src/lib/services/txService.ts b/src/lib/services/txService.ts index e2cb0f022..b414b50e3 100644 --- a/src/lib/services/txService.ts +++ b/src/lib/services/txService.ts @@ -16,7 +16,6 @@ import { import { MsgFurtherAction } from "lib/types"; import type { Addr, ContractAddr, Option, Transaction } from "lib/types"; import { - formatStdFee, getActionMsgType, isTxHash, parseDateOpt, @@ -30,7 +29,6 @@ import { queryTxData } from "./tx"; export interface TxData extends TxResponse { chainId: string; - formattedFee: Option; isTxFailed: boolean; } @@ -43,9 +41,6 @@ export const useTxData = (txHash: Option): UseQueryResult => { return { ...txData, chainId, - formattedFee: txData.tx.auth_info.fee?.amount.length - ? formatStdFee(txData.tx.auth_info.fee) - : undefined, isTxFailed: Boolean(txData.code), }; }, From de1fd165b02444acd22051055ad946f8ebfff958 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Thu, 30 Mar 2023 11:06:45 +0700 Subject: [PATCH 11/19] fix: ibc badge condiiton checking and add opacity animation --- .../tx-details/components/tx-message/TxMsgDetails.tsx | 1 + .../tx-details/components/tx-message/TxMsgExpand.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx index 3343b87db..4020d58b3 100644 --- a/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx @@ -45,6 +45,7 @@ export const TxMsgDetails = ({ gap={6} pt={4} height={isExpand ? "full" : 0} + opacity={isExpand ? 1 : 0} overflow="hidden" transition="all .25s ease-in-out" > diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx index c8a61084e..2e4912eb3 100644 --- a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx @@ -26,12 +26,12 @@ export const TxMsgExpand = ({ onClick, }: TxMsgExpandProps) => { const getAddressType = useGetAddressType(); + const { "@type": type, ...body } = msgBody; + const isIBC = + Boolean(log?.events?.find((event) => event.type === "send_packet")) || + type.startsWith("/ibc"); let msgIcon: IconKeys = "info-circle"; let content: ReactNode; - const isIBC = Boolean( - log?.events?.find((event) => event.type === "send_packet") - ); - const { "@type": type, ...body } = msgBody; switch (type) { case "/cosmwasm.wasm.v1.MsgStoreCode": From 87be24a3fb0edc0a8746738ed4f7ab796203fcb3 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Thu, 30 Mar 2023 13:35:54 +0700 Subject: [PATCH 12/19] style: event box animation --- .../components/tx-message/EventBox.tsx | 30 ++++++++++++------- .../components/tx-message/TxMsgDetails.tsx | 1 - 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/lib/pages/tx-details/components/tx-message/EventBox.tsx b/src/lib/pages/tx-details/components/tx-message/EventBox.tsx index f15ead387..e0502de37 100644 --- a/src/lib/pages/tx-details/components/tx-message/EventBox.tsx +++ b/src/lib/pages/tx-details/components/tx-message/EventBox.tsx @@ -1,7 +1,7 @@ -import { Box, Divider, Flex } from "@chakra-ui/react"; +import { Box, Flex } from "@chakra-ui/react"; import type { Event } from "@cosmjs/stargate"; import type { ReactNode } from "react"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useGetAddressType } from "lib/app-provider"; import type { LinkType } from "lib/components/ExplorerLink"; @@ -20,6 +20,14 @@ interface EventBoxProps { export const EventBox = ({ event, msgIndex }: EventBoxProps) => { const getAddressType = useGetAddressType(); const [expand, setExpand] = useState(true); + const [boxHeight, setBoxHeight] = useState(0); + const stackRef = useRef(null); + + useEffect(() => { + if (stackRef.current) { + setBoxHeight(stackRef.current.clientHeight); + } + }, [stackRef]); const receipts = event.attributes.map(({ key, value }) => { const addrType = getAddressType(value); @@ -120,21 +128,21 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { m={0} /> - {expand && ( - - + + + - - )} + + ); }; diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx index 4020d58b3..3343b87db 100644 --- a/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgDetails.tsx @@ -45,7 +45,6 @@ export const TxMsgDetails = ({ gap={6} pt={4} height={isExpand ? "full" : 0} - opacity={isExpand ? 1 : 0} overflow="hidden" transition="all .25s ease-in-out" > From f39ec71a42d5122d82cd4c874323b641e801ca3b Mon Sep 17 00:00:00 2001 From: poomthiti Date: Fri, 31 Mar 2023 14:40:35 +0700 Subject: [PATCH 13/19] fix: handle null value and findAttr error --- src/lib/components/tx/TxReceiptRender.tsx | 8 ++++++-- .../tx-details/components/MessageSection.tsx | 4 ++-- .../pages/tx-details/components/TxInfo.tsx | 4 ++-- .../components/tx-message/index.tsx | 4 ++-- .../tx-message/msg-receipts/index.tsx | 10 +++++----- .../tx-message/msg-receipts/renderUtils.tsx | 19 +++++++++++++------ src/lib/services/assetService.ts | 6 +++--- src/lib/services/tx.ts | 2 +- src/lib/types/tx/model.ts | 2 +- src/lib/utils/address.ts | 1 + 10 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/lib/components/tx/TxReceiptRender.tsx b/src/lib/components/tx/TxReceiptRender.tsx index 62476dd14..015e14ef5 100644 --- a/src/lib/components/tx/TxReceiptRender.tsx +++ b/src/lib/components/tx/TxReceiptRender.tsx @@ -49,8 +49,12 @@ const ReceiptRow = ({ title, value, html }: TxReceipt) => ( {title} {html || ( - - {value} + + {value === null ? String(value) : value} )} diff --git a/src/lib/pages/tx-details/components/MessageSection.tsx b/src/lib/pages/tx-details/components/MessageSection.tsx index b08ade012..2e291cc04 100644 --- a/src/lib/pages/tx-details/components/MessageSection.tsx +++ b/src/lib/pages/tx-details/components/MessageSection.tsx @@ -7,14 +7,14 @@ import { Text, } from "@chakra-ui/react"; -import type { AssetInfosReturn } from "lib/services/assetService"; +import type { AssetInfosOpt } from "lib/services/assetService"; import type { TxData } from "lib/services/txService"; import { TxMessage } from "./tx-message"; interface MessageSectionProps { txData: TxData; - assetInfos: AssetInfosReturn; + assetInfos: AssetInfosOpt; } export const MessageSection = ({ txData, assetInfos }: MessageSectionProps) => { diff --git a/src/lib/pages/tx-details/components/TxInfo.tsx b/src/lib/pages/tx-details/components/TxInfo.tsx index 6eccf6cc4..c80b35abe 100644 --- a/src/lib/pages/tx-details/components/TxInfo.tsx +++ b/src/lib/pages/tx-details/components/TxInfo.tsx @@ -3,13 +3,13 @@ import { Text, chakra, Flex } from "@chakra-ui/react"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { LabelText } from "lib/components/LabelText"; -import type { AssetInfosReturn } from "lib/services/assetService"; +import type { AssetInfosOpt } from "lib/services/assetService"; import type { TxData } from "lib/services/txService"; import { formatBalanceWithDenom, formatInteger } from "lib/utils"; interface TxInfoProps extends FlexProps { txData: TxData; - assetInfos: AssetInfosReturn; + assetInfos: AssetInfosOpt; } const Container = chakra(Flex, { diff --git a/src/lib/pages/tx-details/components/tx-message/index.tsx b/src/lib/pages/tx-details/components/tx-message/index.tsx index 6495e3bdc..28fa48dab 100644 --- a/src/lib/pages/tx-details/components/tx-message/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/index.tsx @@ -2,7 +2,7 @@ import { Flex } from "@chakra-ui/react"; import type { logs } from "@cosmjs/stargate"; import { useState } from "react"; -import type { AssetInfosReturn } from "lib/services/assetService"; +import type { AssetInfosOpt } from "lib/services/assetService"; import type { MsgBody } from "lib/services/tx"; import type { Option } from "lib/types"; @@ -13,7 +13,7 @@ export interface TxMsgData { msgBody: MsgBody; log: Option; isSingleMsg?: boolean; - assetInfos: AssetInfosReturn; + assetInfos: AssetInfosOpt; } export const TxMessage = ({ isSingleMsg, ...txMsgData }: TxMsgData) => { diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx index 899e1511b..14b5f951d 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx @@ -38,7 +38,7 @@ export const generateReceipts = ( case "/cosmwasm.wasm.v1.MsgStoreCode": { const details = extractTxDetails(type, body, log); return [ - !!details.code_id && { + log && { title: "Stored Code ID", html: getCommonReceiptHtml({ type: "explorer", @@ -102,7 +102,7 @@ export const generateReceipts = ( case "/cosmwasm.wasm.v1.MsgInstantiateContract": { const details = extractTxDetails(type, body, log); return [ - details.contract_address && { + log && { title: "Contract Instance", html: getCommonReceiptHtml({ type: "explorer", @@ -153,7 +153,7 @@ export const generateReceipts = ( case "/cosmwasm.wasm.v1.MsgInstantiateContract2": { const details = extractTxDetails(type, body, log); return [ - details.contract_address && { + log && { title: "Contract Instance", html: getCommonReceiptHtml({ type: "explorer", @@ -597,8 +597,8 @@ export const generateReceipts = ( linkType: getAddressType(details.proposer), }), }, - !!details.proposal_id && proposalIdReceipt(details.proposal_id), - !!details.proposal_type && { + log && proposalIdReceipt(details.proposal_id), + log && { title: "Proposal Type", value: details.proposal_type, }, diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx index 329611f50..340873b33 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx @@ -20,7 +20,7 @@ type HtmlType = "json" | "explorer"; interface CommonReceiptHtmlArgs { type: T; - value: Option; + value: Option | null; linkType?: LinkType; fallback?: string; } @@ -30,16 +30,23 @@ export const getCommonReceiptHtml = ({ type, value, linkType = "invalid_address", - fallback = "-", + fallback = "N/A", }: CommonReceiptHtmlArgs) => { + if (value === undefined) + return ( + + Data not found + + ); + if (!value) return ( - - {fallback} + + {value === null ? String(value) : fallback} ); - // TODO: Find a solution, TS doesn't know that type === 'json' would make typeof value === 'object + // TODO: Find a solution, TS doesn't know that type === "json" would make typeof value === "object" return type === "json" || typeof value === "object" ? ( ({ }), }); -export const proposalIdReceipt = (value: string): TxReceipt => ({ +export const proposalIdReceipt = (value: Option): TxReceipt => ({ title: "Proposal ID", html: getCommonReceiptHtml({ type: "explorer", diff --git a/src/lib/services/assetService.ts b/src/lib/services/assetService.ts index af8be67f9..b425a3194 100644 --- a/src/lib/services/assetService.ts +++ b/src/lib/services/assetService.ts @@ -4,8 +4,10 @@ import { useQuery } from "@tanstack/react-query"; import { getAssetInfos } from "lib/services/asset"; import type { AssetInfo, Option } from "lib/types"; +export type AssetInfosOpt = Option<{ [key: string]: AssetInfo }>; + export const useAssetInfos = (): { - assetInfos: Option<{ [key: string]: AssetInfo }>; + assetInfos: AssetInfosOpt; isLoading: boolean; } => { const { currentChainRecord } = useWallet(); @@ -33,5 +35,3 @@ export const useAssetInfos = (): { isLoading, }; }; - -export type AssetInfosReturn = ReturnType["assetInfos"]; diff --git a/src/lib/services/tx.ts b/src/lib/services/tx.ts index 0d157da09..609265f3f 100644 --- a/src/lib/services/tx.ts +++ b/src/lib/services/tx.ts @@ -93,5 +93,5 @@ export const queryTxData = async ( return { ...data.tx_response, timestamp: parseDateOpt(data.tx_response.timestamp), - } as TxResponse; + }; }; diff --git a/src/lib/types/tx/model.ts b/src/lib/types/tx/model.ts index 1a63c750b..38cecfa1b 100644 --- a/src/lib/types/tx/model.ts +++ b/src/lib/types/tx/model.ts @@ -13,7 +13,7 @@ export interface TxErrorRendering { export interface TxReceipt { title: string; - value?: string | number; + value?: string | number | null; html?: ReactNode; } diff --git a/src/lib/utils/address.ts b/src/lib/utils/address.ts index dc8c06472..3388b2e25 100644 --- a/src/lib/utils/address.ts +++ b/src/lib/utils/address.ts @@ -8,6 +8,7 @@ export const getAddressTypeText = (addressType: AddressReturnType) => { return "(Wallet Address)"; case "validator_address": return "(Validator Address)"; + case "invalid_address": default: return "(Invalid Address)"; } From e701c23b1c267bb0d4e5e57d36f73a1183760634 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Fri, 31 Mar 2023 14:56:52 +0700 Subject: [PATCH 14/19] fix: external icon to launch --- src/lib/components/links/GitHubLink.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/links/GitHubLink.tsx b/src/lib/components/links/GitHubLink.tsx index 6d78ce7c6..d0e56c414 100644 --- a/src/lib/components/links/GitHubLink.tsx +++ b/src/lib/components/links/GitHubLink.tsx @@ -24,7 +24,7 @@ export const GitHubLink = ({ github }: GitHubLinkProps) => { {org}/{repo} - + ); From f178401ededc5923a75bfed704a3f6c6ecbe793f Mon Sep 17 00:00:00 2001 From: poomthiti Date: Mon, 3 Apr 2023 14:58:18 +0700 Subject: [PATCH 15/19] refactor: refactor renderUtils --- .../tx-message/msg-receipts/renderUtils.tsx | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx index 340873b33..f120f8cdb 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx @@ -30,39 +30,48 @@ export const getCommonReceiptHtml = ({ type, value, linkType = "invalid_address", - fallback = "N/A", + fallback, }: CommonReceiptHtmlArgs) => { - if (value === undefined) - return ( - - Data not found - - ); - - if (!value) - return ( - - {value === null ? String(value) : fallback} - - ); - - // TODO: Find a solution, TS doesn't know that type === "json" would make typeof value === "object" - return type === "json" || typeof value === "object" ? ( - - ) : ( - - ); + switch (true) { + case value === null: + return ( + + null + + ); + case !value && fallback: + return ( + + {fallback} + + ); + case !value: + return ( + + Data not found + + ); + // TODO: Find a solution, TS doesn't know that type === "json" would make typeof value === "object" + case type === "json" || typeof value === "object": + return ( + + ); + default: + return ( + + ); + } }; export const getCoinComponent = ( From 19b8e79240396accc3c7965556f13b823ad87041 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Tue, 4 Apr 2023 16:23:48 +0700 Subject: [PATCH 16/19] feat: add amplitude tracking for tx page --- CHANGELOG.md | 1 + src/lib/components/ExplorerLink.tsx | 3 +++ src/lib/components/TokenCard.tsx | 4 ++- .../components/ViewPermissionAddresses.tsx | 12 ++++++++- src/lib/components/copy/Copier.tsx | 6 +++-- src/lib/components/copy/CopyButton.tsx | 6 ++++- src/lib/components/json/JsonReadOnly.tsx | 14 ++++++++-- src/lib/components/modal/CodeSnippet.tsx | 5 +++- .../modal/UnsupportedTokensModal.tsx | 11 +++++++- .../components/CodeInfoSection.tsx | 1 + .../components/TokenSection.tsx | 4 ++- .../pages/execute/components/ExecuteArea.tsx | 6 ++++- .../instantiate/component/FailedModal.tsx | 6 ++++- src/lib/pages/query/components/QueryArea.tsx | 12 +++++++-- .../tx-details/components/MessageSection.tsx | 6 ++--- .../pages/tx-details/components/TxHeader.tsx | 7 ++++- .../pages/tx-details/components/TxInfo.tsx | 1 + .../components/tx-message/EventBox.tsx | 14 +++++++++- .../components/tx-message/TxMsgExpand.tsx | 26 ++++++++++++++++++- .../components/tx-message/index.tsx | 4 ++- .../tx-message/msg-receipts/CoinComponent.tsx | 14 +++++++++- .../tx-message/msg-receipts/index.tsx | 9 +++---- .../tx-message/msg-receipts/renderUtils.tsx | 2 ++ src/lib/pages/tx-details/index.tsx | 14 ++++++++-- src/lib/services/amplitude.tsx | 26 +++++++++++++++++-- 25 files changed, 183 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9647b3bc4..edaaff566 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 +- [#261](https://github.com/alleslabs/celatone-frontend/pull/261) Add amplitude tracking for tx page components - [#224](https://github.com/alleslabs/celatone-frontend/pull/224) Support search by tx and internal tx link - [#226](https://github.com/alleslabs/celatone-frontend/pull/226) Add fully functional transaction details page - [#254](https://github.com/alleslabs/celatone-frontend/pull/254) Add GiHub link to public code and contract detail pages diff --git a/src/lib/components/ExplorerLink.tsx b/src/lib/components/ExplorerLink.tsx index c41820e3c..0ef0e9c37 100644 --- a/src/lib/components/ExplorerLink.tsx +++ b/src/lib/components/ExplorerLink.tsx @@ -30,6 +30,7 @@ interface ExplorerLinkProps extends BoxProps { textFormat?: "truncate" | "ellipsis" | "normal"; maxWidth?: string; textVariant?: TextProps["variant"]; + ampCopierSection?: string; } const getNavigationUrl = ( @@ -139,6 +140,7 @@ export const ExplorerLink = ({ textFormat = "truncate", maxWidth = "160px", textVariant = "body2", + ampCopierSection, ...componentProps }: ExplorerLinkProps) => { const { address, currentChainName } = useWallet(); @@ -187,6 +189,7 @@ export const ExplorerLink = ({ value={copyValue || value} display={showCopyOnHover ? "none" : "block"} ml="8px" + amptrackSection={ampCopierSection} /> )} diff --git a/src/lib/components/TokenCard.tsx b/src/lib/components/TokenCard.tsx index ad7c7283b..866069d5b 100644 --- a/src/lib/components/TokenCard.tsx +++ b/src/lib/components/TokenCard.tsx @@ -13,9 +13,10 @@ import { Copier } from "./copy"; interface TokenCardProps { userBalance: BalanceWithAssetInfo; + amptrackSection?: string; } -export const TokenCard = ({ userBalance }: TokenCardProps) => { +export const TokenCard = ({ userBalance, amptrackSection }: TokenCardProps) => { const [logoError, setLogoError] = useState(false); const { symbol, price, amount, precision, id } = userBalance.balance; @@ -72,6 +73,7 @@ export const TokenCard = ({ userBalance }: TokenCardProps) => { copyLabel="Token ID Copied!" display="none" ml="1px" + amptrackSection={amptrackSection} /> diff --git a/src/lib/components/ViewPermissionAddresses.tsx b/src/lib/components/ViewPermissionAddresses.tsx index f33d9b356..956e2dd5a 100644 --- a/src/lib/components/ViewPermissionAddresses.tsx +++ b/src/lib/components/ViewPermissionAddresses.tsx @@ -2,6 +2,7 @@ import { Button } from "@chakra-ui/react"; import { useState } from "react"; import { useGetAddressType } from "lib/app-provider"; +import { AmpTrackExpand } from "lib/services/amplitude"; import type { PermissionAddresses } from "lib/types"; import { ExplorerLink } from "./ExplorerLink"; @@ -9,8 +10,10 @@ import { CustomIcon } from "./icon"; export const ViewPermissionAddresses = ({ permissionAddresses, + amptrackSection, }: { permissionAddresses: PermissionAddresses; + amptrackSection: string; }) => { const [viewAll, setViewAll] = useState(false); const getAddressType = useGetAddressType(); @@ -33,7 +36,14 @@ export const ViewPermissionAddresses = ({ {permissionAddresses.length > 1 && ( diff --git a/src/lib/pages/query/components/QueryArea.tsx b/src/lib/pages/query/components/QueryArea.tsx index 8da061ede..95fdd4516 100644 --- a/src/lib/pages/query/components/QueryArea.tsx +++ b/src/lib/pages/query/components/QueryArea.tsx @@ -137,7 +137,11 @@ export const QueryArea = ({ - + 100, the copy button is visible. */} {jsonLineCount(res) > 100 && ( - + )} diff --git a/src/lib/pages/tx-details/components/MessageSection.tsx b/src/lib/pages/tx-details/components/MessageSection.tsx index 2e291cc04..19a7f3367 100644 --- a/src/lib/pages/tx-details/components/MessageSection.tsx +++ b/src/lib/pages/tx-details/components/MessageSection.tsx @@ -4,7 +4,7 @@ import { AlertIcon, Badge, Flex, - Text, + Heading, } from "@chakra-ui/react"; import type { AssetInfosOpt } from "lib/services/assetService"; @@ -28,9 +28,9 @@ export const MessageSection = ({ txData, assetInfos }: MessageSectionProps) => { )} - + Messages - + {msgs.length} diff --git a/src/lib/pages/tx-details/components/TxHeader.tsx b/src/lib/pages/tx-details/components/TxHeader.tsx index e9c959822..009c4b81f 100644 --- a/src/lib/pages/tx-details/components/TxHeader.tsx +++ b/src/lib/pages/tx-details/components/TxHeader.tsx @@ -4,6 +4,7 @@ import { Button, Box, Flex, Heading, Text } from "@chakra-ui/react"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; import { useOpenTxTab } from "lib/hooks"; +import { AmpTrackViewJson } from "lib/services/amplitude"; import type { TxData } from "lib/services/txService"; import { dateFromNow, formatUTC } from "lib/utils"; @@ -28,7 +29,10 @@ export const TxHeader = ({ txData, ...flexProps }: TxHeaderProps) => { rightIcon={ } - onClick={() => openLcdTab(txData.txhash)} + onClick={() => { + AmpTrackViewJson("tx_page_transaction_hash"); + openLcdTab(txData.txhash); + }} > View in JSON @@ -42,6 +46,7 @@ export const TxHeader = ({ txData, ...flexProps }: TxHeaderProps) => { type="tx_hash" textFormat="normal" maxWidth="full" + ampCopierSection="tx_header" /> diff --git a/src/lib/pages/tx-details/components/TxInfo.tsx b/src/lib/pages/tx-details/components/TxInfo.tsx index c80b35abe..2950aa375 100644 --- a/src/lib/pages/tx-details/components/TxInfo.tsx +++ b/src/lib/pages/tx-details/components/TxInfo.tsx @@ -32,6 +32,7 @@ export const TxInfo = ({ txData, assetInfos, ...flexProps }: TxInfoProps) => { value={txData.height} type="block_height" showCopyOnHover + ampCopierSection="tx_page_block_height" /> diff --git a/src/lib/pages/tx-details/components/tx-message/EventBox.tsx b/src/lib/pages/tx-details/components/tx-message/EventBox.tsx index e0502de37..f66330b59 100644 --- a/src/lib/pages/tx-details/components/tx-message/EventBox.tsx +++ b/src/lib/pages/tx-details/components/tx-message/EventBox.tsx @@ -9,6 +9,7 @@ import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; import JsonReadOnly from "lib/components/json/JsonReadOnly"; import { TxReceiptRender } from "lib/components/tx"; +import { AmpTrackExpand } from "lib/services/amplitude"; import type { TxReceipt } from "lib/types"; import { jsonPrettify, jsonValidate } from "lib/utils"; @@ -42,6 +43,7 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { showCopyOnHover textFormat="normal" maxWidth="full" + ampCopierSection="tx_page_event_logs" /> ); break; @@ -54,6 +56,7 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { showCopyOnHover textFormat="normal" maxWidth="full" + ampCopierSection="tx_page_event_logs" /> ); break; @@ -65,6 +68,7 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { showCopyOnHover textFormat="normal" maxWidth="full" + ampCopierSection="tx_page_event_logs" /> ); break; @@ -76,6 +80,7 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { canCopy fullWidth isExpandable + amptrackSection="tx_page_event_logs" /> ); else valueComponent = value; @@ -107,7 +112,14 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { align="center" justify="space-between" cursor="pointer" - onClick={() => setExpand((prev) => !prev)} + onClick={() => { + AmpTrackExpand( + expand ? "collapse" : "expand", + "event_box", + "tx_page" + ); + setExpand((prev) => !prev); + }} p={4} > diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx index 2e4912eb3..62e97a567 100644 --- a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx @@ -7,6 +7,7 @@ import { useGetAddressType } from "lib/app-provider"; import { ExplorerLink } from "lib/components/ExplorerLink"; import type { IconKeys } from "lib/components/icon"; import { CustomIcon } from "lib/components/icon"; +import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import type { Addr } from "lib/types"; import type { VoteOption } from "lib/utils"; import { formatBalanceWithDenom, voteOption } from "lib/utils"; @@ -49,6 +50,7 @@ export const TxMsgExpand = ({ showCopyOnHover fontSize="24px" textVariant="body1" + ampCopierSection="tx_page_message_header_code" /> )} @@ -68,6 +70,7 @@ export const TxMsgExpand = ({ } showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_contract" /> )}

from

@@ -76,6 +79,7 @@ export const TxMsgExpand = ({ value={body.code_id} showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_code" /> ); @@ -93,6 +97,7 @@ export const TxMsgExpand = ({ } showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_contract" /> )}

from

@@ -101,6 +106,7 @@ export const TxMsgExpand = ({ value={body.code_id} showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_code" /> ); @@ -117,6 +123,7 @@ export const TxMsgExpand = ({ value={body.contract} showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_contract" /> ); @@ -131,6 +138,7 @@ export const TxMsgExpand = ({ value={body.contract} showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_contract" />{" "} to Code ID{" "} ); @@ -152,6 +161,7 @@ export const TxMsgExpand = ({ value={body.contract} showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_contract" />{" "} to{" "} ); @@ -173,6 +184,7 @@ export const TxMsgExpand = ({ value={body.contract} showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_contract" /> ); @@ -199,6 +211,7 @@ export const TxMsgExpand = ({ value={toAddress} showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_send_address" /> ); @@ -219,6 +232,7 @@ export const TxMsgExpand = ({ } showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_proposal" /> )} @@ -239,6 +253,7 @@ export const TxMsgExpand = ({ value={body.proposal_id} showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_proposal" /> ); @@ -253,6 +268,7 @@ export const TxMsgExpand = ({ value={body.delegator_address} showCopyOnHover textVariant="body1" + ampCopierSection="tx_page_message_header_delegator" />{" "} to{" "} ); @@ -280,7 +297,14 @@ export const TxMsgExpand = ({ borderRadius="8px" transition="all .25s ease-in-out" cursor="pointer" - onClick={onClick} + onClick={() => { + AmpTrack(AmpEvent.USE_TX_MSG_EXPAND, { + action: isExpand ? "collapse" : "expand", + ibc: isIBC, + msg: type, + }); + onClick(); + }} _hover={{ backgroundColor: "pebble.800" }} _after={{ content: '""', diff --git a/src/lib/pages/tx-details/components/tx-message/index.tsx b/src/lib/pages/tx-details/components/tx-message/index.tsx index 28fa48dab..170259635 100644 --- a/src/lib/pages/tx-details/components/tx-message/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/index.tsx @@ -22,7 +22,9 @@ export const TxMessage = ({ isSingleMsg, ...txMsgData }: TxMsgData) => { setExpand((prev) => !prev)} + onClick={() => { + setExpand((prev) => !prev); + }} {...txMsgData} /> diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx index e8a0a9940..85d044dbe 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx @@ -5,6 +5,7 @@ import { useState } from "react"; import { ShowMoreButton } from "lib/components/button"; import { UnsupportedTokensModal } from "lib/components/modal/UnsupportedTokensModal"; import { TokenCard } from "lib/components/TokenCard"; +import { AmpTrackExpand } from "lib/services/amplitude"; import type { AssetInfo, Option } from "lib/types"; type AssetObject = { [key: string]: AssetInfo }; @@ -49,6 +50,7 @@ const MultiCoin = ({ }, assetInfo, }} + amptrackSection="tx_msg_receipts_assets" /> ); })} @@ -60,7 +62,14 @@ const MultiCoin = ({ showMoreText="View All Assets" showLessText="View Less Assets" toggleShowMore={showMore} - setToggleShowMore={() => setShowMore(!showMore)} + setToggleShowMore={() => { + AmpTrackExpand( + showMore ? "collapse" : "expand", + "assets", + "tx_page" + ); + setShowMore(!showMore); + }} /> )} {unsupportedCoins && ( @@ -77,6 +86,7 @@ const MultiCoin = ({ }; })} buttonProps={{ fontSize: "12px", mb: 0 }} + amptrackSection="tx_msg_receipts_unsupported_assets" /> )} @@ -102,6 +112,7 @@ const SingleCoin = ({ }, assetInfo, }} + amptrackSection="tx_msg_receipts_assets" /> ) : ( ); }; diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx index 14b5f951d..688a9d31f 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx @@ -74,6 +74,7 @@ export const generateReceipts = ( ? [details.instantiate_permission.address] : details.instantiate_permission.addresses } + amptrackSection="tx_msg_receipts" />
), @@ -93,6 +94,7 @@ export const generateReceipts = ( buttonText="Click to Copy" hasIcon={false} mt={-1} + amptrackSection="tx_msg_receipts_wasm_byte_code" /> ), @@ -1789,7 +1791,7 @@ export const generateReceipts = ( }, { title: "Duration", - value: formatUTC(parseDate(details.duration)), + value: details.duration, }, ]; } @@ -2099,10 +2101,7 @@ export const generateReceipts = ( }, { title: "Coin", - html: getCommonReceiptHtml({ - type: "json", - value: details.coin, - }), + html: getCoinComponent(details.coin, assetInfos), }, ]; } diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx index f120f8cdb..98fb0fe15 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx @@ -59,6 +59,7 @@ export const getCommonReceiptHtml = ({ canCopy fullWidth isExpandable + amptrackSection="tx_page_msg_receipts" /> ); default: @@ -69,6 +70,7 @@ export const getCommonReceiptHtml = ({ showCopyOnHover textFormat="normal" maxWidth="full" + ampCopierSection="tx_msg_receipts" /> ); } diff --git a/src/lib/pages/tx-details/index.tsx b/src/lib/pages/tx-details/index.tsx index 0e2780aa5..9d94697db 100644 --- a/src/lib/pages/tx-details/index.tsx +++ b/src/lib/pages/tx-details/index.tsx @@ -26,8 +26,18 @@ const TxDetails = () => { const { assetInfos, isLoading: assetLoading } = useAssetInfos(); useEffect(() => { - if (router.isReady) AmpTrack(AmpEvent.TO_TRANSACTION_DETAIL); - }, [router.isReady]); + if (router.isReady && !(txLoading && txFetching)) { + const mapTxFailed = { + true: "fail", + false: "success", + undefined: "not_found", + }; + AmpTrack(AmpEvent.TO_TRANSACTION_DETAIL, { + tx_status: + mapTxFailed[String(txData?.isTxFailed) as keyof typeof mapTxFailed], + }); + } + }, [router.isReady, txData, txLoading, txFetching]); if ((txLoading && txFetching) || assetLoading || !hashParam) return ; diff --git a/src/lib/services/amplitude.tsx b/src/lib/services/amplitude.tsx index 93326e161..152686c02 100644 --- a/src/lib/services/amplitude.tsx +++ b/src/lib/services/amplitude.tsx @@ -1,6 +1,7 @@ import { track } from "@amplitude/analytics-browser"; import type { AttachFundsType } from "lib/components/fund/types"; +import type { Option } from "lib/types"; export enum AmpEvent { INVALID_STATE = "To Invalid State", @@ -82,6 +83,10 @@ export enum AmpEvent { USE_QUICK_EDIT_CODE = "Use Quick Edit Code", USE_OTHER_MODAL = "Use Other Modal", USE_SUBMIT_PROJECT = "Use Submit Project", + USE_VIEW_JSON = "Use View Json", + USE_UNSUPPORTED_ASSETS = "Use Unsupported Assets", + USE_TX_MSG_EXPAND = "Use Transaction Message Expand", + USE_EXPAND = "Use General Expand", // TX TX_SUCCEED = "Tx Succeed", TX_FAILED = "Tx Failed", @@ -116,8 +121,10 @@ export const AmpTrackInvalidState = (title: string) => track(AmpEvent.INVALID_STATE, { title }); export const AmpTrack = ( - event: Exclude -) => track(event); + event: Exclude, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + properties?: Record +) => track(event, properties); export const AmpTrackAction = ( event: ActionAmpEvent, @@ -156,3 +163,18 @@ export const AmpTrackSocial = (url: string) => track(AmpEvent.SOCIAL, { url }); export const AmpTrackCelatone = (url: string) => track(AmpEvent.CELATONE, { url }); + +export const AmpTrackViewJson = (page: string) => + track(AmpEvent.USE_VIEW_JSON, { page }); + +export const AmpTrackUnsupportedToken = (page: Option) => + track(AmpEvent.USE_UNSUPPORTED_ASSETS, { page }); + +export const AmpTrackCopier = (section: Option, type: string) => + track(AmpEvent.USE_COPIER, { section, type }); + +export const AmpTrackExpand = ( + action: "expand" | "collapse", + component: "assets" | "json" | "permission_address" | "event_box", + section: Option +) => track(AmpEvent.USE_EXPAND, { action, component, section }); From 17a3be729113289663e1162800cca203e01fef35 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Tue, 4 Apr 2023 18:48:17 +0700 Subject: [PATCH 17/19] fix: add expedited proposal variant and component bug fixes --- CHANGELOG.md | 2 +- src/lib/components/TokenCard.tsx | 10 ++++++++-- src/lib/components/icon/CustomIcon.tsx | 4 ++-- .../tx-details/components/MessageSection.tsx | 2 +- src/lib/pages/tx-details/components/TxHeader.tsx | 6 +++++- .../components/tx-message/EventBox.tsx | 11 ++--------- .../components/tx-message/TxMsgExpand.tsx | 7 +++++-- .../tx-details/components/tx-message/index.tsx | 1 + .../tx-message/msg-receipts/CoinComponent.tsx | 1 + .../components/tx-message/msg-receipts/index.tsx | 16 +++++++++++++--- .../tx-message/msg-receipts/renderUtils.tsx | 8 ++++++-- src/lib/styles/theme/components/badge.ts | 4 ++++ src/lib/utils/tx/types.ts | 1 + 13 files changed, 50 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edaaff566..201fce68e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features -- [#261](https://github.com/alleslabs/celatone-frontend/pull/261) Add amplitude tracking for tx page components +- [#262](https://github.com/alleslabs/celatone-frontend/pull/262) Add amplitude tracking for tx page components - [#224](https://github.com/alleslabs/celatone-frontend/pull/224) Support search by tx and internal tx link - [#226](https://github.com/alleslabs/celatone-frontend/pull/226) Add fully functional transaction details page - [#254](https://github.com/alleslabs/celatone-frontend/pull/254) Add GiHub link to public code and contract detail pages diff --git a/src/lib/components/TokenCard.tsx b/src/lib/components/TokenCard.tsx index 866069d5b..6d305840a 100644 --- a/src/lib/components/TokenCard.tsx +++ b/src/lib/components/TokenCard.tsx @@ -1,3 +1,4 @@ +import type { FlexProps } from "@chakra-ui/react"; import { Badge, Flex, Image, Text, Tooltip } from "@chakra-ui/react"; import { useState } from "react"; @@ -11,12 +12,16 @@ import { import { Copier } from "./copy"; -interface TokenCardProps { +interface TokenCardProps extends FlexProps { userBalance: BalanceWithAssetInfo; amptrackSection?: string; } -export const TokenCard = ({ userBalance, amptrackSection }: TokenCardProps) => { +export const TokenCard = ({ + userBalance, + amptrackSection, + ...cardProps +}: TokenCardProps) => { const [logoError, setLogoError] = useState(false); const { symbol, price, amount, precision, id } = userBalance.balance; @@ -38,6 +43,7 @@ export const TokenCard = ({ userBalance, amptrackSection }: TokenCardProps) => { p={3} background="pebble.900" borderRadius="8px" + {...cardProps} > ), - viewBox: "0.5 -2.5 16 16", + viewBox: "0.5 -2.5 18 18", }, "submit-proposal-solid": { svg: ( @@ -1116,7 +1116,7 @@ export const ICONS = { fill="currentColor" /> ), - viewBox: "0.5 -2.5 16 16", + viewBox: "0.5 -2.5 18 18", }, swap: { svg: ( diff --git a/src/lib/pages/tx-details/components/MessageSection.tsx b/src/lib/pages/tx-details/components/MessageSection.tsx index 19a7f3367..2060afa55 100644 --- a/src/lib/pages/tx-details/components/MessageSection.tsx +++ b/src/lib/pages/tx-details/components/MessageSection.tsx @@ -31,7 +31,7 @@ export const MessageSection = ({ txData, assetInfos }: MessageSectionProps) => { Messages - + {msgs.length} diff --git a/src/lib/pages/tx-details/components/TxHeader.tsx b/src/lib/pages/tx-details/components/TxHeader.tsx index 009c4b81f..ba71cab3c 100644 --- a/src/lib/pages/tx-details/components/TxHeader.tsx +++ b/src/lib/pages/tx-details/components/TxHeader.tsx @@ -50,7 +50,11 @@ export const TxHeader = ({ txData, ...flexProps }: TxHeaderProps) => { /> - + {txData.isTxFailed ? ( <> { const getAddressType = useGetAddressType(); const [expand, setExpand] = useState(true); - const [boxHeight, setBoxHeight] = useState(0); const stackRef = useRef(null); - useEffect(() => { - if (stackRef.current) { - setBoxHeight(stackRef.current.clientHeight); - } - }, [stackRef]); - const receipts = event.attributes.map(({ key, value }) => { const addrType = getAddressType(value); let valueComponent: ReactNode; @@ -142,7 +135,7 @@ export const EventBox = ({ event, msgIndex }: EventBoxProps) => { diff --git a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx index 62e97a567..b172eb18e 100644 --- a/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx +++ b/src/lib/pages/tx-details/components/tx-message/TxMsgExpand.tsx @@ -1,3 +1,4 @@ +/* eslint-disable complexity */ import { Badge, Flex } from "@chakra-ui/react"; import type { Coin } from "@cosmjs/stargate"; import { findAttribute } from "@cosmjs/stargate/build/logs"; @@ -23,6 +24,7 @@ export const TxMsgExpand = ({ msgBody, log, isExpand, + isSingleMsg, assetInfos, onClick, }: TxMsgExpandProps) => { @@ -221,7 +223,7 @@ export const TxMsgExpand = ({ msgIcon = "submit-proposal"; content = ( <> - Submit Proposal + Submit Proposal {body.is_expedited && " Expedited "} {log && ( <>

ID

@@ -300,8 +302,9 @@ export const TxMsgExpand = ({ onClick={() => { AmpTrack(AmpEvent.USE_TX_MSG_EXPAND, { action: isExpand ? "collapse" : "expand", - ibc: isIBC, msg: type, + ibc: isIBC, + isSingleMsg, }); onClick(); }} diff --git a/src/lib/pages/tx-details/components/tx-message/index.tsx b/src/lib/pages/tx-details/components/tx-message/index.tsx index 170259635..1005189ec 100644 --- a/src/lib/pages/tx-details/components/tx-message/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/index.tsx @@ -25,6 +25,7 @@ export const TxMessage = ({ isSingleMsg, ...txMsgData }: TxMsgData) => { onClick={() => { setExpand((prev) => !prev); }} + isSingleMsg={isSingleMsg} {...txMsgData} /> diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx index 85d044dbe..668c2ebe2 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx @@ -113,6 +113,7 @@ const SingleCoin = ({ assetInfo, }} amptrackSection="tx_msg_receipts_assets" + w="50%" /> ) : ( ({ null ); - case !value && fallback: + case Boolean(!value && fallback): return ( {fallback} @@ -55,7 +55,11 @@ export const getCommonReceiptHtml = ({ case type === "json" || typeof value === "object": return ( Date: Wed, 5 Apr 2023 13:10:29 +0700 Subject: [PATCH 18/19] fix: enhance getAddressType --- src/lib/app-provider/hooks/useAddress.ts | 38 ++++++++++++++----- .../components/tx-message/index.tsx | 4 +- src/lib/services/amplitude.tsx | 6 ++- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/lib/app-provider/hooks/useAddress.ts b/src/lib/app-provider/hooks/useAddress.ts index 418e137a0..3f3ffbeac 100644 --- a/src/lib/app-provider/hooks/useAddress.ts +++ b/src/lib/app-provider/hooks/useAddress.ts @@ -44,13 +44,9 @@ export const getAddressTypeByLength = ( ? addressLengthMap[chainName]?.[address.length] ?? "invalid_address" : "invalid_address"; -export const useGetAddressType = () => { - const { currentChainName } = useWallet(); - return useCallback( - (address: Option): AddressReturnType => - getAddressTypeByLength(currentChainName, address), - [currentChainName] - ); +const getPrefix = (basePrefix: string, addressType: AddressReturnType) => { + if (addressType === "validator_address") return `${basePrefix}valoper`; + return basePrefix; }; const validateAddress = ( @@ -60,8 +56,10 @@ const validateAddress = ( ) => { if (!currentChainRecord) return "Invalid network"; - if (!address.startsWith(currentChainRecord.chain.bech32_prefix)) - return `Invalid prefix (expected "${currentChainRecord.chain.bech32_prefix}")`; + const prefix = getPrefix(currentChainRecord.chain.bech32_prefix, addressType); + + if (!address.startsWith(prefix)) + return `Invalid prefix (expected "${prefix}")`; if (getAddressTypeByLength(currentChainRecord.name, address) !== addressType) return "Invalid address length"; @@ -74,6 +72,23 @@ const validateAddress = ( return null; }; +export const useGetAddressType = () => { + const { currentChainName, currentChainRecord } = useWallet(); + return useCallback( + (address: Option): AddressReturnType => { + const addressType = getAddressTypeByLength(currentChainName, address); + if ( + !address || + addressType === "invalid_address" || + validateAddress(currentChainRecord, address, addressType) + ) + return "invalid_address"; + return addressType; + }, + [currentChainName, currentChainRecord] + ); +}; + // TODO: refactor export const useValidateAddress = () => { const { currentChainRecord } = useWallet(); @@ -89,5 +104,10 @@ export const useValidateAddress = () => { validateAddress(currentChainRecord, address, "user_address"), [currentChainRecord] ), + validateValidatorAddress: useCallback( + (address: string) => + validateAddress(currentChainRecord, address, "validator_address"), + [currentChainRecord] + ), }; }; diff --git a/src/lib/pages/tx-details/components/tx-message/index.tsx b/src/lib/pages/tx-details/components/tx-message/index.tsx index 1005189ec..d68294600 100644 --- a/src/lib/pages/tx-details/components/tx-message/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/index.tsx @@ -22,9 +22,7 @@ export const TxMessage = ({ isSingleMsg, ...txMsgData }: TxMsgData) => { { - setExpand((prev) => !prev); - }} + onClick={() => setExpand((prev) => !prev)} isSingleMsg={isSingleMsg} {...txMsgData} /> diff --git a/src/lib/services/amplitude.tsx b/src/lib/services/amplitude.tsx index 152686c02..8b2a0e4a6 100644 --- a/src/lib/services/amplitude.tsx +++ b/src/lib/services/amplitude.tsx @@ -115,7 +115,11 @@ type SpecialAmpEvent = | AmpEvent.MINTSCAN | AmpEvent.WEBSITE | AmpEvent.SOCIAL - | AmpEvent.CELATONE; + | AmpEvent.CELATONE + | AmpEvent.USE_VIEW_JSON + | AmpEvent.USE_UNSUPPORTED_ASSETS + | AmpEvent.USE_COPIER + | AmpEvent.USE_EXPAND; export const AmpTrackInvalidState = (title: string) => track(AmpEvent.INVALID_STATE, { title }); From 35eeecd3d20c4e28bdde9b889acb22db296048c8 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Wed, 5 Apr 2023 13:40:34 +0700 Subject: [PATCH 19/19] refactor: change tempalte literal to double quotes --- src/lib/app-fns/explorer/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/app-fns/explorer/index.ts b/src/lib/app-fns/explorer/index.ts index a41b2b8ae..1076bc7b6 100644 --- a/src/lib/app-fns/explorer/index.ts +++ b/src/lib/app-fns/explorer/index.ts @@ -28,9 +28,9 @@ export const getProposalUrl = (chainName: string) => { pathSuffix = "proposals"; break; case "terra2": - return `https://station.terra.money/proposal/phoenix-1`; + return "https://station.terra.money/proposal/phoenix-1"; case "terra2testnet": - return `https://station.terra.money/proposal/pisco-1`; + return "https://station.terra.money/proposal/pisco-1"; default: break; }