From 748dbcb33d513fc83db89d6cb199ca8139ef849a Mon Sep 17 00:00:00 2001 From: evilpeach Date: Thu, 6 Jun 2024 23:12:52 +0700 Subject: [PATCH 01/12] feat: lite version on block index --- CHANGELOG.md | 1 + src/config/chain/osmosis.ts | 2 +- src/lib/app-provider/env.ts | 1 + .../table/transactions/TransactionsTable.tsx | 21 +++- .../transactions/TransactionsTableHeader.tsx | 4 +- .../TransactionsTableMobileCard.tsx | 14 ++- .../transactions/TransactionsTableRow.tsx | 40 ++++--- src/lib/hooks/useSingleMessageProps.ts | 8 +- .../block-details/components/BlockInfo.tsx | 16 ++- ...lockTxsTable.tsx => BlockTxsTableFull.tsx} | 3 +- .../components/BlockTxsTableLite.tsx | 32 +++++ .../block-details/components/InvalidBlock.tsx | 3 + .../pages/block-details/components/index.ts | 3 +- src/lib/pages/block-details/data.ts | 51 ++++++++ src/lib/pages/block-details/full.tsx | 32 +++++ src/lib/pages/block-details/index.tsx | 37 ++---- src/lib/pages/block-details/lite.tsx | 32 +++++ src/lib/services/block/index.ts | 15 ++- src/lib/services/block/lcd.ts | 10 +- src/lib/services/types/block.ts | 111 ++++++++++++++++-- src/lib/services/types/tx.ts | 3 +- src/lib/services/types/validator.ts | 1 + src/lib/types/validator.ts | 5 + src/lib/utils/address.ts | 27 ++++- .../utils/tx/__test__/createTxHash.test.ts | 13 ++ .../tx/__test__/extractTxLogs.example.ts | 4 - src/lib/utils/tx/createTxHash.ts | 5 + src/lib/utils/tx/index.ts | 1 + 28 files changed, 411 insertions(+), 84 deletions(-) rename src/lib/pages/block-details/components/{BlockTxsTable.tsx => BlockTxsTableFull.tsx} (95%) create mode 100644 src/lib/pages/block-details/components/BlockTxsTableLite.tsx create mode 100644 src/lib/pages/block-details/components/InvalidBlock.tsx create mode 100644 src/lib/pages/block-details/data.ts create mode 100644 src/lib/pages/block-details/full.tsx create mode 100644 src/lib/pages/block-details/lite.tsx create mode 100644 src/lib/utils/tx/__test__/createTxHash.test.ts create mode 100644 src/lib/utils/tx/createTxHash.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9414a8cb2..4f38df970 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 +- [#958](https://github.com/alleslabs/celatone-frontend/pull/958) Support lite version for block index page - [#961](https://github.com/alleslabs/celatone-frontend/pull/961) Add and refactor proposal related lcd endpoints - [#952](https://github.com/alleslabs/celatone-frontend/pull/952) Support module details page lite version with LCD endpoint - [#940](https://github.com/alleslabs/celatone-frontend/pull/940) Support my published modules page lite version with LCD endpoint diff --git a/src/config/chain/osmosis.ts b/src/config/chain/osmosis.ts index ed53dcfad..bd0a58d26 100644 --- a/src/config/chain/osmosis.ts +++ b/src/config/chain/osmosis.ts @@ -4,7 +4,7 @@ import type { ChainConfigs } from "./types"; export const OSMOSIS_CHAIN_CONFIGS: ChainConfigs = { "osmosis-1": { - tier: "full", + tier: "lite", chain: "osmosis", registryChainName: "osmosis", prettyName: "Osmosis", diff --git a/src/lib/app-provider/env.ts b/src/lib/app-provider/env.ts index c5610d4c1..ab65fc160 100644 --- a/src/lib/app-provider/env.ts +++ b/src/lib/app-provider/env.ts @@ -19,6 +19,7 @@ export enum CELATONE_QUERY_KEYS { // BLOCK BLOCKS = "CELATONE_QUERY_BLOCKS", BLOCK_DATA = "CELATONE_QUERY_BLOCK_DATA", + BLOCK_DATA_LCD = "CELATONE_QUERY_BLOCK_DATA_LCD", BLOCK_LATEST_HEIGHT_LCD = "CELATONE_QUERY_BLOCK_LATEST_HEIGHT_LCD", // CODE GQL CODES = "CELATONE_QUERY_CODES", diff --git a/src/lib/components/table/transactions/TransactionsTable.tsx b/src/lib/components/table/transactions/TransactionsTable.tsx index cca5fad8b..75d16b7e8 100644 --- a/src/lib/components/table/transactions/TransactionsTable.tsx +++ b/src/lib/components/table/transactions/TransactionsTable.tsx @@ -11,6 +11,7 @@ interface TransactionsTableProps { transactions: Option; isLoading: boolean; emptyState: JSX.Element; + showSuccess?: boolean; showRelations: boolean; showTimestamp?: boolean; showAction?: boolean; @@ -20,6 +21,7 @@ export const TransactionsTable = ({ transactions, isLoading, emptyState, + showSuccess = true, showRelations, showTimestamp = true, showAction = false, @@ -29,11 +31,17 @@ export const TransactionsTable = ({ if (isLoading) return ; if (!transactions?.length) return emptyState; - const templateColumns = `32px 190px 48px minmax(380px, 1fr) ${ - showRelations ? "90px " : "" - }max(180px) ${showTimestamp ? "max(228px) " : ""}${ - showAction ? "100px " : "" - }`; + const columns: string[] = [ + "32px", + "190px", + ...(showSuccess ? ["48px"] : []), + "minmax(380px, 1fr)", + ...(showRelations ? ["90px"] : []), + "max(180px)", + ...(showTimestamp ? ["max(228px)"] : []), + ...(showAction ? ["100px"] : []), + ]; + const templateColumns: string = columns.join(" "); return isMobile ? ( @@ -41,6 +49,7 @@ export const TransactionsTable = ({ @@ -50,6 +59,7 @@ export const TransactionsTable = ({ Transaction Hash - + {showSuccess && } Messages {showRelations && Relations} Sender diff --git a/src/lib/components/table/transactions/TransactionsTableMobileCard.tsx b/src/lib/components/table/transactions/TransactionsTableMobileCard.tsx index 3056121b7..dafbfd17c 100644 --- a/src/lib/components/table/transactions/TransactionsTableMobileCard.tsx +++ b/src/lib/components/table/transactions/TransactionsTableMobileCard.tsx @@ -13,11 +13,13 @@ import { RelationChip } from "./RelationChip"; interface TransactionsTableMobileCardProps { transaction: Transaction; + showSuccess: boolean; showRelations: boolean; showTimestamp: boolean; } export const TransactionsTableMobileCard = ({ transaction, + showSuccess, showRelations, showTimestamp, }: TransactionsTableMobileCardProps) => { @@ -33,10 +35,14 @@ export const TransactionsTableMobileCard = ({ topContent={ <> - {transaction.success ? ( - - ) : ( - + {showSuccess && ( + <> + {transaction.success ? ( + + ) : ( + + )} + )} - - {transaction.success ? ( - - ) : ( - - )} - + {showSuccess && ( + + {transaction.success ? ( + + ) : ( + + )} + + )} @@ -85,14 +89,20 @@ export const TransactionsTableRow = ({ /> {showTimestamp && ( - - - {formatUTC(transaction.created)} - - {`(${dateFromNow(transaction.created)})`} - - - + <> + {transaction.created ? ( + + + {formatUTC(transaction.created)} + + {`(${dateFromNow(transaction.created)})`} + + + + ) : ( + N/A + )} + )} {showAction && ( diff --git a/src/lib/hooks/useSingleMessageProps.ts b/src/lib/hooks/useSingleMessageProps.ts index 1fc7ec91a..337906eed 100644 --- a/src/lib/hooks/useSingleMessageProps.ts +++ b/src/lib/hooks/useSingleMessageProps.ts @@ -1,7 +1,7 @@ import router from "next/router"; import type { GetAddressTypeByLengthFn } from "lib/app-provider"; -import { useGetAddressTypeByLength } from "lib/app-provider"; +import { useGetAddressTypeByLength, useTierConfig } from "lib/app-provider"; import type { SingleMsgProps } from "lib/components/action-msg/SingleMsg"; import type { LinkType } from "lib/components/ExplorerLink"; import { useContractStore } from "lib/providers/store"; @@ -55,6 +55,7 @@ const instantiateSingleMsgProps = ( isInstantiate2: boolean, getAddressTypeByLength: GetAddressTypeByLengthFn ) => { + // TODO: need to handle undefined case const detail = messages[0].detail as DetailInstantiate; // TODO - revisit, instantiate detail response when query from contract transaction table doesn't contain contract addr const contractAddress = @@ -598,11 +599,16 @@ export const useSingleActionMsgProps = ( messages: Message[], singleMsg: Option ): SingleMsgProps => { + const isFullTier = useTierConfig() === "full"; const { getContractLocalInfo } = useContractStore(); const { data: assetInfos } = useAssetInfos({ withPrices: false }); const { data: movePoolInfos } = useMovePoolInfos({ withPrices: false }); const getAddressTypeByLength = useGetAddressTypeByLength(); + // HACK: to prevent the error when message.detail is undefined + // TODO: revist and support custom message detail on lite tier later + if (!isFullTier) return otherMessageSingleMsgProps(isSuccess, messages, type); + switch (type) { case "MsgExecuteContract": return executeSingleMsgProps( diff --git a/src/lib/pages/block-details/components/BlockInfo.tsx b/src/lib/pages/block-details/components/BlockInfo.tsx index 950457186..4d9f7ab7a 100644 --- a/src/lib/pages/block-details/components/BlockInfo.tsx +++ b/src/lib/pages/block-details/components/BlockInfo.tsx @@ -1,6 +1,6 @@ import { Box, Flex, Heading } from "@chakra-ui/react"; -import { useCelatoneApp } from "lib/app-provider"; +import { useCelatoneApp, useTierConfig } from "lib/app-provider"; import { LabelText } from "lib/components/LabelText"; import { ValidatorBadge } from "lib/components/ValidatorBadge"; import type { BlockData } from "lib/types"; @@ -12,6 +12,7 @@ interface BlockInfoProps { export const BlockInfo = ({ blockData }: BlockInfoProps) => { const { currentChainId } = useCelatoneApp(); + const isFullTier = useTierConfig() === "full"; return ( @@ -24,11 +25,14 @@ export const BlockInfo = ({ blockData }: BlockInfoProps) => { {currentChainId} - - {`${blockData.gasUsed ? formatInteger(blockData.gasUsed) : 0} / ${ - blockData.gasLimit ? formatInteger(blockData.gasLimit) : 0 - }`} - + + {isFullTier && ( + + {`${blockData.gasUsed ? formatInteger(blockData.gasUsed) : 0} / ${ + blockData.gasLimit ? formatInteger(blockData.gasLimit) : 0 + }`} + + )} { +export const BlockTxsTableFull = ({ height }: BlockTxsTableProps) => { const { pagesQuantity, setTotalData, @@ -43,6 +43,7 @@ export const BlockTxsTable = ({ height }: BlockTxsTableProps) => { withBorder /> } + showSuccess showRelations={false} showTimestamp={false} /> diff --git a/src/lib/pages/block-details/components/BlockTxsTableLite.tsx b/src/lib/pages/block-details/components/BlockTxsTableLite.tsx new file mode 100644 index 000000000..0d30829d6 --- /dev/null +++ b/src/lib/pages/block-details/components/BlockTxsTableLite.tsx @@ -0,0 +1,32 @@ +import { EmptyState } from "lib/components/state"; +import { TableTitle, TransactionsTable } from "lib/components/table"; +import { useBlockDataLcd } from "lib/services/block"; + +interface BlockTxsTableProps { + height: number; +} + +export const BlockTxsTableLite = ({ height }: BlockTxsTableProps) => { + const { data, isLoading } = useBlockDataLcd(height); + const total = data?.transactions.length; + + return ( + <> + + + } + showSuccess={false} + showRelations={false} + showTimestamp={false} + /> + + ); +}; diff --git a/src/lib/pages/block-details/components/InvalidBlock.tsx b/src/lib/pages/block-details/components/InvalidBlock.tsx new file mode 100644 index 000000000..3cb41cd2a --- /dev/null +++ b/src/lib/pages/block-details/components/InvalidBlock.tsx @@ -0,0 +1,3 @@ +import { InvalidState } from "lib/components/state"; + +export const InvalidBlock = () => ; diff --git a/src/lib/pages/block-details/components/index.ts b/src/lib/pages/block-details/components/index.ts index 0f62f4ffe..0c0b2da0c 100644 --- a/src/lib/pages/block-details/components/index.ts +++ b/src/lib/pages/block-details/components/index.ts @@ -1,3 +1,4 @@ export * from "./BlockDetailsTop"; export * from "./BlockInfo"; -export * from "./BlockTxsTable"; +export * from "./BlockTxsTableLite"; +export * from "./BlockTxsTableFull"; diff --git a/src/lib/pages/block-details/data.ts b/src/lib/pages/block-details/data.ts new file mode 100644 index 000000000..97e2c20a9 --- /dev/null +++ b/src/lib/pages/block-details/data.ts @@ -0,0 +1,51 @@ +import { fromBase64, toHex } from "@cosmjs/encoding"; +import { useMemo } from "react"; + +import { useBlockDataLcd } from "lib/services/block"; +import type { BlockDataResponseLcd } from "lib/services/types"; +import { useValidatorsLcd } from "lib/services/validator"; +import type { Nullable, Validator } from "lib/types"; +import { consensusPubkeyToHexAddress } from "lib/utils"; + +export const useBlockDataWithValidatorLcd = ( + height: number +): { + data: BlockDataResponseLcd | undefined; + isLoading: boolean; +} => { + const { data: blockData, isLoading: isLoadingBlockData } = + useBlockDataLcd(height); + const { data: validators, isLoading: isLoadingValidators } = + useValidatorsLcd(); + + const validator = useMemo>(() => { + if (!blockData || !validators) return null; + + const found = validators.find( + (each) => + consensusPubkeyToHexAddress(each.consensusPubkey) === + toHex(fromBase64(blockData.rawProposerAddress)).toUpperCase() + ); + if (!found) return null; + return { + validatorAddress: found.validatorAddress, + moniker: found.moniker, + identity: found.identity, + }; + }, [blockData, validators]); + + return { + data: blockData + ? { + block: { + ...blockData.block, + proposer: validator, + }, + transactions: blockData.transactions, + pagination: blockData.pagination, + rawProposerAddress: blockData.rawProposerAddress, + } + : undefined, + isLoading: isLoadingBlockData || isLoadingValidators, + }; +}; diff --git a/src/lib/pages/block-details/full.tsx b/src/lib/pages/block-details/full.tsx new file mode 100644 index 000000000..564aca523 --- /dev/null +++ b/src/lib/pages/block-details/full.tsx @@ -0,0 +1,32 @@ +import { Breadcrumb } from "lib/components/Breadcrumb"; +import { Loading } from "lib/components/Loading"; +import { UserDocsLink } from "lib/components/UserDocsLink"; +import { useBlockData } from "lib/services/block"; + +import { BlockDetailsTop, BlockInfo, BlockTxsTableFull } from "./components"; +import { InvalidBlock } from "./components/InvalidBlock"; + +export const BlockDetailsFull = ({ height }: { height: number }) => { + const { data: blockData, isLoading } = useBlockData(height); + + if (isLoading) return ; + if (!blockData) return ; + return ( + <> + + + + + + + ); +}; diff --git a/src/lib/pages/block-details/index.tsx b/src/lib/pages/block-details/index.tsx index 606544673..33aada463 100644 --- a/src/lib/pages/block-details/index.tsx +++ b/src/lib/pages/block-details/index.tsx @@ -2,44 +2,25 @@ import { useRouter } from "next/router"; import { useEffect } from "react"; import { AmpEvent, track } from "lib/amplitude"; -import { Breadcrumb } from "lib/components/Breadcrumb"; -import { Loading } from "lib/components/Loading"; +import { useTierConfig } from "lib/app-provider"; import PageContainer from "lib/components/PageContainer"; -import { InvalidState } from "lib/components/state"; -import { UserDocsLink } from "lib/components/UserDocsLink"; -import { useBlockData } from "lib/services/block"; -import { BlockDetailsTop, BlockInfo, BlockTxsTable } from "./components"; +import { InvalidBlock } from "./components/InvalidBlock"; +import { BlockDetailsFull } from "./full"; +import { BlockDetailsLite } from "./lite"; import { zBlockDetailQueryParams } from "./types"; -const InvalidBlock = () => ; - interface BlockDetailsBodyProps { height: number; } const BlockDetailsBody = ({ height }: BlockDetailsBodyProps) => { - const { data: blockData, isLoading } = useBlockData(height); + const isFullTier = useTierConfig() === "full"; - if (isLoading) return ; - if (!blockData) return ; - return ( - <> - - - - - - + return isFullTier ? ( + + ) : ( + ); }; diff --git a/src/lib/pages/block-details/lite.tsx b/src/lib/pages/block-details/lite.tsx new file mode 100644 index 000000000..6ce931599 --- /dev/null +++ b/src/lib/pages/block-details/lite.tsx @@ -0,0 +1,32 @@ +import { Breadcrumb } from "lib/components/Breadcrumb"; +import { Loading } from "lib/components/Loading"; +import { UserDocsLink } from "lib/components/UserDocsLink"; + +import { BlockDetailsTop, BlockInfo, BlockTxsTableLite } from "./components"; +import { InvalidBlock } from "./components/InvalidBlock"; +import { useBlockDataWithValidatorLcd } from "./data"; + +export const BlockDetailsLite = ({ height }: { height: number }) => { + const { data, isLoading } = useBlockDataWithValidatorLcd(height); + + if (isLoading) return ; + if (!data) return ; + return ( + <> + + + + + + + ); +}; diff --git a/src/lib/services/block/index.ts b/src/lib/services/block/index.ts index 0c15751b9..a218a0c49 100644 --- a/src/lib/services/block/index.ts +++ b/src/lib/services/block/index.ts @@ -10,7 +10,7 @@ import type { BlocksResponse } from "lib/services/types"; import type { BlockData } from "lib/types"; import { getBlockData, getBlocks } from "./api"; -import { getLatestBlockLcd } from "./lcd"; +import { getBlockDataLcd, getLatestBlockLcd } from "./lcd"; export const useBlocks = ( limit: number, @@ -39,6 +39,19 @@ export const useBlockData = (height: number, enabled = true) => { ); }; +export const useBlockDataLcd = (height: number) => { + const endpoint = useLcdEndpoint(); + + return useQuery( + [CELATONE_QUERY_KEYS.BLOCK_DATA_LCD, endpoint, height], + async () => getBlockDataLcd(endpoint, height), + { + retry: false, + refetchOnWindowFocus: false, + } + ); +}; + export const useLatestBlockLcd = () => { const endpoint = useLcdEndpoint(); diff --git a/src/lib/services/block/lcd.ts b/src/lib/services/block/lcd.ts index 9c08cb7bb..ff551cecd 100644 --- a/src/lib/services/block/lcd.ts +++ b/src/lib/services/block/lcd.ts @@ -1,9 +1,17 @@ import axios from "axios"; -import { zBlockLcd } from "../types"; +import { zBlockDataResponseLcd, zBlockLcd } from "../types"; import { parseWithError } from "lib/utils"; export const getLatestBlockLcd = async (endpoint: string) => axios .get(`${endpoint}/cosmos/base/tendermint/v1beta1/blocks/latest`) .then(({ data }) => parseWithError(zBlockLcd, data).block.header.height); + +export const getBlockDataLcd = async (endpoint: string, height: number) => { + return axios + .get( + `${endpoint}/cosmos/tx/v1beta1/txs/block/${encodeURIComponent(height)}` + ) + .then(({ data }) => parseWithError(zBlockDataResponseLcd, data)); +}; diff --git a/src/lib/services/types/block.ts b/src/lib/services/types/block.ts index f95cce17b..cae0616eb 100644 --- a/src/lib/services/types/block.ts +++ b/src/lib/services/types/block.ts @@ -1,8 +1,24 @@ import { z } from "zod"; -import type { Block, BlockData, Validator } from "lib/types"; -import { zUtcDate, zValidatorAddr } from "lib/types"; -import { parseTxHash, snakeToCamel } from "lib/utils"; +import type { + BechAddr, + Block, + BlockData, + Message, + Pagination, + Transaction, + Validator, +} from "lib/types"; +import { + ActionMsgType, + MsgFurtherAction, + zPagination, + zUtcDate, + zValidatorAddr, +} from "lib/types"; +import { createTxHash, parseTxHash, snakeToCamel } from "lib/utils"; + +import { zTx } from "./tx"; const zNullableValidator = z.nullable( z @@ -59,14 +75,85 @@ export const zBlockDataResponse = z gasLimit: val.gas_limit, })); -export const zBlockLcd = z - .object({ - block: z.object({ - header: z.object({ - chain_id: z.string(), - height: z.coerce.number(), - // TODO: Fill in the rest of the block fields - }), +export const zBlockLcd = z.object({ + block: z.object({ + header: z.object({ + chain_id: z.string(), + height: z.coerce.number(), + time: zUtcDate, + proposer_address: z.string(), + }), + data: z.object({ + txs: z.array(z.string()), }), + }), + block_id: z.object({ + hash: z.string(), + }), +}); + +export const zBlockDataResponseLcd = zBlockLcd + .extend({ + txs: z.array(zTx), + pagination: zPagination, }) - .transform(snakeToCamel); + .transform(snakeToCamel) + .transform<{ + block: BlockData; + transactions: Transaction[]; + pagination: Pagination; + rawProposerAddress: string; + }>((val) => { + // 1. Create Tx Hashes + const txHashes = val.block.data.txs.map(createTxHash); + + // 2. Parse Tx to Transaction + const transactions = val.txs.map((tx, idx) => { + const txBody = tx.body; + const message = txBody.messages[0]; + const sender = (message?.sender || + message?.signer || + message?.fromAddress || + "") as BechAddr; + + const messages = txBody.messages.map((msg) => ({ + log: undefined, + type: msg["@type"], + })); + + return { + hash: txHashes[idx], + messages, + sender, + isSigner: true, + height: val.block.header.height, + created: val.block.header.time, + success: false, // NOTE: Hidden in Lite Tier + // TODO: implement below later + actionMsgType: ActionMsgType.OTHER_ACTION_MSG, + furtherAction: MsgFurtherAction.NONE, + isIbc: false, + isOpinit: false, + isInstantiate: false, + }; + }); + + // 3. Create Block Data + const block: BlockData = { + hash: Buffer.from(val.blockId.hash, "base64").toString("hex"), + height: val.block.header.height, + timestamp: val.block.header.time, + proposer: null, // NOTE: Will be filled in the next step + gasLimit: undefined, + gasUsed: undefined, + }; + + return { + block, + transactions, + pagination: val.pagination, + rawProposerAddress: val.block.header.proposerAddress, + }; + }); + +export type BlockDataResponseLcd = z.infer; diff --git a/src/lib/services/types/tx.ts b/src/lib/services/types/tx.ts index 15e72df68..e9f967bb4 100644 --- a/src/lib/services/types/tx.ts +++ b/src/lib/services/types/tx.ts @@ -88,9 +88,8 @@ const zTxBody = z .transform(snakeToCamel); export type TxBody = z.infer; -const zTx = z +export const zTx = z .object({ - "@type": z.string(), body: zTxBody, auth_info: zAuthInfo, signatures: z.array(z.string()), diff --git a/src/lib/services/types/validator.ts b/src/lib/services/types/validator.ts index 5bac97b7b..efff5f4e9 100644 --- a/src/lib/services/types/validator.ts +++ b/src/lib/services/types/validator.ts @@ -63,6 +63,7 @@ const zValidatorInfoLcd = z isActive: val.status === "BOND_STATUS_BONDED", votingPower: val.tokens, website: val.description.website, + consensusPubkey: val.consensus_pubkey, })); export const zValidatorResponseLcd = z.object({ diff --git a/src/lib/types/validator.ts b/src/lib/types/validator.ts index a34c041bc..5ecb939c2 100644 --- a/src/lib/types/validator.ts +++ b/src/lib/types/validator.ts @@ -39,6 +39,11 @@ export const zValidatorData = z .transform(({ website, ...val }) => ({ ...snakeToCamel(val), website: formatUrl(website), + // TODO: add from api + consensusPubkey: { + "@type": "", + key: "", + }, })); export type ValidatorData = z.infer; diff --git a/src/lib/utils/address.ts b/src/lib/utils/address.ts index 2828fa933..8823c4740 100644 --- a/src/lib/utils/address.ts +++ b/src/lib/utils/address.ts @@ -1,4 +1,11 @@ -import { fromBech32, fromHex, toBech32, toHex } from "@cosmjs/encoding"; +import { Ripemd160, sha256 } from "@cosmjs/crypto"; +import { + fromBase64, + fromBech32, + fromHex, + toBech32, + toHex, +} from "@cosmjs/encoding"; import type { AddressReturnType } from "lib/app-provider"; import type { BechAddr, HexAddr, Option } from "lib/types"; @@ -41,3 +48,21 @@ export const hexToBech32Address = ( const strip = padHexAddress(hexAddr, length).slice(2); return toBech32(prefix, fromHex(strip)) as BechAddr; }; + +export function consensusPubkeyToHexAddress(consensusPubkey?: { + "@type": string; + key: string; +}) { + if (!consensusPubkey) return ""; + const raw = ""; + if (consensusPubkey["@type"] === "/cosmos.crypto.ed25519.PubKey") { + const pubkey = fromBase64(consensusPubkey.key); + if (pubkey) return toHex(sha256(pubkey)).slice(0, 40).toUpperCase(); + } + + if (consensusPubkey["@type"] === "/cosmos.crypto.secp256k1.PubKey") { + const pubkey = fromBase64(consensusPubkey.key); + if (pubkey) return toHex(new Ripemd160().update(sha256(pubkey)).digest()); + } + return raw; +} diff --git a/src/lib/utils/tx/__test__/createTxHash.test.ts b/src/lib/utils/tx/__test__/createTxHash.test.ts new file mode 100644 index 000000000..14c9c9fc2 --- /dev/null +++ b/src/lib/utils/tx/__test__/createTxHash.test.ts @@ -0,0 +1,13 @@ +import { createTxHash } from "../createTxHash"; + +describe("createTxHash", () => { + test("success", () => { + expect( + createTxHash( + "CpUCCpICChovaW5pdGlhLm1vdmUudjEuTXNnRXhlY3V0ZRLzAQoraW5pdDFwZ2d3bTZua3F6djdqbWdmOGtzZnc5Mzdza2Ezc3NjODRuOTIyMBIqMHhFNjA2MDgxMTVDRjE2QzhBN0UwNzkwMjEwQzBBNjcyMTVCN0JFQzA5GhdleHRlcm5hbF9pbnRlbnRfbWFuYWdlciIsdmVyaWZ5X2Nsb3NlX3Bvc2l0aW9uX3RyYW5zZmVyX2ludGVudF9maWxsZWQyJSRhYThjNjg0Ni04OGJjLTRmYjAtODYxZS01ZDgyZWYwYjQ4NjYyIPTuyaaQLdM3TWJy5evGnv+8yN+DKDxzy/fGo97NVMUBMgjaVuUAAAAAABJaClIKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECDycOMnG0mAWpG2gBCPgq1NBZ263AGLCMGzX3OuQQZi8SBAoCCAEYu7wCEgQQqYgUGkBGZNRBO0VYqen0E4GTpdFZ5tJVdjPlFQ8cHhbzZYmAlRUiaMOaQLhQaLhmVdoMpy7HlN/4BWSPHn923pgi3xvq" + ) + ).toEqual( + "5BCB87ACD981F366183B3624CE552294012216167DDCEBA5BE9717969AA48249" + ); + }); +}); diff --git a/src/lib/utils/tx/__test__/extractTxLogs.example.ts b/src/lib/utils/tx/__test__/extractTxLogs.example.ts index cd2e74ee8..f7e003afa 100644 --- a/src/lib/utils/tx/__test__/extractTxLogs.example.ts +++ b/src/lib/utils/tx/__test__/extractTxLogs.example.ts @@ -271,7 +271,6 @@ export const fromLogs: TestCase = { '[{"events":[{"type":"message","attributes":[{"key":"action","value":"/cosmwasm.wasm.v1.MsgMigrateContract"},{"key":"module","value":"wasm"},{"key":"sender","value":"osmo18rf2vketuhfvrw0n986mghms33ahm884wsrfsj"}]},{"type":"migrate","attributes":[{"key":"code_id","value":"17"},{"key":"_contract_address","value":"osmo1cvtzwsj8lam9at8vfgxfnveyzjne8eecqjnsh6k3jvt3l7ve6zms3wtpd0"}]}]}]', timestamp: parseDate("2023-06-29T06:09:47Z"), tx: { - "@type": "/cosmos.tx.v1beta1.Tx", authInfo: { fee: { amount: [ @@ -464,7 +463,6 @@ export const fromLogsTxFailed: TestCase = { "failed to execute message; message index: 0: was (1000uosmo), need (400000000uosmo): minimum deposit is too small", timestamp: parseDate("2022-09-06T09:03:15Z"), tx: { - "@type": "/cosmos.tx.v1beta1.Tx", authInfo: { fee: { amount: [ @@ -750,7 +748,6 @@ export const fromEvents: TestCase = { rawLog: "", timestamp: parseDate("2024-01-16T16:51:20Z"), tx: { - "@type": "/cosmos.tx.v1beta1.Tx", authInfo: { fee: { amount: [ @@ -1023,7 +1020,6 @@ export const fromEventsTxFailed: TestCase = { "failed to execute message; message index: 0: VM failure: status OUT_OF_GAS of type Execution, location=0000000000000000000000002ab506311ffe3aaf8871f84a7ba8a685e025dbba::ed25519, function=1, code_offset=12", timestamp: parseDate("2024-01-26T07:05:00Z"), tx: { - "@type": "/cosmos.tx.v1beta1.Tx", authInfo: { fee: { amount: [ diff --git a/src/lib/utils/tx/createTxHash.ts b/src/lib/utils/tx/createTxHash.ts new file mode 100644 index 000000000..fed502543 --- /dev/null +++ b/src/lib/utils/tx/createTxHash.ts @@ -0,0 +1,5 @@ +import { sha256 } from "@cosmjs/crypto"; +import { toHex } from "@cosmjs/encoding"; + +export const createTxHash = (txRaw: string) => + toHex(sha256(Buffer.from(txRaw, "base64"))).toUpperCase(); diff --git a/src/lib/utils/tx/index.ts b/src/lib/utils/tx/index.ts index 4fc6d9e62..7b63d2246 100644 --- a/src/lib/utils/tx/index.ts +++ b/src/lib/utils/tx/index.ts @@ -1,4 +1,5 @@ export * from "./composeMsg"; +export * from "./createTxHash"; export * from "./extractTxDetails"; export * from "./extractTxLogs"; export * from "./findAttr"; From 298fba23c0730c3371858cc837f175fbad83d5d3 Mon Sep 17 00:00:00 2001 From: evilpeach Date: Fri, 7 Jun 2024 13:04:53 +0700 Subject: [PATCH 02/12] fix: change osmosis to full --- src/config/chain/osmosis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/chain/osmosis.ts b/src/config/chain/osmosis.ts index bd0a58d26..ed53dcfad 100644 --- a/src/config/chain/osmosis.ts +++ b/src/config/chain/osmosis.ts @@ -4,7 +4,7 @@ import type { ChainConfigs } from "./types"; export const OSMOSIS_CHAIN_CONFIGS: ChainConfigs = { "osmosis-1": { - tier: "lite", + tier: "full", chain: "osmosis", registryChainName: "osmosis", prettyName: "Osmosis", From 79c4f92c13174fc933ee15dcbe1665b4362eaf72 Mon Sep 17 00:00:00 2001 From: evilpeach Date: Mon, 10 Jun 2024 22:23:53 +0700 Subject: [PATCH 03/12] fix: remove rawProposerAddress, refactor --- src/lib/pages/block-details/data.ts | 6 ++++-- src/lib/services/types/block.ts | 3 +-- src/lib/types/block.ts | 1 + src/lib/types/validator.ts | 31 ++++++++++++++++++++++------- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/lib/pages/block-details/data.ts b/src/lib/pages/block-details/data.ts index 97e2c20a9..81f3c65fa 100644 --- a/src/lib/pages/block-details/data.ts +++ b/src/lib/pages/block-details/data.ts @@ -21,10 +21,13 @@ export const useBlockDataWithValidatorLcd = ( const validator = useMemo>(() => { if (!blockData || !validators) return null; + const { proposerAddress } = blockData.block; + if (!proposerAddress) return null; + const found = validators.find( (each) => consensusPubkeyToHexAddress(each.consensusPubkey) === - toHex(fromBase64(blockData.rawProposerAddress)).toUpperCase() + toHex(fromBase64(proposerAddress)).toUpperCase() ); if (!found) return null; return { @@ -43,7 +46,6 @@ export const useBlockDataWithValidatorLcd = ( }, transactions: blockData.transactions, pagination: blockData.pagination, - rawProposerAddress: blockData.rawProposerAddress, } : undefined, isLoading: isLoadingBlockData || isLoadingValidators, diff --git a/src/lib/services/types/block.ts b/src/lib/services/types/block.ts index cae0616eb..3839c688c 100644 --- a/src/lib/services/types/block.ts +++ b/src/lib/services/types/block.ts @@ -102,7 +102,6 @@ export const zBlockDataResponseLcd = zBlockLcd block: BlockData; transactions: Transaction[]; pagination: Pagination; - rawProposerAddress: string; }>((val) => { // 1. Create Tx Hashes const txHashes = val.block.data.txs.map(createTxHash); @@ -144,6 +143,7 @@ export const zBlockDataResponseLcd = zBlockLcd height: val.block.header.height, timestamp: val.block.header.time, proposer: null, // NOTE: Will be filled in the next step + proposerAddress: val.block.header.proposerAddress, gasLimit: undefined, gasUsed: undefined, }; @@ -152,7 +152,6 @@ export const zBlockDataResponseLcd = zBlockLcd block, transactions, pagination: val.pagination, - rawProposerAddress: val.block.header.proposerAddress, }; }); diff --git a/src/lib/types/block.ts b/src/lib/types/block.ts index 14a8206d0..84046b22a 100644 --- a/src/lib/types/block.ts +++ b/src/lib/types/block.ts @@ -7,6 +7,7 @@ export interface Block { timestamp: Date; txCount: number; proposer: Nullable; + proposerAddress?: string; // Base64 } export interface BlockData extends Omit { diff --git a/src/lib/types/validator.ts b/src/lib/types/validator.ts index 5ecb939c2..17d5d20ff 100644 --- a/src/lib/types/validator.ts +++ b/src/lib/types/validator.ts @@ -3,8 +3,10 @@ import { z } from "zod"; import { snakeToCamel } from "lib/utils/formatter/snakeToCamel"; import { formatUrl } from "lib/utils/formatter/url"; +import type { BechAddr20, ValidatorAddr } from "./addrs"; import { zBechAddr20, zValidatorAddr } from "./addrs"; import { zBig } from "./big"; +import type { Nullable, Option } from "./common"; import { zRatio } from "./currency"; import type { Ratio } from "./currency"; @@ -21,6 +23,26 @@ export const zValidator = z })); export type Validator = z.infer; +export type ValidatorData = { + rank: Nullable; + validatorAddress: ValidatorAddr; + accountAddress: BechAddr20; + identity: string; + moniker: string; + details: string; + commissionRate: Ratio; + // NOTE: consensusPubkey is present only in the lcd response + consensusPubkey: Option<{ + "@type": string; + key: string; + }>; + isJailed: boolean; + isActive: boolean; + votingPower: Big; + uptime?: number; + website: string; +}; + export const zValidatorData = z .object({ rank: z.number().nullable(), @@ -36,16 +58,11 @@ export const zValidatorData = z uptime: z.number().optional(), website: z.string(), }) - .transform(({ website, ...val }) => ({ + .transform(({ website, ...val }) => ({ ...snakeToCamel(val), website: formatUrl(website), - // TODO: add from api - consensusPubkey: { - "@type": "", - key: "", - }, + consensusPubkey: undefined, })); -export type ValidatorData = z.infer; export enum BlockVote { PROPOSE = "PROPOSE", From e9f5822dcad5f615f846eb2e5e55132683c13655 Mon Sep 17 00:00:00 2001 From: songwongtp <16089160+songwongtp@users.noreply.github.com> Date: Tue, 11 Jun 2024 00:10:15 +0700 Subject: [PATCH 04/12] feat: use consensus address --- CHANGELOG.md | 1 + src/lib/pages/block-details/data.ts | 26 ++++++------------- src/lib/pages/block-details/lite.tsx | 6 ++--- src/lib/services/block/index.ts | 19 +++++++++++++- src/lib/services/block/lcd.ts | 5 ++-- src/lib/services/types/block.ts | 5 ++-- src/lib/services/types/validator.ts | 15 ++++++----- src/lib/services/validator/index.ts | 29 ++++++++++++++++++++-- src/lib/services/validator/lcd.ts | 9 ++++--- src/lib/types/addrs.ts | 3 +++ src/lib/types/block.ts | 1 - src/lib/types/validator.ts | 37 +++++++++------------------- src/lib/utils/address.ts | 27 +------------------- src/lib/utils/index.ts | 3 ++- src/lib/utils/validator.ts | 32 ++++++++++++++++++++++++ 15 files changed, 125 insertions(+), 93 deletions(-) create mode 100644 src/lib/utils/validator.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f38df970..9d3afc6f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Improvements +- [#967](https://github.com/alleslabs/celatone-frontend/pull/967) Utilize consensus address for better consistency - [#959](https://github.com/alleslabs/celatone-frontend/pull/959) Update @cosmos-kit/react to v2.15.0 and friends - [#947](https://github.com/alleslabs/celatone-frontend/pull/947) Add zod type for sign mode infos - [#917](https://github.com/alleslabs/celatone-frontend/pull/917) Update filter selection input to support multiple use case diff --git a/src/lib/pages/block-details/data.ts b/src/lib/pages/block-details/data.ts index 81f3c65fa..adb39f57e 100644 --- a/src/lib/pages/block-details/data.ts +++ b/src/lib/pages/block-details/data.ts @@ -1,16 +1,13 @@ -import { fromBase64, toHex } from "@cosmjs/encoding"; import { useMemo } from "react"; import { useBlockDataLcd } from "lib/services/block"; -import type { BlockDataResponseLcd } from "lib/services/types"; import { useValidatorsLcd } from "lib/services/validator"; -import type { Nullable, Validator } from "lib/types"; -import { consensusPubkeyToHexAddress } from "lib/utils"; +import type { BlockData, Nullable, Option, Validator } from "lib/types"; export const useBlockDataWithValidatorLcd = ( height: number ): { - data: BlockDataResponseLcd | undefined; + data: Option; isLoading: boolean; } => { const { data: blockData, isLoading: isLoadingBlockData } = @@ -18,18 +15,15 @@ export const useBlockDataWithValidatorLcd = ( const { data: validators, isLoading: isLoadingValidators } = useValidatorsLcd(); - const validator = useMemo>(() => { + const proposer = useMemo>(() => { if (!blockData || !validators) return null; - const { proposerAddress } = blockData.block; - if (!proposerAddress) return null; - const found = validators.find( - (each) => - consensusPubkeyToHexAddress(each.consensusPubkey) === - toHex(fromBase64(proposerAddress)).toUpperCase() + (validator) => + validator.consensusAddress === blockData.proposerConsensusAddr ); if (!found) return null; + return { validatorAddress: found.validatorAddress, moniker: found.moniker, @@ -40,12 +34,8 @@ export const useBlockDataWithValidatorLcd = ( return { data: blockData ? { - block: { - ...blockData.block, - proposer: validator, - }, - transactions: blockData.transactions, - pagination: blockData.pagination, + ...blockData.block, + proposer, } : undefined, isLoading: isLoadingBlockData || isLoadingValidators, diff --git a/src/lib/pages/block-details/lite.tsx b/src/lib/pages/block-details/lite.tsx index 6ce931599..a80b17e8e 100644 --- a/src/lib/pages/block-details/lite.tsx +++ b/src/lib/pages/block-details/lite.tsx @@ -16,11 +16,11 @@ export const BlockDetailsLite = ({ height }: { height: number }) => { - - + + { export const useBlockDataLcd = (height: number) => { const endpoint = useLcdEndpoint(); + const { + chain: { bech32_prefix: prefix }, + } = useCurrentChain(); return useQuery( [CELATONE_QUERY_KEYS.BLOCK_DATA_LCD, endpoint, height], - async () => getBlockDataLcd(endpoint, height), + async () => { + const { rawProposerConsensusAddress, ...rest } = await getBlockDataLcd( + endpoint, + height + ); + return { + ...rest, + proposerConsensusAddr: convertRawConsensusAddrToConsensusAddr( + rawProposerConsensusAddress, + prefix + ), + }; + }, { retry: false, refetchOnWindowFocus: false, diff --git a/src/lib/services/block/lcd.ts b/src/lib/services/block/lcd.ts index ff551cecd..ac4cf322d 100644 --- a/src/lib/services/block/lcd.ts +++ b/src/lib/services/block/lcd.ts @@ -8,10 +8,9 @@ export const getLatestBlockLcd = async (endpoint: string) => .get(`${endpoint}/cosmos/base/tendermint/v1beta1/blocks/latest`) .then(({ data }) => parseWithError(zBlockLcd, data).block.header.height); -export const getBlockDataLcd = async (endpoint: string, height: number) => { - return axios +export const getBlockDataLcd = async (endpoint: string, height: number) => + axios .get( `${endpoint}/cosmos/tx/v1beta1/txs/block/${encodeURIComponent(height)}` ) .then(({ data }) => parseWithError(zBlockDataResponseLcd, data)); -}; diff --git a/src/lib/services/types/block.ts b/src/lib/services/types/block.ts index 3839c688c..3dd3fc6ec 100644 --- a/src/lib/services/types/block.ts +++ b/src/lib/services/types/block.ts @@ -100,6 +100,7 @@ export const zBlockDataResponseLcd = zBlockLcd .transform(snakeToCamel) .transform<{ block: BlockData; + rawProposerConsensusAddress: string; transactions: Transaction[]; pagination: Pagination; }>((val) => { @@ -143,16 +144,14 @@ export const zBlockDataResponseLcd = zBlockLcd height: val.block.header.height, timestamp: val.block.header.time, proposer: null, // NOTE: Will be filled in the next step - proposerAddress: val.block.header.proposerAddress, gasLimit: undefined, gasUsed: undefined, }; return { block, + rawProposerConsensusAddress: val.block.header.proposerAddress, transactions, pagination: val.pagination, }; }); - -export type BlockDataResponseLcd = z.infer; diff --git a/src/lib/services/types/validator.ts b/src/lib/services/types/validator.ts index efff5f4e9..078dcf04d 100644 --- a/src/lib/services/types/validator.ts +++ b/src/lib/services/types/validator.ts @@ -1,12 +1,13 @@ import { z } from "zod"; -import type { ValidatorData } from "lib/types"; +import type { ConsensusPubkey, ValidatorData } from "lib/types"; import { BlockVote, SlashingEvent, zBechAddr, zBig, zCoin, + zConsensusPubkey, zPagination, zProposalStatus, zProposalType, @@ -20,10 +21,7 @@ import { parseTxHash, snakeToCamel, valoperToAddr } from "lib/utils"; const zValidatorInfoLcd = z .object({ operator_address: zValidatorAddr, - consensus_pubkey: z.object({ - "@type": z.string(), - key: z.string(), - }), + consensus_pubkey: zConsensusPubkey, jailed: z.boolean(), status: z.enum([ "BOND_STATUS_BONDED", @@ -51,7 +49,11 @@ const zValidatorInfoLcd = z }), min_self_delegation: z.string(), }) - .transform((val) => ({ + .transform< + Omit & { + consensusPubkey: ConsensusPubkey; + } + >((val) => ({ rank: null, validatorAddress: val.operator_address, accountAddress: valoperToAddr(val.operator_address), @@ -65,6 +67,7 @@ const zValidatorInfoLcd = z website: val.description.website, consensusPubkey: val.consensus_pubkey, })); +export type ValidatorInfoLcd = z.infer; export const zValidatorResponseLcd = z.object({ validator: zValidatorInfoLcd, diff --git a/src/lib/services/validator/index.ts b/src/lib/services/validator/index.ts index e8aac8f03..8bcce06a3 100644 --- a/src/lib/services/validator/index.ts +++ b/src/lib/services/validator/index.ts @@ -29,6 +29,7 @@ import type { ValidatorAddr, ValidatorData, } from "lib/types"; +import { convertConsensusPubkeyToConsensusAddr } from "lib/utils"; import { getHistoricalPowers, @@ -83,10 +84,22 @@ export const useValidators = ( export const useValidatorsLcd = (enabled = true) => { const endpoint = useLcdEndpoint(); + const { + chain: { bech32_prefix: prefix }, + } = useCurrentChain(); return useQuery( [CELATONE_QUERY_KEYS.VALIDATORS_LCD, endpoint], - async () => getValidatorsLcd(endpoint), + async () => { + const res = await getValidatorsLcd(endpoint); + return res.map((val) => ({ + ...val, + consensusAddress: convertConsensusPubkeyToConsensusAddr( + val.consensusPubkey, + prefix + ), + })); + }, { enabled, retry: 1, @@ -117,10 +130,22 @@ export const useValidatorDataLcd = ( enabled = true ) => { const endpoint = useLcdEndpoint(); + const { + chain: { bech32_prefix: prefix }, + } = useCurrentChain(); return useQuery( [CELATONE_QUERY_KEYS.VALIDATOR_DATA_LCD, endpoint, validatorAddr], - async () => getValidatorDataLcd(endpoint, validatorAddr), + async () => { + const res = await getValidatorDataLcd(endpoint, validatorAddr); + return { + ...res, + consensusAddress: convertConsensusPubkeyToConsensusAddr( + res.consensusPubkey, + prefix + ), + }; + }, { enabled: enabled && Boolean(validatorAddr), retry: 1, diff --git a/src/lib/services/validator/lcd.ts b/src/lib/services/validator/lcd.ts index cc0f722c4..81486ce59 100644 --- a/src/lib/services/validator/lcd.ts +++ b/src/lib/services/validator/lcd.ts @@ -6,18 +6,21 @@ import { getEpochProvisionsLcd, getMintParamsLcd, } from "../staking/lcd"; -import type { ValidatorDelegatorsResponse } from "lib/services/types"; +import type { + ValidatorDelegatorsResponse, + ValidatorInfoLcd, +} from "lib/services/types"; import { zValidatorDelegatorsResponse, zValidatorResponseLcd, zValidatorsResponseLcd, } from "lib/services/types"; import { big } from "lib/types"; -import type { Nullable, ValidatorAddr, ValidatorData } from "lib/types"; +import type { Nullable, ValidatorAddr } from "lib/types"; import { parseWithError } from "lib/utils"; export const getValidatorsLcd = async (endpoint: string) => { - const result: ValidatorData[] = []; + const result: ValidatorInfoLcd[] = []; const fetchFn = async (paginationKey: Nullable) => { const res = await axios diff --git a/src/lib/types/addrs.ts b/src/lib/types/addrs.ts index 5aadd7871..be2e0bb3c 100644 --- a/src/lib/types/addrs.ts +++ b/src/lib/types/addrs.ts @@ -34,3 +34,6 @@ export type Addr = z.infer; export const zValidatorAddr = z.string().brand("ValidatorAddr"); export type ValidatorAddr = z.infer; + +export const zConsensusAddr = z.string().brand("ConsensusAddr"); +export type ConsensusAddr = z.infer; diff --git a/src/lib/types/block.ts b/src/lib/types/block.ts index 84046b22a..14a8206d0 100644 --- a/src/lib/types/block.ts +++ b/src/lib/types/block.ts @@ -7,7 +7,6 @@ export interface Block { timestamp: Date; txCount: number; proposer: Nullable; - proposerAddress?: string; // Base64 } export interface BlockData extends Omit { diff --git a/src/lib/types/validator.ts b/src/lib/types/validator.ts index 17d5d20ff..dc744d756 100644 --- a/src/lib/types/validator.ts +++ b/src/lib/types/validator.ts @@ -3,12 +3,10 @@ import { z } from "zod"; import { snakeToCamel } from "lib/utils/formatter/snakeToCamel"; import { formatUrl } from "lib/utils/formatter/url"; -import type { BechAddr20, ValidatorAddr } from "./addrs"; -import { zBechAddr20, zValidatorAddr } from "./addrs"; +import { zBechAddr20, zConsensusAddr, zValidatorAddr } from "./addrs"; import { zBig } from "./big"; -import type { Nullable, Option } from "./common"; -import { zRatio } from "./currency"; import type { Ratio } from "./currency"; +import { zRatio } from "./currency"; export const zValidator = z .object({ @@ -23,31 +21,12 @@ export const zValidator = z })); export type Validator = z.infer; -export type ValidatorData = { - rank: Nullable; - validatorAddress: ValidatorAddr; - accountAddress: BechAddr20; - identity: string; - moniker: string; - details: string; - commissionRate: Ratio; - // NOTE: consensusPubkey is present only in the lcd response - consensusPubkey: Option<{ - "@type": string; - key: string; - }>; - isJailed: boolean; - isActive: boolean; - votingPower: Big; - uptime?: number; - website: string; -}; - export const zValidatorData = z .object({ rank: z.number().nullable(), validator_address: zValidatorAddr, account_address: zBechAddr20, + consensus_address: zConsensusAddr, identity: z.string(), moniker: z.string(), details: z.string(), @@ -58,11 +37,17 @@ export const zValidatorData = z uptime: z.number().optional(), website: z.string(), }) - .transform(({ website, ...val }) => ({ + .transform(({ website, ...val }) => ({ ...snakeToCamel(val), website: formatUrl(website), - consensusPubkey: undefined, })); +export type ValidatorData = z.infer; + +export const zConsensusPubkey = z.object({ + "@type": z.string(), + key: z.string(), +}); +export type ConsensusPubkey = z.infer; export enum BlockVote { PROPOSE = "PROPOSE", diff --git a/src/lib/utils/address.ts b/src/lib/utils/address.ts index 8823c4740..2828fa933 100644 --- a/src/lib/utils/address.ts +++ b/src/lib/utils/address.ts @@ -1,11 +1,4 @@ -import { Ripemd160, sha256 } from "@cosmjs/crypto"; -import { - fromBase64, - fromBech32, - fromHex, - toBech32, - toHex, -} from "@cosmjs/encoding"; +import { fromBech32, fromHex, toBech32, toHex } from "@cosmjs/encoding"; import type { AddressReturnType } from "lib/app-provider"; import type { BechAddr, HexAddr, Option } from "lib/types"; @@ -48,21 +41,3 @@ export const hexToBech32Address = ( const strip = padHexAddress(hexAddr, length).slice(2); return toBech32(prefix, fromHex(strip)) as BechAddr; }; - -export function consensusPubkeyToHexAddress(consensusPubkey?: { - "@type": string; - key: string; -}) { - if (!consensusPubkey) return ""; - const raw = ""; - if (consensusPubkey["@type"] === "/cosmos.crypto.ed25519.PubKey") { - const pubkey = fromBase64(consensusPubkey.key); - if (pubkey) return toHex(sha256(pubkey)).slice(0, 40).toUpperCase(); - } - - if (consensusPubkey["@type"] === "/cosmos.crypto.secp256k1.PubKey") { - const pubkey = fromBase64(consensusPubkey.key); - if (pubkey) return toHex(new Ripemd160().update(sha256(pubkey)).digest()); - } - return raw; -} diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 7afd8c2dd..16c7296fd 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1,4 +1,5 @@ export * from "./address"; +export * from "./array"; export * from "./assetValue"; export * from "./base64"; export * from "./bech32"; @@ -36,6 +37,6 @@ export * from "./truncate"; export * from "./tx"; export * from "./txHash"; export * from "./validate"; +export * from "./validator"; export * from "./window"; export * from "./zod"; -export * from "./array"; diff --git a/src/lib/utils/validator.ts b/src/lib/utils/validator.ts new file mode 100644 index 000000000..30784749d --- /dev/null +++ b/src/lib/utils/validator.ts @@ -0,0 +1,32 @@ +import { Ripemd160, sha256 } from "@cosmjs/crypto"; +import { fromBase64, fromHex, toBech32, toHex } from "@cosmjs/encoding"; + +import type { ConsensusPubkey } from "lib/types"; +import { zConsensusAddr } from "lib/types"; + +export const convertRawConsensusAddrToConsensusAddr = ( + rawConsensusAddr: string, + prefix: string +) => { + const data = fromBase64(rawConsensusAddr); + return zConsensusAddr.parse(toBech32(`${prefix}valcons`, data)); +}; + +export const convertConsensusPubkeyToConsensusAddr = ( + consensusPubkey: ConsensusPubkey, + prefix: string +) => { + if (consensusPubkey["@type"] === "/cosmos.crypto.ed25519.PubKey") { + const pubkey = fromBase64(consensusPubkey.key); + const data = fromHex(toHex(sha256(pubkey)).slice(0, 40)); + return zConsensusAddr.parse(toBech32(`${prefix}valcons`, data)); + } + + if (consensusPubkey["@type"] === "/cosmos.crypto.secp256k1.PubKey") { + const pubkey = fromBase64(consensusPubkey.key); + const data = new Ripemd160().update(sha256(pubkey)).digest(); + return zConsensusAddr.parse(toBech32(`${prefix}valcons`, data)); + } + + return zConsensusAddr.parse(""); +}; From a32d9c44beda3ee1d509ed6604bd53d898ba9016 Mon Sep 17 00:00:00 2001 From: songwongtp <16089160+songwongtp@users.noreply.github.com> Date: Tue, 11 Jun 2024 00:13:15 +0700 Subject: [PATCH 05/12] fix: remove if --- src/lib/pages/block-details/data.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/pages/block-details/data.ts b/src/lib/pages/block-details/data.ts index adb39f57e..aced800b8 100644 --- a/src/lib/pages/block-details/data.ts +++ b/src/lib/pages/block-details/data.ts @@ -22,13 +22,14 @@ export const useBlockDataWithValidatorLcd = ( (validator) => validator.consensusAddress === blockData.proposerConsensusAddr ); - if (!found) return null; - return { - validatorAddress: found.validatorAddress, - moniker: found.moniker, - identity: found.identity, - }; + return found + ? { + validatorAddress: found.validatorAddress, + moniker: found.moniker, + identity: found.identity, + } + : null; }, [blockData, validators]); return { From 0e11ad86dc44e8dd8bebb20ff287add597dae220 Mon Sep 17 00:00:00 2001 From: evilpeach Date: Tue, 11 Jun 2024 10:49:40 +0700 Subject: [PATCH 06/12] feat: show error pruned if block is too old --- src/lib/pages/block-details/lite.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/pages/block-details/lite.tsx b/src/lib/pages/block-details/lite.tsx index a80b17e8e..7ffff1238 100644 --- a/src/lib/pages/block-details/lite.tsx +++ b/src/lib/pages/block-details/lite.tsx @@ -1,6 +1,8 @@ import { Breadcrumb } from "lib/components/Breadcrumb"; import { Loading } from "lib/components/Loading"; +import { InvalidState } from "lib/components/state"; import { UserDocsLink } from "lib/components/UserDocsLink"; +import { useLatestBlockLcd } from "lib/services/block"; import { BlockDetailsTop, BlockInfo, BlockTxsTableLite } from "./components"; import { InvalidBlock } from "./components/InvalidBlock"; @@ -8,8 +10,11 @@ import { useBlockDataWithValidatorLcd } from "./data"; export const BlockDetailsLite = ({ height }: { height: number }) => { const { data, isLoading } = useBlockDataWithValidatorLcd(height); + const { data: latestHeight } = useLatestBlockLcd(); if (isLoading) return ; + if (latestHeight && latestHeight > height && !data) + return ; if (!data) return ; return ( <> From e6a8612865ff1759099a74336e90567d720d9379 Mon Sep 17 00:00:00 2001 From: evilpeach Date: Tue, 11 Jun 2024 11:14:05 +0700 Subject: [PATCH 07/12] fix: extract sender --- src/lib/services/types/block.ts | 13 +++++++------ src/lib/services/types/tx.ts | 8 +++----- src/lib/utils/tx/extractSender.ts | 13 +++++++++++++ src/lib/utils/tx/index.ts | 1 + 4 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 src/lib/utils/tx/extractSender.ts diff --git a/src/lib/services/types/block.ts b/src/lib/services/types/block.ts index 3dd3fc6ec..769afa429 100644 --- a/src/lib/services/types/block.ts +++ b/src/lib/services/types/block.ts @@ -1,7 +1,6 @@ import { z } from "zod"; import type { - BechAddr, Block, BlockData, Message, @@ -16,7 +15,12 @@ import { zUtcDate, zValidatorAddr, } from "lib/types"; -import { createTxHash, parseTxHash, snakeToCamel } from "lib/utils"; +import { + createTxHash, + extractSender, + parseTxHash, + snakeToCamel, +} from "lib/utils"; import { zTx } from "./tx"; @@ -111,10 +115,7 @@ export const zBlockDataResponseLcd = zBlockLcd const transactions = val.txs.map((tx, idx) => { const txBody = tx.body; const message = txBody.messages[0]; - const sender = (message?.sender || - message?.signer || - message?.fromAddress || - "") as BechAddr; + const sender = extractSender(message); const messages = txBody.messages.map((msg) => ({ log: undefined, diff --git a/src/lib/services/types/tx.ts b/src/lib/services/types/tx.ts index a86c80489..fa32c602f 100644 --- a/src/lib/services/types/tx.ts +++ b/src/lib/services/types/tx.ts @@ -7,7 +7,7 @@ import type { } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import { z } from "zod"; -import type { BechAddr, Message, Transaction } from "lib/types"; +import type { Message, Transaction } from "lib/types"; import { ActionMsgType, MsgFurtherAction, @@ -18,6 +18,7 @@ import { zUtcDate, } from "lib/types"; import { + extractSender, extractTxLogs, getActionMsgType, getMsgFurtherAction, @@ -148,10 +149,7 @@ export const zTxsResponseItemFromLcd = zTxResponse.transform( (val) => { const txBody = val.tx.body; const message = txBody.messages[0]; - const sender = (message?.sender || - message?.signer || - message?.fromAddress || - "") as BechAddr; + const sender = extractSender(message); const logs = extractTxLogs(val); diff --git a/src/lib/utils/tx/extractSender.ts b/src/lib/utils/tx/extractSender.ts new file mode 100644 index 000000000..ce42c5f4b --- /dev/null +++ b/src/lib/utils/tx/extractSender.ts @@ -0,0 +1,13 @@ +import type { BechAddr, MessageResponse } from "lib/types"; + +// TODO: we should extract the sender from the message depending on the message type +export const extractSender = (message: MessageResponse): BechAddr => + (message?.sender || + message?.signer || + message?.fromAddress || + message?.delegatorAddress || + message?.proposer || + message?.depositor || + message?.submitter || + message?.voter || + "") as BechAddr; diff --git a/src/lib/utils/tx/index.ts b/src/lib/utils/tx/index.ts index 7b63d2246..3fba43fe5 100644 --- a/src/lib/utils/tx/index.ts +++ b/src/lib/utils/tx/index.ts @@ -1,5 +1,6 @@ export * from "./composeMsg"; export * from "./createTxHash"; +export * from "./extractSender"; export * from "./extractTxDetails"; export * from "./extractTxLogs"; export * from "./findAttr"; From 889c875f2c8e869c87e608076078aa6134c4efa4 Mon Sep 17 00:00:00 2001 From: evilpeach Date: Tue, 11 Jun 2024 11:24:15 +0700 Subject: [PATCH 08/12] fix: layout --- .../block-details/components/BlockInfo.tsx | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/lib/pages/block-details/components/BlockInfo.tsx b/src/lib/pages/block-details/components/BlockInfo.tsx index 4d9f7ab7a..9482d7bd6 100644 --- a/src/lib/pages/block-details/components/BlockInfo.tsx +++ b/src/lib/pages/block-details/components/BlockInfo.tsx @@ -20,28 +20,43 @@ export const BlockInfo = ({ blockData }: BlockInfoProps) => { Block Info - - - - {currentChainId} - - - {isFullTier && ( + {isFullTier ? ( + + + + {currentChainId} + {`${blockData.gasUsed ? formatInteger(blockData.gasUsed) : 0} / ${ blockData.gasLimit ? formatInteger(blockData.gasLimit) : 0 }`} - )} + + + + - - - - + ) : ( + + + {currentChainId} + + + + + + )} ); }; From 6795abc1ef5cb78fcb8599d5d89af347a02c0bda Mon Sep 17 00:00:00 2001 From: evilpeach Date: Wed, 12 Jun 2024 04:20:22 +0700 Subject: [PATCH 09/12] feat: add convertAccountPubkeyToAddress --- src/lib/pages/block-details/data.ts | 2 +- src/lib/pages/past-txs/lite.tsx | 1 + src/lib/services/block/index.ts | 25 +++++--- src/lib/services/tx/index.ts | 88 ++++++++++++++++++++++------- src/lib/services/types/block.ts | 18 ++---- src/lib/services/types/tx.ts | 24 ++++---- src/lib/types/account.ts | 9 +++ src/lib/types/tx/transaction.ts | 6 +- src/lib/types/validator.ts | 6 +- src/lib/utils/address.ts | 31 +++++++++- src/lib/utils/tx/extractSender.ts | 13 ----- src/lib/utils/tx/index.ts | 1 - 12 files changed, 147 insertions(+), 77 deletions(-) delete mode 100644 src/lib/utils/tx/extractSender.ts diff --git a/src/lib/pages/block-details/data.ts b/src/lib/pages/block-details/data.ts index aced800b8..c360a8b48 100644 --- a/src/lib/pages/block-details/data.ts +++ b/src/lib/pages/block-details/data.ts @@ -20,7 +20,7 @@ export const useBlockDataWithValidatorLcd = ( const found = validators.find( (validator) => - validator.consensusAddress === blockData.proposerConsensusAddr + validator.consensusAddress === blockData.proposerConsensusAddress ); return found diff --git a/src/lib/pages/past-txs/lite.tsx b/src/lib/pages/past-txs/lite.tsx index cd40343de..af235862b 100644 --- a/src/lib/pages/past-txs/lite.tsx +++ b/src/lib/pages/past-txs/lite.tsx @@ -45,6 +45,7 @@ export const PastTxsLite = () => { pageSize, offset, { + enabled: !!address, onSuccess: ({ total }) => setTotalData(total), } ); diff --git a/src/lib/services/block/index.ts b/src/lib/services/block/index.ts index a004b56d0..60d663dbf 100644 --- a/src/lib/services/block/index.ts +++ b/src/lib/services/block/index.ts @@ -8,8 +8,11 @@ import { useLcdEndpoint, } from "lib/app-provider"; import type { BlocksResponse } from "lib/services/types"; -import type { BlockData } from "lib/types"; -import { convertRawConsensusAddrToConsensusAddr } from "lib/utils"; +import type { BlockData, ConsensusAddr, Transaction } from "lib/types"; +import { + convertAccountPubkeyToAccountAddress, + convertRawConsensusAddrToConsensusAddr, +} from "lib/utils"; import { getBlockData, getBlocks } from "./api"; import { getBlockDataLcd, getLatestBlockLcd } from "./lcd"; @@ -47,19 +50,25 @@ export const useBlockDataLcd = (height: number) => { chain: { bech32_prefix: prefix }, } = useCurrentChain(); - return useQuery( + return useQuery<{ + block: BlockData; + proposerConsensusAddress: ConsensusAddr; + transactions: Transaction[]; + }>( [CELATONE_QUERY_KEYS.BLOCK_DATA_LCD, endpoint, height], async () => { - const { rawProposerConsensusAddress, ...rest } = await getBlockDataLcd( - endpoint, - height - ); + const { rawProposerConsensusAddress, transactions, ...rest } = + await getBlockDataLcd(endpoint, height); return { ...rest, - proposerConsensusAddr: convertRawConsensusAddrToConsensusAddr( + proposerConsensusAddress: convertRawConsensusAddrToConsensusAddr( rawProposerConsensusAddress, prefix ), + transactions: transactions.map((tx) => ({ + ...tx, + sender: convertAccountPubkeyToAccountAddress(tx.signerPubkey, prefix), + })), }; }, { diff --git a/src/lib/services/tx/index.ts b/src/lib/services/tx/index.ts index e4744935e..72cdce6db 100644 --- a/src/lib/services/tx/index.ts +++ b/src/lib/services/tx/index.ts @@ -6,13 +6,13 @@ import type { AccountTxsResponse, BlockTxsResponse, TxData, - TxsByAddressResponseLcd, TxsResponse, } from "../types/tx"; import { CELATONE_QUERY_KEYS, useBaseApiRoute, useCelatoneApp, + useCurrentChain, useInitia, useLcdEndpoint, useMoveConfig, @@ -25,9 +25,15 @@ import type { BechAddr20, BechAddr32, Option, + Transaction, TxFilters, } from "lib/types"; -import { extractTxLogs, isTxHash, snakeToCamel } from "lib/utils"; +import { + convertAccountPubkeyToAccountAddress, + extractTxLogs, + isTxHash, + snakeToCamel, +} from "lib/utils"; import { getTxData, @@ -238,11 +244,31 @@ export const useTxsByContractAddressLcd = ( address: BechAddr32, limit: number, offset: number, - options: UseQueryOptions = {} + options: UseQueryOptions = {} ) => { const endpoint = useLcdEndpoint(); + const { + chain: { bech32_prefix: prefix }, + } = useCurrentChain(); - return useQuery( + const queryfn = useCallback( + () => + getTxsByContractAddressLcd(endpoint, address, limit, offset).then( + (txs) => ({ + items: txs.items.map((tx) => ({ + ...tx, + sender: convertAccountPubkeyToAccountAddress( + tx.signerPubkey, + prefix + ), + })), + total: txs.total, + }) + ), + [address, endpoint, limit, offset, prefix] + ); + + return useQuery( [ CELATONE_QUERY_KEYS.TXS_BY_CONTRACT_ADDRESS_LCD, endpoint, @@ -250,7 +276,7 @@ export const useTxsByContractAddressLcd = ( limit, offset, ], - async () => getTxsByContractAddressLcd(endpoint, address, limit, offset), + queryfn, { retry: 1, refetchOnWindowFocus: false, ...options } ); }; @@ -284,34 +310,56 @@ export const useTxsByAddressLcd = ( search: string, limit: number, offset: number, - options: Pick, "onSuccess"> = {} + options: UseQueryOptions = {} ) => { const endpoint = useLcdEndpoint(); const { validateContractAddress } = useValidateAddress(); + const { + chain: { bech32_prefix: prefix }, + } = useCurrentChain(); - const queryfn = useCallback(() => { - if (isTxHash(search)) return getTxsByHashLcd(endpoint, search); + const queryfn = useCallback(async () => { + const txs = await (async () => { + if (isTxHash(search)) return getTxsByHashLcd(endpoint, search); - if (!validateContractAddress(search)) - return getTxsByContractAddressLcd( - endpoint, - search as BechAddr32, - offset, - limit - ); + if (!validateContractAddress(search)) + return getTxsByContractAddressLcd( + endpoint, + search as BechAddr32, + limit, + offset + ); - if (!address) throw new Error("address is undefined (useTxsByAddressLcd)"); - return getTxsByAccountAddressLcd(endpoint, address, limit, offset); - }, [address, endpoint, limit, offset, search, validateContractAddress]); + if (!address) + throw new Error("address is undefined (useTxsByAddressLcd)"); + return getTxsByAccountAddressLcd(endpoint, address, limit, offset); + })(); - return useQuery( + return { + items: txs.items.map((tx) => ({ + ...tx, + sender: convertAccountPubkeyToAccountAddress(tx.signerPubkey, prefix), + })), + total: txs.total, + }; + }, [ + address, + endpoint, + limit, + offset, + prefix, + search, + validateContractAddress, + ]); + + return useQuery( [ CELATONE_QUERY_KEYS.TXS_BY_ADDRESS_LCD, endpoint, address, search, - offset, limit, + offset, ], queryfn, { ...options, retry: 1, refetchOnWindowFocus: false } diff --git a/src/lib/services/types/block.ts b/src/lib/services/types/block.ts index 769afa429..3e7926dde 100644 --- a/src/lib/services/types/block.ts +++ b/src/lib/services/types/block.ts @@ -4,8 +4,7 @@ import type { Block, BlockData, Message, - Pagination, - Transaction, + TransactionWithSignerPubkey, Validator, } from "lib/types"; import { @@ -15,12 +14,7 @@ import { zUtcDate, zValidatorAddr, } from "lib/types"; -import { - createTxHash, - extractSender, - parseTxHash, - snakeToCamel, -} from "lib/utils"; +import { createTxHash, parseTxHash, snakeToCamel } from "lib/utils"; import { zTx } from "./tx"; @@ -105,8 +99,7 @@ export const zBlockDataResponseLcd = zBlockLcd .transform<{ block: BlockData; rawProposerConsensusAddress: string; - transactions: Transaction[]; - pagination: Pagination; + transactions: TransactionWithSignerPubkey[]; }>((val) => { // 1. Create Tx Hashes const txHashes = val.block.data.txs.map(createTxHash); @@ -114,8 +107,6 @@ export const zBlockDataResponseLcd = zBlockLcd // 2. Parse Tx to Transaction const transactions = val.txs.map((tx, idx) => { const txBody = tx.body; - const message = txBody.messages[0]; - const sender = extractSender(message); const messages = txBody.messages.map((msg) => ({ log: undefined, @@ -125,7 +116,7 @@ export const zBlockDataResponseLcd = zBlockLcd return { hash: txHashes[idx], messages, - sender, + signerPubkey: tx.authInfo.signerInfos[0].publicKey, isSigner: true, height: val.block.header.height, created: val.block.header.time, @@ -153,6 +144,5 @@ export const zBlockDataResponseLcd = zBlockLcd block, rawProposerConsensusAddress: val.block.header.proposerAddress, transactions, - pagination: val.pagination, }; }); diff --git a/src/lib/services/types/tx.ts b/src/lib/services/types/tx.ts index fa32c602f..024cb9e92 100644 --- a/src/lib/services/types/tx.ts +++ b/src/lib/services/types/tx.ts @@ -7,18 +7,22 @@ import type { } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import { z } from "zod"; -import type { Message, Transaction } from "lib/types"; +import type { + Message, + Transaction, + TransactionWithSignerPubkey, +} from "lib/types"; import { ActionMsgType, MsgFurtherAction, zBechAddr, zCoin, zMessageResponse, + zPubkey, zUint8Schema, zUtcDate, } from "lib/types"; import { - extractSender, extractTxLogs, getActionMsgType, getMsgFurtherAction, @@ -53,10 +57,7 @@ zModeInfo = z.object({ const zSignerInfo = z .object({ - public_key: z.object({ - "@type": z.string(), - key: z.string(), - }), + public_key: zPubkey, mode_info: zModeInfo.optional(), sequence: z.string(), }) @@ -145,11 +146,9 @@ export interface TxData extends TxResponse { isTxFailed: boolean; } -export const zTxsResponseItemFromLcd = zTxResponse.transform( - (val) => { +export const zTxsResponseItemFromLcd = + zTxResponse.transform((val) => { const txBody = val.tx.body; - const message = txBody.messages[0]; - const sender = extractSender(message); const logs = extractTxLogs(val); @@ -161,7 +160,7 @@ export const zTxsResponseItemFromLcd = zTxResponse.transform( return { hash: val.txhash, messages, - sender, + signerPubkey: val.tx.authInfo.signerInfos[0].publicKey, isSigner: true, height: Number(val.height), created: val.timestamp, @@ -173,8 +172,7 @@ export const zTxsResponseItemFromLcd = zTxResponse.transform( isOpinit: false, isInstantiate: false, }; - } -); + }); export const zTxsByAddressResponseLcd = z .object({ diff --git a/src/lib/types/account.ts b/src/lib/types/account.ts index e24662efb..c1095046e 100644 --- a/src/lib/types/account.ts +++ b/src/lib/types/account.ts @@ -1,3 +1,5 @@ +import { z } from "zod"; + export enum AccountType { BaseAccount = "BaseAccount", InterchainAccount = "InterchainAccount", @@ -10,3 +12,10 @@ export enum AccountType { PermanentLockedAccount = "PermanentLockedAccount", BaseVestingAccount = "BaseVestingAccount", } + +export const zPubkey = z.object({ + "@type": z.string(), + key: z.string(), +}); + +export type Pubkey = z.infer; diff --git a/src/lib/types/tx/transaction.ts b/src/lib/types/tx/transaction.ts index 60085740b..e076850b0 100644 --- a/src/lib/types/tx/transaction.ts +++ b/src/lib/types/tx/transaction.ts @@ -1,7 +1,7 @@ import type { Log } from "@cosmjs/stargate/build/logs"; import { z } from "zod"; -import type { BechAddr, Option } from "lib/types"; +import type { BechAddr, Option, Pubkey } from "lib/types"; export enum ActionMsgType { SINGLE_ACTION_MSG = "SINGLE_ACTION_MSG", @@ -41,6 +41,10 @@ export interface Transaction { isOpinit: boolean; } +export type TransactionWithSignerPubkey = Omit & { + signerPubkey: Pubkey; +}; + /* Filter for INITIA */ export interface InitiaTxFilters { isOpinit: boolean; diff --git a/src/lib/types/validator.ts b/src/lib/types/validator.ts index dc744d756..25fcabad6 100644 --- a/src/lib/types/validator.ts +++ b/src/lib/types/validator.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { snakeToCamel } from "lib/utils/formatter/snakeToCamel"; import { formatUrl } from "lib/utils/formatter/url"; +import { zPubkey } from "./account"; import { zBechAddr20, zConsensusAddr, zValidatorAddr } from "./addrs"; import { zBig } from "./big"; import type { Ratio } from "./currency"; @@ -43,10 +44,7 @@ export const zValidatorData = z })); export type ValidatorData = z.infer; -export const zConsensusPubkey = z.object({ - "@type": z.string(), - key: z.string(), -}); +export const zConsensusPubkey = zPubkey; export type ConsensusPubkey = z.infer; export enum BlockVote { diff --git a/src/lib/utils/address.ts b/src/lib/utils/address.ts index 2828fa933..9e9b64579 100644 --- a/src/lib/utils/address.ts +++ b/src/lib/utils/address.ts @@ -1,7 +1,15 @@ -import { fromBech32, fromHex, toBech32, toHex } from "@cosmjs/encoding"; +import { Ripemd160, sha256 } from "@cosmjs/crypto"; +import { + fromBase64, + fromBech32, + fromHex, + toBech32, + toHex, +} from "@cosmjs/encoding"; import type { AddressReturnType } from "lib/app-provider"; -import type { BechAddr, HexAddr, Option } from "lib/types"; +import { zBechAddr20 } from "lib/types"; +import type { BechAddr, HexAddr, Option, Pubkey } from "lib/types"; import { sha256Hex } from "./sha256"; @@ -41,3 +49,22 @@ export const hexToBech32Address = ( const strip = padHexAddress(hexAddr, length).slice(2); return toBech32(prefix, fromHex(strip)) as BechAddr; }; + +export const convertAccountPubkeyToAccountAddress = ( + accountPubkey: Pubkey, + prefix: string +) => { + if (accountPubkey["@type"] === "/cosmos.crypto.ed25519.PubKey") { + const pubkey = fromBase64(accountPubkey.key); + const data = fromHex(toHex(sha256(pubkey)).slice(0, 40)); + return zBechAddr20.parse(toBech32(prefix, data)); + } + + if (accountPubkey["@type"] === "/cosmos.crypto.secp256k1.PubKey") { + const pubkey = fromBase64(accountPubkey.key); + const data = new Ripemd160().update(sha256(pubkey)).digest(); + return zBechAddr20.parse(toBech32(prefix, data)); + } + + return zBechAddr20.parse(""); +}; diff --git a/src/lib/utils/tx/extractSender.ts b/src/lib/utils/tx/extractSender.ts deleted file mode 100644 index ce42c5f4b..000000000 --- a/src/lib/utils/tx/extractSender.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { BechAddr, MessageResponse } from "lib/types"; - -// TODO: we should extract the sender from the message depending on the message type -export const extractSender = (message: MessageResponse): BechAddr => - (message?.sender || - message?.signer || - message?.fromAddress || - message?.delegatorAddress || - message?.proposer || - message?.depositor || - message?.submitter || - message?.voter || - "") as BechAddr; diff --git a/src/lib/utils/tx/index.ts b/src/lib/utils/tx/index.ts index 3fba43fe5..7b63d2246 100644 --- a/src/lib/utils/tx/index.ts +++ b/src/lib/utils/tx/index.ts @@ -1,6 +1,5 @@ export * from "./composeMsg"; export * from "./createTxHash"; -export * from "./extractSender"; export * from "./extractTxDetails"; export * from "./extractTxLogs"; export * from "./findAttr"; From 9aca0e761774fe0d54407765476eac19da54f279 Mon Sep 17 00:00:00 2001 From: evilpeach Date: Wed, 12 Jun 2024 04:32:56 +0700 Subject: [PATCH 10/12] fix: change pruned wording --- src/lib/pages/block-details/lite.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/pages/block-details/lite.tsx b/src/lib/pages/block-details/lite.tsx index 7ffff1238..b2ca2a15b 100644 --- a/src/lib/pages/block-details/lite.tsx +++ b/src/lib/pages/block-details/lite.tsx @@ -1,6 +1,6 @@ import { Breadcrumb } from "lib/components/Breadcrumb"; import { Loading } from "lib/components/Loading"; -import { InvalidState } from "lib/components/state"; +import { EmptyState } from "lib/components/state"; import { UserDocsLink } from "lib/components/UserDocsLink"; import { useLatestBlockLcd } from "lib/services/block"; @@ -14,7 +14,15 @@ export const BlockDetailsLite = ({ height }: { height: number }) => { if (isLoading) return ; if (latestHeight && latestHeight > height && !data) - return ; + return ( + + ); if (!data) return ; return ( <> From 94978fe03ad5c83b3494bced4abc6b07dea2b1eb Mon Sep 17 00:00:00 2001 From: evilpeach Date: Wed, 12 Jun 2024 13:03:47 +0700 Subject: [PATCH 11/12] fix: add height in empty state --- src/lib/pages/block-details/lite.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/pages/block-details/lite.tsx b/src/lib/pages/block-details/lite.tsx index b2ca2a15b..1a301898d 100644 --- a/src/lib/pages/block-details/lite.tsx +++ b/src/lib/pages/block-details/lite.tsx @@ -16,7 +16,7 @@ export const BlockDetailsLite = ({ height }: { height: number }) => { if (latestHeight && latestHeight > height && !data) return ( Date: Wed, 12 Jun 2024 14:47:29 +0700 Subject: [PATCH 12/12] fix: as comments --- src/lib/pages/block-details/lite.tsx | 5 +++-- src/lib/services/tx/api.ts | 2 +- src/lib/services/tx/index.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/pages/block-details/lite.tsx b/src/lib/pages/block-details/lite.tsx index 1a301898d..a647d15a9 100644 --- a/src/lib/pages/block-details/lite.tsx +++ b/src/lib/pages/block-details/lite.tsx @@ -10,9 +10,10 @@ import { useBlockDataWithValidatorLcd } from "./data"; export const BlockDetailsLite = ({ height }: { height: number }) => { const { data, isLoading } = useBlockDataWithValidatorLcd(height); - const { data: latestHeight } = useLatestBlockLcd(); + const { data: latestHeight, isLoading: isLatestHeightLoading } = + useLatestBlockLcd(); - if (isLoading) return ; + if (isLoading || isLatestHeightLoading) return ; if (latestHeight && latestHeight > height && !data) return (