diff --git a/declarations.d.ts b/declarations.d.ts index 008bf9c3d2..a466b8ab9f 100644 --- a/declarations.d.ts +++ b/declarations.d.ts @@ -88,3 +88,11 @@ type MsgCoin = { denom: string; amount: string; } + +type ContractMessage = { + method: string; + success: boolean; + transaction_hash: string; + timestamp: string; + height: number; +} diff --git a/public/locales/en/accounts.json b/public/locales/en/accounts.json index 78c6eb6f31..c68827ee49 100644 --- a/public/locales/en/accounts.json +++ b/public/locales/en/accounts.json @@ -33,5 +33,9 @@ "creationTime": "Creation Time", "bio": "Bio", "status": "Status", - "completionTime": "Completion Time" + "completionTime": "Completion Time", + "smartContractDetails": "Smart Contract Details", + "deployer": "Deployer", + "label": "Label", + "codeId": "Code Id" } diff --git a/public/locales/en/transactions.json b/public/locales/en/transactions.json index 82d55015af..6797b8ce75 100644 --- a/public/locales/en/transactions.json +++ b/public/locales/en/transactions.json @@ -34,5 +34,6 @@ "feegrant": "Feegrant", "vesting": "Vesting", "logs": "Logs", - "arguments": "Arguments" + "arguments": "Arguments", + "method": "Method" } diff --git a/src/components/cosmwasm/contract_messages/hooks.ts b/src/components/cosmwasm/contract_messages/hooks.ts new file mode 100644 index 0000000000..438432ef99 --- /dev/null +++ b/src/components/cosmwasm/contract_messages/hooks.ts @@ -0,0 +1,81 @@ +import { useState } from 'react'; +import * as R from 'ramda'; +import axios from 'axios'; +import { + CosmWasmExecutesDocument, +} from '@graphql/cosmwasm'; +import { ContractMessagesState } from './types'; + +const LIMIT = 50; + +export const useMessages = (addr: string) => { + const fetchMessages = async (address: string, offset: number, limit: number) => { + return axios.post(process.env.NEXT_PUBLIC_GRAPHQL_URL, { + variables: { + address, + offset, + limit, + }, + query: CosmWasmExecutesDocument, + }); + }; + + const fetchMessagesCompletionHandler = (data: any) => { + const extractedData = R.pathOr([], ['data', 'cosmwasm_execute'], data); + const itemsLength = extractedData.length; + if (itemsLength === 0) { + return; + } + const newItems = R.uniq([...state.data, ...formatMessages(extractedData)]); + const stateChange = { + data: newItems, + hasNextPage: itemsLength === LIMIT, + isNextPageLoading: false, + offsetCount: state.offsetCount + itemsLength, + }; + + handleSetState(stateChange); + }; + + const [state, setState] = useState({ + data: [], + hasNextPage: false, + isNextPageLoading: false, + offsetCount: 0, + }); + + const handleSetState = (stateChange: any) => { + setState((prevState) => R.mergeDeepLeft(stateChange, prevState)); + }; + + fetchMessages(addr, state.offsetCount, LIMIT).then(({ data }) => { + fetchMessagesCompletionHandler(data); + }); + + const loadNextPage = async () => { + handleSetState({ + isNextPageLoading: true, + }); + // refetch query + fetchMessages(addr, state.offsetCount, LIMIT).then(({ data }) => { + fetchMessagesCompletionHandler(data); + }); + }; + + const formatMessages = (data: any) => { + return data.map((x) => { + return ({ + height: x.transaction.block.height, + transaction_hash: x.transaction_hash, + method: x.method, + success: x.success, + timestamp: x.transaction.block.timestamp, + }); + }); + }; + + return ({ + state, + loadNextPage, + }); +}; diff --git a/src/components/cosmwasm/contract_messages/index.tsx b/src/components/cosmwasm/contract_messages/index.tsx new file mode 100644 index 0000000000..6167446845 --- /dev/null +++ b/src/components/cosmwasm/contract_messages/index.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import classnames from 'classnames'; +import { Typography } from '@material-ui/core'; +import useTranslation from 'next-translate/useTranslation'; +import { + ContractMessagesList, + Box, +} from '@components'; +import { useStyles } from './styles'; +import { useMessages } from './hooks'; + +type ContractMessagesComponent = { + className?: string; + address: string; +} + +const ContractMessages: React.FC = (props) => { + const classes = useStyles(); + const { t } = useTranslation('transactions'); + + const { + state, + loadNextPage, + } = useMessages(props.address); + + const loadMoreItems = state.isNextPageLoading ? () => null : loadNextPage; + const isItemLoaded = (index) => !state.hasNextPage || index < state.data.length; + const itemCount = state.hasNextPage ? state.data.length + 1 : state.data.length; + + return ( + + + {t('messages')} + +
+ +
+
+ ); +}; + +export default ContractMessages; diff --git a/src/components/cosmwasm/contract_messages/styles.ts b/src/components/cosmwasm/contract_messages/styles.ts new file mode 100644 index 0000000000..65cc0f4b6c --- /dev/null +++ b/src/components/cosmwasm/contract_messages/styles.ts @@ -0,0 +1,24 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = () => { + const styles = makeStyles( + (theme) => { + return ({ + root: { + '& .MuiTypography-h2': { + marginBottom: theme.spacing(2), + }, + }, + list: { + minHeight: '500px', + height: '50vh', + [theme.breakpoints.up('lg')]: { + minHeight: '65vh', + }, + }, + }); + }, + )(); + + return styles; +}; diff --git a/src/components/cosmwasm/contract_messages/types.ts b/src/components/cosmwasm/contract_messages/types.ts new file mode 100644 index 0000000000..b4f4697d59 --- /dev/null +++ b/src/components/cosmwasm/contract_messages/types.ts @@ -0,0 +1,6 @@ +export type ContractMessagesState = { + hasNextPage: boolean; + isNextPageLoading: boolean; + offsetCount: number; + data: ContractMessage[]; + } diff --git a/src/components/cosmwasm/contract_messages_list/components/desktop/index.tsx b/src/components/cosmwasm/contract_messages_list/components/desktop/index.tsx new file mode 100644 index 0000000000..c5ba3783b7 --- /dev/null +++ b/src/components/cosmwasm/contract_messages_list/components/desktop/index.tsx @@ -0,0 +1,196 @@ +import React from 'react'; +import classnames from 'classnames'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import numeral from 'numeral'; +import dayjs from '@utils/dayjs'; +import Link from 'next/link'; +import { + TRANSACTION_DETAILS, + BLOCK_DETAILS, +} from '@utils/go_to_page'; +import InfiniteLoader from 'react-window-infinite-loader'; +import { VariableSizeGrid as Grid } from 'react-window'; +import { Typography } from '@material-ui/core'; +import useTranslation from 'next-translate/useTranslation'; +import { mergeRefs } from '@utils/merge_refs'; +import { + Loading, Result, +} from '@components'; +import { useGrid } from '@hooks'; +import { getMiddleEllipsis } from '@utils/get_middle_ellipsis'; +import { ContractMessagesListState } from '../../types'; +import { columns } from './utils'; +import { useStyles } from './styles'; + +const Desktop: React.FC = ({ + className, + itemCount, + loadMoreItems, + isItemLoaded, + messages, +}) => { + const { + gridRef, + columnRef, + onResize, + getColumnWidth, + getRowHeight, + } = useGrid(columns); + + const classes = useStyles(); + const { t } = useTranslation('transactions'); + + const items = messages.map((x) => ({ + block: ( + + + {numeral(x.height).format('0,0')} + + + ), + hash: ( + + + {getMiddleEllipsis(x.transaction_hash, { + beginning: 20, ending: 15, + })} + + + ), + result: ( + + ), + time: dayjs.utc(x.timestamp).fromNow(), + method: x.method, + })); + return ( +
+ + {({ + height, width, + }) => { + return ( + <> + {/* ======================================= */} + {/* Table Header */} + {/* ======================================= */} + getColumnWidth(width, index)} + height={50} + rowCount={1} + rowHeight={() => 50} + width={width} + > + {({ + columnIndex, style, + }) => { + const { + key, align, + } = columns[columnIndex]; + + return ( +
+ + {t(key)} + +
+ ); + }} +
+ {/* ======================================= */} + {/* Table Body */} + {/* ======================================= */} + + {({ + onItemsRendered, ref, + }) => { + return ( + { + onItemsRendered({ + overscanStartIndex: overscanRowStartIndex, + overscanStopIndex: overscanRowStopIndex, + visibleStartIndex: visibleRowStartIndex, + visibleStopIndex: visibleRowStopIndex, + }); + }} + ref={mergeRefs(gridRef, ref)} + columnCount={columns.length} + columnWidth={(index) => getColumnWidth(width, index)} + height={height - 50} + rowCount={itemCount} + rowHeight={getRowHeight} + width={width} + className="scrollbar" + > + {({ + columnIndex, rowIndex, style, + }) => { + if (!isItemLoaded(rowIndex) && columnIndex === 0) { + return ( +
+ +
+ ); + } + + if (!isItemLoaded(rowIndex)) { + return null; + } + + const { + key, align, + } = columns[columnIndex]; + const item = items[rowIndex][key]; + return ( +
+ + {item} + +
+ ); + }} +
+ ); + }} +
+ + ); + }} +
+
+ ); +}; + +export default Desktop; diff --git a/src/components/cosmwasm/contract_messages_list/components/desktop/styles.ts b/src/components/cosmwasm/contract_messages_list/components/desktop/styles.ts new file mode 100644 index 0000000000..cdbe426728 --- /dev/null +++ b/src/components/cosmwasm/contract_messages_list/components/desktop/styles.ts @@ -0,0 +1,21 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = () => { + const styles = makeStyles( + (theme) => { + return ({ + root: { + height: '100%', + }, + cell: { + ...theme.mixins.tableCell, + }, + body: { + color: theme.palette.custom.fonts.fontTwo, + }, + }); + }, + )(); + + return styles; +}; diff --git a/src/components/cosmwasm/contract_messages_list/components/desktop/utils.ts b/src/components/cosmwasm/contract_messages_list/components/desktop/utils.ts new file mode 100644 index 0000000000..b0e21a7450 --- /dev/null +++ b/src/components/cosmwasm/contract_messages_list/components/desktop/utils.ts @@ -0,0 +1,29 @@ +export const columns:{ + key: string; + align?: 'left' | 'center' | 'right' | 'justify' | 'inherit'; + width: number; +}[] = [ + { + key: 'block', + width: 15, + }, + { + key: 'hash', + width: 30, + }, + { + key: 'method', + align: 'right', + width: 15, + }, + { + key: 'result', + align: 'right', + width: 20, + }, + { + key: 'time', + align: 'right', + width: 20, + }, +]; diff --git a/src/components/cosmwasm/contract_messages_list/components/index.ts b/src/components/cosmwasm/contract_messages_list/components/index.ts new file mode 100644 index 0000000000..3f1746a986 --- /dev/null +++ b/src/components/cosmwasm/contract_messages_list/components/index.ts @@ -0,0 +1,7 @@ +import Desktop from './desktop'; +import Mobile from './mobile'; + +export { + Desktop, + Mobile, +}; diff --git a/src/components/cosmwasm/contract_messages_list/components/mobile/index.tsx b/src/components/cosmwasm/contract_messages_list/components/mobile/index.tsx new file mode 100644 index 0000000000..460ab442a7 --- /dev/null +++ b/src/components/cosmwasm/contract_messages_list/components/mobile/index.tsx @@ -0,0 +1,127 @@ +import React from 'react'; +import classnames from 'classnames'; +import numeral from 'numeral'; +import dayjs from '@utils/dayjs'; +import Link from 'next/link'; +import { + TRANSACTION_DETAILS, + BLOCK_DETAILS, +} from '@utils/go_to_page'; +import { + Typography, Divider, +} from '@material-ui/core'; +import { VariableSizeList as List } from 'react-window'; +import InfiniteLoader from 'react-window-infinite-loader'; +import AutoSizer from 'react-virtualized-auto-sizer'; + +import { mergeRefs } from '@utils/merge_refs'; +import { + SingleContractMessageMobile, + Loading, + Result, +} from '@components'; +import { + useList, + useListRow, +} from '@hooks'; +import { getMiddleEllipsis } from '@utils/get_middle_ellipsis'; +import { useStyles } from './styles'; +import { ContractMessagesListState } from '../../types'; + +const Mobile: React.FC = ({ + className, + itemCount, + loadMoreItems, + isItemLoaded, + messages, +}) => { + const classes = useStyles(); + + const { + listRef, + getRowHeight, + setRowHeight, + } = useList(); + + const items = messages.map((x) => ({ + block: ( + + + {numeral(x.height).format('0,0')} + + + ), + hash: ( + + + {getMiddleEllipsis(x.transaction_hash, { + beginning: 15, ending: 5, + })} + + + ), + result: ( + + ), + time: dayjs.utc(x.timestamp).fromNow(), + method: x.method, + })); + + return ( +
+ + {({ + height, width, + }) => { + return ( + + {({ + onItemsRendered, ref, + }) => ( + + {({ + index, style, + }) => { + const { rowRef } = useListRow(index, setRowHeight); + if (!isItemLoaded(index)) { + return ( +
+
+ +
+
+ ); + } + const item = items[index]; + return ( +
+
+ + {index !== itemCount - 1 && } +
+
+ ); + }} +
+ )} +
+ ); + }} +
+
+ ); +}; + +export default Mobile; diff --git a/src/components/cosmwasm/contract_messages_list/components/mobile/styles.ts b/src/components/cosmwasm/contract_messages_list/components/mobile/styles.ts new file mode 100644 index 0000000000..310137787b --- /dev/null +++ b/src/components/cosmwasm/contract_messages_list/components/mobile/styles.ts @@ -0,0 +1,15 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = () => { + const styles = makeStyles( + () => { + return ({ + root: { + height: '100%', + }, + }); + }, + )(); + + return styles; +}; diff --git a/src/components/cosmwasm/contract_messages_list/index.tsx b/src/components/cosmwasm/contract_messages_list/index.tsx new file mode 100644 index 0000000000..f5b04cc934 --- /dev/null +++ b/src/components/cosmwasm/contract_messages_list/index.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import dynamic from 'next/dynamic'; +import { NoData } from '@components'; +import { useScreenSize } from '@hooks'; +import { useStyles } from './styles'; +import { ContractMessagesListState } from './types'; + +const Desktop = dynamic(() => import('./components/desktop')); +const Mobile = dynamic(() => import('./components/mobile')); + +const ContractMessagesList: React.FC = (props) => { + const { isDesktop } = useScreenSize(); + // setting fallback values + const { + hasNextPage = false, + isNextPageLoading = false, + loadNextPage = () => null, + loadMoreItems = () => null, + isItemLoaded = () => true, + itemCount, + messages, + } = props; + const classes = useStyles(); + + const formatProps = { + hasNextPage, + isNextPageLoading, + isItemLoaded, + loadNextPage, + loadMoreItems, + itemCount, + messages, + }; + + if (!itemCount) { + return ( + + ); + } + + return ( + <> + {isDesktop ? ( + + ) : ( + + )} + + ); +}; + +export default ContractMessagesList; diff --git a/src/components/cosmwasm/contract_messages_list/styles.ts b/src/components/cosmwasm/contract_messages_list/styles.ts new file mode 100644 index 0000000000..ac675be276 --- /dev/null +++ b/src/components/cosmwasm/contract_messages_list/styles.ts @@ -0,0 +1,23 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = () => { + const styles = makeStyles( + (theme) => { + return ({ + mobile: { + [theme.breakpoints.up('lg')]: { + display: 'none', + }, + }, + desktop: { + display: 'none', + [theme.breakpoints.up('lg')]: { + display: 'block', + }, + }, + }); + }, { index: 1 }, + )(); + + return styles; +}; diff --git a/src/components/cosmwasm/contract_messages_list/types.ts b/src/components/cosmwasm/contract_messages_list/types.ts new file mode 100644 index 0000000000..959561d7a6 --- /dev/null +++ b/src/components/cosmwasm/contract_messages_list/types.ts @@ -0,0 +1,10 @@ +export type ContractMessagesListState = { + className?: string; + hasNextPage?: boolean; + isNextPageLoading?: boolean; + loadNextPage?: (any) => void; + loadMoreItems?: (any) => void; + isItemLoaded?: (index: number) => boolean; + itemCount: number; + messages: ContractMessage[]; + } diff --git a/src/components/cosmwasm/contract_overview/index.tsx b/src/components/cosmwasm/contract_overview/index.tsx new file mode 100644 index 0000000000..1157c7b354 --- /dev/null +++ b/src/components/cosmwasm/contract_overview/index.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import classnames from 'classnames'; +import useTranslation from 'next-translate/useTranslation'; +import { + Typography, +} from '@material-ui/core'; +import { useScreenSize } from '@hooks'; +import { getMiddleEllipsis } from '@utils/get_middle_ellipsis'; +import { Box } from '@components'; +import { useStyles } from './styles'; + +const ContractOverview: React.FC<{ + className?: string; + address: string; + deployerAddress: string; + label: string; + codeId: string; +}> = ({ + className, + address, + deployerAddress, + label, + codeId, +}) => { + const { isDesktop } = useScreenSize(); + const classes = useStyles(); + const { t } = useTranslation('accounts'); + + return ( + <> + +
+ + {t('address')} + +
+ + { + !isDesktop ? ( + getMiddleEllipsis(address, { + beginning: 15, ending: 5, + }) + ) : ( + address + ) + } + +
+
+ +
+ + {t('deployer')} + +
+ + { + !isDesktop ? ( + getMiddleEllipsis(deployerAddress, { + beginning: 15, ending: 5, + }) + ) : ( + deployerAddress + ) + } + +
+
+ +
+ + {`${t('label')}: `} + { + !isDesktop ? ( + getMiddleEllipsis(label, { + beginning: 15, ending: 5, + }) + ) : ( + label + ) + } + +
+ +
+ + {`${t('codeId')}: `} + { + !isDesktop ? ( + getMiddleEllipsis(codeId, { + beginning: 15, ending: 5, + }) + ) : ( + codeId + ) + } + +
+
+ + ); +}; + +export default ContractOverview; diff --git a/src/components/cosmwasm/contract_overview/styles.ts b/src/components/cosmwasm/contract_overview/styles.ts new file mode 100644 index 0000000000..5d6356001a --- /dev/null +++ b/src/components/cosmwasm/contract_overview/styles.ts @@ -0,0 +1,81 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = () => { + const styles = makeStyles( + (theme) => { + return ({ + root: { + [theme.breakpoints.up('md')]: { + display: 'grid', + gridTemplateColumns: 'repeat(2,1fr)', + }, + }, + dialog: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + '& .MuiTypography-body1': { + marginBottom: theme.spacing(2), + }, + '& .dialog__share--wrapper': { + marginTop: theme.spacing(2), + }, + '& .share-buttons': { + '&:not(:last-child)': { + marginRight: theme.spacing(1), + }, + '&.email': { + '& circle': { + fill: theme.palette.primary.main, + }, + }, + }, + }, + actionIcons: { + '&:hover': { + cursor: 'pointer', + }, + }, + icons: { + '& svg': { + width: theme.spacing(4.5), + height: theme.spacing(4.5), + }, + }, + item: { + padding: theme.spacing(2, 0), + color: theme.palette.custom.fonts.fontTwo, + '&:first-child': { + paddingTop: 0, + }, + '&:last-child': { + paddingBottom: 0, + }, + '&:not(:last-child)': { + borderBottom: `solid 1px ${theme.palette.divider}`, + }, + '& .label': { + marginBottom: theme.spacing(1), + }, + '& .detail': { + '&.MuiTypography-body1': { + wordWrap: 'break-word', + }, + }, + [theme.breakpoints.up('md')]: { + padding: 0, + '&:not(:last-child)': { + borderBottom: 'none', + }, + '& .label': { + marginBottom: 0, + }, + }, + }, + }); + }, + )(); + + return styles; +}; diff --git a/src/components/cosmwasm/index.ts b/src/components/cosmwasm/index.ts new file mode 100644 index 0000000000..7ffff257b0 --- /dev/null +++ b/src/components/cosmwasm/index.ts @@ -0,0 +1,11 @@ +import ContractOverview from './contract_overview'; +import ContractMessagesList from './contract_messages_list'; +import ContractMessages from './contract_messages'; +import SingleContractMessageMobile from './single_contract_message_mobile'; + +export { + ContractOverview, + ContractMessagesList, + ContractMessages, + SingleContractMessageMobile, +}; diff --git a/src/components/cosmwasm/single_contract_message_mobile/index.tsx b/src/components/cosmwasm/single_contract_message_mobile/index.tsx new file mode 100644 index 0000000000..ab6cca6472 --- /dev/null +++ b/src/components/cosmwasm/single_contract_message_mobile/index.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import classnames from 'classnames'; +import { + Typography, +} from '@material-ui/core'; +import useTranslation from 'next-translate/useTranslation'; +import { useStyles } from './styles'; + +const SingleContractMessageMobile:React.FC<{ + className?: string; + block: React.ReactNode; + hash: React.ReactNode; + time: string; + method: string; + result?: React.ReactNode; +}> = ({ + className, block, hash, time, method, result, +}) => { + const { t } = useTranslation('transactions'); + const classes = useStyles(); + + return ( +
+
+ + {t('block')} + + {block} +
+
+ + {t('hash')} + + + {hash} + +
+
+ {!!method && ( +
+ + {t('method')} + + + {method} + +
+ )} +
+ + {t('result')} + + {result} +
+
+
+ + {t('time')} + + + {time} + +
+
+ ); +}; + +export default SingleContractMessageMobile; diff --git a/src/components/cosmwasm/single_contract_message_mobile/styles.ts b/src/components/cosmwasm/single_contract_message_mobile/styles.ts new file mode 100644 index 0000000000..ec4f05a727 --- /dev/null +++ b/src/components/cosmwasm/single_contract_message_mobile/styles.ts @@ -0,0 +1,37 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = () => { + const styles = makeStyles( + (theme) => { + return ({ + root: { + marginBottom: theme.spacing(2), + marginTop: theme.spacing(2), + }, + item: { + marginBottom: theme.spacing(2), + '& .label': { + marginBottom: theme.spacing(1), + color: theme.palette.custom.fonts.fontThree, + }, + '& p.value': { + color: theme.palette.custom.fonts.fontTwo, + }, + '& a': { + color: theme.palette.custom.fonts.highlight, + }, + }, + flex: { + display: 'flex', + alignItems: 'flex-start', + justifyContent: 'flex-start', + '& > div': { + width: '50%', + }, + }, + }); + }, + )(); + + return styles; +}; diff --git a/src/components/index.ts b/src/components/index.ts index 6be254cb91..92ca482452 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -29,6 +29,9 @@ import LoadAndExist from './load_and_exist'; import DesmosProfile from './desmos_profile'; import TransactionListDetails from './transactions_list_details'; import AvatarNameListMsg from './avatar_name_list_msg'; +import { + ContractOverview, ContractMessages, ContractMessagesList, SingleContractMessageMobile, +} from './cosmwasm'; export { Layout, @@ -62,4 +65,8 @@ export { DesmosProfile, TransactionListDetails, AvatarNameListMsg, + ContractOverview, + ContractMessages, + ContractMessagesList, + SingleContractMessageMobile, }; diff --git a/src/graphql/cosmwasm.ts b/src/graphql/cosmwasm.ts new file mode 100644 index 0000000000..828b48ac5a --- /dev/null +++ b/src/graphql/cosmwasm.ts @@ -0,0 +1,28 @@ +export const CosmWasmInstantiateDocument = /* GraphQL */` +query CosmWasmInstantiate($address: String!) { + cosmwasm_instantiate(where: {result_contract_address: {_eq: $address}}, limit: 1) { + admin + result_contract_address + label + sender + success + code_id + } +} +`; + +export const CosmWasmExecutesDocument = /* GraphQL */` +query CosmWasmExecutes($address: String!, $offset: Int, $limit: Int) { + cosmwasm_execute(where: {contract: {_eq: $address}}, offset: $offset, limit: $limit) { + method + success + transaction_hash + transaction { + block { + timestamp + height + } + } + } +} +`; diff --git a/src/models/msg/cosmwasm/msg_clear_contract_admin.ts b/src/models/msg/cosmwasm/msg_clear_contract_admin.ts index 91c98e6e2f..5d5af4a651 100644 --- a/src/models/msg/cosmwasm/msg_clear_contract_admin.ts +++ b/src/models/msg/cosmwasm/msg_clear_contract_admin.ts @@ -1,7 +1,7 @@ import * as R from 'ramda'; - import { Categories } from '../types'; +import { Categories } from '../types'; - class MsgClearContractAdmin { +class MsgClearContractAdmin { public category: Categories; public type: string; public json: any; @@ -9,7 +9,6 @@ import * as R from 'ramda'; public contract: string; constructor(payload: any) { - console.log("MsgClearContractAdmin payload ", payload); this.category = 'cosmwasm'; this.type = payload.type; this.json = payload.json; @@ -18,7 +17,6 @@ import * as R from 'ramda'; } static fromJson(json: any) { - console.log("MsgClearContractAdmin json ", json); return new MsgClearContractAdmin({ json, type: json['@type'], @@ -26,6 +24,6 @@ import * as R from 'ramda'; contract: R.pathOr('', ['contract'], json), }); } - } +} - export default MsgClearContractAdmin; \ No newline at end of file +export default MsgClearContractAdmin; diff --git a/src/models/msg/cosmwasm/msg_execute_contract.ts b/src/models/msg/cosmwasm/msg_execute_contract.ts index 141ff30497..b38d78150c 100644 --- a/src/models/msg/cosmwasm/msg_execute_contract.ts +++ b/src/models/msg/cosmwasm/msg_execute_contract.ts @@ -21,13 +21,13 @@ class MsgExecuteContract { } static fromJson(json: any) { - json["msg_decoded"] = atob(json["msg"]) + json.msg_decoded = atob(json.msg); return new MsgExecuteContract({ - contract: json.contract, - msg: json.msg, - sender: json.sender, - type: json["@type"], - json: json, + contract: json.contract, + msg: json.msg, + sender: json.sender, + type: json['@type'], + json, }); } diff --git a/src/models/msg/cosmwasm/msg_instantiate_contract.ts b/src/models/msg/cosmwasm/msg_instantiate_contract.ts index 5a178dfe6c..0c57aa15ff 100644 --- a/src/models/msg/cosmwasm/msg_instantiate_contract.ts +++ b/src/models/msg/cosmwasm/msg_instantiate_contract.ts @@ -1,7 +1,7 @@ import * as R from 'ramda'; - import { Categories } from '../types'; +import { Categories } from '../types'; - class MsgInstantiateContract { +class MsgInstantiateContract { public category: Categories; public type: string; public json: any; @@ -36,6 +36,6 @@ import * as R from 'ramda'; })), }); } - } +} - export default MsgInstantiateContract; \ No newline at end of file +export default MsgInstantiateContract; diff --git a/src/models/msg/cosmwasm/msg_migrate_contract.ts b/src/models/msg/cosmwasm/msg_migrate_contract.ts index aa5911a3f2..9526d7a127 100644 --- a/src/models/msg/cosmwasm/msg_migrate_contract.ts +++ b/src/models/msg/cosmwasm/msg_migrate_contract.ts @@ -1,7 +1,7 @@ import * as R from 'ramda'; - import { Categories } from '../types'; +import { Categories } from '../types'; - class MsgMigrateContract { +class MsgMigrateContract { public category: Categories; public type: string; public json: any; @@ -30,6 +30,6 @@ import * as R from 'ramda'; migrateMsg: R.pathOr('', ['migrate_msg'], json), }); } - } +} - export default MsgMigrateContract; \ No newline at end of file +export default MsgMigrateContract; diff --git a/src/models/msg/cosmwasm/msg_store_code.ts b/src/models/msg/cosmwasm/msg_store_code.ts index 422b61ebe8..5e03ebe183 100644 --- a/src/models/msg/cosmwasm/msg_store_code.ts +++ b/src/models/msg/cosmwasm/msg_store_code.ts @@ -1,7 +1,7 @@ import * as R from 'ramda'; - import { Categories } from '../types'; +import { Categories } from '../types'; - class MsgStoreCode { +class MsgStoreCode { public category: Categories; public type: string; public json: any; @@ -24,6 +24,6 @@ import * as R from 'ramda'; wasmByteCode: R.pathOr('', ['wasm_byte_code'], json), }); } - } +} - export default MsgStoreCode; \ No newline at end of file +export default MsgStoreCode; diff --git a/src/models/msg/cosmwasm/msg_update_contract_admin.ts b/src/models/msg/cosmwasm/msg_update_contract_admin.ts index b521b40a7e..f37c491766 100644 --- a/src/models/msg/cosmwasm/msg_update_contract_admin.ts +++ b/src/models/msg/cosmwasm/msg_update_contract_admin.ts @@ -1,7 +1,7 @@ import * as R from 'ramda'; - import { Categories } from '../types'; +import { Categories } from '../types'; - class MsgUpdateContractAdmin { +class MsgUpdateContractAdmin { public category: Categories; public type: string; public json: any; @@ -27,6 +27,6 @@ import * as R from 'ramda'; contract: R.pathOr('', ['contract'], json), }); } - } +} - export default MsgUpdateContractAdmin; \ No newline at end of file +export default MsgUpdateContractAdmin; diff --git a/src/screens/account_details/components/index.ts b/src/screens/account_details/components/index.ts index 91858aa257..155ef1ad8b 100644 --- a/src/screens/account_details/components/index.ts +++ b/src/screens/account_details/components/index.ts @@ -3,6 +3,7 @@ import Balance from './balance'; import Staking from './staking'; import Transactions from './transactions'; import OtherTokens from './other_tokens'; +import SimpleBalance from './simple_balance'; export { Overview, @@ -10,4 +11,5 @@ export { Staking, Transactions, OtherTokens, + SimpleBalance, }; diff --git a/src/screens/account_details/components/simple_balance/index.tsx b/src/screens/account_details/components/simple_balance/index.tsx new file mode 100644 index 0000000000..796c4c4b9b --- /dev/null +++ b/src/screens/account_details/components/simple_balance/index.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import classnames from 'classnames'; +import Big from 'big.js'; +import numeral from 'numeral'; +import * as R from 'ramda'; +import { useRecoilValue } from 'recoil'; +import { readMarket } from '@recoil/market'; +import { + Typography, + Divider, +} from '@material-ui/core'; +import useTranslation from 'next-translate/useTranslation'; +import { Box } from '@components'; +import { chainConfig } from '@configs'; +import { formatNumber } from '@utils/format_token'; +import { useStyles } from './styles'; + +const Balance: React.FC<{ + className?: string; + total: TokenUnit; +}> = (props) => { + const { t } = useTranslation('accounts'); + const { + classes, + } = useStyles(); + const market = useRecoilValue(readMarket); + + const totalAmount = `$${numeral(Big(market.price || 0).times(props.total.value).toPrecision()).format('0,0.00')}`; + + // format + const totalDisplay = formatNumber(props.total.value, props.total.exponent); + + return ( + + + {t('balance')} + +
+ +
+
+ + {t('total', { + unit: props.total.displayDenom.toUpperCase(), + })} + + + {totalDisplay} + +
+
+ + $ + {numeral(market.price).format('0,0.[00]', Math.floor)} + {' '} + / + {' '} + {R.pathOr('', ['tokenUnits', chainConfig.primaryTokenUnit, 'display'], chainConfig).toUpperCase()} + + + {totalAmount} + +
+
+
+
+ ); +}; + +export default Balance; diff --git a/src/screens/account_details/components/simple_balance/styles.ts b/src/screens/account_details/components/simple_balance/styles.ts new file mode 100644 index 0000000000..ed62cb867a --- /dev/null +++ b/src/screens/account_details/components/simple_balance/styles.ts @@ -0,0 +1,98 @@ +import { + makeStyles, useTheme, +} from '@material-ui/core/styles'; + +export const useStyles = () => { + const defaultTheme = useTheme(); + const styles = makeStyles( + (theme) => { + return ({ + root: { + '& .MuiTypography-h2': { + marginBottom: theme.spacing(2), + }, + [theme.breakpoints.up('lg')]: { + display: 'flex', + flexDirection: 'column', + }, + }, + chart: { + height: '300px', + [theme.breakpoints.up('md')]: { + height: '200px', + width: '200px', + }, + [theme.breakpoints.up('lg')]: { + height: '150px', + width: '150px', + }, + }, + chartWrapper: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + [theme.breakpoints.up('md')]: { + flexDirection: 'row', + alignItems: 'center', + }, + }, + legends: { + color: theme.palette.custom.fonts.fontTwo, + '& .legends__single--container': { + marginBottom: theme.spacing(1), + [theme.breakpoints.up('md')]: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + }, + }, + '& .single__label--container': { + display: 'flex', + alignItems: 'center', + marginBottom: theme.spacing(0.5), + }, + '& .legend-color': { + width: theme.spacing(1.75), + height: theme.spacing(1.75), + borderRadius: '2px', + marginRight: theme.spacing(1), + }, + [theme.breakpoints.up('md')]: { + flex: 1, + marginLeft: theme.spacing(3), + }, + }, + divider: { + margin: theme.spacing(2, 0), + }, + total: { + '& .total__single--container': { + marginBottom: theme.spacing(1), + '& .label': { + marginBottom: theme.spacing(0.5), + color: theme.palette.custom.fonts.fontTwo, + [theme.breakpoints.up('md')]: { + color: theme.palette.custom.fonts.fontOne, + }, + }, + [theme.breakpoints.up('md')]: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + }, + }, + '& .total__secondary--container': { + [theme.breakpoints.up('md')]: { + color: theme.palette.custom.fonts.fontTwo, + }, + }, + }, + }); + }, + )(); + + return { + classes: styles, + theme: defaultTheme, + }; +}; diff --git a/src/screens/account_details/hooks.ts b/src/screens/account_details/hooks.ts index 3d24330d82..2feeeabbce 100644 --- a/src/screens/account_details/hooks.ts +++ b/src/screens/account_details/hooks.ts @@ -18,6 +18,7 @@ import { fetchDelegationBalance, fetchRewards, fetchUnbondingBalance, + fetchCosmWasmInstantiation, } from './utils'; const defaultTokenUnit: TokenUnit = { @@ -48,6 +49,14 @@ const initialState: AccountDetailState = { total: defaultTokenUnit, }, rewards: {}, + cosmwasm: { + admin: '', + code_id: '', + label: '', + result_contract_address: '', + sender: '', + success: false, + }, }; export const useAccountDetails = () => { @@ -105,6 +114,7 @@ export const useAccountDetails = () => { fetchDelegationBalance(address), fetchUnbondingBalance(address), fetchRewards(address), + fetchCosmWasmInstantiation(address), ]; const [ commission, @@ -112,6 +122,7 @@ export const useAccountDetails = () => { delegation, unbonding, rewards, + cosmWasmInstantiation, ] = await Promise.allSettled(promises); const formattedRawData: any = {}; @@ -120,8 +131,11 @@ export const useAccountDetails = () => { formattedRawData.delegationBalance = R.pathOr([], ['value', 'delegationBalance'], delegation); formattedRawData.unbondingBalance = R.pathOr([], ['value', 'unbondingBalance'], unbonding); formattedRawData.delegationRewards = R.pathOr([], ['value', 'delegationRewards'], rewards); - handleSetState(formatAllBalance(formattedRawData)); + + const rawData: any = {}; + rawData.cosmwasm = R.pathOr([], ['value'], cosmWasmInstantiation); + handleSetState(rawData); }; // ========================== diff --git a/src/screens/account_details/index.tsx b/src/screens/account_details/index.tsx index 8b27d728ba..ccfdf6e73d 100644 --- a/src/screens/account_details/index.tsx +++ b/src/screens/account_details/index.tsx @@ -4,6 +4,8 @@ import { Layout, LoadAndExist, DesmosProfile, + ContractOverview, + ContractMessages, } from '@components'; import { NextSeo } from 'next-seo'; import { useStyles } from './styles'; @@ -13,6 +15,7 @@ import { Staking, Transactions, OtherTokens, + SimpleBalance, } from './components'; import { useAccountDetails } from './hooks'; @@ -23,15 +26,17 @@ const AccountDetails = () => { state, } = useAccountDetails(); + const isSmartContract = state.cosmwasm.result_contract_address === state.overview.address; + return ( <> - + { coverUrl={state.desmosProfile.coverUrl} /> )} - - + {isSmartContract + ? ( + + ) + : ( + + )} + {isSmartContract + ? + : ( + + )} - - + {!isSmartContract + && ( + + )} + {isSmartContract + ? ( + + ) + : ( + + )} diff --git a/src/screens/account_details/types.ts b/src/screens/account_details/types.ts index c0f72a49a8..769bf06f70 100644 --- a/src/screens/account_details/types.ts +++ b/src/screens/account_details/types.ts @@ -23,6 +23,15 @@ export type RewardsType = { [value:string]: TokenUnit[]; } +export type CosmwasmType = { + admin: string; + code_id: string, + label: string, + result_contract_address: string, + sender: string, + success: boolean +} + export type AccountDetailState = { loading: boolean; exists: boolean; @@ -34,4 +43,5 @@ export type AccountDetailState = { count: number; }; rewards: RewardsType; + cosmwasm: CosmwasmType; } diff --git a/src/screens/account_details/utils.ts b/src/screens/account_details/utils.ts index c822220aa4..19a31a9276 100644 --- a/src/screens/account_details/utils.ts +++ b/src/screens/account_details/utils.ts @@ -10,6 +10,10 @@ import { AccountDelegationRewardsDocument, } from '@graphql/account_actions'; +import { + CosmWasmInstantiateDocument, +} from '@graphql/cosmwasm'; + export const fetchCommission = async (address: string) => { const defaultReturnValue = { commission: { @@ -121,3 +125,25 @@ export const fetchRewards = async (address: string) => { return defaultReturnValue; } }; + +export const fetchCosmWasmInstantiation = async (address: string) => { + const defaultReturnValue = { + admin: '', + code_id: '', + label: '', + result_contract_address: '', + sender: '', + success: true, + }; + try { + const { data } = await axios.post(process.env.NEXT_PUBLIC_GRAPHQL_URL, { + variables: { + address, + }, + query: CosmWasmInstantiateDocument, + }); + return R.pathOr(defaultReturnValue, ['data', 'cosmwasm_instantiate', 0], data); + } catch (error) { + return defaultReturnValue; + } +}; diff --git a/src/screens/params/utils.ts b/src/screens/params/utils.ts index 6748537c4f..1358043efc 100644 --- a/src/screens/params/utils.ts +++ b/src/screens/params/utils.ts @@ -84,7 +84,7 @@ export const formatMinting = (data: Minting, t: any) => { }, { label: t('inflationRateChange'), - detail: `${(data.inflationRateChange * 100).toFixed(20).replace(/\.?0+$/,"")}%`, + detail: `${(data.inflationRateChange * 100).toFixed(20).replace(/\.?0+$/, '')}%`, }, { label: t('mintDenom'), @@ -115,11 +115,10 @@ export const formatDistribution = (data: Distribution, t: any) => { }; export const formatGov = (data: Gov, t: any) => { - return ([ { label: t('minDeposit'), - detail: `${parseFloat(data.minDeposit.value).toFixed(20).replace(/\.?0+$/,"")} ${data.minDeposit.displayDenom.toUpperCase()}`, + detail: `${parseFloat(data.minDeposit.value).toFixed(20).replace(/\.?0+$/, '')} ${data.minDeposit.displayDenom.toUpperCase()}`, }, { label: t('maxDepositPeriod'), diff --git a/src/screens/proposal_details/components/overview/index.tsx b/src/screens/proposal_details/components/overview/index.tsx index c699974471..2adbf004c4 100644 --- a/src/screens/proposal_details/components/overview/index.tsx +++ b/src/screens/proposal_details/components/overview/index.tsx @@ -7,7 +7,6 @@ import useTranslation from 'next-translate/useTranslation'; import { useRecoilValue } from 'recoil'; import { readDate } from '@recoil/settings'; import JSONPretty from 'react-json-pretty'; -var JSONPrettyMon = require('react-json-pretty/dist/monikai'); import { Typography, Divider, @@ -26,6 +25,8 @@ import { import { useStyles } from './styles'; import { getProposalType } from '../../utils'; +const JSONPrettyMon = require('react-json-pretty/dist/monikai'); + const Overview: React.FC<{ className?: string; title: string; @@ -80,7 +81,7 @@ const Overview: React.FC<{ ); @@ -98,7 +99,7 @@ const Overview: React.FC<{ {t('upgradedClientState')} - + ); } @@ -183,4 +184,4 @@ const Overview: React.FC<{ ); }; -export default Overview; \ No newline at end of file +export default Overview;