diff --git a/src/app/components/Search/search-utils.ts b/src/app/components/Search/search-utils.ts
index 752c2fc784..c097b1eee9 100644
--- a/src/app/components/Search/search-utils.ts
+++ b/src/app/components/Search/search-utils.ts
@@ -112,6 +112,11 @@ export const validateAndNormalize = {
return searchTerm.toLowerCase()
}
},
+ accountNameFragment: (searchTerm: string) => {
+ if (searchTerm?.length >= textSearchMininumLength) {
+ return searchTerm.toLowerCase()
+ }
+ },
} satisfies { [name: string]: (searchTerm: string) => string | undefined }
export function isSearchValid(searchTerm: string) {
diff --git a/src/app/data/pontusx-account-names.ts b/src/app/data/pontusx-account-names.ts
index 56f36bdfee..b5474f0c96 100644
--- a/src/app/data/pontusx-account-names.ts
+++ b/src/app/data/pontusx-account-names.ts
@@ -1,6 +1,9 @@
import axios from 'axios'
import { useQuery } from '@tanstack/react-query'
import type { AccountNameInfo } from '../hooks/useAccountName'
+import { Layer } from '../../oasis-nexus/api'
+import { Network } from '../../types/network'
+import { findTextMatch } from '../components/HighlightedText/text-matching'
import * as process from 'process'
const DATA_SOURCE_URL = 'https://raw.githubusercontent.com/deltaDAO/mvg-portal/main/pontusxAddresses.json'
@@ -63,3 +66,29 @@ export const usePontusXAccountName = (address: string, enabled: boolean): Accoun
loading: isLoading,
}
}
+
+export const useSearchForPontusXAccountsByName = (
+ network: Network,
+ nameFragment: string,
+ enabled: boolean,
+) => {
+ const { isLoading, error, data: allNames } = usePontusXAccountNames(enabled)
+ if (error) {
+ console.log('Failed to load Pontus-X account names', error)
+ }
+
+ const textMatcher =
+ nameFragment && enabled
+ ? (entry: AccountEntry): boolean => {
+ return !!findTextMatch(entry.name, [nameFragment])
+ }
+ : () => false
+ return {
+ results: (allNames?.list || []).filter(textMatcher).map(entry => ({
+ network,
+ layer: Layer.pontusx,
+ address: entry.address,
+ })),
+ isLoading,
+ }
+}
diff --git a/src/app/hooks/useAccountName.ts b/src/app/hooks/useAccountName.ts
index 20e052bed9..fe7a95ea77 100644
--- a/src/app/hooks/useAccountName.ts
+++ b/src/app/hooks/useAccountName.ts
@@ -1,7 +1,7 @@
import { SearchScope } from '../../types/searchScope'
import Chance from 'chance'
import { Layer } from '../../oasis-nexus/api'
-import { usePontusXAccountName } from '../data/pontusx-account-names'
+import { usePontusXAccountName, useSearchForPontusXAccountsByName } from '../data/pontusx-account-names'
const NO_MATCH = '__no_match__'
@@ -59,3 +59,14 @@ export const useAccountName = (scope: SearchScope, address: string, dropCache =
loading: false,
}
}
+
+export const useSearchForAccountsByName = (scope: SearchScope, nameFragment = '') => {
+ const isValidPontusXSearch = scope.layer === Layer.pontusx && !!nameFragment
+ const pontusXResults = useSearchForPontusXAccountsByName(scope.network, nameFragment, isValidPontusXSearch)
+ return isValidPontusXSearch
+ ? pontusXResults
+ : {
+ isLoading: false,
+ results: [],
+ }
+}
diff --git a/src/app/pages/AccountDetailsPage/DeferredConsensusAccountDetails.tsx b/src/app/pages/AccountDetailsPage/DeferredConsensusAccountDetails.tsx
new file mode 100644
index 0000000000..b3e3ee690d
--- /dev/null
+++ b/src/app/pages/AccountDetailsPage/DeferredConsensusAccountDetails.tsx
@@ -0,0 +1,20 @@
+import { FC } from 'react'
+import { AllTokenPrices } from '../../../coin-gecko/api'
+import { Network } from '../../../types/network'
+
+/**
+ * Load and display details of a RuntimeAccount
+ */
+export const DeferredConsensusAccountDetails: FC<{
+ network: Network
+ address: string
+ tokenPrices: AllTokenPrices
+ showLayer?: boolean
+}> = () =>
+ // {
+ // network, address, tokenPrices, highlightedPartOfName, showLayer
+ // },
+ {
+ // TODO: load and display consensus account details when API and component becomes available
+ return null
+ }
diff --git a/src/app/pages/AccountDetailsPage/DeferredRuntimeAccountDetails.tsx b/src/app/pages/AccountDetailsPage/DeferredRuntimeAccountDetails.tsx
new file mode 100644
index 0000000000..e00cdd468d
--- /dev/null
+++ b/src/app/pages/AccountDetailsPage/DeferredRuntimeAccountDetails.tsx
@@ -0,0 +1,27 @@
+import { FC } from 'react'
+import { Runtime, useGetRuntimeAccountsAddress } from '../../../oasis-nexus/api'
+import { AllTokenPrices } from '../../../coin-gecko/api'
+import { AccountDetailsView } from './AccountDetailsView'
+import { Network } from '../../../types/network'
+
+/**
+ * Load and display details of a RuntimeAccount
+ */
+export const DeferredRuntimeAccountDetails: FC<{
+ network: Network
+ layer: Runtime
+ address: string
+ tokenPrices: AllTokenPrices
+ showLayer?: boolean
+}> = ({ network, layer, address, tokenPrices, showLayer }) => {
+ const { data, isLoading, isError } = useGetRuntimeAccountsAddress(network, layer, address)
+ return (
+
+ )
+}
diff --git a/src/app/pages/SearchResultsPage/SearchResultsList.tsx b/src/app/pages/SearchResultsPage/SearchResultsList.tsx
index 366259b74c..bf3eecd5d6 100644
--- a/src/app/pages/SearchResultsPage/SearchResultsList.tsx
+++ b/src/app/pages/SearchResultsPage/SearchResultsList.tsx
@@ -7,6 +7,7 @@ import { RuntimeTransactionDetailView } from '../RuntimeTransactionDetailPage'
import { AccountDetailsView } from '../AccountDetailsPage/AccountDetailsView'
import {
AccountResult,
+ AccountAddressResult,
BlockResult,
ContractResult,
ProposalResult,
@@ -21,6 +22,9 @@ import { AllTokenPrices } from '../../../coin-gecko/api'
import { ResultListFrame } from './ResultListFrame'
import { TokenDetails } from '../../components/Tokens/TokenDetails'
import { ProposalDetailView } from '../ProposalDetailsPage'
+import { DeferredRuntimeAccountDetails } from '../AccountDetailsPage/DeferredRuntimeAccountDetails'
+import { Layer } from '../../../oasis-nexus/api'
+import { DeferredConsensusAccountDetails } from '../AccountDetailsPage/DeferredConsensusAccountDetails'
/**
* Component for displaying a list of search results
@@ -94,6 +98,33 @@ export const SearchResultsList: FC<{
linkLabel={t('search.results.accounts.viewLink')}
/>
+ item.resultType === 'accountAddress',
+ )}
+ resultComponent={item =>
+ item.layer === Layer.consensus ? (
+
+ ) : (
+
+ )
+ }
+ link={acc => RouteUtils.getAccountRoute(acc, acc.address)}
+ linkLabel={t('search.results.accounts.viewLink')}
+ />
+
item.resultType === 'contract')}
diff --git a/src/app/pages/SearchResultsPage/hooks.ts b/src/app/pages/SearchResultsPage/hooks.ts
index 395a96cbfb..b5450202e5 100644
--- a/src/app/pages/SearchResultsPage/hooks.ts
+++ b/src/app/pages/SearchResultsPage/hooks.ts
@@ -19,6 +19,7 @@ import {
import { RouteUtils } from '../../utils/route-utils'
import { SearchParams } from '../../components/Search/search-utils'
import { SearchScope } from '../../../types/searchScope'
+import { useSearchForAccountsByName } from '../../hooks/useAccountName'
function isDefined(item: T): item is NonNullable {
return item != null
@@ -27,7 +28,7 @@ function isDefined(item: T): item is NonNullable {
export type ConditionalResults = { isLoading: boolean; results: T[] }
type SearchResultItemCore = HasScope & {
- resultType: 'block' | 'transaction' | 'account' | 'contract' | 'token' | 'proposal'
+ resultType: 'block' | 'transaction' | 'account' | 'accountAddress' | 'contract' | 'token' | 'proposal'
}
export type BlockResult = SearchResultItemCore & RuntimeBlock & { resultType: 'block' }
@@ -36,6 +37,10 @@ export type TransactionResult = SearchResultItemCore & RuntimeTransaction & { re
export type AccountResult = SearchResultItemCore & RuntimeAccount & { resultType: 'account' }
+export type AccountAddressResult = SearchResultItemCore & { address: string } & {
+ resultType: 'accountAddress'
+}
+
export type ContractResult = SearchResultItemCore & RuntimeAccount & { resultType: 'contract' }
export type TokenResult = SearchResultItemCore & EvmToken & { resultType: 'token' }
@@ -46,6 +51,7 @@ export type SearchResultItem =
| BlockResult
| TransactionResult
| AccountResult
+ | AccountAddressResult
| ContractResult
| TokenResult
| ProposalResult
@@ -193,6 +199,25 @@ export function useNetworkProposalsConditionally(
}
}
+type AccountAddressInfo = Pick
+
+export function useNamedAccountConditionally(
+ currentScope: SearchScope | undefined,
+ nameFragment: string | undefined,
+): ConditionalResults {
+ const queries = RouteUtils.getVisibleScopes(currentScope).map(scope =>
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ useSearchForAccountsByName(scope, nameFragment),
+ )
+ return {
+ isLoading: queries.some(query => query.isLoading),
+ results: queries
+ .map(query => query.results)
+ .filter(isDefined)
+ .flat(),
+ }
+}
+
export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams) => {
const queries = {
blockHeight: useBlocksByHeightConditionally(currentScope, q.blockHeight),
@@ -201,6 +226,7 @@ export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams
oasisAccount: useRuntimeAccountConditionally(currentScope, q.consensusAccount),
// TODO: remove evmBech32Account and use evmAccount when API is ready
evmBech32Account: useRuntimeAccountConditionally(currentScope, q.evmBech32Account),
+ accountsByName: useNamedAccountConditionally(currentScope, q.accountNameFragment),
tokens: useRuntimeTokenConditionally(currentScope, q.evmTokenNameFragment),
proposals: useNetworkProposalsConditionally(q.networkProposalNameFragment),
}
@@ -211,6 +237,7 @@ export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams
...(queries.oasisAccount.results || []),
...(queries.evmBech32Account.results || []),
].filter(isAccountNonEmpty)
+ const accountAddresses = queries.accountsByName.results || []
const tokens = queries.tokens.results
.map(l => l.evm_tokens)
.flat()
@@ -228,6 +255,9 @@ export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams
...accounts
.filter(account => account.evm_contract)
.map((account): ContractResult => ({ ...account, resultType: 'contract' })),
+ ...accountAddresses.map(
+ (account): AccountAddressResult => ({ ...account, resultType: 'accountAddress' }),
+ ),
...tokens.map((token): TokenResult => ({ ...token, resultType: 'token' })),
...proposals.map((proposal): ProposalResult => ({ ...proposal, resultType: 'proposal' })),
]
diff --git a/src/app/pages/SearchResultsPage/useRedirectIfSingleResult.ts b/src/app/pages/SearchResultsPage/useRedirectIfSingleResult.ts
index ebcb816b8c..e3e704e029 100644
--- a/src/app/pages/SearchResultsPage/useRedirectIfSingleResult.ts
+++ b/src/app/pages/SearchResultsPage/useRedirectIfSingleResult.ts
@@ -35,6 +35,9 @@ export function useRedirectIfSingleResult(
case 'account':
redirectTo = RouteUtils.getAccountRoute(item, item.address_eth ?? item.address)
break
+ case 'accountAddress':
+ redirectTo = `${RouteUtils.getAccountRoute(item, item.address)}?q=${searchTerm}`
+ break
case 'contract':
redirectTo = RouteUtils.getAccountRoute(item, item.address_eth ?? item.address)
break