Skip to content

Commit

Permalink
Implement named accounts for Oasis consensus, Emerald and Sapphire
Browse files Browse the repository at this point in the history
  • Loading branch information
csillag committed May 7, 2024
1 parent 39f1a69 commit d339653
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 96 deletions.
6 changes: 4 additions & 2 deletions src/app/components/Account/AccountLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { RouteUtils } from '../../utils/route-utils'
import InfoIcon from '@mui/icons-material/Info'
import Typography from '@mui/material/Typography'
import { SearchScope } from '../../../types/searchScope'
import { useAccountName } from '../../hooks/useAccountName'
import { useAccountMetadata } from '../../hooks/useAccountMetadata'
import { trimLongString } from '../../utils/trimLongString'
import { MaybeWithTooltip } from '../AdaptiveTrimmer/MaybeWithTooltip'
import Box from '@mui/material/Box'
Expand Down Expand Up @@ -69,7 +69,9 @@ export const AccountLink: FC<Props> = ({
extraTooltip,
}) => {
const { isTablet } = useScreenSize()
const { name: accountName } = useAccountName(scope, address)
const { metadata: accountMetadata } = useAccountMetadata(scope, address)
const accountName = accountMetadata?.name // TODO: we should also use the description

const to = RouteUtils.getAccountRoute(scope, address)

const tooltipPostfix = extraTooltip ? (
Expand Down
18 changes: 18 additions & 0 deletions src/app/data/named-accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export type AccountMetadata = {
name?: string
description?: string
}

export type AccountMetadataInfo = {
metadata?: AccountMetadata
loading: boolean
}

export type AccountMap = Map<string, AccountMetadata>

export type AccountEntry = { address: string } & AccountMetadata

export type AccountData = {
map: AccountMap
list: AccountEntry[]
}
120 changes: 120 additions & 0 deletions src/app/data/oasis-account-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import axios from 'axios'
import { useQuery } from '@tanstack/react-query'
import { Layer } from '../../oasis-nexus/api'
import { Network } from '../../types/network'
import { findTextMatch } from '../components/HighlightedText/text-matching'
import * as process from 'process'
import { AccountData, AccountEntry, AccountMap, AccountMetadata, AccountMetadataInfo } from './named-accounts'

const dataSources: Record<Network, Partial<Record<Layer, string>>> = {
[Network.mainnet]: {
[Layer.consensus]:
'https://raw.githubusercontent.com/oasisprotocol/nexus/main/account-names/mainnet_consensus.json',
[Layer.emerald]:
'https://raw.githubusercontent.com/oasisprotocol/nexus/main/account-names/mainnet_paratime.json',
[Layer.sapphire]:
'https://raw.githubusercontent.com/oasisprotocol/nexus/main/account-names/mainnet_paratime.json',
},
[Network.testnet]: {
[Layer.consensus]:
'https://raw.githubusercontent.com/oasisprotocol/nexus/main/account-names/testnet_consensus.json',
[Layer.emerald]:
'https://raw.githubusercontent.com/oasisprotocol/nexus/main/account-names/testnet_paratime.json',
[Layer.sapphire]:
'https://raw.githubusercontent.com/oasisprotocol/nexus/main/account-names/testnet_paratime.json',
},
}

const getOasisAccountsMetadata = (network: Network, layer: Layer) =>
new Promise<AccountData>((resolve, reject) => {
const url = dataSources[network][layer]
if (!url) {
reject('No data available for this layer')
} else {
axios.get(url).then(response => {
if (response.status !== 200) reject("Couldn't load names")
if (!response.data) reject("Couldn't load names")
// console.log('Response data is', response.data)
const map: AccountMap = new Map()
const list: AccountEntry[] = []
Array.from(response.data).forEach((entry: any) => {
const address = entry.Address
const metadata: AccountMetadata = {
name: entry.Name,
description: entry.Description,
}
// Register the metadata in its native form
list.push({
address,
...metadata,
})
map.set(address, metadata)
})
resolve({
map,
list,
})
}, reject)
}
})

export const useOasisAccountsMetadata = (network: Network, layer: Layer, enabled: boolean) => {
return useQuery(['oasisAccounts', network, layer], () => getOasisAccountsMetadata(network, layer), {
enabled,
staleTime: Infinity,
})
}

export const useOasisAccountMetadata = (
network: Network,
layer: Layer,
address: string,
enabled: boolean,
): AccountMetadataInfo => {
// When running jest tests, we don't want to load from Pontus-X.
if (process.env.NODE_ENV === 'test') {
return {
metadata: {
name: undefined,
},
loading: false,
}
}
// This is not a condition that can change while the app is running, so it's OK.
// eslint-disable-next-line react-hooks/rules-of-hooks
const { isLoading, error, data: allData } = useOasisAccountsMetadata(network, layer, enabled)
if (error) {
console.log('Failed to load Oasis account metadata', error)
}
return {
metadata: allData?.map.get(address),
loading: isLoading,
}
}

export const useSearchForOasisAccountsByName = (
network: Network,
layer: Layer,
nameFragment: string,
enabled: boolean,
) => {
const { isLoading, error, data: allData } = useOasisAccountsMetadata(network, layer, enabled)
if (error) {
console.log('Failed to load Oasis account metadata', error)
}

const textMatcher =
nameFragment && enabled
? (entry: AccountEntry): boolean => {
return !!findTextMatch(entry.name, [nameFragment])
}
: () => false
return {
results: (allData?.list || []).filter(textMatcher).map(entry => ({
network,
layer,
address: entry.address,
})),
isLoading,
}
}
32 changes: 11 additions & 21 deletions src/app/data/pontusx-account-names.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
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'
import { AccountData, AccountEntry, AccountMap, AccountMetadataInfo } from './named-accounts'

const DATA_SOURCE_URL = 'https://raw.githubusercontent.com/deltaDAO/mvg-portal/main/pontusxAddresses.json'

type AccountMap = Map<string, string>
type AccountEntry = {
name: string
address: string
}
type AccountData = {
map: AccountMap
list: AccountEntry[]
}

const getPontusXAccountNames = () =>
const getPontusXAccountsMetadata = () =>
new Promise<AccountData>((resolve, reject) => {
axios.get(DATA_SOURCE_URL).then(response => {
if (response.status !== 200) reject("Couldn't load names")
if (!response.data) reject("Couldn't load names")
const map = new Map()
const map: AccountMap = new Map()
const list: AccountEntry[] = []
Object.entries(response.data).forEach(([address, name]) => {
map.set(address, name)
map.set(address, { name: name as string })
const normalizedEntry: AccountEntry = {
name: name as string,
address,
Expand All @@ -40,29 +30,29 @@ const getPontusXAccountNames = () =>
}, reject)
})

export const usePontusXAccountNames = (enabled: boolean) => {
return useQuery(['pontusXNames'], getPontusXAccountNames, {
export const usePontusXAccountsMetadata = (enabled: boolean) => {
return useQuery(['pontusXNames'], getPontusXAccountsMetadata, {
enabled,
staleTime: Infinity,
})
}

export const usePontusXAccountName = (address: string, enabled: boolean): AccountNameInfo => {
export const usePontusXAccountMetadata = (address: string, enabled: boolean): AccountMetadataInfo => {
// When running jest tests, we don't want to load from Pontus-X.
if (process.env.NODE_ENV === 'test') {
return {
name: undefined,
metadata: undefined,
loading: false,
}
}
// This is not a condition that can change while the app is running, so it's OK.
// eslint-disable-next-line react-hooks/rules-of-hooks
const { isLoading, error, data: allNames } = usePontusXAccountNames(enabled)
const { isLoading, error, data: allData } = usePontusXAccountsMetadata(enabled)
if (error) {
console.log('Failed to load Pontus-X account names', error)
}
return {
name: allNames?.map.get(address),
metadata: allData?.map.get(address),
loading: isLoading,
}
}
Expand All @@ -72,7 +62,7 @@ export const useSearchForPontusXAccountsByName = (
nameFragment: string,
enabled: boolean,
) => {
const { isLoading, error, data: allNames } = usePontusXAccountNames(enabled)
const { isLoading, error, data: allNames } = usePontusXAccountsMetadata(enabled)
if (error) {
console.log('Failed to load Pontus-X account names', error)
}
Expand Down
36 changes: 36 additions & 0 deletions src/app/hooks/useAccountMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { SearchScope } from '../../types/searchScope'
import { Layer } from '../../oasis-nexus/api'
import { usePontusXAccountMetadata, useSearchForPontusXAccountsByName } from '../data/pontusx-account-names'
import { AccountMetadataInfo } from '../data/named-accounts'
import { useOasisAccountMetadata, useSearchForOasisAccountsByName } from '../data/oasis-account-names'
import { getOasisAddress } from '../utils/helpers'

/**
* Find out the metadata for an account
*
* This is the entry point that should be used by the application,
* since this function also includes caching.
*/
export const useAccountMetadata = (scope: SearchScope, address: string): AccountMetadataInfo => {
const isPontusX = scope.layer === Layer.pontusx
const pontusXData = usePontusXAccountMetadata(address, isPontusX)
const oasisData = useOasisAccountMetadata(scope.network, scope.layer, getOasisAddress(address), !isPontusX)
return isPontusX ? pontusXData : oasisData
}

export const useSearchForAccountsByName = (scope: SearchScope, nameFragment = '') => {
const isValidPontusXSearch = scope.layer === Layer.pontusx && !!nameFragment
const pontusXResults = useSearchForPontusXAccountsByName(scope.network, nameFragment, isValidPontusXSearch)
const isValidOasisSearch = scope.layer !== Layer.pontusx && !!nameFragment
const oasisResults = useSearchForOasisAccountsByName(
scope.network,
scope.layer,
nameFragment,
isValidOasisSearch,
)
return {
isLoading:
(isValidPontusXSearch && pontusXResults.isLoading) || (isValidOasisSearch && oasisResults.isLoading),
results: [...pontusXResults.results, ...oasisResults.results],
}
}
72 changes: 0 additions & 72 deletions src/app/hooks/useAccountName.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/app/pages/SearchResultsPage/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +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'
import { useSearchForAccountsByName } from '../../hooks/useAccountMetadata'

function isDefined<T>(item: T): item is NonNullable<T> {
return item != null
Expand Down

0 comments on commit d339653

Please sign in to comment.