Skip to content

Commit

Permalink
Merge pull request #1039 from oasisprotocol/ml/fix-address-formatting…
Browse files Browse the repository at this point in the history
…-on-empty-account

Fix address formatting on empty account
  • Loading branch information
lubej authored Dec 4, 2023
2 parents 97a7f9f + 47ee210 commit d5bac56
Show file tree
Hide file tree
Showing 15 changed files with 202 additions and 60 deletions.
1 change: 1 addition & 0 deletions .changelog/1039.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix address formatting on empty account
25 changes: 25 additions & 0 deletions src/app/hooks/useTransformToOasisAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useEffect, useState } from 'react'
import { getOasisAddress } from '../utils/helpers'

export const useTransformToOasisAddress = (ethOrOasisAddress: string): string | null => {
const [oasisAddress, setOasisAddress] = useState<string | null>(null)

useEffect(() => {
let shouldUpdate = true

const transformToOasisAddress = async (addr: string) => {
const oasisAddr = await getOasisAddress(addr)
if (shouldUpdate) {
setOasisAddress(oasisAddr)
}
}

transformToOasisAddress(ethOrOasisAddress)

return () => {
shouldUpdate = false
}
}, [ethOrOasisAddress, setOasisAddress])

return oasisAddress
}
4 changes: 2 additions & 2 deletions src/app/pages/AccountDetailsPage/AccountNFTCollectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export const accountNFTCollectionContainerId = 'nftCollection'

export const AccountNFTCollectionCard: FC<AccountDetailsContext> = ({ scope, address }) => {
const { t } = useTranslation()
const oasisContractAddress = useLoaderData() as string
const contractAddress = useLoaderData() as string
const { inventory, isFetched, isLoading, isTotalCountClipped, pagination, totalCount } =
useAccountTokenInventory(scope, address, oasisContractAddress)
useAccountTokenInventory(scope, address, contractAddress)
const firstToken = inventory?.length ? inventory?.[0].token : undefined

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE } from '../../config'
import { ErrorBoundary } from '../../components/ErrorBoundary'
import { LinkableDiv } from '../../components/PageLayout/LinkableDiv'
import { CardEmptyState } from './CardEmptyState'
import { useAccount } from './hook'
import { useTokenTransfers } from '../TokenDashboardPage/hook'
import { TokenTransfers } from '../../components/Tokens/TokenTransfers'
import { AccountDetailsContext } from './index'

export const accountTokenTransfersContainerId = 'transfers'

