Merge pull request #4 from dydxprotocol/transfers-history-table

[TRCL-1842] Transfers history table
This commit is contained in:
aleka
2023-08-25 14:13:53 -04:00
committed by GitHub
17 changed files with 350 additions and 55 deletions
+1 -1
View File
@@ -35,7 +35,7 @@
"@cosmjs/encoding": "^0.31.0",
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@dydxprotocol/abacus": "^0.2.28",
"@dydxprotocol/abacus": "^0.2.37",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-client": "^0.29.0",
"@ethersproject/providers": "^5.7.2",
+11 -11
View File
@@ -1,9 +1,5 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
'@0xsquid/sdk':
specifier: ^1.7.2
@@ -27,8 +23,8 @@ dependencies:
specifier: ^0.31.0
version: 0.31.0
'@dydxprotocol/abacus':
specifier: ^0.2.28
version: 0.2.28
specifier: ^0.2.37
version: 0.2.37
'@dydxprotocol/v4-client':
specifier: ^0.29.0
version: 0.29.0
@@ -1143,8 +1139,8 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true
/@dydxprotocol/abacus@0.2.28:
resolution: {integrity: sha512-mGmhwQM8aiV/cD+d3lvW76ysoPN7uVsDyAiVcRdzGqgj+gbM0IIJDYXG14xHruOTi+gK1Aga5MUz2pdvmPDhYA==}
/@dydxprotocol/abacus@0.2.37:
resolution: {integrity: sha512-amoq9aVXo+lVTQGu2jsyy5/0bniOMEXpyJDmaAr2TpbrtQtw2WgyAP+se9Q5wt3yh0Mzdtsh7P2h090kH7/bZQ==}
dev: false
/@dydxprotocol/dydxjs@0.3.0:
@@ -8277,7 +8273,7 @@ packages:
resolution: {integrity: sha512-WIdaQ8uW1vIbYvNnAVunkC6yxTrneJC7VQ5UUQ0kuw8b0C0A39KTIpoQHCfc8tV7o9vF4niwRhdXEdfAgQEsQQ==}
dependencies:
cosmos-directory-types: 0.0.6
node-fetch-native: 1.2.0
node-fetch-native: 1.4.0
dev: false
/cosmos-directory-types@0.0.6:
@@ -12462,8 +12458,8 @@ packages:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
/node-fetch-native@1.2.0:
resolution: {integrity: sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ==}
/node-fetch-native@1.4.0:
resolution: {integrity: sha512-F5kfEj95kX8tkDhUCYdV8dg3/8Olx/94zB8+ZNthFs6Bz31UpUi8Xh40TN3thLwXgrwXry1pEg9lJ++tLWTcqA==}
dev: false
/node-fetch@2.6.12:
@@ -15855,3 +15851,7 @@ packages:
release-it: 15.11.0
semver: 7.5.1
dev: false
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
+28
View File
@@ -0,0 +1,28 @@
import type { Story } from '@ladle/react';
import { CopyButton, type CopyButtonProps } from '@/components/CopyButton';
import { StoryWrapper } from '.ladle/components';
export const CopyButtonStory: Story<CopyButtonProps> = (args) => (
<StoryWrapper>
<CopyButton {...args} />
</StoryWrapper>
);
CopyButtonStory.args = {
value: 'some text to copy',
};
CopyButtonStory.argTypes = {
shownAsText: {
options: [true, false],
control: { type: 'select' },
defaultValue: false,
},
children: {
options: ['some text to copy'],
control: { type: 'select' },
defaultValue: undefined,
}
};
+65
View File
@@ -0,0 +1,65 @@
import { useState } from 'react';
import styled, { css, type AnyStyledComponent } from 'styled-components';
import { ButtonAction } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Button, ButtonProps } from './Button';
import { Icon, IconName } from './Icon';
export type CopyButtonProps = {
value?: string;
shownAsText?: boolean;
children?: React.ReactNode;
} & ButtonProps;
export const CopyButton = ({ value, shownAsText, children, ...buttonProps }: CopyButtonProps) => {
const stringGetter = useStringGetter();
const [copied, setCopied] = useState(false);
const onCopy = () => {
if (!value) return;
setCopied(true);
navigator.clipboard.writeText(value);
setTimeout(() => setCopied(false), 500);
};
return shownAsText ? (
<Styled.InlineRow onClick={onCopy} copied={copied}>
{children}
<Icon iconName={IconName.Copy} />
</Styled.InlineRow>
) : (
<Button
{...buttonProps}
action={copied ? ButtonAction.Create : ButtonAction.Primary}
onClick={onCopy}
>
<Icon iconName={IconName.Copy} />
{children ?? stringGetter({ key: copied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })}
</Button>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.InlineRow = styled.div<{ copied: boolean }>`
${layoutMixins.inlineRow}
cursor: pointer;
${({ copied }) =>
copied
? css`
filter: brightness(0.8);
`
: css`
&:hover {
filter: brightness(1.1);
text-decoration: underline;
}
`}
`;
+2
View File
@@ -112,6 +112,8 @@ export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill;
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
export type SubaccountFundingPayments =
Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment[];
export type SubaccountTransfer = Abacus.exchange.dydx.abacus.output.SubaccountTransfer;
export type SubaccountTransfers = Abacus.exchange.dydx.abacus.output.SubaccountTransfer[];
// ------ Historical PnL ------ //
export type SubAccountHistoricalPNL = Abacus.exchange.dydx.abacus.output.SubaccountHistoricalPNL;
+2
View File
@@ -177,6 +177,7 @@ export const APP_STRING_KEYS = {
RECEIVE: 'GENERAL.RECEIVE',
RECENT: 'GENERAL.RECENT',
RECENT_TRADES_SHORT: 'GENERAL.RECENT_TRADES_SHORT',
RECIPIENT: 'GENERAL.RECIPIENT',
REFERRAL_CODE: 'GENERAL.REFERRAL_CODE',
REFERRALS: 'GENERAL.REFERRALS',
REFERRER_PERCENT_OFF: 'GENERAL.REFERRER_PERCENT_OFF',
@@ -188,6 +189,7 @@ export const APP_STRING_KEYS = {
SELECT_NETWORK: 'GENERAL.SELECT_NETWORK',
SELL: 'GENERAL.SELL',
SEND: 'GENERAL.SEND',
SENDER: 'GENERAL.SENDER',
SHARE: 'GENERAL.SHARE',
SHORT_POSITION_SHORT: 'GENERAL.SHORT_POSITION_SHORT',
SIDE: 'GENERAL.SIDE',
+6
View File
@@ -19,6 +19,7 @@ import {
setFundingPayments,
setHistoricalPnl,
setSubaccount,
setTransfers,
setWallet,
} from '@/state/account';
@@ -102,6 +103,11 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
dispatch(setFundingPayments(fundingPayments));
}
if (changes.has(Changes.transfers)) {
const transfers = updatedState.subaccountTransfers(subaccountId)?.toArray() || [];
dispatch(setTransfers(transfers));
}
if (changes.has(Changes.historicalPnl)) {
const historicalPnl =
updatedState.subaccountHistoricalPnl(subaccountId)?.toArray() || [];
+2
View File
@@ -182,6 +182,7 @@
"RECEIVE": "Receive",
"RECENT": "Recent",
"RECENT_TRADES_SHORT": "Trades",
"RECIPIENT": "Recipient",
"REFERRAL_CODE": "Referral Code",
"REFERRALS": "Referrals",
"REFERRER_PERCENT_OFF": "{DISCOUNT}% off",
@@ -193,6 +194,7 @@
"SELECT_NETWORK": "Select Network",
"SELL": "Sell",
"SEND": "Send",
"SENDER": "Sender",
"SHARE": "Share",
"SHORT_POSITION_SHORT": "Short",
"SIDE": "Side",
+6 -5
View File
@@ -28,13 +28,14 @@ export const History = () => {
label: <h3>{stringGetter({ key: STRING_KEYS.TRADES })}</h3>,
href: HistoryRoute.Trades,
},
{
value: HistoryRoute.Transfers,
label: <h3>{stringGetter({ key: STRING_KEYS.TRANSFERS })}</h3>,
href: HistoryRoute.Transfers,
tag: 'USDC',
},
// TODO - TRCL-1693 -
// {
// value: HistoryRoute.Transfers,
// label: <h3>{stringGetter({ key: STRING_KEYS.TRANSFERS })}</h3>,
// href: HistoryRoute.Transfers,
// },
// {
// value: HistoryRoute.Payments,
// label: <h3>{stringGetter({ key: STRING_KEYS.PAYMENTS })}</h3>,
// href: HistoryRoute.Payments,
+5 -3
View File
@@ -9,6 +9,7 @@ import { useBreakpoints, useDocumentTitle, useStringGetter } from '@/hooks';
import { FillsTable, FillsTableColumnKey } from '@/views/tables/FillsTable';
import { FundingPaymentsTable } from '@/views/tables/FundingPaymentsTable';
import { TransferHistoryTable } from '@/views/tables/TransferHistoryTable';
import { Icon, IconName } from '@/components/Icon';
import { NavigationMenu } from '@/components/NavigationMenu';
import { WithSidebar } from '@/components/WithSidebar';
@@ -61,7 +62,10 @@ export default () => {
/>
}
/>
<Route path={HistoryRoute.Transfers} element={<div />} />
<Route
path={HistoryRoute.Transfers}
element={<TransferHistoryTable withOuterBorder={isNotTablet} />}
/>
<Route
path={HistoryRoute.Payments}
element={<FundingPaymentsTable withOuterBorder={isNotTablet} />}
@@ -118,8 +122,6 @@ export default () => {
},
],
},
// TODO(aforaleka) Add back subitems when there are clearer designs
// or when transfers and payments are ready
]}
/>
)
+6 -6
View File
@@ -40,12 +40,12 @@ export const PortfolioNavMobile = () => {
label: stringGetter({ key: STRING_KEYS.TRADES }),
description: stringGetter({ key: STRING_KEYS.TRADES_DESCRIPTION }),
},
// TODO: TRCL-1693 - re-enable when Payments and Transfers are ready
// {
// value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Transfers}`,
// label: stringGetter({ key: STRING_KEYS.TRANSFERS }),
// description: stringGetter({ key: STRING_KEYS.TRANSFERS_DESCRIPTION }),
// },
{
value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Transfers}`,
label: stringGetter({ key: STRING_KEYS.TRANSFERS }),
description: stringGetter({ key: STRING_KEYS.TRANSFERS_DESCRIPTION }),
},
// TODO: TRCL-1693 - re-enable when Payments are ready
// {
// value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Payments}`,
// label: stringGetter({ key: STRING_KEYS.PAYMENTS }),
+7
View File
@@ -9,6 +9,7 @@ import type {
SubaccountFundingPayments,
Wallet,
SubaccountOrder,
SubaccountTransfers,
HistoricalPnlPeriods,
SubAccountHistoricalPNLs,
} from '@/constants/abacus';
@@ -22,6 +23,7 @@ import { getLocalStorage } from '@/lib/localStorage';
export type AccountState = {
fills?: SubaccountFills;
fundingPayments?: SubaccountFundingPayments;
transfers?: SubaccountTransfers;
clearedOrderIds?: string[];
uncommittedOrderClientIds?: number[];
hasUnseenFillUpdates: boolean;
@@ -38,6 +40,7 @@ export type AccountState = {
const initialState: AccountState = {
fills: undefined,
fundingPayments: undefined,
transfers: undefined,
clearedOrderIds: undefined,
uncommittedOrderClientIds: undefined,
hasUnseenFillUpdates: false,
@@ -81,6 +84,9 @@ export const accountSlice = createSlice({
setFundingPayments: (state, action: PayloadAction<any>) => {
state.fundingPayments = action.payload;
},
setTransfers: (state, action: PayloadAction<any>) => {
state.transfers = action.payload;
},
clearOrder: (state, action: PayloadAction<string>) => ({
...state,
clearedOrderIds: [...(state.clearedOrderIds || []), action.payload],
@@ -148,6 +154,7 @@ export const accountSlice = createSlice({
export const {
setFills,
setFundingPayments,
setTransfers,
clearOrder,
setOnboardingGuard,
setOnboardingState,
+6
View File
@@ -193,6 +193,12 @@ export const getCurrentMarketFills = createSelector(
!currentMarketId ? [] : marketFills[currentMarketId]
);
/**
* @param state
* @returns list of transfers for the currently connected subaccount
*/
export const getSubaccountTransfers = (state: RootState) => state.account?.transfers;
/**
* @param state
* @returns list of funding payments for the currently connected subaccount
+4
View File
@@ -18,5 +18,9 @@ export const tradeViewMixins: Record<
tbody {
font: var(--font-small-book);
}
thead tr {
box-shadow: none;
}
`,
};
+2 -14
View File
@@ -9,7 +9,7 @@ import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { AlertMessage } from '@/components/AlertMessage';
import { Button } from '@/components/Button';
import { CopyButton } from '@/components/CopyButton';
import { Dialog } from '@/components/Dialog';
import { Checkbox } from '@/components/Checkbox';
import { Icon, IconName } from '@/components/Icon';
@@ -30,21 +30,12 @@ export const MnemonicExportDialog = ({ setIsOpen }: ElementProps) => {
const [hasAcknowledged, setHasAcknowledged] = useState(false);
const [currentStep, setCurrentStep] = useState(MnemonicExportStep.AcknowledgeRisk);
const [isShowing, setIsShowing] = useState(false);
const [copied, setCopied] = useState(false);
const stringGetter = useStringGetter();
const { hdKey } = useAccounts();
const { mnemonic } = hdKey ?? {};
const onCopy = () => {
setCopied(true);
if (mnemonic) {
navigator.clipboard.writeText(mnemonic);
}
setTimeout(() => setCopied(false), 500);
};
const title = {
[MnemonicExportStep.AcknowledgeRisk]: stringGetter({ key: STRING_KEYS.REVEAL_SECRET_PHRASE }),
[MnemonicExportStep.DisplayMnemonic]: stringGetter({ key: STRING_KEYS.EXPORT_SECRET_PHRASE }),
@@ -116,10 +107,7 @@ export const MnemonicExportDialog = ({ setIsOpen }: ElementProps) => {
</Styled.WordList>
}
>
<Button action={copied ? ButtonAction.Create : ButtonAction.Primary} onClick={onCopy}>
<Icon iconName={IconName.Copy} />
{stringGetter({ key: copied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })}
</Button>
<CopyButton value={mnemonic} />
</WithReceipt>
</>
),
+2 -15
View File
@@ -1,7 +1,6 @@
import { useState } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
import { ButtonAction } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DydxChainAsset } from '@/constants/wallets';
@@ -10,9 +9,8 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { useAccounts, useStringGetter } from '@/hooks';
import { AssetIcon } from '@/components/AssetIcon';
import { Button } from '@/components/Button';
import { CopyButton } from '@/components/CopyButton';
import { Dialog } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
import { QrCode } from '@/components/QrCode';
import { SelectItem, SelectMenu } from '@/components/SelectMenu';
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
@@ -31,14 +29,6 @@ export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.DYDX, setIsOpen }
const { dydxAddress } = useAccounts();
const [asset, setAsset] = useState(selectedAsset);
const [copied, setCopied] = useState(false);
const onCopy = () => {
if (!dydxAddress) return;
setCopied(true);
navigator.clipboard.writeText(dydxAddress);
setTimeout(() => setCopied(false), 500);
};
const assetOptions = [
{
@@ -89,10 +79,7 @@ export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.DYDX, setIsOpen }
>
<QrCode hasLogo value={dydxAddress!} />
</Styled.WithDetailsReceipt>
<Button action={copied ? ButtonAction.Create : ButtonAction.Primary} onClick={onCopy}>
<Icon iconName={IconName.Copy} />
{stringGetter({ key: copied ? STRING_KEYS.COPIED : STRING_KEYS.COPY_ADDRESS })}
</Button>
<CopyButton value={dydxAddress} />
</>
)}
</Styled.Content>
+195
View File
@@ -0,0 +1,195 @@
import styled, { type AnyStyledComponent, css } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import type { ColumnSize } from '@react-types/table';
import { type SubaccountTransfer } from '@/constants/abacus';
import { ButtonAction } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { tradeViewMixins } from '@/styles/tradeViewMixins';
import { Button } from '@/components/Button';
import { CopyButton } from '@/components/CopyButton';
import { Icon } from '@/components/Icon';
import { Link } from '@/components/Link';
import { Output, OutputType } from '@/components/Output';
import { Table, TableCell, TableColumnHeader, type ColumnDef } from '@/components/Table';
import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton';
import { getSubaccountTransfers } from '@/state/accountSelectors';
import { calculateCanAccountTrade } from '@/state/accountCalculators';
import { openDialog } from '@/state/dialogs';
import { truncateAddress } from '@/lib/wallet';
const MOBILE_TRANSFERS_PER_PAGE = 50;
export enum TransferHistoryTableColumnKey {
Time = 'Time',
Action = 'Action',
SenderRecipient = 'Sender-Recipient',
Amount = 'Amount',
TxHash = 'TxHash',
}
const getTransferHistoryTableColumnDef = ({
key,
stringGetter,
width,
}: {
key: TransferHistoryTableColumnKey;
isTablet?: boolean;
stringGetter: StringGetterFunction;
width?: ColumnSize;
}): ColumnDef<SubaccountTransfer> => ({
width,
...(
{
[TransferHistoryTableColumnKey.Time]: {
columnKey: TransferHistoryTableColumnKey.Time,
getCellValue: (row) => row.updatedAtMilliseconds,
label: stringGetter({ key: STRING_KEYS.TIME }),
renderCell: ({ updatedAtMilliseconds }) => (
<Styled.TimeOutput
type={OutputType.RelativeTime}
relativeTimeFormatOptions={{ format: 'singleCharacter' }}
value={updatedAtMilliseconds}
/>
),
},
[TransferHistoryTableColumnKey.Action]: {
columnKey: TransferHistoryTableColumnKey.Action,
getCellValue: (row) => row.resources.typeStringKey,
label: stringGetter({ key: STRING_KEYS.ACTION }),
renderCell: ({ resources }) =>
resources.typeStringKey && stringGetter({ key: resources.typeStringKey }),
},
[TransferHistoryTableColumnKey.SenderRecipient]: {
columnKey: TransferHistoryTableColumnKey.SenderRecipient,
getCellValue: (row) => `${row.fromAddress}-${row.toAddress}`,
label: (
<TableColumnHeader>
<span>{stringGetter({ key: STRING_KEYS.SENDER })}</span>
<span>{stringGetter({ key: STRING_KEYS.RECIPIENT })}</span>
</TableColumnHeader>
),
renderCell: ({ fromAddress, toAddress }) => (
<TableCell stacked>
<CopyButton shownAsText value={fromAddress ?? undefined}>
{fromAddress ? truncateAddress(fromAddress) : '-'}
</CopyButton>{' '}
<CopyButton shownAsText value={toAddress ?? undefined}>
{toAddress ? truncateAddress(toAddress) : '-'}
</CopyButton>
</TableCell>
),
},
[TransferHistoryTableColumnKey.Amount]: {
columnKey: TransferHistoryTableColumnKey.Amount,
getCellValue: (row) => row.amount,
label: stringGetter({ key: STRING_KEYS.AMOUNT }),
renderCell: ({ amount }) => <Output type={OutputType.Fiat} value={amount} />,
},
[TransferHistoryTableColumnKey.TxHash]: {
columnKey: TransferHistoryTableColumnKey.TxHash,
getCellValue: (row) => row.transactionHash,
label: stringGetter({ key: STRING_KEYS.TRANSACTION }),
renderCell: ({ transactionHash, resources }) =>
transactionHash ? (
<Styled.TxHash withIcon href={resources.blockExplorerUrl}>
{truncateAddress(transactionHash, '')}
</Styled.TxHash>
) : (
'-'
),
},
} as Record<TransferHistoryTableColumnKey, ColumnDef<SubaccountTransfer>>
)[key],
});
type ElementProps = {
columnKeys?: TransferHistoryTableColumnKey[];
columnWidths?: Partial<Record<TransferHistoryTableColumnKey, ColumnSize>>;
};
type StyleProps = {
withOuterBorder?: boolean;
withInnerBorders?: boolean;
};
export const TransferHistoryTable = ({
columnKeys = Object.values(TransferHistoryTableColumnKey),
columnWidths,
withOuterBorder,
withInnerBorders = true,
}: ElementProps & StyleProps) => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { isMobile, isTablet } = useBreakpoints();
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const transfers = useSelector(getSubaccountTransfers, shallowEqual) ?? [];
return (
<Styled.Table
label="Transfers"
data={isMobile ? transfers.slice(0, MOBILE_TRANSFERS_PER_PAGE) : transfers}
getRowKey={(row: SubaccountTransfer) => row.id}
columns={columnKeys.map((key: TransferHistoryTableColumnKey) =>
getTransferHistoryTableColumnDef({
key,
isTablet,
stringGetter,
width: columnWidths?.[key],
})
)}
slotEmpty={
<>
{stringGetter({ key: STRING_KEYS.TRANSFERS_EMPTY_STATE })}
{canAccountTrade ? (
<Button
action={ButtonAction.Primary}
onClick={() => dispatch(openDialog({ type: DialogTypes.Deposit }))}
>
{stringGetter({ key: STRING_KEYS.DEPOSIT_FUNDS })}
</Button>
) : (
<OnboardingTriggerButton />
)}
</>
}
selectionBehavior="replace"
withOuterBorder={withOuterBorder}
withInnerBorders={withInnerBorders}
withScrollSnapColumns
withScrollSnapRows
/>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Table = styled(Table)`
${tradeViewMixins.horizontalTable}
`;
Styled.InlineRow = styled.div`
${layoutMixins.inlineRow}
`;
Styled.Icon = styled(Icon)`
font-size: 3em;
`;
Styled.TimeOutput = styled(Output)`
color: var(--color-text-0);
`;
Styled.TxHash = styled(Link)`
justify-content: flex-end;
`;