Skip to content

Commit

Permalink
Display info for smart contract address
Browse files Browse the repository at this point in the history
  • Loading branch information
avalkov committed Apr 13, 2022
1 parent 83dd72c commit 907592e
Show file tree
Hide file tree
Showing 38 changed files with 1,309 additions and 62 deletions.
8 changes: 8 additions & 0 deletions declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,11 @@ type MsgCoin = {
denom: string;
amount: string;
}

type ContractMessage = {
method: string;
success: boolean;
transaction_hash: string;
timestamp: string;
height: number;
}
6 changes: 5 additions & 1 deletion public/locales/en/accounts.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
3 changes: 2 additions & 1 deletion public/locales/en/transactions.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@
"feegrant": "Feegrant",
"vesting": "Vesting",
"logs": "Logs",
"arguments": "Arguments"
"arguments": "Arguments",
"method": "Method"
}
81 changes: 81 additions & 0 deletions src/components/cosmwasm/contract_messages/hooks.ts
Original file line number Diff line number Diff line change
@@ -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<ContractMessagesState>({
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,
});
};
50 changes: 50 additions & 0 deletions src/components/cosmwasm/contract_messages/index.tsx
Original file line number Diff line number Diff line change
@@ -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<ContractMessagesComponent> = (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 (
<Box className={classnames(props.className, classes.root)}>
<Typography variant="h2">
{t('messages')}
</Typography>
<div className={classes.list}>
<ContractMessagesList
messages={state.data}
itemCount={itemCount}
hasNextPage={state.hasNextPage}
isNextPageLoading={state.isNextPageLoading}
loadNextPage={loadNextPage}
loadMoreItems={loadMoreItems}
isItemLoaded={isItemLoaded}
/>
</div>
</Box>
);
};

export default ContractMessages;
24 changes: 24 additions & 0 deletions src/components/cosmwasm/contract_messages/styles.ts
Original file line number Diff line number Diff line change
@@ -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;
};
6 changes: 6 additions & 0 deletions src/components/cosmwasm/contract_messages/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type ContractMessagesState = {
hasNextPage: boolean;
isNextPageLoading: boolean;
offsetCount: number;
data: ContractMessage[];
}
Original file line number Diff line number Diff line change
@@ -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<ContractMessagesListState> = ({
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: (
<Link href={BLOCK_DETAILS(x.height)} passHref>
<Typography variant="body1" component="a">
{numeral(x.height).format('0,0')}
</Typography>
</Link>
),
hash: (
<Link href={TRANSACTION_DETAILS(x.transaction_hash)} passHref>
<Typography variant="body1" component="a">
{getMiddleEllipsis(x.transaction_hash, {
beginning: 20, ending: 15,
})}
</Typography>
</Link>
),
result: (
<Result success={x.success} />
),
time: dayjs.utc(x.timestamp).fromNow(),
method: x.method,
}));
return (
<div className={classnames(className, classes.root)}>
<AutoSizer onResize={onResize}>
{({
height, width,
}) => {
return (
<>
{/* ======================================= */}
{/* Table Header */}
{/* ======================================= */}
<Grid
ref={columnRef}
columnCount={columns.length}
columnWidth={(index) => getColumnWidth(width, index)}
height={50}
rowCount={1}
rowHeight={() => 50}
width={width}
>
{({
columnIndex, style,
}) => {
const {
key, align,
} = columns[columnIndex];

return (
<div
style={style}
className={classes.cell}
>
<Typography
variant="h4"
align={align}
>
{t(key)}
</Typography>
</div>
);
}}
</Grid>
{/* ======================================= */}
{/* Table Body */}
{/* ======================================= */}
<InfiniteLoader
isItemLoaded={isItemLoaded}
itemCount={itemCount}
loadMoreItems={loadMoreItems}
>
{({
onItemsRendered, ref,
}) => {
return (
<Grid
onItemsRendered={({
visibleRowStartIndex,
visibleRowStopIndex,
overscanRowStopIndex,
overscanRowStartIndex,
}) => {
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 (
<div
style={{
...style,
width,
}}
>
<Loading />
</div>
);
}

if (!isItemLoaded(rowIndex)) {
return null;
}

const {
key, align,
} = columns[columnIndex];
const item = items[rowIndex][key];
return (
<div
style={style}
className={classnames(classes.cell, classes.body, {
odd: !(rowIndex % 2),
})}
>
<Typography
variant="body1"
align={align}
component="div"
>
{item}
</Typography>
</div>
);
}}
</Grid>
);
}}
</InfiniteLoader>
</>
);
}}
</AutoSizer>
</div>
);
};

export default Desktop;
Loading

0 comments on commit 907592e

Please sign in to comment.