Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions src/__generated__/gql.ts

Large diffs are not rendered by default.

155 changes: 150 additions & 5 deletions src/__generated__/graphql.ts

Large diffs are not rendered by default.

41 changes: 15 additions & 26 deletions src/api/accounts.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import type { QueryHookOptions } from '@apollo/client';
import { gql, useQuery } from '@apollo/client';
import { endOfToday } from 'date-fns/endOfToday';
import { startOfToday } from 'date-fns/startOfToday';
import { subDays } from 'date-fns/subDays';

import { QUERY_DEFAULT_LIMIT } from '@/constants/query-default-limit';
import type { AccountSorts } from '@/constants/query-sorts';
Expand All @@ -12,6 +9,7 @@ import type {
AccountStatsResponse
} from '@/schemas';
import type { PaginatedQueryVariables } from '@/types/query';
import { getAccountStatsUtcDateRange } from '@/utils/get-account-stats-utc-date-range';

export const accounts = {
useGetAll: (
Expand All @@ -31,21 +29,9 @@ export const accounts = {
free
frozen
reserved
flagEvents: accountEvents(
where: {
_or: [
{ high_security_set_id: { _is_null: false } }
{ multisig_id: { _is_null: false } }
]
}
limit: 20
) {
highSecuritySet {
who_id
guardian_id
}
multisig_id
}
is_high_security
is_guardian
is_multisig
}
meta: chain_stats_by_pk(id: "global") {
totalCount: total_accounts
Expand Down Expand Up @@ -115,32 +101,35 @@ export const accounts = {
useGetStats: (
config?: Omit<QueryHookOptions<AccountStatsResponse>, 'variables'>
) => {
const startDate = subDays(startOfToday(), 7).toISOString();
const endDate = endOfToday().toISOString();
const { startDate, endDate } = getAccountStatsUtcDateRange();

// daily_active_account is one row per (UTC day, account) with sent/received flags,
// so the 7-day distinct counts read a few thousand indexed rows instead of the transfer table.
const GET_ACCOUNTS_STATS = gql`
query GetAccountsStats($startDate: timestamptz!, $endDate: timestamptz!) {
all: chain_stats_by_pk(id: "global") {
total_accounts
}

recentlyActive: account_aggregate(
recentlyActive: daily_active_account_aggregate(
where: {
transfersFrom: { timestamp: { _gte: $startDate, _lte: $endDate } }
date: { _gte: $startDate, _lte: $endDate }
sent: { _eq: true }
}
) {
aggregate {
count
count(columns: account_id, distinct: true)
}
}

recentlyDeposited: account_aggregate(
recentlyDeposited: daily_active_account_aggregate(
where: {
transfersTo: { timestamp: { _gte: $startDate, _lte: $endDate } }
date: { _gte: $startDate, _lte: $endDate }
received: { _eq: true }
}
) {
aggregate {
count
count(columns: account_id, distinct: true)
}
}
}
Expand Down
192 changes: 156 additions & 36 deletions src/api/miner-rewards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,57 +11,177 @@ import type {
} from '@/schemas';
import type { PaginatedQueryVariables } from '@/types/query';
import { useGetRecentDateRange } from '@/utils/get-recent-date-range';
import {
extractMinerId,
isUnfilteredMinerRewards
} from '@/utils/miner-reward-filters';

// Field lists are written out per document (no template interpolation) so graphql-codegen
// can still extract typed documents for these queries.
const GET_MINER_REWARDS = gql`
query GetMinerRewards(
$limit: Int
$offset: Int
$orderBy: [miner_reward_order_by!]
$where: miner_reward_bool_exp
) {
minerRewards: miner_reward(
limit: $limit
offset: $offset
order_by: $orderBy
where: $where
) {
block {
height
hash
}
reward
miner {
id
}
timestamp
}
meta: miner_reward_aggregate(where: $where) {
aggregate {
totalCount: count
}
}
}
`;

/** Unfiltered list: total from O(1) chain_stats instead of a full-table count. */
const GET_MINER_REWARDS_WITH_CHAIN_TOTAL = gql`
query GetMinerRewardsWithChainTotal(
$limit: Int
$offset: Int
$orderBy: [miner_reward_order_by!]
$where: miner_reward_bool_exp
) {
minerRewards: miner_reward(
limit: $limit
offset: $offset
order_by: $orderBy
where: $where
) {
block {
height
hash
}
reward
miner {
id
}
timestamp
}
meta: chain_stats_by_pk(id: "global") {
totalCount: total_miner_rewards
}
}
`;

/** Single-miner list: total from O(1) account_stats (one row per reward) instead of a filtered count. */
const GET_MINER_REWARDS_WITH_MINER_TOTAL = gql`
query GetMinerRewardsWithMinerTotal(
$limit: Int
$offset: Int
$orderBy: [miner_reward_order_by!]
$where: miner_reward_bool_exp
$minerId: String!
) {
minerRewards: miner_reward(
limit: $limit
offset: $offset
order_by: $orderBy
where: $where
) {
block {
height
hash
}
reward
miner {
id
}
timestamp
}
meta: account_stats_by_pk(id: $minerId) {
totalCount: total_mined_blocks
}
}
`;

type StatsTotalListResponse = {
minerRewards: MinerRewardListResponse['minerRewards'];
meta: { totalCount: number } | null;
};

function documentForListTotals(options: {
useChainTotal: boolean;
useMinerTotal: boolean;
}) {
if (options.useChainTotal) {
return GET_MINER_REWARDS_WITH_CHAIN_TOTAL;
}
if (options.useMinerTotal) {
return GET_MINER_REWARDS_WITH_MINER_TOTAL;
}
return GET_MINER_REWARDS;
}

/** A missing stats row means nothing has been indexed for that scope yet, i.e. zero rewards. */
function normalizeListResponse(
data: MinerRewardListResponse | StatsTotalListResponse | undefined
): MinerRewardListResponse | undefined {
if (!data) return undefined;
const meta = data.meta as
| MinerRewardListResponse['meta']
| StatsTotalListResponse['meta']
| undefined;
if (meta && 'aggregate' in meta) {
return data as MinerRewardListResponse;
}
return {
minerRewards: data.minerRewards,
meta: { aggregate: { totalCount: meta?.totalCount ?? 0 } }
};
}

export const minerRewards = {
useGetAll: (
config?: QueryHookOptions<
MinerRewardListResponse,
PaginatedQueryVariables<MinerRewardSorts, Miner_Reward_Bool_Exp>
PaginatedQueryVariables<MinerRewardSorts, Miner_Reward_Bool_Exp> & {
minerId?: string;
}
>
) => {
const GET_MINER_REWARDS = gql`
query GetMinerRewards(
$limit: Int
$offset: Int
$orderBy: [miner_reward_order_by!]
$where: miner_reward_bool_exp
) {
minerRewards: miner_reward(
limit: $limit
offset: $offset
order_by: $orderBy
where: $where
) {
block {
height
hash
}
reward
miner {
id
}
timestamp
}
meta: miner_reward_aggregate(where: $where) {
aggregate {
totalCount: count
}
}
}
`;
const where = config?.variables?.where;
const minerId = extractMinerId(where);
const useChainTotal = isUnfilteredMinerRewards(where);
const useMinerTotal = !useChainTotal && !!minerId;

return useQuery<
MinerRewardListResponse,
PaginatedQueryVariables<MinerRewardSorts, Miner_Reward_Bool_Exp>
>(GET_MINER_REWARDS, {
const document = documentForListTotals({ useChainTotal, useMinerTotal });

const result = useQuery(document, {
...config,
variables: {
orderBy: config?.variables?.orderBy ?? { timestamp: 'desc' },
limit: config?.variables?.limit ?? QUERY_DEFAULT_LIMIT,
offset: config?.variables?.offset ?? 0,
where: config?.variables?.where
where,
...(useMinerTotal ? { minerId } : {})
}
});

return {
...result,
data: normalizeListResponse(
result.data as
| MinerRewardListResponse
| StatsTotalListResponse
| undefined
)
};
},
useGetStats: (
config?: Omit<QueryHookOptions<MinerRewardsStatsResponse>, 'variables'>
Expand Down
25 changes: 7 additions & 18 deletions src/components/common/table-columns/ACCOUNT_COLUMNS.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,10 @@ import { createColumnHelper } from '@tanstack/react-table';
import { Badge } from '@/components/ui/badge';
import { LinkWithCopy } from '@/components/ui/composites/link-with-copy/LinkWithCopy';
import { RESOURCES } from '@/constants/resources';
import type { Account } from '@/schemas';
import type { AccountListItem } from '@/schemas';
import { formatMonetaryValue } from '@/utils/formatter';

const columnHelper = createColumnHelper<Account>();

const getAccountFlags = (account: Account) => {
const events = account.flagEvents ?? [];
const isHighSec = events.some(
(event) => event.highSecuritySet?.who_id === account.id
);
const isGuardian = events.some(
(event) => event.highSecuritySet?.guardian_id === account.id
);
const isMultisig = events.some((event) => !!event.multisig_id);

return { isHighSec, isGuardian, isMultisig };
};
const columnHelper = createColumnHelper<AccountListItem>();

export const ACCOUNT_COLUMNS = [
columnHelper.accessor('id', {
Expand All @@ -38,9 +25,11 @@ export const ACCOUNT_COLUMNS = [
id: 'flags',
header: 'Flags',
cell: (props) => {
const { isHighSec, isGuardian, isMultisig } = getAccountFlags(
props.row.original
);
const {
is_high_security: isHighSec,
is_guardian: isGuardian,
is_multisig: isMultisig
} = props.row.original;
const hasFlags = isHighSec || isGuardian || isMultisig;

if (!hasFlags) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { createAccountUnifiedTransactionColumns } from '@/components/common/tabl
import { DATA_POOL_INTERVAL } from '@/constants/data-pool-interval';
import { QUERY_DEFAULT_LIMIT } from '@/constants/query-default-limit';
import type { UnifiedListTransactionSorts } from '@/constants/query-sorts';
import { useBrowsablePageDepth } from '@/hooks/useBrowsablePageDepth';
import { useOrderBy } from '@/hooks/useOrderBy';
import { useTableState } from '@/hooks/useTableState';
import type { UnifiedListTransaction } from '@/schemas';
import { browsableRowCount } from '@/utils/browsable-page-depth';
import { transformSortLiteral } from '@/utils/transform-sort';
import { withExcludedRewardTransfers } from '@/utils/unified-transaction-filters';

Expand All @@ -26,6 +28,11 @@ export const useAccountAllTransactions = (accountId: string) => {

const orderByObject = useOrderBy<UnifiedListTransactionSorts>(orderBy ?? '');
const sortingValue = transformSortLiteral(orderBy);
const { beyondDepth } = useBrowsablePageDepth({
currentPageIndex,
limit,
handleChangePagination
});

const where = useMemo(
() =>
Expand All @@ -43,6 +50,7 @@ export const useAccountAllTransactions = (accountId: string) => {
data,
error: fetchError
} = api.unifiedTransactions.useGetAll({
skip: beyondDepth,
pollInterval: DATA_POOL_INTERVAL,
variables: {
orderBy: orderByObject,
Expand All @@ -57,7 +65,7 @@ export const useAccountAllTransactions = (accountId: string) => {
[accountId]
);
const [rowCount, setRowCount] = useState<number>(
data?.meta.aggregate.totalCount ?? 0
browsableRowCount(data?.meta.aggregate.totalCount ?? 0)
);

const table = useReactTable<UnifiedListTransaction>({
Expand Down Expand Up @@ -93,7 +101,7 @@ export const useAccountAllTransactions = (accountId: string) => {

useEffect(() => {
if (!loading && data?.meta.aggregate.totalCount != null)
setRowCount(data.meta.aggregate.totalCount);
setRowCount(browsableRowCount(data.meta.aggregate.totalCount));
}, [loading, data?.meta.aggregate.totalCount]);

return {
Expand Down
Loading
Loading