feat(explorer): add treasury view (#5798)
This commit is contained in:
parent
a2a04c57d2
commit
19fb406d49
@ -7,6 +7,7 @@ export type AssetBalanceProps = {
|
|||||||
price: string;
|
price: string;
|
||||||
showAssetLink?: boolean;
|
showAssetLink?: boolean;
|
||||||
showAssetSymbol?: boolean;
|
showAssetSymbol?: boolean;
|
||||||
|
rounded?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,12 +19,17 @@ const AssetBalance = ({
|
|||||||
price,
|
price,
|
||||||
showAssetLink = true,
|
showAssetLink = true,
|
||||||
showAssetSymbol = false,
|
showAssetSymbol = false,
|
||||||
|
rounded = false,
|
||||||
}: AssetBalanceProps) => {
|
}: AssetBalanceProps) => {
|
||||||
const { data: asset, loading } = useAssetDataProvider(assetId);
|
const { data: asset, loading } = useAssetDataProvider(assetId);
|
||||||
|
|
||||||
const label =
|
const label =
|
||||||
!loading && asset && asset.decimals
|
!loading && asset && asset.decimals
|
||||||
? addDecimalsFixedFormatNumber(price, asset.decimals)
|
? addDecimalsFixedFormatNumber(
|
||||||
|
price,
|
||||||
|
asset.decimals,
|
||||||
|
rounded ? 0 : undefined
|
||||||
|
)
|
||||||
: price;
|
: price;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -41,6 +41,7 @@ export const Header = () => {
|
|||||||
Routes.ASSETS,
|
Routes.ASSETS,
|
||||||
Routes.MARKETS,
|
Routes.MARKETS,
|
||||||
Routes.GOVERNANCE,
|
Routes.GOVERNANCE,
|
||||||
|
Routes.TREASURY,
|
||||||
Routes.NETWORK_PARAMETERS,
|
Routes.NETWORK_PARAMETERS,
|
||||||
Routes.GENESIS,
|
Routes.GENESIS,
|
||||||
].map((n) => pages.find((r) => r.path === n))
|
].map((n) => pages.find((r) => r.path === n))
|
||||||
|
@ -32,9 +32,17 @@ export function getNameForParty(id: string, data?: ExplorerNodeNamesQuery) {
|
|||||||
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
|
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||||
id: string;
|
id: string;
|
||||||
truncate?: boolean;
|
truncate?: boolean;
|
||||||
|
networkLabel?: string;
|
||||||
|
truncateLength?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
const PartyLink = ({
|
||||||
|
id,
|
||||||
|
truncate = false,
|
||||||
|
truncateLength = 4,
|
||||||
|
networkLabel = t('Network'),
|
||||||
|
...props
|
||||||
|
}: PartyLinkProps) => {
|
||||||
const { data } = useExplorerNodeNamesQuery();
|
const { data } = useExplorerNodeNamesQuery();
|
||||||
const name = useMemo(() => getNameForParty(id, data), [data, id]);
|
const name = useMemo(() => getNameForParty(id, data), [data, id]);
|
||||||
const useName = name !== id;
|
const useName = name !== id;
|
||||||
@ -44,7 +52,7 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
|||||||
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
|
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
|
||||||
return (
|
return (
|
||||||
<span className="font-mono" data-testid="network">
|
<span className="font-mono" data-testid="network">
|
||||||
{t('Network')}
|
{networkLabel}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -70,7 +78,11 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
|||||||
{useName ? (
|
{useName ? (
|
||||||
name
|
name
|
||||||
) : (
|
) : (
|
||||||
<Hash text={truncate ? truncateMiddle(id, 4, 4) : id} />
|
<Hash
|
||||||
|
text={
|
||||||
|
truncate ? truncateMiddle(id, truncateLength, truncateLength) : id
|
||||||
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
|
@ -34,10 +34,7 @@ export function TransferStatusView({ status, loading }: TransferStatusProps) {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p className="leading-10 my-2">
|
<p className="leading-10 my-2">
|
||||||
<Icon
|
<TransferStatusIcon status={status} />
|
||||||
name={getIconForStatus(status)}
|
|
||||||
className={getColourForStatus(status)}
|
|
||||||
/>
|
|
||||||
</p>
|
</p>
|
||||||
<p className="leading-10 my-2">{TransferStatusMapping[status]}</p>
|
<p className="leading-10 my-2">{TransferStatusMapping[status]}</p>
|
||||||
</>
|
</>
|
||||||
@ -47,6 +44,21 @@ export function TransferStatusView({ status, loading }: TransferStatusProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TransferStatusIconProps {
|
||||||
|
status: TransferStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TransferStatusIcon({ status }: TransferStatusIconProps) {
|
||||||
|
return (
|
||||||
|
<span title={TransferStatusMapping[status]}>
|
||||||
|
<Icon
|
||||||
|
name={getIconForStatus(status)}
|
||||||
|
className={getColourForStatus(status)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple mapping from status to icon name
|
* Simple mapping from status to icon name
|
||||||
* @param status TransferStatus
|
* @param status TransferStatus
|
||||||
@ -60,6 +72,8 @@ export function getIconForStatus(status: TransferStatus): IconName {
|
|||||||
return IconNames.TICK;
|
return IconNames.TICK;
|
||||||
case TransferStatus.STATUS_REJECTED:
|
case TransferStatus.STATUS_REJECTED:
|
||||||
return IconNames.CROSS;
|
return IconNames.CROSS;
|
||||||
|
case TransferStatus.STATUS_CANCELLED:
|
||||||
|
return IconNames.CROSS;
|
||||||
default:
|
default:
|
||||||
return IconNames.TIME;
|
return IconNames.TIME;
|
||||||
}
|
}
|
||||||
@ -78,6 +92,8 @@ export function getColourForStatus(status: TransferStatus): string {
|
|||||||
return 'text-green-500';
|
return 'text-green-500';
|
||||||
case TransferStatus.STATUS_REJECTED:
|
case TransferStatus.STATUS_REJECTED:
|
||||||
return 'text-red-500';
|
return 'text-red-500';
|
||||||
|
case TransferStatus.STATUS_CANCELLED:
|
||||||
|
return 'text-red-600';
|
||||||
default:
|
default:
|
||||||
return 'text-yellow-500';
|
return 'text-yellow-500';
|
||||||
}
|
}
|
||||||
|
@ -12,4 +12,5 @@ export const Routes = {
|
|||||||
ORACLES: 'oracles',
|
ORACLES: 'oracles',
|
||||||
NETWORK_PARAMETERS: 'network-parameters',
|
NETWORK_PARAMETERS: 'network-parameters',
|
||||||
DISCLAIMER: 'disclaimer',
|
DISCLAIMER: 'disclaimer',
|
||||||
|
TREASURY: 'treasury',
|
||||||
};
|
};
|
||||||
|
@ -30,6 +30,7 @@ import { PartyAccountsByAsset } from './parties/id/accounts';
|
|||||||
import { Disclaimer } from './pages/disclaimer';
|
import { Disclaimer } from './pages/disclaimer';
|
||||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||||
import RestrictedPage from './restricted';
|
import RestrictedPage from './restricted';
|
||||||
|
import { NetworkTreasury } from './treasury';
|
||||||
|
|
||||||
export type Navigable = {
|
export type Navigable = {
|
||||||
path: string;
|
path: string;
|
||||||
@ -229,6 +230,17 @@ export const useRouterConfig = () => {
|
|||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const treasuryRoutes: Route[] = [
|
||||||
|
{
|
||||||
|
path: Routes.TREASURY,
|
||||||
|
handle: {
|
||||||
|
name: t('Treasury'),
|
||||||
|
text: t('Treasury'),
|
||||||
|
breadcrumb: () => <Link to={Routes.TREASURY}>{t('Treasury')}</Link>,
|
||||||
|
},
|
||||||
|
element: <NetworkTreasury />,
|
||||||
|
},
|
||||||
|
];
|
||||||
const validators: Route[] = featureFlags.EXPLORER_VALIDATORS
|
const validators: Route[] = featureFlags.EXPLORER_VALIDATORS
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
@ -358,6 +370,7 @@ export const useRouterConfig = () => {
|
|||||||
...marketsRoutes,
|
...marketsRoutes,
|
||||||
...networkParametersRoutes,
|
...networkParametersRoutes,
|
||||||
...validators,
|
...validators,
|
||||||
|
...treasuryRoutes,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
12
apps/explorer/src/app/routes/treasury/Treasury.graphql
Normal file
12
apps/explorer/src/app/routes/treasury/Treasury.graphql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
query ExplorerTreasury {
|
||||||
|
assetsConnection(pagination: { last: 1000 }) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
networkTreasuryAccount {
|
||||||
|
balance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
query ExplorerTreasuryTransfers {
|
||||||
|
transfersConnection(
|
||||||
|
partyId: "network"
|
||||||
|
direction: ToOrFrom
|
||||||
|
pagination: { last: 200 }
|
||||||
|
) {
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
}
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
transfer {
|
||||||
|
timestamp
|
||||||
|
from
|
||||||
|
amount
|
||||||
|
to
|
||||||
|
status
|
||||||
|
reason
|
||||||
|
toAccountType
|
||||||
|
fromAccountType
|
||||||
|
asset {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
id
|
||||||
|
status
|
||||||
|
kind {
|
||||||
|
... on OneOffTransfer {
|
||||||
|
deliverOn
|
||||||
|
}
|
||||||
|
... on RecurringTransfer {
|
||||||
|
startEpoch
|
||||||
|
}
|
||||||
|
... on OneOffGovernanceTransfer {
|
||||||
|
deliverOn
|
||||||
|
}
|
||||||
|
... on RecurringGovernanceTransfer {
|
||||||
|
endEpoch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
apps/explorer/src/app/routes/treasury/__generated__/Treasury.ts
generated
Normal file
52
apps/explorer/src/app/routes/treasury/__generated__/Treasury.ts
generated
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import * as Apollo from '@apollo/client';
|
||||||
|
const defaultOptions = {} as const;
|
||||||
|
export type ExplorerTreasuryQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ExplorerTreasuryQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, networkTreasuryAccount?: { __typename?: 'AccountBalance', balance: string } | null } } | null> | null } | null };
|
||||||
|
|
||||||
|
|
||||||
|
export const ExplorerTreasuryDocument = gql`
|
||||||
|
query ExplorerTreasury {
|
||||||
|
assetsConnection(pagination: {last: 1000}) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
networkTreasuryAccount {
|
||||||
|
balance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useExplorerTreasuryQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useExplorerTreasuryQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useExplorerTreasuryQuery` 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 } = useExplorerTreasuryQuery({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useExplorerTreasuryQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>(ExplorerTreasuryDocument, options);
|
||||||
|
}
|
||||||
|
export function useExplorerTreasuryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>(ExplorerTreasuryDocument, options);
|
||||||
|
}
|
||||||
|
export type ExplorerTreasuryQueryHookResult = ReturnType<typeof useExplorerTreasuryQuery>;
|
||||||
|
export type ExplorerTreasuryLazyQueryHookResult = ReturnType<typeof useExplorerTreasuryLazyQuery>;
|
||||||
|
export type ExplorerTreasuryQueryResult = Apollo.QueryResult<ExplorerTreasuryQuery, ExplorerTreasuryQueryVariables>;
|
84
apps/explorer/src/app/routes/treasury/__generated__/TreasuryTransfers.ts
generated
Normal file
84
apps/explorer/src/app/routes/treasury/__generated__/TreasuryTransfers.ts
generated
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import * as Apollo from '@apollo/client';
|
||||||
|
const defaultOptions = {} as const;
|
||||||
|
export type ExplorerTreasuryTransfersQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ExplorerTreasuryTransfersQuery = { __typename?: 'Query', transfersConnection?: { __typename?: 'TransferConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'TransferEdge', node: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', timestamp: any, from: string, amount: string, to: string, status: Types.TransferStatus, reason?: string | null, toAccountType: Types.AccountType, fromAccountType: Types.AccountType, id: string, asset?: { __typename?: 'Asset', id: string } | null, kind: { __typename?: 'OneOffGovernanceTransfer', deliverOn?: any | null } | { __typename?: 'OneOffTransfer', deliverOn?: any | null } | { __typename?: 'RecurringGovernanceTransfer', endEpoch?: number | null } | { __typename?: 'RecurringTransfer', startEpoch: number } } } } | null> | null } | null };
|
||||||
|
|
||||||
|
|
||||||
|
export const ExplorerTreasuryTransfersDocument = gql`
|
||||||
|
query ExplorerTreasuryTransfers {
|
||||||
|
transfersConnection(
|
||||||
|
partyId: "network"
|
||||||
|
direction: ToOrFrom
|
||||||
|
pagination: {last: 200}
|
||||||
|
) {
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
}
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
transfer {
|
||||||
|
timestamp
|
||||||
|
from
|
||||||
|
amount
|
||||||
|
to
|
||||||
|
status
|
||||||
|
reason
|
||||||
|
toAccountType
|
||||||
|
fromAccountType
|
||||||
|
asset {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
id
|
||||||
|
status
|
||||||
|
kind {
|
||||||
|
... on OneOffTransfer {
|
||||||
|
deliverOn
|
||||||
|
}
|
||||||
|
... on RecurringTransfer {
|
||||||
|
startEpoch
|
||||||
|
}
|
||||||
|
... on OneOffGovernanceTransfer {
|
||||||
|
deliverOn
|
||||||
|
}
|
||||||
|
... on RecurringGovernanceTransfer {
|
||||||
|
endEpoch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useExplorerTreasuryTransfersQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useExplorerTreasuryTransfersQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useExplorerTreasuryTransfersQuery` 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 } = useExplorerTreasuryTransfersQuery({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useExplorerTreasuryTransfersQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>(ExplorerTreasuryTransfersDocument, options);
|
||||||
|
}
|
||||||
|
export function useExplorerTreasuryTransfersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>(ExplorerTreasuryTransfersDocument, options);
|
||||||
|
}
|
||||||
|
export type ExplorerTreasuryTransfersQueryHookResult = ReturnType<typeof useExplorerTreasuryTransfersQuery>;
|
||||||
|
export type ExplorerTreasuryTransfersLazyQueryHookResult = ReturnType<typeof useExplorerTreasuryTransfersLazyQuery>;
|
||||||
|
export type ExplorerTreasuryTransfersQueryResult = Apollo.QueryResult<ExplorerTreasuryTransfersQuery, ExplorerTreasuryTransfersQueryVariables>;
|
@ -0,0 +1,37 @@
|
|||||||
|
// NOTE: These are a temporary measure, pulled from an old branch on console.
|
||||||
|
|
||||||
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
|
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { USDc } from './usdc';
|
||||||
|
import { Vega } from './vega';
|
||||||
|
import { USDt } from './usdt';
|
||||||
|
|
||||||
|
export interface AssetIconProps {
|
||||||
|
symbol: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A poorly implemented, limited support for asset icons.
|
||||||
|
*
|
||||||
|
* These are committed as 'deprecated' to discourage use outside the Treasury page. Rather
|
||||||
|
* than use this, a better approach would be to use source contract addresses to match assets.
|
||||||
|
* This will be done separately.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export function AssetIcon({ symbol }: AssetIconProps) {
|
||||||
|
const s = symbol.toLowerCase();
|
||||||
|
switch (s) {
|
||||||
|
case 'a4a16e250a09a86061ec83c2f9466fc9dc33d332f86876ee74b6f128a5cd6710': // mainnet
|
||||||
|
case 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d': // mainnet
|
||||||
|
return <USDc size={32} />;
|
||||||
|
case 'd1984e3d365faa05bcafbe41f50f90e3663ee7c0da22bb1e24b164e9532691b2': // mainnet
|
||||||
|
case 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55': // testnet
|
||||||
|
return <Vega size={32} />;
|
||||||
|
case 'bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba': // mainnet
|
||||||
|
case 'ede4076aef07fd79502d14326c54ab3911558371baaf697a19d077f4f89de399': // testnet
|
||||||
|
return <USDt size={32} />;
|
||||||
|
default:
|
||||||
|
return <Icon name={IconNames.BANK_ACCOUNT} size={8} />;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* See note in index.tsx. This component is intended as a placeholder for a
|
||||||
|
* better, more generic solution.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export const USDc = ({ size = 16 }: { size?: number }) => {
|
||||||
|
return (
|
||||||
|
<svg width={size} height={size} viewBox="0 0 2000 2000">
|
||||||
|
<path
|
||||||
|
d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z"
|
||||||
|
fill="#2775ca"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* See note in index.tsx. This component is intended as a placeholder for a
|
||||||
|
* better, more generic solution.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export const USDt = ({ size = 16 }: { size?: number }) => {
|
||||||
|
return (
|
||||||
|
<svg width={size} height={size} viewBox="0 0 339.43 295.27">
|
||||||
|
<path
|
||||||
|
fill="#50af95"
|
||||||
|
d="M62.15,1.45l-61.89,130a2.52,2.52,0,0,0,.54,2.94L167.95,294.56a2.55,2.55,0,0,0,3.53,0L338.63,134.4a2.52,2.52,0,0,0,.54-2.94l-61.89-130A2.5,2.5,0,0,0,275,0H64.45a2.5,2.5,0,0,0-2.3,1.45h0Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#fff"
|
||||||
|
d="M191.19,144.8v0c-1.2.09-7.4,0.46-21.23,0.46-11,0-18.81-.33-21.55-0.46v0c-42.51-1.87-74.24-9.27-74.24-18.13s31.73-16.25,74.24-18.15v28.91c2.78,0.2,10.74.67,21.74,0.67,13.2,0,19.81-.55,21-0.66v-28.9c42.42,1.89,74.08,9.29,74.08,18.13s-31.65,16.24-74.08,18.12h0Zm0-39.25V79.68h59.2V40.23H89.21V79.68H148.4v25.86c-48.11,2.21-84.29,11.74-84.29,23.16s36.18,20.94,84.29,23.16v82.9h42.78V151.83c48-2.21,84.12-11.73,84.12-23.14s-36.09-20.93-84.12-23.15h0Zm0,0h0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* See note in index.tsx. This component is intended as a placeholder for a
|
||||||
|
* better, more generic solution.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export const Vega = ({ size = 16 }: { size?: number }) => {
|
||||||
|
return (
|
||||||
|
<svg width={size} height={size} viewBox="0 0 42 42">
|
||||||
|
<rect width="42" height="42" rx="21" fill="black" />
|
||||||
|
<path d="M13 27.2726H16.4545V10H13V27.2726Z" fill="white" />
|
||||||
|
<path d="M25.667 23.8181H29.1215V10H25.667V23.8181Z" fill="white" />
|
||||||
|
<path d="M19.333 33.6059H22.7875V30.1514H19.333V33.6059Z" fill="white" />
|
||||||
|
<path
|
||||||
|
d="M22.7871 30.7271H26.2416V27.2726H22.7871V30.7271Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M29.1211 27.2726H31.9999V23.8181H29.1211V27.2726Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M16.4551 30.7271H19.3339V27.2726H16.4551V30.7271Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,171 @@
|
|||||||
|
import type { DeepPartial } from '@apollo/client/utilities';
|
||||||
|
import { parseResultsToAccounts } from './network-accounts-table';
|
||||||
|
import {
|
||||||
|
ExplorerTreasuryDocument,
|
||||||
|
type ExplorerTreasuryQuery,
|
||||||
|
} from '../__generated__/Treasury';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { NetworkAccountsTable } from './network-accounts-table';
|
||||||
|
|
||||||
|
describe('parseResultsToAccounts', () => {
|
||||||
|
it('should return an array of non-zero treasury accounts', () => {
|
||||||
|
const data: DeepPartial<ExplorerTreasuryQuery> = {
|
||||||
|
assetsConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'asset1',
|
||||||
|
networkTreasuryAccount: {
|
||||||
|
balance: '100',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'has0assets',
|
||||||
|
networkTreasuryAccount: {
|
||||||
|
balance: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'asset3',
|
||||||
|
networkTreasuryAccount: {
|
||||||
|
balance: '50',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'hasnonetworktreasuryaccount',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = parseResultsToAccounts(data as ExplorerTreasuryQuery);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(2);
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
assetId: 'asset1',
|
||||||
|
balance: '100',
|
||||||
|
type: 'ACCOUNT_TYPE_NETWORK_TREASURY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetId: 'asset3',
|
||||||
|
balance: '50',
|
||||||
|
type: 'ACCOUNT_TYPE_NETWORK_TREASURY',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty array if no non-zero accounts are found', () => {
|
||||||
|
const data: DeepPartial<ExplorerTreasuryQuery> = {
|
||||||
|
assetsConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'asset1',
|
||||||
|
networkTreasuryAccount: {
|
||||||
|
balance: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'asset2',
|
||||||
|
networkTreasuryAccount: {
|
||||||
|
balance: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = parseResultsToAccounts(data as ExplorerTreasuryQuery);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(0);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing data', () => {
|
||||||
|
const result = parseResultsToAccounts(
|
||||||
|
undefined as unknown as ExplorerTreasuryQuery
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(0);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('NetworkAccountsTable', () => {
|
||||||
|
const mockData: ExplorerTreasuryQuery = {
|
||||||
|
assetsConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'asset1',
|
||||||
|
networkTreasuryAccount: {
|
||||||
|
balance: '100',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'asset2',
|
||||||
|
networkTreasuryAccount: {
|
||||||
|
balance: '50',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mocks = [
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
query: ExplorerTreasuryDocument,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: mockData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it('should render network accounts (as many as match - often just 1)', async () => {
|
||||||
|
render(
|
||||||
|
<MockedProvider mocks={mocks} addTypename={false}>
|
||||||
|
<MemoryRouter>
|
||||||
|
<NetworkAccountsTable />
|
||||||
|
</MemoryRouter>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for the data to load
|
||||||
|
await screen.findByText('Loading...');
|
||||||
|
|
||||||
|
// Assert that the network accounts are rendered
|
||||||
|
expect(screen.getByText('asset1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('asset2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle loading state', async () => {
|
||||||
|
render(
|
||||||
|
<MockedProvider mocks={mocks} addTypename={false}>
|
||||||
|
<MemoryRouter>
|
||||||
|
<NetworkAccountsTable />
|
||||||
|
</MemoryRouter>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert that the loading state is rendered
|
||||||
|
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,87 @@
|
|||||||
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import {
|
||||||
|
type ExplorerTreasuryQuery,
|
||||||
|
useExplorerTreasuryQuery,
|
||||||
|
} from '../__generated__/Treasury';
|
||||||
|
import AssetBalance from '../../../components/asset-balance/asset-balance';
|
||||||
|
import { AssetLink } from '../../../components/links';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||||
|
import { AssetIcon } from './asset-icon';
|
||||||
|
import { type NonZeroAccount } from '../network-treasury';
|
||||||
|
import { AccountType } from '@vegaprotocol/types';
|
||||||
|
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||||
|
|
||||||
|
export const NetworkAccountsTable = () => {
|
||||||
|
const { data, loading, error } = useExplorerTreasuryQuery({
|
||||||
|
// This needs to ignore error as old assets may no longer properly resolve
|
||||||
|
errorPolicy: 'ignore',
|
||||||
|
});
|
||||||
|
const { screenSize } = useScreenDimensions();
|
||||||
|
const shouldRound = useMemo(
|
||||||
|
() => ['xs', 'sm', 'md', 'lg'].includes(screenSize),
|
||||||
|
[screenSize]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AsyncRenderer
|
||||||
|
data={data}
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
render={(data) => {
|
||||||
|
const c = parseResultsToAccounts(data);
|
||||||
|
return (
|
||||||
|
<section className="md:flex md:flex-row flex-wrap">
|
||||||
|
{c.map((a) => (
|
||||||
|
<div className="basis-1/2 md:basis-1/4">
|
||||||
|
<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">
|
||||||
|
<AssetIcon symbol={a.assetId} />
|
||||||
|
</p>
|
||||||
|
<p className="mt-3" data-testid="name">
|
||||||
|
<AssetLink assetId={a.assetId} />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center py-5" data-testid="balance">
|
||||||
|
<AssetBalance
|
||||||
|
assetId={a.assetId}
|
||||||
|
price={a.balance}
|
||||||
|
showAssetSymbol={true}
|
||||||
|
rounded={shouldRound}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function parseResultsToAccounts(
|
||||||
|
data: ExplorerTreasuryQuery
|
||||||
|
): NonZeroAccount[] {
|
||||||
|
const nonZeroAccounts: NonZeroAccount[] = [];
|
||||||
|
if (data?.assetsConnection?.edges) {
|
||||||
|
const edges = removePaginationWrapper(data?.assetsConnection?.edges);
|
||||||
|
if (edges) {
|
||||||
|
edges.forEach((edge) => {
|
||||||
|
if (
|
||||||
|
edge.networkTreasuryAccount &&
|
||||||
|
edge.networkTreasuryAccount?.balance !== '0'
|
||||||
|
) {
|
||||||
|
nonZeroAccounts.push({
|
||||||
|
assetId: edge.id,
|
||||||
|
balance: edge.networkTreasuryAccount?.balance,
|
||||||
|
type: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nonZeroAccounts;
|
||||||
|
}
|
@ -0,0 +1,262 @@
|
|||||||
|
import { AccountType } from '@vegaprotocol/types';
|
||||||
|
import {
|
||||||
|
typeLabel,
|
||||||
|
getToAccountTypeLabel,
|
||||||
|
filterAccountTransfers,
|
||||||
|
} from './network-transfers-table';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { NetworkTransfersTable } from './network-transfers-table';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
ExplorerTreasuryTransfersDocument,
|
||||||
|
type ExplorerTreasuryTransfersQuery,
|
||||||
|
} from '../__generated__/TreasuryTransfers';
|
||||||
|
import type { DeepPartial } from '@apollo/client/utilities';
|
||||||
|
|
||||||
|
describe('typeLabel', () => {
|
||||||
|
it('should return "Transfer" for "OneOffTransfer" kind', () => {
|
||||||
|
expect(typeLabel('OneOffTransfer')).toBe('Transfer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "Transfer" for "RecurringTransfer" kind', () => {
|
||||||
|
expect(typeLabel('RecurringTransfer')).toBe('Transfer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "Governance" for "OneOffGovernanceTransfer" kind', () => {
|
||||||
|
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "Governance" for "RecurringGovernanceTransfer" kind', () => {
|
||||||
|
expect(typeLabel('RecurringGovernanceTransfer')).toBe('Governance');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "Unknown" for unknown kind', () => {
|
||||||
|
expect(typeLabel()).toBe('Unknown');
|
||||||
|
expect(typeLabel('')).toBe('Unknown');
|
||||||
|
expect(typeLabel('InvalidKind')).toBe('Unknown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getToAccountTypeLabel', () => {
|
||||||
|
it('should return "Treasury" when type is ACCOUNT_TYPE_NETWORK_TREASURY', () => {
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_NETWORK_TREASURY)
|
||||||
|
).toBe('Treasury');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "Fees" when type is any of the fee account types', () => {
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE)
|
||||||
|
).toBe('Fees');
|
||||||
|
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_FEES_MAKER)).toBe(
|
||||||
|
'Fees'
|
||||||
|
);
|
||||||
|
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY)).toBe(
|
||||||
|
'Fees'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_LP_LIQUIDITY_FEES)
|
||||||
|
).toBe('Fees');
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(
|
||||||
|
AccountType.ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD
|
||||||
|
)
|
||||||
|
).toBe('Fees');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "Insurance" when type is ACCOUNT_TYPE_GLOBAL_INSURANCE', () => {
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_GLOBAL_INSURANCE)
|
||||||
|
).toBe('Insurance');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "Rewards" when type is any of the reward account types', () => {
|
||||||
|
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_GLOBAL_REWARD)).toBe(
|
||||||
|
'Rewards'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION)
|
||||||
|
).toBe('Rewards');
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES)
|
||||||
|
).toBe('Rewards');
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES)
|
||||||
|
).toBe('Rewards');
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES)
|
||||||
|
).toBe('Rewards');
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS)
|
||||||
|
).toBe('Rewards');
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN)
|
||||||
|
).toBe('Rewards');
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY)
|
||||||
|
).toBe('Rewards');
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING)
|
||||||
|
).toBe('Rewards');
|
||||||
|
expect(getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_VESTED_REWARDS)).toBe(
|
||||||
|
'Rewards'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
getToAccountTypeLabel(AccountType.ACCOUNT_TYPE_VESTING_REWARDS)
|
||||||
|
).toBe('Rewards');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "Other" for any other type', () => {
|
||||||
|
expect(getToAccountTypeLabel(undefined)).toBe('Other');
|
||||||
|
expect(getToAccountTypeLabel('unknown' as AccountType)).toBe('Other');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('filterAccountTransfers', () => {
|
||||||
|
it('filters out transactions that are not to or from a treasury account', () => {
|
||||||
|
const data: DeepPartial<ExplorerTreasuryTransfersQuery> = {
|
||||||
|
transfersConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
transfer: {
|
||||||
|
toAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||||
|
fromAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
transfer: {
|
||||||
|
toAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||||
|
fromAccountType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
transfer: {
|
||||||
|
toAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
fromAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
transfer: {
|
||||||
|
toAccountType: AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||||
|
fromAccountType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = filterAccountTransfers(
|
||||||
|
data as ExplorerTreasuryTransfersQuery
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty array if no transfers match the filter', () => {
|
||||||
|
const data: DeepPartial<ExplorerTreasuryTransfersQuery> = {
|
||||||
|
transfersConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
transfer: {
|
||||||
|
toAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
fromAccountType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
transfer: {
|
||||||
|
toAccountType: AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||||
|
fromAccountType:
|
||||||
|
AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = filterAccountTransfers(
|
||||||
|
data as ExplorerTreasuryTransfersQuery
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('NetworkTransfersTable', () => {
|
||||||
|
it('renders table headers correctly', async () => {
|
||||||
|
const mocks = [
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
query: ExplorerTreasuryTransfersDocument,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
transfersConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
transfer: {
|
||||||
|
id: '123',
|
||||||
|
toAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||||
|
fromAccountType:
|
||||||
|
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||||
|
amount: '100',
|
||||||
|
asset: {
|
||||||
|
id: '1',
|
||||||
|
},
|
||||||
|
timestamp: '2022-01-01T00:00:00Z',
|
||||||
|
from: 'network',
|
||||||
|
to: '7100a8a82ef45adb9efa070cc821c6c5c48172d6dc5f842431549490fe5897a0',
|
||||||
|
reason: '',
|
||||||
|
status: 'COMPLETED',
|
||||||
|
kind: {
|
||||||
|
__typename: 'OneOffGovernanceTransfer',
|
||||||
|
deliverOn: '123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MockedProvider mocks={mocks} addTypename={true}>
|
||||||
|
<MemoryRouter>
|
||||||
|
<NetworkTransfersTable />
|
||||||
|
</MemoryRouter>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await screen.findByText('Amount')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Asset')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Age')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('From')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('To')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Status')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Type')).toBeInTheDocument();
|
||||||
|
|
||||||
|
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'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,253 @@
|
|||||||
|
import { AsyncRenderer, Icon } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import AssetBalance from '../../../components/asset-balance/asset-balance';
|
||||||
|
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
||||||
|
import { AssetLink, PartyLink } from '../../../components/links';
|
||||||
|
import {
|
||||||
|
type ExplorerTreasuryTransfersQuery,
|
||||||
|
useExplorerTreasuryTransfersQuery,
|
||||||
|
} from '../__generated__/TreasuryTransfers';
|
||||||
|
import { TimeAgo } from '../../../components/time-ago';
|
||||||
|
import { TransferStatusIcon } from '../../../components/txs/details/transfer/blocks/transfer-status';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
export const colours = {
|
||||||
|
INCOMING: '!fill-vega-green-600 text-vega-green-600 mr-2',
|
||||||
|
OUTGOING: '!fill-vega-pink-600 text-vega-pink-600 mr-2',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const theadClasses =
|
||||||
|
'py-2 border text-center bg-vega-light-150 dark:bg-vega-dark-150';
|
||||||
|
|
||||||
|
export function getToAccountTypeLabel(type?: AccountType): string {
|
||||||
|
switch (type) {
|
||||||
|
case AccountType.ACCOUNT_TYPE_NETWORK_TREASURY:
|
||||||
|
return t('Treasury');
|
||||||
|
case AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE:
|
||||||
|
case AccountType.ACCOUNT_TYPE_FEES_MAKER:
|
||||||
|
case AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY:
|
||||||
|
case AccountType.ACCOUNT_TYPE_LP_LIQUIDITY_FEES:
|
||||||
|
case AccountType.ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD:
|
||||||
|
return t('Fees');
|
||||||
|
case AccountType.ACCOUNT_TYPE_GLOBAL_INSURANCE:
|
||||||
|
return t('Insurance');
|
||||||
|
case AccountType.ACCOUNT_TYPE_GLOBAL_REWARD:
|
||||||
|
case AccountType.ACCOUNT_TYPE_REWARD_AVERAGE_POSITION:
|
||||||
|
case AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES:
|
||||||
|
case AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES:
|
||||||
|
case AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES:
|
||||||
|
case AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS:
|
||||||
|
case AccountType.ACCOUNT_TYPE_REWARD_RELATIVE_RETURN:
|
||||||
|
case AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY:
|
||||||
|
case AccountType.ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING:
|
||||||
|
case AccountType.ACCOUNT_TYPE_VESTED_REWARDS:
|
||||||
|
case AccountType.ACCOUNT_TYPE_VESTING_REWARDS:
|
||||||
|
return t('Rewards');
|
||||||
|
default:
|
||||||
|
return t('Other');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function typeLabel(kind?: string): string {
|
||||||
|
switch (kind) {
|
||||||
|
case 'OneOffTransfer':
|
||||||
|
case 'RecurringTransfer':
|
||||||
|
return t('Transfer');
|
||||||
|
case 'OneOffGovernanceTransfer':
|
||||||
|
case 'RecurringGovernanceTransfer':
|
||||||
|
return t('Governance');
|
||||||
|
default:
|
||||||
|
return t('Unknown');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterAccountTransfers(data: ExplorerTreasuryTransfersQuery) {
|
||||||
|
return data.transfersConnection?.edges
|
||||||
|
?.filter((edge) => {
|
||||||
|
if (
|
||||||
|
edge?.node.transfer.toAccountType ===
|
||||||
|
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else if (
|
||||||
|
edge?.node.transfer.fromAccountType ===
|
||||||
|
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.map((edge) => {
|
||||||
|
return edge?.node.transfer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NetworkTransfersTable = () => {
|
||||||
|
const { data, loading, error } = useExplorerTreasuryTransfersQuery({
|
||||||
|
// This needs to ignore error as old assets may no longer properly resolve
|
||||||
|
errorPolicy: 'ignore',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { screenSize } = useScreenDimensions();
|
||||||
|
const shouldRound = useMemo(
|
||||||
|
() => ['xs', 'sm', 'md', 'lg'].includes(screenSize),
|
||||||
|
[screenSize]
|
||||||
|
);
|
||||||
|
const shouldTruncate = useMemo(
|
||||||
|
() => ['xs', 'sm', 'md', 'lg', 'xl'].includes(screenSize),
|
||||||
|
[screenSize]
|
||||||
|
);
|
||||||
|
const shouldHideColumns = useMemo(
|
||||||
|
() => ['xs', 'sm'].includes(screenSize),
|
||||||
|
[screenSize]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<AsyncRenderer
|
||||||
|
data={data}
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
render={(data) => {
|
||||||
|
const c = filterAccountTransfers(data);
|
||||||
|
if (!c) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<table className="table-fixed border-spacing-3">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className={theadClasses}>{t('Amount')}</th>
|
||||||
|
<th className={theadClasses}>{t('Asset')}</th>
|
||||||
|
<th className={theadClasses}>{t('Age')}</th>
|
||||||
|
<th className={theadClasses}>{t('From')}</th>
|
||||||
|
<th className={theadClasses}>{t('To')}</th>
|
||||||
|
<th
|
||||||
|
className={`${theadClasses} ${
|
||||||
|
shouldHideColumns ? 'hidden' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t('Status')}
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
className={`${theadClasses} ${
|
||||||
|
shouldHideColumns ? 'hidden' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t('Type')}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{c.map((a) => {
|
||||||
|
const isIncoming =
|
||||||
|
a?.toAccountType ===
|
||||||
|
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY;
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
{a && a.amount && a.asset && (
|
||||||
|
<td
|
||||||
|
className={`px-2 py-1 border whitespace-nowrap text-right ${
|
||||||
|
isIncoming ? colours.INCOMING : colours.OUTGOING
|
||||||
|
}`}
|
||||||
|
title={a.amount}
|
||||||
|
>
|
||||||
|
{a &&
|
||||||
|
a.toAccountType ===
|
||||||
|
AccountType.ACCOUNT_TYPE_NETWORK_TREASURY ? (
|
||||||
|
<Icon
|
||||||
|
name={IconNames.PLUS}
|
||||||
|
className={colours.INCOMING}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon
|
||||||
|
name={IconNames.MINUS}
|
||||||
|
className={colours.OUTGOING}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<AssetBalance
|
||||||
|
assetId={a.asset.id}
|
||||||
|
price={a.amount}
|
||||||
|
showAssetLink={false}
|
||||||
|
rounded={shouldRound}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
<td className="px-2 py-1 border whitespace-nowrap">
|
||||||
|
{a && a.amount && a.asset && (
|
||||||
|
<AssetLink
|
||||||
|
assetId={a.asset.id}
|
||||||
|
showAssetSymbol={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="px-2 py-1 border">
|
||||||
|
{a && a.timestamp && <TimeAgo date={a.timestamp} />}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="px-2 py-1 border"
|
||||||
|
data-testid="from-account"
|
||||||
|
>
|
||||||
|
{a && a.from && (
|
||||||
|
<PartyLink
|
||||||
|
id={a.from}
|
||||||
|
truncate={true}
|
||||||
|
truncateLength={shouldTruncate ? 4 : 15}
|
||||||
|
networkLabel={t('Treasury')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="px-2 py-1 border" data-testid="to-account">
|
||||||
|
{a && a.to && (
|
||||||
|
<PartyLink
|
||||||
|
id={a.to}
|
||||||
|
networkLabel={t('Treasury')}
|
||||||
|
truncate={true}
|
||||||
|
truncateLength={shouldTruncate ? 4 : 15}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{a && !a.to && (
|
||||||
|
<span
|
||||||
|
className="underline decoration-dotted"
|
||||||
|
title={AccountTypeMapping[a.toAccountType]}
|
||||||
|
>
|
||||||
|
{getToAccountTypeLabel(a.toAccountType)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className={`px-2 py-1 border text-center ${
|
||||||
|
shouldHideColumns ? 'hidden' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{a && a.status && (
|
||||||
|
<TransferStatusIcon status={a.status} />
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className={`px-2 py-1 border ${
|
||||||
|
shouldHideColumns ? 'hidden' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="underline decoration-dotted"
|
||||||
|
title={a?.kind.__typename}
|
||||||
|
data-testid="transfer-kind"
|
||||||
|
>
|
||||||
|
{a && typeLabel(a.kind.__typename)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
1
apps/explorer/src/app/routes/treasury/index.tsx
Normal file
1
apps/explorer/src/app/routes/treasury/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './network-treasury';
|
28
apps/explorer/src/app/routes/treasury/network-treasury.tsx
Normal file
28
apps/explorer/src/app/routes/treasury/network-treasury.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||||
|
import type { AccountType } from '@vegaprotocol/types';
|
||||||
|
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';
|
||||||
|
|
||||||
|
export type NonZeroAccount = {
|
||||||
|
assetId: string;
|
||||||
|
balance: string;
|
||||||
|
type: AccountType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NetworkTreasury = () => {
|
||||||
|
useDocumentTitle(['Network Treasury']);
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<RouteTitle data-testid="block-header">{t(`Treasury`)}</RouteTitle>
|
||||||
|
<div>
|
||||||
|
<NetworkAccountsTable />
|
||||||
|
</div>
|
||||||
|
<div className="mt-5">
|
||||||
|
<h2 className="text-3xl mb-2">{t('Transfers')}</h2>
|
||||||
|
<NetworkTransfersTable />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user