export const AccountTokenTransfersCard: FC<AccountDetailsContext> = ({ scope, address }) => {
export const AccountTokenTransfersCard: FC<AccountDetailsContext> = ({ scope, address, account }) => {
const { t } = useTranslation()
return (
<Card>
Expand All @@ -23,19 +22,17 @@ export const AccountTokenTransfersCard: FC<AccountDetailsContext> = ({ scope, ad
</LinkableDiv>
<CardContent>
<ErrorBoundary light={true}>
<AccountTokenTransfers scope={scope} address={address} />
<AccountTokenTransfers scope={scope} address={address} account={account} />
</ErrorBoundary>
</CardContent>
</Card>
)
}

const AccountTokenTransfers: FC<AccountDetailsContext> = ({ scope, address }) => {
const AccountTokenTransfers: FC<AccountDetailsContext> = ({ scope, address, account }) => {
const { t } = useTranslation()

const { isLoading, isFetched, results } = useTokenTransfers(scope, address)

const { account } = useAccount(scope, address)
const transfers = results.data

return (
Expand Down
8 changes: 3 additions & 5 deletions src/app/pages/AccountDetailsPage/AccountTokensCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE } from '../../config'
import { EvmTokenType, Layer } from '../../../oasis-nexus/api'
import { AppErrors } from '../../../types/errors'
import { LinkableDiv } from '../../components/PageLayout/LinkableDiv'
import { useAccount } from './hook'
import { TokenLink } from '../../components/Tokens/TokenLink'
import { AccountLink } from '../../components/Account/AccountLink'
import {
Expand All @@ -40,7 +39,7 @@ export const ContractLink: FC<{ scope: SearchScope; address: string }> = ({ scop
)
}

export const AccountTokensCard: FC<AccountTokensCardProps> = ({ scope, address, type }) => {
export const AccountTokensCard: FC<AccountTokensCardProps> = ({ scope, account, type }) => {
const { t } = useTranslation()
const locationHash = useLocation().hash.replace('#', '')
const tokenListLabel = getTokenTypePluralName(t, type)
Expand All @@ -64,7 +63,6 @@ export const AccountTokensCard: FC<AccountTokensCardProps> = ({ scope, address,
// There can be no ERC-20 or ERC-721 tokens on consensus
throw AppErrors.UnsupportedLayer
}
const { isLoading, account } = useAccount(scope, address)
const tableRows = (account?.tokenBalances[type] || []).map(item => ({
key: item.token_contract_addr,
data: [
Expand Down Expand Up @@ -114,7 +112,7 @@ export const AccountTokensCard: FC<AccountTokensCardProps> = ({ scope, address,
<LinkableDiv id={accountTokenContainerId}>
<CardHeader disableTypography component="h3" title={tokenListLabel} />
<CardContent>
{!isLoading && !account?.tokenBalances[type]?.length && (
{!!account && !account?.tokenBalances[type]?.length && (
<CardEmptyState
label={t('account.emptyTokenList', {
spec: getTokenTypeStrictName(t, type),
Expand All @@ -128,7 +126,7 @@ export const AccountTokensCard: FC<AccountTokensCardProps> = ({ scope, address,
rows={tableRows}
rowsNumber={NUMBER_OF_ITEMS_ON_SEPARATE_PAGE}
name={tokenListLabel}
isLoading={isLoading}
isLoading={!account}
pagination={false}
/>
</CardContent>
Expand Down
33 changes: 26 additions & 7 deletions src/app/pages/AccountDetailsPage/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import { useSearchParamsPagination } from '../../components/Table/useSearchParam
import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE } from '../../config'
import { SearchScope } from '../../../types/searchScope'
import { EventFilterMode } from '../../components/RuntimeEvents/EventListFilterSwitch'
import { useTransformToOasisAddress } from '../../hooks/useTransformToOasisAddress'

export const useAccount = (scope: SearchScope, oasisAddress: string) => {
export const useAccount = (scope: SearchScope, address: string) => {
const { network, layer } = scope
if (layer === Layer.consensus) {
// There can be no ERC-20 or ERC-721 tokens on consensus
throw AppErrors.UnsupportedLayer
}
const query = useGetRuntimeAccountsAddress(network, layer, oasisAddress!)
const query = useGetRuntimeAccountsAddress(network, layer, address)
const account = query.data?.data
const { isLoading, isError, isFetched } = query

Expand All @@ -33,13 +34,20 @@ export const useAccountTransactions = (scope: SearchScope, address: string) => {
// Loading transactions on the consensus layer is not supported yet.
// We should use useGetConsensusTransactions()
}

const oasisAddress = useTransformToOasisAddress(address)
const query = useGetRuntimeTransactions(
network,
layer, // This is OK since consensus has been handled separately
{
limit: NUMBER_OF_ITEMS_ON_SEPARATE_PAGE,
offset: offset,
rel: address,
rel: oasisAddress!,
},
{
query: {
enabled: !!oasisAddress,
},
},
)
const { isFetched, isLoading, data } = query
Expand Down Expand Up @@ -69,10 +77,21 @@ export const useAccountEvents = (scope: SearchScope, address: string, filterMode
// Loading events on the consensus layer is not supported yet.
// We should use useGetConsensusEvents()
}
const query = useGetRuntimeEvents(network, layer, {
rel: address,
// TODO: implement filtering for non-transactional events
})

const oasisAddress = useTransformToOasisAddress(address)
const query = useGetRuntimeEvents(
network,
layer,
{
rel: oasisAddress!,
// TODO: implement filtering for non-transactional events
},
{
query: {
enabled: !!oasisAddress,
},
},
)
const { isFetched, isLoading, isError, data } = query
const events = data?.data.events.filter(
event => filterMode === EventFilterMode.All || event.type !== RuntimeEventType.accountstransfer,
Expand Down
5 changes: 3 additions & 2 deletions src/app/pages/AccountDetailsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { RouterTabs } from '../../components/RouterTabs'
import { useTokenPrice } from '../../../coin-gecko/api'
import { Ticker } from '../../../types/ticker'

import { EvmTokenType } from '../../../oasis-nexus/api'
import { EvmTokenType, RuntimeAccount } from '../../../oasis-nexus/api'
import { accountTokenContainerId } from './AccountTokensCard'
import { useAccount, useAccountEvents } from './hook'
import { useRequiredScopeParam } from '../../hooks/useScopeParam'
Expand All @@ -22,6 +22,7 @@ import { EventFilterMode } from '../../components/RuntimeEvents/EventListFilterS
export type AccountDetailsContext = {
scope: SearchScope
address: string
account?: RuntimeAccount
}

export const useAccountDetailsProps = () => useOutletContext<AccountDetailsContext>()
Expand Down Expand Up @@ -54,7 +55,7 @@ export const AccountDetailsPage: FC = () => {

const isLoading = isAccountLoading || isTokenLoading

const context: AccountDetailsContext = { scope, address }
const context: AccountDetailsContext = { scope, address, account }

return (
<PageLayout>
Expand Down
2 changes: 0 additions & 2 deletions src/app/pages/TokenDashboardPage/TokenGasUsedCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export const TokenGasUsedCard: FC<{ scope: SearchScope; address: string }> = ({

const { account, isFetched } = useAccount(scope, address)

console.log('Account', account)

return (
<SnapshotCard title={t('common.gasUsed')} withConstantHeight>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
Expand Down
71 changes: 57 additions & 14 deletions src/app/pages/TokenDashboardPage/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SearchScope } from '../../../types/searchScope'
import { useSearchParamsPagination } from '../../components/Table/useSearchParamsPagination'
import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE } from '../../config'
import { useComprehensiveSearchParamsPagination } from '../../components/Table/useComprehensiveSearchParamsPagination'
import { useTransformToOasisAddress } from '../../hooks/useTransformToOasisAddress'

export const useTokenInfo = (scope: SearchScope, address: string, enabled = true) => {
const { network, layer } = scope
Expand Down Expand Up @@ -44,16 +45,23 @@ export const useTokenTransfers = (scope: SearchScope, address: string) => {
// Loading transactions on the consensus layer is not supported yet.
// We should use useGetConsensusTransactions()
}

const oasisAddress = useTransformToOasisAddress(address)
const query = useGetRuntimeEvents(
network,
layer, // This is OK since consensus has been handled separately
{
...pagination.paramsForQuery,
rel: address,
rel: oasisAddress!,
type: 'evm.log',
// The following is the hex-encoded signature for Transfer(address,address,uint256)
evm_log_signature: 'ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
},
{
query: {
enabled: !!oasisAddress,
},
},
)

const { isFetched, isLoading, data } = query
Expand All @@ -79,10 +87,22 @@ export const useTokenHolders = (scope: SearchScope, address: string) => {
throw AppErrors.UnsupportedLayer
// There are no token holders on the consensus layer.
}
const query = useGetRuntimeEvmTokensAddressHolders(network, layer, address, {
limit: NUMBER_OF_ITEMS_ON_SEPARATE_PAGE,
offset: offset,
})

const oasisAddress = useTransformToOasisAddress(address)
const query = useGetRuntimeEvmTokensAddressHolders(
network,
layer,
oasisAddress!,
{
limit: NUMBER_OF_ITEMS_ON_SEPARATE_PAGE,
offset: offset,
},
{
query: {
enabled: !!oasisAddress,
},
},
)

const { isFetched, isLoading, data } = query

Expand Down Expand Up @@ -116,10 +136,21 @@ export const useTokenInventory = (scope: SearchScope, address: string) => {
throw AppErrors.UnsupportedLayer
// There are no tokens on the consensus layer.
}
const query = useGetRuntimeEvmTokensAddressNfts(network, layer, address, {
limit: NUMBER_OF_INVENTORY_ITEMS,
offset: offset,
})
const oasisAddress = useTransformToOasisAddress(address)
const query = useGetRuntimeEvmTokensAddressNfts(
network,
layer,
oasisAddress!,
{
limit: NUMBER_OF_INVENTORY_ITEMS,
offset: offset,
},
{
query: {
enabled: !!oasisAddress,
},
},
)
const { isFetched, isLoading, data } = query
const inventory = data?.data.evm_nfts

Expand Down Expand Up @@ -151,11 +182,23 @@ export const useAccountTokenInventory = (scope: SearchScope, address: string, to
throw AppErrors.UnsupportedLayer
// There are no tokens on the consensus layer.
}
const query = useGetRuntimeAccountsAddressNfts(network, layer, address, {
limit: NUMBER_OF_INVENTORY_ITEMS,
offset: offset,
token_address: tokenAddress,
})

const oasisAddress = useTransformToOasisAddress(address)
const query = useGetRuntimeAccountsAddressNfts(
network,
layer,
oasisAddress!,
{
limit: NUMBER_OF_INVENTORY_ITEMS,
offset: offset,
token_address: tokenAddress,
},
{
query: {
enabled: !!oasisAddress,
},
},
)
const { isFetched, isLoading, data } = query
const inventory = data?.data.evm_nfts

Expand Down
25 changes: 23 additions & 2 deletions src/app/utils/__tests__/getEthAccountAddress.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getEthAccountAddressFromPreimage } from '../helpers'
import { suggestedParsedAccount } from '../test-fixtures'
import { getEthAccountAddressFromPreimage, getEthAddressForAccount } from '../helpers'
import { suggestedEmptyAccount, suggestedParsedAccount } from '../test-fixtures'

describe('getEthAccountAddress', () => {
// TODO: enable when jest fixes "TypeError: Expected Uint8Array"
Expand All @@ -9,4 +9,25 @@ describe('getEthAccountAddress', () => {
suggestedParsedAccount.address_eth,
)
})

it('should return input address on empty account', () => {
const validEthAddress = suggestedParsedAccount.address_eth
expect(getEthAddressForAccount(suggestedEmptyAccount, validEthAddress)).toBe(
'0xBA504818FdD8D3dBA2Ef8fD9B4F4D5c71aD1d1D3',
)
})

it('should return undefined on empty account with invalid ETH address', () => {
expect(getEthAddressForAccount(suggestedEmptyAccount, '0x0')).toBeUndefined()
})

it('should return undefined on empty account with undefined supplied as ETH address', () => {
expect(getEthAddressForAccount(suggestedEmptyAccount, undefined)).toBeUndefined()
})

it('should valid address from preimage', () => {
expect(getEthAddressForAccount(suggestedParsedAccount, undefined)).toBe(
suggestedParsedAccount.address_eth,
)
})
})
13 changes: 12 additions & 1 deletion src/app/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Buffer } from 'buffer'
import * as oasis from '@oasisprotocol/client'
import * as oasisRT from '@oasisprotocol/client-rt'
// eslint-disable-next-line no-restricted-imports
import { AddressPreimage } from '../../oasis-nexus/generated/api'
import { AddressPreimage, RuntimeAccount } from '../../oasis-nexus/generated/api'
import BigNumber from 'bignumber.js'
import { validateMnemonic } from 'bip39'

Expand Down Expand Up @@ -69,6 +69,17 @@ export function getEthAccountAddressFromPreimage(preimage: AddressPreimage | und
return getEthAccountAddressFromBase64(preimage.address_data)
}

export function getEthAddressForAccount(
account: RuntimeAccount,
possibleEthAddress?: string,
): string | undefined {
// In case of an empty account
if (account.stats.num_txns <= 0 && possibleEthAddress && isValidEthAddress(possibleEthAddress))
return possibleEthAddress

return getEthAccountAddressFromPreimage(account.address_preimage)
}

export function uniq<T>(input: T[] | undefined): T[] {
return input === undefined ? [] : [...new Set(input)]
}
Expand Down
Loading

0 comments on commit d5bac56

Please sign in to comment.