feat(explorer): add epoch to block pages (#5817)
This commit is contained in:
parent
bc8a427788
commit
bf094288fd
@ -8,12 +8,14 @@ import EpochMissingOverview from './epoch-missing';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconProps } from '@vegaprotocol/ui-toolkit';
|
||||
import isPast from 'date-fns/isPast';
|
||||
import { EpochSymbol } from '../links/block-link/block-link';
|
||||
|
||||
const borderClass =
|
||||
'border-solid border-2 border-vega-dark-200 border-collapse';
|
||||
|
||||
export type EpochOverviewProps = {
|
||||
id?: string;
|
||||
icon?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -24,7 +26,7 @@ export type EpochOverviewProps = {
|
||||
*
|
||||
* The details are hidden in a tooltip, behind the epoch number
|
||||
*/
|
||||
const EpochOverview = ({ id }: EpochOverviewProps) => {
|
||||
const EpochOverview = ({ id, icon = true }: EpochOverviewProps) => {
|
||||
const { data, error, loading } = useExplorerEpochQuery({
|
||||
variables: { id: id || '' },
|
||||
});
|
||||
@ -38,7 +40,12 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
|
||||
}
|
||||
|
||||
if (!ti || loading || error) {
|
||||
return <span>{id}</span>;
|
||||
return (
|
||||
<span>
|
||||
<EpochSymbol />
|
||||
{id}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const description = (
|
||||
@ -90,7 +97,11 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
|
||||
return (
|
||||
<Tooltip description={description}>
|
||||
<p>
|
||||
<IconForEpoch start={ti.start} end={ti.end} />
|
||||
{icon ? (
|
||||
<IconForEpoch start={ti.start} end={ti.end} />
|
||||
) : (
|
||||
<EpochSymbol />
|
||||
)}
|
||||
{id}
|
||||
</p>
|
||||
</Tooltip>
|
||||
|
@ -0,0 +1,10 @@
|
||||
query ExplorerEpochForBlock($block: String!) {
|
||||
epoch(block: $block) {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
end
|
||||
lastBlock
|
||||
}
|
||||
}
|
||||
}
|
53
apps/explorer/src/app/components/links/block-link/__generated__/EpochByBlock.ts
generated
Normal file
53
apps/explorer/src/app/components/links/block-link/__generated__/EpochByBlock.ts
generated
Normal file
@ -0,0 +1,53 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerEpochForBlockQueryVariables = Types.Exact<{
|
||||
block: Types.Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerEpochForBlockQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, lastBlock?: string | null } } };
|
||||
|
||||
|
||||
export const ExplorerEpochForBlockDocument = gql`
|
||||
query ExplorerEpochForBlock($block: String!) {
|
||||
epoch(block: $block) {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
end
|
||||
lastBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerEpochForBlockQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerEpochForBlockQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerEpochForBlockQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerEpochForBlockQuery({
|
||||
* variables: {
|
||||
* block: // value for 'block'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerEpochForBlockQuery(baseOptions: Apollo.QueryHookOptions<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>(ExplorerEpochForBlockDocument, options);
|
||||
}
|
||||
export function useExplorerEpochForBlockLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>(ExplorerEpochForBlockDocument, options);
|
||||
}
|
||||
export type ExplorerEpochForBlockQueryHookResult = ReturnType<typeof useExplorerEpochForBlockQuery>;
|
||||
export type ExplorerEpochForBlockLazyQueryHookResult = ReturnType<typeof useExplorerEpochForBlockLazyQuery>;
|
||||
export type ExplorerEpochForBlockQueryResult = Apollo.QueryResult<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>;
|
@ -4,17 +4,56 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
import { useExplorerEpochForBlockQuery } from './__generated__/EpochByBlock';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
height: string;
|
||||
showEpoch?: boolean;
|
||||
};
|
||||
|
||||
const BlockLink = ({ height, ...props }: BlockLinkProps) => {
|
||||
const BlockLink = ({ height, showEpoch = false, ...props }: BlockLinkProps) => {
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
|
||||
<Hash text={height} />
|
||||
</Link>
|
||||
<>
|
||||
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
|
||||
<Hash text={height} />
|
||||
</Link>
|
||||
{showEpoch && <EpochForBlock block={height} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export function EpochForBlock(props: { block: string }) {
|
||||
const { error, data, loading } = useExplorerEpochForBlockQuery({
|
||||
errorPolicy: 'ignore',
|
||||
variables: { block: props.block },
|
||||
});
|
||||
|
||||
// NOTE: 0.73.x & <0.74.2 can error showing epoch, so for now we hide loading
|
||||
// or error states and only display if we get usable data
|
||||
if (error || loading || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="ml-2" title={t('Epoch')}>
|
||||
<EpochSymbol />
|
||||
{data.epoch.id}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export const EPOCH_SYMBOL = 'ⓔ';
|
||||
|
||||
export function EpochSymbol() {
|
||||
return (
|
||||
<em
|
||||
title={t('Epoch')}
|
||||
className="mr-1 cursor-default text-xl leading-none align-text-bottom not-italic"
|
||||
>
|
||||
{EPOCH_SYMBOL}
|
||||
</em>
|
||||
);
|
||||
}
|
||||
|
||||
export default BlockLink;
|
||||
|
@ -0,0 +1,16 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import GovernanceLink from './governance-link';
|
||||
|
||||
describe('GovernanceLink', () => {
|
||||
it('renders the link with the correct text', () => {
|
||||
render(<GovernanceLink text="Governance internet website" />);
|
||||
const linkElement = screen.getByText('Governance internet website');
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the link with the correct href and sensible default text', () => {
|
||||
render(<GovernanceLink />);
|
||||
const linkElement = screen.getByText('Governance');
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1,18 @@
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { ENV } from '../../../config/env';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export type GovernanceLinkProps = {
|
||||
text?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Just a link to the governance page, with optional text
|
||||
*/
|
||||
const GovernanceLink = ({ text = t('Governance') }: GovernanceLinkProps) => {
|
||||
const base = ENV.dataSources.governanceUrl;
|
||||
|
||||
return <ExternalLink href={base}>{text}</ExternalLink>;
|
||||
};
|
||||
|
||||
export default GovernanceLink;
|
@ -10,6 +10,8 @@ import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
|
||||
import { TxDataView } from '../../tx-data-view';
|
||||
import Hash from '../../../links/hash';
|
||||
import { Signature } from '../../../signature/signature';
|
||||
import { useExplorerEpochForBlockQuery } from '../../../links/block-link/__generated__/EpochByBlock';
|
||||
import EpochOverview from '../../../epoch-overview/epoch';
|
||||
|
||||
interface TxDetailsSharedProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@ -44,6 +46,11 @@ export const TxDetailsShared = ({
|
||||
blockData,
|
||||
hideTypeRow = false,
|
||||
}: TxDetailsSharedProps) => {
|
||||
const { data } = useExplorerEpochForBlockQuery({
|
||||
errorPolicy: 'ignore',
|
||||
variables: { block: txData?.block.toString() || '' },
|
||||
});
|
||||
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
@ -74,7 +81,7 @@ export const TxDetailsShared = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Block')}</TableCell>
|
||||
<TableCell>
|
||||
<BlockLink height={height} />
|
||||
<BlockLink height={height} showEpoch={false} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
@ -83,6 +90,7 @@ export const TxDetailsShared = ({
|
||||
<Signature signature={txData.signature} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
|
||||
<TableCell>
|
||||
@ -100,6 +108,14 @@ export const TxDetailsShared = ({
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{data && data.epoch && (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell scope="row">{t('Epoch')}</TableCell>
|
||||
<TableCell modifier="bordered">
|
||||
<EpochOverview id={data.epoch.id} icon={false} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Response code')}</TableCell>
|
||||
<TableCell>
|
||||
|
@ -113,13 +113,16 @@ export const TxDetailsTransfer = ({
|
||||
|
||||
/**
|
||||
* Gets a string description of this transfer
|
||||
* @param txData A full transfer
|
||||
* @param tx A full transfer
|
||||
* @returns string Transfer label
|
||||
*/
|
||||
export function getTypeLabelForTransfer(tx: Transfer) {
|
||||
if (tx.to === SPECIAL_CASE_NETWORK || tx.to === SPECIAL_CASE_NETWORK_ID) {
|
||||
if (tx.toAccountType === 'ACCOUNT_TYPE_NETWORK_TREASURY') {
|
||||
return 'Treasury transfer';
|
||||
}
|
||||
if (tx.recurring && tx.recurring.dispatchStrategy) {
|
||||
return 'Reward top up transfer';
|
||||
return 'Reward transfer';
|
||||
}
|
||||
// Else: we don't know that it's a reward transfer, so let's not guess
|
||||
} else if (tx.recurring) {
|
||||
|
@ -2,6 +2,7 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import type { components } from '../../../types/explorer';
|
||||
import { VoteIcon } from '../vote-icon/vote-icon';
|
||||
import { ExternalChainIcon } from '../links/external-explorer-link/external-chain-icon';
|
||||
import { getTypeLabelForTransfer } from './details/tx-transfer';
|
||||
|
||||
interface TxOrderTypeProps {
|
||||
orderType: string;
|
||||
@ -95,7 +96,7 @@ export function getLabelForOrderType(
|
||||
|
||||
/**
|
||||
* Given a proposal, will return a specific label
|
||||
* @param chainEvent
|
||||
* @param proposal
|
||||
* @returns
|
||||
*/
|
||||
export function getLabelForProposal(
|
||||
@ -142,6 +143,36 @@ export function getLabelForProposal(
|
||||
}
|
||||
}
|
||||
|
||||
type label = {
|
||||
type: string;
|
||||
colours: string;
|
||||
};
|
||||
|
||||
export function getLabelForTransfer(
|
||||
transfer: components['schemas']['commandsv1Transfer']
|
||||
): label {
|
||||
const type = getTypeLabelForTransfer(transfer);
|
||||
|
||||
if (transfer.toAccountType === 'ACCOUNT_TYPE_NETWORK_TREASURY') {
|
||||
return {
|
||||
type,
|
||||
colours:
|
||||
'text-vega-green dark:text-green bg-vega-dark-150 dark:bg-vega-dark-250',
|
||||
};
|
||||
} else if (transfer.recurring) {
|
||||
return {
|
||||
type,
|
||||
colours:
|
||||
'text-vega-yellow dark:text-yellow bg-vega-dark-150 dark:bg-vega-dark-250',
|
||||
};
|
||||
}
|
||||
return {
|
||||
type,
|
||||
colours:
|
||||
'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-250',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a chain event, will try to provide a more useful label
|
||||
* @param chainEvent
|
||||
@ -225,9 +256,10 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
|
||||
if (type === 'Chain Event' && !!command?.chainEvent) {
|
||||
type = getLabelForChainEvent(command.chainEvent);
|
||||
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
|
||||
} else if (type === 'Validator Heartbeat') {
|
||||
colours =
|
||||
'text-white dark-text-white bg-vega-light-200 dark:bg-vega-dark-100';
|
||||
} else if (type === 'Transfer Funds' && command?.transfer) {
|
||||
const res = getLabelForTransfer(command.transfer);
|
||||
type = res.type;
|
||||
colours = res.colours;
|
||||
} else if (type === 'Proposal' || type === 'Governance Proposal') {
|
||||
if (command && !!command.proposalSubmission) {
|
||||
type = getLabelForProposal(command.proposalSubmission);
|
||||
|
@ -16,6 +16,8 @@ import { useBlockInfo } from '@vegaprotocol/tendermint';
|
||||
import { NodeLink } from '../../../components/links';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
import EmptyList from '../../../components/empty-list/empty-list';
|
||||
import { useExplorerEpochForBlockQuery } from '../../../components/links/block-link/__generated__/EpochByBlock';
|
||||
import EpochOverview from '../../../components/epoch-overview/epoch';
|
||||
|
||||
type Params = { block: string };
|
||||
|
||||
@ -26,6 +28,11 @@ const Block = () => {
|
||||
state: { data: blockData, loading, error },
|
||||
} = useBlockInfo(Number(block));
|
||||
|
||||
const { data } = useExplorerEpochForBlockQuery({
|
||||
errorPolicy: 'ignore',
|
||||
variables: { block: block?.toString() || '' },
|
||||
});
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="block-header">{t(`BLOCK ${block}`)}</RouteTitle>
|
||||
@ -75,6 +82,7 @@ const Block = () => {
|
||||
<code>{blockData.result.block.header.consensus_hash}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Mined by</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
@ -97,6 +105,14 @@ const Block = () => {
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{data && data.epoch && (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell scope="row">{t('Epoch')}</TableCell>
|
||||
<TableCell modifier="bordered">
|
||||
<EpochOverview id={data.epoch.id} icon={false} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Transactions</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
|
@ -33,7 +33,10 @@ export const NetworkAccountsTable = () => {
|
||||
return (
|
||||
<section className="md:flex md:flex-row flex-wrap">
|
||||
{c.map((a) => (
|
||||
<div className="basis-1/2 md:basis-1/4">
|
||||
<div
|
||||
className="basis-1/2 md:basis-1/4"
|
||||
key={`${a.assetId}-${a.balance}`}
|
||||
>
|
||||
<div className="bg-white rounded overflow-hidden shadow-lg dark:bg-black dark:border-slate-500 dark:border">
|
||||
<div className="text-center p-6 bg-gray-100 dark:bg-slate-900 border-b dark:border-slate-500">
|
||||
<p className="flex justify-center">
|
||||
|
@ -16,19 +16,21 @@ import type { DeepPartial } from '@apollo/client/utilities';
|
||||
|
||||
describe('typeLabel', () => {
|
||||
it('should return "Transfer" for "OneOffTransfer" kind', () => {
|
||||
expect(typeLabel('OneOffTransfer')).toBe('Transfer');
|
||||
expect(typeLabel('OneOffTransfer')).toBe('Transfer - one time');
|
||||
});
|
||||
|
||||
it('should return "Transfer" for "RecurringTransfer" kind', () => {
|
||||
expect(typeLabel('RecurringTransfer')).toBe('Transfer');
|
||||
expect(typeLabel('RecurringTransfer')).toBe('Transfer - repeating');
|
||||
});
|
||||
|
||||
it('should return "Governance" for "OneOffGovernanceTransfer" kind', () => {
|
||||
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance');
|
||||
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance - one time');
|
||||
});
|
||||
|
||||
it('should return "Governance" for "RecurringGovernanceTransfer" kind', () => {
|
||||
expect(typeLabel('RecurringGovernanceTransfer')).toBe('Governance');
|
||||
expect(typeLabel('RecurringGovernanceTransfer')).toBe(
|
||||
'Governance - repeating'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return "Unknown" for unknown kind', () => {
|
||||
@ -256,7 +258,7 @@ describe('NetworkTransfersTable', () => {
|
||||
expect(screen.getByTestId('from-account').textContent).toEqual('Treasury');
|
||||
expect(screen.getByTestId('to-account').textContent).toEqual('7100…97a0');
|
||||
expect(screen.getByTestId('transfer-kind').textContent).toEqual(
|
||||
'Governance'
|
||||
'Governance - one time'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -12,6 +12,7 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { useMemo } from 'react';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import ProposalLink from '../../../components/links/proposal-link/proposal-link';
|
||||
|
||||
export const colours = {
|
||||
INCOMING: '!fill-vega-green-600 text-vega-green-600 mr-2',
|
||||
@ -50,14 +51,24 @@ export function getToAccountTypeLabel(type?: AccountType): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function isGovernanceTransfer(kind?: string): boolean {
|
||||
if (kind && kind.includes('Governance')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function typeLabel(kind?: string): string {
|
||||
switch (kind) {
|
||||
case 'OneOffTransfer':
|
||||
return t('Transfer - one time');
|
||||
case 'RecurringTransfer':
|
||||
return t('Transfer');
|
||||
return t('Transfer - repeating');
|
||||
case 'OneOffGovernanceTransfer':
|
||||
return t('Governance - one time');
|
||||
case 'RecurringGovernanceTransfer':
|
||||
return t('Governance');
|
||||
return t('Governance - repeating');
|
||||
default:
|
||||
return t('Unknown');
|
||||
}
|
||||
@ -239,6 +250,11 @@ export const NetworkTransfersTable = () => {
|
||||
>
|
||||
{a && typeLabel(a.kind.__typename)}
|
||||
</span>
|
||||
{isGovernanceTransfer(a?.kind.__typename) && a?.id && (
|
||||
<span className="ml-4">
|
||||
<ProposalLink id={a?.id} text="View" />
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { NetworkAccountsTable } from './components/network-accounts-table';
|
||||
import { NetworkTransfersTable } from './components/network-transfers-table';
|
||||
import GovernanceLink from '../../components/links/governance-link/governance-link';
|
||||
|
||||
export type NonZeroAccount = {
|
||||
assetId: string;
|
||||
@ -16,7 +17,33 @@ export const NetworkTreasury = () => {
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="block-header">{t(`Treasury`)}</RouteTitle>
|
||||
<div>
|
||||
<details className="w-full md:w-3/5 cursor-pointer shadow-lg p-5 dark:border-l-2 dark:border-vega-green">
|
||||
<summary>{t('About the Network Treasury')}</summary>
|
||||
<section className="mt-4 b-1 border-grey">
|
||||
<p className="mb-2">
|
||||
The network treasury can hold funds from any active settlement asset
|
||||
on the network. It is funded periodically by transfers from Gobalsky
|
||||
as part of the Community Adoption Fund (CAF), but in future may
|
||||
receive funds from any sources.
|
||||
</p>
|
||||
<p className="mb-2">
|
||||
Funds in the network treasury can be used by creating governance
|
||||
initiated transfers via{' '}
|
||||
<GovernanceLink text={t('community governance')} />. These transfers
|
||||
can be initiated by anyone and be used to fund reward pools, or can
|
||||
be used to fund other activities the{' '}
|
||||
<abbr className="decoration-dotted" title="Community Adoption Fund">
|
||||
CAF
|
||||
</abbr>{' '}
|
||||
is exploring.
|
||||
</p>
|
||||
<p>
|
||||
This page shows details of the balances in the treasury, pending
|
||||
transfers, and historic transfer movements to and from the treasury.
|
||||
</p>
|
||||
</section>
|
||||
</details>
|
||||
<div className="mt-6">
|
||||
<NetworkAccountsTable />
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
|
Loading…
Reference in New Issue
Block a user