From d230995f569e7cce5e66d29cfcad340a1bbb602f Mon Sep 17 00:00:00 2001 From: Marc-Aurele Besner <82244926+marc-aurele-besner@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:50:50 -0400 Subject: [PATCH 1/6] add support for account squid --- explorer/codegen.ts | 5 + explorer/gql/types/accounts.ts | 525 +++++++++++++++++++++++ explorer/src/constants/indexers.ts | 7 +- explorer/src/providers/ChainProvider.tsx | 1 + 4 files changed, 535 insertions(+), 3 deletions(-) create mode 100644 explorer/gql/types/accounts.ts diff --git a/explorer/codegen.ts b/explorer/codegen.ts index 8d6410e7c..c11a144f7 100644 --- a/explorer/codegen.ts +++ b/explorer/codegen.ts @@ -6,6 +6,11 @@ dotenv.config() const config: CodegenConfig = { generates: { + './gql/types/accounts.ts': { + schema: defaultIndexer.squids.accounts, + documents: ['./src/**/accounts.query.ts'], + plugins: ['typescript', 'typescript-operations'], + }, './gql/types/leaderboard.ts': { schema: defaultIndexer.squids.leaderboard, documents: ['./src/**/leaderboard.query.ts'], diff --git a/explorer/gql/types/accounts.ts b/explorer/gql/types/accounts.ts new file mode 100644 index 000000000..6794ff4ff --- /dev/null +++ b/explorer/gql/types/accounts.ts @@ -0,0 +1,525 @@ +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + numeric: { input: any; output: any; } + timestamptz: { input: any; output: any; } +}; + +/** Boolean expression to compare columns of type "Int". All fields are combined with logical 'AND'. */ +export type Int_Comparison_Exp = { + _eq?: InputMaybe; + _gt?: InputMaybe; + _gte?: InputMaybe; + _in?: InputMaybe>; + _is_null?: InputMaybe; + _lt?: InputMaybe; + _lte?: InputMaybe; + _neq?: InputMaybe; + _nin?: InputMaybe>; +}; + +/** Boolean expression to compare columns of type "String". All fields are combined with logical 'AND'. */ +export type String_Comparison_Exp = { + _eq?: InputMaybe; + _gt?: InputMaybe; + _gte?: InputMaybe; + /** does the column match the given case-insensitive pattern */ + _ilike?: InputMaybe; + _in?: InputMaybe>; + /** does the column match the given POSIX regular expression, case insensitive */ + _iregex?: InputMaybe; + _is_null?: InputMaybe; + /** does the column match the given pattern */ + _like?: InputMaybe; + _lt?: InputMaybe; + _lte?: InputMaybe; + _neq?: InputMaybe; + /** does the column NOT match the given case-insensitive pattern */ + _nilike?: InputMaybe; + _nin?: InputMaybe>; + /** does the column NOT match the given POSIX regular expression, case insensitive */ + _niregex?: InputMaybe; + /** does the column NOT match the given pattern */ + _nlike?: InputMaybe; + /** does the column NOT match the given POSIX regular expression, case sensitive */ + _nregex?: InputMaybe; + /** does the column NOT match the given SQL regular expression */ + _nsimilar?: InputMaybe; + /** does the column match the given POSIX regular expression, case sensitive */ + _regex?: InputMaybe; + /** does the column match the given SQL regular expression */ + _similar?: InputMaybe; +}; + +/** columns and relationships of "account" */ +export type Account = { + __typename?: 'account'; + created_at: Scalars['Int']['output']; + free: Scalars['numeric']['output']; + id: Scalars['String']['output']; + nonce: Scalars['numeric']['output']; + reserved: Scalars['numeric']['output']; + total?: Maybe; + updated_at: Scalars['Int']['output']; +}; + +/** Boolean expression to filter rows from the table "account". All fields are combined with a logical 'AND'. */ +export type Account_Bool_Exp = { + _and?: InputMaybe>; + _not?: InputMaybe; + _or?: InputMaybe>; + created_at?: InputMaybe; + free?: InputMaybe; + id?: InputMaybe; + nonce?: InputMaybe; + reserved?: InputMaybe; + total?: InputMaybe; + updated_at?: InputMaybe; +}; + +/** Ordering options when selecting data from "account". */ +export type Account_Order_By = { + created_at?: InputMaybe; + free?: InputMaybe; + id?: InputMaybe; + nonce?: InputMaybe; + reserved?: InputMaybe; + total?: InputMaybe; + updated_at?: InputMaybe; +}; + +/** select columns of table "account" */ +export enum Account_Select_Column { + /** column name */ + CreatedAt = 'created_at', + /** column name */ + Free = 'free', + /** column name */ + Id = 'id', + /** column name */ + Nonce = 'nonce', + /** column name */ + Reserved = 'reserved', + /** column name */ + Total = 'total', + /** column name */ + UpdatedAt = 'updated_at' +} + +/** Streaming cursor of the table "account" */ +export type Account_Stream_Cursor_Input = { + /** Stream column input with initial value */ + initial_value: Account_Stream_Cursor_Value_Input; + /** cursor ordering */ + ordering?: InputMaybe; +}; + +/** Initial value of the column from where the streaming should start */ +export type Account_Stream_Cursor_Value_Input = { + created_at?: InputMaybe; + free?: InputMaybe; + id?: InputMaybe; + nonce?: InputMaybe; + reserved?: InputMaybe; + total?: InputMaybe; + updated_at?: InputMaybe; +}; + +/** ordering argument of a cursor */ +export enum Cursor_Ordering { + /** ascending ordering of the cursor */ + Asc = 'ASC', + /** descending ordering of the cursor */ + Desc = 'DESC' +} + +/** Boolean expression to compare columns of type "numeric". All fields are combined with logical 'AND'. */ +export type Numeric_Comparison_Exp = { + _eq?: InputMaybe; + _gt?: InputMaybe; + _gte?: InputMaybe; + _in?: InputMaybe>; + _is_null?: InputMaybe; + _lt?: InputMaybe; + _lte?: InputMaybe; + _neq?: InputMaybe; + _nin?: InputMaybe>; +}; + +/** column ordering options */ +export enum Order_By { + /** in ascending order, nulls last */ + Asc = 'asc', + /** in ascending order, nulls first */ + AscNullsFirst = 'asc_nulls_first', + /** in ascending order, nulls last */ + AscNullsLast = 'asc_nulls_last', + /** in descending order, nulls first */ + Desc = 'desc', + /** in descending order, nulls first */ + DescNullsFirst = 'desc_nulls_first', + /** in descending order, nulls last */ + DescNullsLast = 'desc_nulls_last' +} + +export type Query_Root = { + __typename?: 'query_root'; + /** fetch data from the table: "account" */ + account: Array; + /** fetch data from the table: "account" using primary key columns */ + account_by_pk?: Maybe; + /** fetch data from the table: "transfer" */ + transfer: Array; + /** fetch aggregated fields from the table: "transfer" */ + transfer_aggregate: Transfer_Aggregate; + /** fetch data from the table: "transfer" using primary key columns */ + transfer_by_pk?: Maybe; +}; + + +export type Query_RootAccountArgs = { + distinct_on?: InputMaybe>; + limit?: InputMaybe; + offset?: InputMaybe; + order_by?: InputMaybe>; + where?: InputMaybe; +}; + + +export type Query_RootAccount_By_PkArgs = { + id: Scalars['String']['input']; +}; + + +export type Query_RootTransferArgs = { + distinct_on?: InputMaybe>; + limit?: InputMaybe; + offset?: InputMaybe; + order_by?: InputMaybe>; + where?: InputMaybe; +}; + + +export type Query_RootTransfer_AggregateArgs = { + distinct_on?: InputMaybe>; + limit?: InputMaybe; + offset?: InputMaybe; + order_by?: InputMaybe>; + where?: InputMaybe; +}; + + +export type Query_RootTransfer_By_PkArgs = { + id: Scalars['String']['input']; +}; + +export type Subscription_Root = { + __typename?: 'subscription_root'; + /** fetch data from the table: "account" */ + account: Array; + /** fetch data from the table: "account" using primary key columns */ + account_by_pk?: Maybe; + /** fetch data from the table in a streaming manner: "account" */ + account_stream: Array; + /** fetch data from the table: "transfer" */ + transfer: Array; + /** fetch aggregated fields from the table: "transfer" */ + transfer_aggregate: Transfer_Aggregate; + /** fetch data from the table: "transfer" using primary key columns */ + transfer_by_pk?: Maybe; + /** fetch data from the table in a streaming manner: "transfer" */ + transfer_stream: Array; +}; + + +export type Subscription_RootAccountArgs = { + distinct_on?: InputMaybe>; + limit?: InputMaybe; + offset?: InputMaybe; + order_by?: InputMaybe>; + where?: InputMaybe; +}; + + +export type Subscription_RootAccount_By_PkArgs = { + id: Scalars['String']['input']; +}; + + +export type Subscription_RootAccount_StreamArgs = { + batch_size: Scalars['Int']['input']; + cursor: Array>; + where?: InputMaybe; +}; + + +export type Subscription_RootTransferArgs = { + distinct_on?: InputMaybe>; + limit?: InputMaybe; + offset?: InputMaybe; + order_by?: InputMaybe>; + where?: InputMaybe; +}; + + +export type Subscription_RootTransfer_AggregateArgs = { + distinct_on?: InputMaybe>; + limit?: InputMaybe; + offset?: InputMaybe; + order_by?: InputMaybe>; + where?: InputMaybe; +}; + + +export type Subscription_RootTransfer_By_PkArgs = { + id: Scalars['String']['input']; +}; + + +export type Subscription_RootTransfer_StreamArgs = { + batch_size: Scalars['Int']['input']; + cursor: Array>; + where?: InputMaybe; +}; + +/** Boolean expression to compare columns of type "timestamptz". All fields are combined with logical 'AND'. */ +export type Timestamptz_Comparison_Exp = { + _eq?: InputMaybe; + _gt?: InputMaybe; + _gte?: InputMaybe; + _in?: InputMaybe>; + _is_null?: InputMaybe; + _lt?: InputMaybe; + _lte?: InputMaybe; + _neq?: InputMaybe; + _nin?: InputMaybe>; +}; + +/** columns and relationships of "transfer" */ +export type Transfer = { + __typename?: 'transfer'; + created_at: Scalars['Int']['output']; + date: Scalars['timestamptz']['output']; + fee: Scalars['numeric']['output']; + from: Scalars['String']['output']; + id: Scalars['String']['output']; + timestamp: Scalars['numeric']['output']; + to: Scalars['String']['output']; + value: Scalars['numeric']['output']; +}; + +/** aggregated selection of "transfer" */ +export type Transfer_Aggregate = { + __typename?: 'transfer_aggregate'; + aggregate?: Maybe; + nodes: Array; +}; + +/** aggregate fields of "transfer" */ +export type Transfer_Aggregate_Fields = { + __typename?: 'transfer_aggregate_fields'; + avg?: Maybe; + count: Scalars['Int']['output']; + max?: Maybe; + min?: Maybe; + stddev?: Maybe; + stddev_pop?: Maybe; + stddev_samp?: Maybe; + sum?: Maybe; + var_pop?: Maybe; + var_samp?: Maybe; + variance?: Maybe; +}; + + +/** aggregate fields of "transfer" */ +export type Transfer_Aggregate_FieldsCountArgs = { + columns?: InputMaybe>; + distinct?: InputMaybe; +}; + +/** aggregate avg on columns */ +export type Transfer_Avg_Fields = { + __typename?: 'transfer_avg_fields'; + created_at?: Maybe; + fee?: Maybe; + timestamp?: Maybe; + value?: Maybe; +}; + +/** Boolean expression to filter rows from the table "transfer". All fields are combined with a logical 'AND'. */ +export type Transfer_Bool_Exp = { + _and?: InputMaybe>; + _not?: InputMaybe; + _or?: InputMaybe>; + created_at?: InputMaybe; + date?: InputMaybe; + fee?: InputMaybe; + from?: InputMaybe; + id?: InputMaybe; + timestamp?: InputMaybe; + to?: InputMaybe; + value?: InputMaybe; +}; + +/** aggregate max on columns */ +export type Transfer_Max_Fields = { + __typename?: 'transfer_max_fields'; + created_at?: Maybe; + date?: Maybe; + fee?: Maybe; + from?: Maybe; + id?: Maybe; + timestamp?: Maybe; + to?: Maybe; + value?: Maybe; +}; + +/** aggregate min on columns */ +export type Transfer_Min_Fields = { + __typename?: 'transfer_min_fields'; + created_at?: Maybe; + date?: Maybe; + fee?: Maybe; + from?: Maybe; + id?: Maybe; + timestamp?: Maybe; + to?: Maybe; + value?: Maybe; +}; + +/** Ordering options when selecting data from "transfer". */ +export type Transfer_Order_By = { + created_at?: InputMaybe; + date?: InputMaybe; + fee?: InputMaybe; + from?: InputMaybe; + id?: InputMaybe; + timestamp?: InputMaybe; + to?: InputMaybe; + value?: InputMaybe; +}; + +/** select columns of table "transfer" */ +export enum Transfer_Select_Column { + /** column name */ + CreatedAt = 'created_at', + /** column name */ + Date = 'date', + /** column name */ + Fee = 'fee', + /** column name */ + From = 'from', + /** column name */ + Id = 'id', + /** column name */ + Timestamp = 'timestamp', + /** column name */ + To = 'to', + /** column name */ + Value = 'value' +} + +/** aggregate stddev on columns */ +export type Transfer_Stddev_Fields = { + __typename?: 'transfer_stddev_fields'; + created_at?: Maybe; + fee?: Maybe; + timestamp?: Maybe; + value?: Maybe; +}; + +/** aggregate stddev_pop on columns */ +export type Transfer_Stddev_Pop_Fields = { + __typename?: 'transfer_stddev_pop_fields'; + created_at?: Maybe; + fee?: Maybe; + timestamp?: Maybe; + value?: Maybe; +}; + +/** aggregate stddev_samp on columns */ +export type Transfer_Stddev_Samp_Fields = { + __typename?: 'transfer_stddev_samp_fields'; + created_at?: Maybe; + fee?: Maybe; + timestamp?: Maybe; + value?: Maybe; +}; + +/** Streaming cursor of the table "transfer" */ +export type Transfer_Stream_Cursor_Input = { + /** Stream column input with initial value */ + initial_value: Transfer_Stream_Cursor_Value_Input; + /** cursor ordering */ + ordering?: InputMaybe; +}; + +/** Initial value of the column from where the streaming should start */ +export type Transfer_Stream_Cursor_Value_Input = { + created_at?: InputMaybe; + date?: InputMaybe; + fee?: InputMaybe; + from?: InputMaybe; + id?: InputMaybe; + timestamp?: InputMaybe; + to?: InputMaybe; + value?: InputMaybe; +}; + +/** aggregate sum on columns */ +export type Transfer_Sum_Fields = { + __typename?: 'transfer_sum_fields'; + created_at?: Maybe; + fee?: Maybe; + timestamp?: Maybe; + value?: Maybe; +}; + +/** aggregate var_pop on columns */ +export type Transfer_Var_Pop_Fields = { + __typename?: 'transfer_var_pop_fields'; + created_at?: Maybe; + fee?: Maybe; + timestamp?: Maybe; + value?: Maybe; +}; + +/** aggregate var_samp on columns */ +export type Transfer_Var_Samp_Fields = { + __typename?: 'transfer_var_samp_fields'; + created_at?: Maybe; + fee?: Maybe; + timestamp?: Maybe; + value?: Maybe; +}; + +/** aggregate variance on columns */ +export type Transfer_Variance_Fields = { + __typename?: 'transfer_variance_fields'; + created_at?: Maybe; + fee?: Maybe; + timestamp?: Maybe; + value?: Maybe; +}; + +export type TransfersByAccountIdQueryVariables = Exact<{ + limit: Scalars['Int']['input']; + offset?: InputMaybe; + where?: InputMaybe; + orderBy: Array | Transfer_Order_By; +}>; + + +export type TransfersByAccountIdQuery = { __typename?: 'query_root', transfer_aggregate: { __typename?: 'transfer_aggregate', aggregate?: { __typename?: 'transfer_aggregate_fields', count: number } | null }, transfer: Array<{ __typename?: 'transfer', created_at: number, date: any, fee: any, from: string, id: string, timestamp: any, to: string, value: any }> }; diff --git a/explorer/src/constants/indexers.ts b/explorer/src/constants/indexers.ts index 75465062f..bb05f5938 100644 --- a/explorer/src/constants/indexers.ts +++ b/explorer/src/constants/indexers.ts @@ -5,6 +5,7 @@ export interface Indexer { network: NetworkId squids: { old: string + accounts?: string leaderboard?: string staking?: string } @@ -16,9 +17,9 @@ export const indexers: Indexer[] = [ network: NetworkId.GEMINI_3H, squids: { old: 'https://squid.gemini-3h.subspace.network/graphql', - leaderboard: - 'https://autonomys-labs.squids.live/leaderboard-squid/v/v6/addons/hasura/v1/graphql', - staking: 'https://autonomys-labs.squids.live/staking-squid/v/v24/addons/hasura/v1/graphql', + accounts: 'https://autonomys-labs.squids.live/accounts-squid/addons/hasura/v1/graphql', + leaderboard: 'https://autonomys-labs.squids.live/leaderboard-squid/addons/hasura/v1/graphql', + staking: 'https://autonomys-labs.squids.live/staking-squid/addons/hasura/v1/graphql', }, }, { diff --git a/explorer/src/providers/ChainProvider.tsx b/explorer/src/providers/ChainProvider.tsx index 8009cf5e9..406f53e7f 100644 --- a/explorer/src/providers/ChainProvider.tsx +++ b/explorer/src/providers/ChainProvider.tsx @@ -41,6 +41,7 @@ export const SelectedChainProvider: FC = ({ indexerSet, chil uri: ({ getContext }: Operation) => { const { clientName } = getContext() + if (clientName === 'accounts' && indexerSet.squids.accounts) return indexerSet.squids.accounts if (clientName === 'leaderboard' && indexerSet.squids.leaderboard) return indexerSet.squids.leaderboard if (clientName === 'staking' && indexerSet.squids.staking) return indexerSet.squids.staking From fb7ab8006d83e50e28781d65473e164eedb5e88e Mon Sep 17 00:00:00 2001 From: Marc-Aurele Besner <82244926+marc-aurele-besner@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:19:01 -0400 Subject: [PATCH 2/6] add TransfersByAccountId query for accounts-squid --- .../Consensus/Account/accounts.query.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 explorer/src/components/Consensus/Account/accounts.query.ts diff --git a/explorer/src/components/Consensus/Account/accounts.query.ts b/explorer/src/components/Consensus/Account/accounts.query.ts new file mode 100644 index 000000000..ef3c550b3 --- /dev/null +++ b/explorer/src/components/Consensus/Account/accounts.query.ts @@ -0,0 +1,26 @@ +import { gql } from '@apollo/client' + +export const QUERY_ACCOUNT_TRANSFERS = gql` + query TransfersByAccountId( + $limit: Int! + $offset: Int + $where: transfer_bool_exp + $orderBy: [transfer_order_by!]! + ) { + transfer_aggregate(order_by: $orderBy, where: $where) { + aggregate { + count + } + } + transfer(order_by: $orderBy, limit: $limit, offset: $offset, where: $where) { + created_at + date + fee + from + id + timestamp + to + value + } + } +` From 2492fac7b6b5886dcda57163784e2d95b4b0df19 Mon Sep 17 00:00:00 2001 From: Marc-Aurele Besner <82244926+marc-aurele-besner@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:19:17 -0400 Subject: [PATCH 3/6] add AccountTransfersList component --- .../Account/AccountTransfersList.tsx | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 explorer/src/components/Consensus/Account/AccountTransfersList.tsx diff --git a/explorer/src/components/Consensus/Account/AccountTransfersList.tsx b/explorer/src/components/Consensus/Account/AccountTransfersList.tsx new file mode 100644 index 000000000..a8e71da1b --- /dev/null +++ b/explorer/src/components/Consensus/Account/AccountTransfersList.tsx @@ -0,0 +1,243 @@ +/* eslint-disable camelcase */ +import { Tooltip } from '@/components/common/Tooltip' +import { useApolloClient } from '@apollo/client' +import { SortingState } from '@tanstack/react-table' +import { AccountIcon } from 'components/common/AccountIcon' +import { SortedTable } from 'components/common/SortedTable' +import { Spinner } from 'components/common/Spinner' +import { NotFound } from 'components/layout/NotFound' +import { PAGE_SIZE, TOKEN } from 'constants/general' +import { INTERNAL_ROUTES, Routes } from 'constants/routes' +import dayjs from 'dayjs' +import relativeTime from 'dayjs/plugin/relativeTime' +import { formatUnits } from 'ethers' +import { + Order_By as OrderBy, + Transfer, + Transfer_Select_Column, + TransfersByAccountIdQuery, + TransfersByAccountIdQueryVariables, + Transfer_Bool_Exp as TransferWhere, +} from 'gql/types/accounts' +import useChains from 'hooks/useChains' +import useMediaQuery from 'hooks/useMediaQuery' +import { useSquidQuery } from 'hooks/useSquidQuery' +import { useWindowFocus } from 'hooks/useWindowFocus' +import Link from 'next/link' +import { FC, useCallback, useEffect, useMemo, useState } from 'react' +import { useInView } from 'react-intersection-observer' +import { downloadFullData } from 'utils/downloadFullData' +import { bigNumberToNumber } from 'utils/number' +import { shortString } from 'utils/string' +import { countTablePages } from 'utils/table' +import { QUERY_ACCOUNT_TRANSFERS } from './accounts.query' + +dayjs.extend(relativeTime) + +type Props = { + accountId: string +} + +type Row = { + row: { + index: number + original: Transfer + } +} + +export const AccountTransfersList: FC = ({ accountId }) => { + const { ref, inView } = useInView() + const [sorting, setSorting] = useState([ + { id: Transfer_Select_Column.CreatedAt, desc: true }, + ]) + const [pagination, setPagination] = useState({ + pageSize: PAGE_SIZE, + pageIndex: 0, + }) + const isLargeLaptop = useMediaQuery('(min-width: 1440px)') + const { network } = useChains() + const apolloClient = useApolloClient() + const inFocus = useWindowFocus() + + const orderBy = useMemo( + () => + sorting && sorting.length > 0 + ? sorting[0].id.endsWith('aggregate') + ? { [sorting[0].id]: sorting[0].desc ? { count: OrderBy.Desc } : { count: OrderBy.Asc } } + : { [sorting[0].id]: sorting[0].desc ? OrderBy.Desc : OrderBy.Asc } + : { id: OrderBy.Asc }, + [sorting], + ) + + const where: TransferWhere = useMemo( + () => ({ + _or: [{ from: { _eq: accountId } }, { to: { _eq: accountId } }], + }), + [accountId], + ) + + const variables = useMemo(() => { + return { + limit: pagination.pageSize, + offset: pagination.pageIndex > 0 ? pagination.pageIndex * pagination.pageSize : undefined, + orderBy, + where, + } + }, [orderBy, pagination.pageIndex, pagination.pageSize, where]) + + const { data, loading, setIsVisible } = useSquidQuery< + TransfersByAccountIdQuery, + TransfersByAccountIdQueryVariables + >(QUERY_ACCOUNT_TRANSFERS, { + variables, + skip: !inFocus, + pollInterval: 6000, + context: { clientName: 'accounts' }, + }) + + const fullDataDownloader = useCallback( + () => + downloadFullData(apolloClient, QUERY_ACCOUNT_TRANSFERS, 'extrinsicsConnection', { + orderBy, + where, + }), + [apolloClient, orderBy, where], + ) + + const transfers = useMemo(() => data && data.transfer, [data]) + const totalCount = useMemo(() => (data && data.transfer_aggregate.aggregate?.count) || 0, [data]) + const pageCount = useMemo( + () => (totalCount ? countTablePages(totalCount, pagination.pageSize) : 0), + [totalCount, pagination.pageSize], + ) + + const columns = useMemo( + () => [ + { + accessorKey: 'direction', + header: 'Direction', + enableSorting: false, + cell: ({ row }: Row) => ( +
+ {row.original.from === accountId ? 'Sent' : 'Received'} +
+ ), + }, + { + accessorKey: 'from', + header: 'From', + enableSorting: true, + cell: ({ row }: Row) => ( +
+ {isLargeLaptop && } + +
{isLargeLaptop ? row.original.from : shortString(row.original.from)}
+ +
+ ), + }, + { + accessorKey: 'to', + header: 'To', + enableSorting: true, + cell: ({ row }: Row) => ( +
+ {isLargeLaptop && } + +
{isLargeLaptop ? row.original.to : shortString(row.original.to)}
+ +
+ ), + }, + { + accessorKey: 'value', + header: 'Amount', + enableSorting: true, + cell: ({ row }) => ( +
+ + {`${bigNumberToNumber(row.original.value)} ${TOKEN.symbol}`} + +
+ ), + }, + { + accessorKey: 'fee', + header: 'Fee paid', + enableSorting: true, + cell: ({ row }) => ( +
+ + {`${bigNumberToNumber(row.original.fee, 6)} ${TOKEN.symbol}`} + +
+ ), + }, + { + accessorKey: 'created_at', + header: 'Created at', + enableSorting: true, + cell: ({ row }) => ( +
+ + +
{row.original.created_at}
+ +
+
+ ), + }, + ], + [accountId, isLargeLaptop, network], + ) + + const noData = useMemo(() => { + if (loading) return + if (!data) return + return null + }, [data, loading]) + + useEffect(() => { + setIsVisible(inView) + }, [inView, setIsVisible]) + + return ( +
+
+ {!loading && transfers ? ( + + ) : ( + noData + )} +
+
+ ) +} From 7caa315a2d212034e0c3cb23c5d93592a84564b2 Mon Sep 17 00:00:00 2001 From: Marc-Aurele Besner <82244926+marc-aurele-besner@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:19:33 -0400 Subject: [PATCH 4/6] add AccountTransfersList in Account details --- .../src/components/Consensus/Account/Account.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/explorer/src/components/Consensus/Account/Account.tsx b/explorer/src/components/Consensus/Account/Account.tsx index fcd5ac07c..2a14e09b3 100644 --- a/explorer/src/components/Consensus/Account/Account.tsx +++ b/explorer/src/components/Consensus/Account/Account.tsx @@ -1,7 +1,9 @@ 'use client' import { sendGAEvent } from '@next/third-parties/google' +import { PageTabs } from 'components/common/PageTabs' import { Spinner } from 'components/common/Spinner' +import { Tab } from 'components/common/Tabs' import { NotFound } from 'components/layout/NotFound' import { Routes } from 'constants/routes' import { @@ -23,6 +25,7 @@ import { AccountDetailsCard } from './AccountDetailsCard' import { AccountExtrinsicList } from './AccountExtrinsicList' import { AccountGraphs } from './AccountGraphs' import { AccountRewardsHistory } from './AccountRewardsHistory' +import { AccountTransfersList } from './AccountTransfersList' import { QUERY_ACCOUNT_BY_ID } from './query' export const Account: FC = () => { @@ -77,11 +80,18 @@ export const Account: FC = () => { accountAddress={accountId} isDesktop={isDesktop} /> -
+
- + + + + + + + + ) : ( noData From 6c4a56c3eb37716572ed6c07e314cbf504a419ba Mon Sep 17 00:00:00 2001 From: Marc-Aurele Besner <82244926+marc-aurele-besner@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:19:52 -0400 Subject: [PATCH 5/6] temporary remove account extrinsic filter --- .../Account/AccountExtrinsicList.tsx | 50 +++++++------------ 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/explorer/src/components/Consensus/Account/AccountExtrinsicList.tsx b/explorer/src/components/Consensus/Account/AccountExtrinsicList.tsx index b89645ed1..7eecac246 100644 --- a/explorer/src/components/Consensus/Account/AccountExtrinsicList.tsx +++ b/explorer/src/components/Consensus/Account/AccountExtrinsicList.tsx @@ -1,4 +1,3 @@ -/* eslint-disable camelcase */ import { useApolloClient } from '@apollo/client' import { SortingState } from '@tanstack/react-table' import { CopyButton } from 'components/common/CopyButton' @@ -12,7 +11,6 @@ import dayjs from 'dayjs' import { Extrinsic, ExtrinsicOrderByInput, - ExtrinsicWhereInput, ExtrinsicsByAccountIdQuery, ExtrinsicsByAccountIdQueryVariables, } from 'gql/graphql' @@ -27,7 +25,6 @@ import { downloadFullData } from 'utils/downloadFullData' import { sort } from 'utils/sort' import { shortString } from 'utils/string' import { countTablePages } from 'utils/table' -import { AccountExtrinsicFilterDropdown } from './AccountExtrinsicFilterDropdown' import { QUERY_ACCOUNT_EXTRINSICS } from './query' type Props = { @@ -48,7 +45,6 @@ export const AccountExtrinsicList: FC = ({ accountId }) => { pageSize: PAGE_SIZE, pageIndex: 0, }) - const [filters, setFilters] = useState({}) const { network, section } = useChains() const apolloClient = useApolloClient() @@ -61,12 +57,12 @@ export const AccountExtrinsicList: FC = ({ accountId }) => { const where = useMemo( () => ({ - ...filters, signer: { + /* eslint-disable camelcase */ id_eq: accountId, }, }), - [accountId, filters], + [accountId], ) const variables = useMemo(() => { @@ -204,31 +200,23 @@ export const AccountExtrinsicList: FC = ({ accountId }) => { }, [inView, setIsVisible]) return ( -
-
-
-
Action Filter:
- -
-
-
- {!loading && extrinsics ? ( - - ) : ( - noData - )} -
+
+ {!loading && extrinsics ? ( + + ) : ( + noData + )}
) } From 3ae5dad15902c45eb677a253c3cb822a62a5969e Mon Sep 17 00:00:00 2001 From: Marc-Aurele Besner <82244926+marc-aurele-besner@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:48:18 -0400 Subject: [PATCH 6/6] improve the account transfers section --- explorer/gql/types/accounts.ts | 37 +++++++- .../Account/AccountTransfersList.tsx | 86 ++++++++++++++----- .../Consensus/Account/accounts.query.ts | 13 +-- explorer/src/utils/string.ts | 7 ++ 4 files changed, 116 insertions(+), 27 deletions(-) diff --git a/explorer/gql/types/accounts.ts b/explorer/gql/types/accounts.ts index 6794ff4ff..5db257b14 100644 --- a/explorer/gql/types/accounts.ts +++ b/explorer/gql/types/accounts.ts @@ -16,6 +16,19 @@ export type Scalars = { timestamptz: { input: any; output: any; } }; +/** Boolean expression to compare columns of type "Boolean". All fields are combined with logical 'AND'. */ +export type Boolean_Comparison_Exp = { + _eq?: InputMaybe; + _gt?: InputMaybe; + _gte?: InputMaybe; + _in?: InputMaybe>; + _is_null?: InputMaybe; + _lt?: InputMaybe; + _lte?: InputMaybe; + _neq?: InputMaybe; + _nin?: InputMaybe>; +}; + /** Boolean expression to compare columns of type "Int". All fields are combined with logical 'AND'. */ export type Int_Comparison_Exp = { _eq?: InputMaybe; @@ -311,9 +324,12 @@ export type Transfer = { __typename?: 'transfer'; created_at: Scalars['Int']['output']; date: Scalars['timestamptz']['output']; + event_id: Scalars['String']['output']; + extrinsic_id: Scalars['String']['output']; fee: Scalars['numeric']['output']; from: Scalars['String']['output']; id: Scalars['String']['output']; + success: Scalars['Boolean']['output']; timestamp: Scalars['numeric']['output']; to: Scalars['String']['output']; value: Scalars['numeric']['output']; @@ -365,9 +381,12 @@ export type Transfer_Bool_Exp = { _or?: InputMaybe>; created_at?: InputMaybe; date?: InputMaybe; + event_id?: InputMaybe; + extrinsic_id?: InputMaybe; fee?: InputMaybe; from?: InputMaybe; id?: InputMaybe; + success?: InputMaybe; timestamp?: InputMaybe; to?: InputMaybe; value?: InputMaybe; @@ -378,6 +397,8 @@ export type Transfer_Max_Fields = { __typename?: 'transfer_max_fields'; created_at?: Maybe; date?: Maybe; + event_id?: Maybe; + extrinsic_id?: Maybe; fee?: Maybe; from?: Maybe; id?: Maybe; @@ -391,6 +412,8 @@ export type Transfer_Min_Fields = { __typename?: 'transfer_min_fields'; created_at?: Maybe; date?: Maybe; + event_id?: Maybe; + extrinsic_id?: Maybe; fee?: Maybe; from?: Maybe; id?: Maybe; @@ -403,9 +426,12 @@ export type Transfer_Min_Fields = { export type Transfer_Order_By = { created_at?: InputMaybe; date?: InputMaybe; + event_id?: InputMaybe; + extrinsic_id?: InputMaybe; fee?: InputMaybe; from?: InputMaybe; id?: InputMaybe; + success?: InputMaybe; timestamp?: InputMaybe; to?: InputMaybe; value?: InputMaybe; @@ -418,12 +444,18 @@ export enum Transfer_Select_Column { /** column name */ Date = 'date', /** column name */ + EventId = 'event_id', + /** column name */ + ExtrinsicId = 'extrinsic_id', + /** column name */ Fee = 'fee', /** column name */ From = 'from', /** column name */ Id = 'id', /** column name */ + Success = 'success', + /** column name */ Timestamp = 'timestamp', /** column name */ To = 'to', @@ -470,9 +502,12 @@ export type Transfer_Stream_Cursor_Input = { export type Transfer_Stream_Cursor_Value_Input = { created_at?: InputMaybe; date?: InputMaybe; + event_id?: InputMaybe; + extrinsic_id?: InputMaybe; fee?: InputMaybe; from?: InputMaybe; id?: InputMaybe; + success?: InputMaybe; timestamp?: InputMaybe; to?: InputMaybe; value?: InputMaybe; @@ -522,4 +557,4 @@ export type TransfersByAccountIdQueryVariables = Exact<{ }>; -export type TransfersByAccountIdQuery = { __typename?: 'query_root', transfer_aggregate: { __typename?: 'transfer_aggregate', aggregate?: { __typename?: 'transfer_aggregate_fields', count: number } | null }, transfer: Array<{ __typename?: 'transfer', created_at: number, date: any, fee: any, from: string, id: string, timestamp: any, to: string, value: any }> }; +export type TransfersByAccountIdQuery = { __typename?: 'query_root', transfer_aggregate: { __typename?: 'transfer_aggregate', aggregate?: { __typename?: 'transfer_aggregate_fields', count: number } | null }, transfer: Array<{ __typename?: 'transfer', id: string, extrinsic_id: string, event_id: string, from: string, to: string, value: any, fee: any, success: boolean, timestamp: any, date: any, created_at: number }> }; diff --git a/explorer/src/components/Consensus/Account/AccountTransfersList.tsx b/explorer/src/components/Consensus/Account/AccountTransfersList.tsx index a8e71da1b..3099c330d 100644 --- a/explorer/src/components/Consensus/Account/AccountTransfersList.tsx +++ b/explorer/src/components/Consensus/Account/AccountTransfersList.tsx @@ -1,10 +1,11 @@ /* eslint-disable camelcase */ -import { Tooltip } from '@/components/common/Tooltip' import { useApolloClient } from '@apollo/client' import { SortingState } from '@tanstack/react-table' import { AccountIcon } from 'components/common/AccountIcon' import { SortedTable } from 'components/common/SortedTable' import { Spinner } from 'components/common/Spinner' +import { StatusIcon } from 'components/common/StatusIcon' +import { Tooltip } from 'components/common/Tooltip' import { NotFound } from 'components/layout/NotFound' import { PAGE_SIZE, TOKEN } from 'constants/general' import { INTERNAL_ROUTES, Routes } from 'constants/routes' @@ -28,7 +29,7 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react' import { useInView } from 'react-intersection-observer' import { downloadFullData } from 'utils/downloadFullData' import { bigNumberToNumber } from 'utils/number' -import { shortString } from 'utils/string' +import { formatExtrinsicId, shortString } from 'utils/string' import { countTablePages } from 'utils/table' import { QUERY_ACCOUNT_TRANSFERS } from './accounts.query' @@ -113,12 +114,55 @@ export const AccountTransfersList: FC = ({ accountId }) => { const columns = useMemo( () => [ + { + accessorKey: 'created_at', + header: 'Block', + enableSorting: true, + cell: ({ row }: Row) => ( +
+ +
{row.original.created_at}
+ +
+ ), + }, + { + accessorKey: 'extrinsic_id', + header: 'Extrinsic', + enableSorting: true, + cell: ({ row }: Row) => ( +
+ +
{formatExtrinsicId(row.original.extrinsic_id)}
+ +
+ ), + }, { accessorKey: 'direction', header: 'Direction', enableSorting: false, cell: ({ row }: Row) => ( -
+
{row.original.from === accountId ? 'Sent' : 'Received'}
), @@ -135,7 +179,7 @@ export const AccountTransfersList: FC = ({ accountId }) => { href={INTERNAL_ROUTES.accounts.id.page(network, Routes.consensus, row.original.from)} className='hover:text-primaryAccent' > -
{isLargeLaptop ? row.original.from : shortString(row.original.from)}
+
{shortString(row.original.from)}
), @@ -152,7 +196,7 @@ export const AccountTransfersList: FC = ({ accountId }) => { href={INTERNAL_ROUTES.accounts.id.page(network, Routes.consensus, row.original.to)} className='hover:text-primaryAccent' > -
{isLargeLaptop ? row.original.to : shortString(row.original.to)}
+
{shortString(row.original.to)}
), @@ -182,25 +226,25 @@ export const AccountTransfersList: FC = ({ accountId }) => { ), }, { - accessorKey: 'created_at', - header: 'Created at', + accessorKey: 'success', + header: 'Status', + enableSorting: true, + cell: ({ row }) => ( +
+ +
+ ), + }, + { + accessorKey: 'date', + header: 'Date', enableSorting: true, cell: ({ row }) => (
- - -
{row.original.created_at}
- -
+ {dayjs(row.original.date).fromNow(true)} ago
), }, diff --git a/explorer/src/components/Consensus/Account/accounts.query.ts b/explorer/src/components/Consensus/Account/accounts.query.ts index ef3c550b3..085c99a2d 100644 --- a/explorer/src/components/Consensus/Account/accounts.query.ts +++ b/explorer/src/components/Consensus/Account/accounts.query.ts @@ -13,14 +13,17 @@ export const QUERY_ACCOUNT_TRANSFERS = gql` } } transfer(order_by: $orderBy, limit: $limit, offset: $offset, where: $where) { - created_at - date - fee - from id - timestamp + extrinsic_id + event_id + from to value + fee + success + timestamp + date + created_at } } ` diff --git a/explorer/src/utils/string.ts b/explorer/src/utils/string.ts index 93daa5438..058281a9a 100644 --- a/explorer/src/utils/string.ts +++ b/explorer/src/utils/string.ts @@ -14,3 +14,10 @@ export const capitalizeFirstLetter = (string: string) => export const limitText = (text: string, limit = 20) => text.length > limit ? text.slice(0, limit) + '...' : text + +export const formatExtrinsicId = (extrinsicId: string): string => { + const parts = extrinsicId.split('-') + const part1 = parts[0].replace(/^0+/, '') + const part2 = parts[2].replace(/^0+/, '') + return part1 + '-' + part2 +}