Skip to content

Commit

Permalink
fix: transaction history fetch and upsert (#5662)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaladinlight authored Nov 21, 2023
1 parent a32d4ec commit 1d207ef
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 47 deletions.
1 change: 0 additions & 1 deletion .env.dev
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# feature flags
REACT_APP_FEATURE_NFT_METADATA=true
REACT_APP_FEATURE_FOX_DISCOUNTS=true
REACT_APP_FEATURE_THORCHAIN_LENDING=true
REACT_APP_FEATURE_ARBITRUM_NOVA=true
Expand Down
1 change: 0 additions & 1 deletion .env.develop
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# feature flags
REACT_APP_FEATURE_NFT_METADATA=true
REACT_APP_FEATURE_CHATWOOT=true
REACT_APP_FEATURE_THORCHAIN_LENDING=true
REACT_APP_FEATURE_ARBITRUM_NOVA=true
Expand Down
2 changes: 1 addition & 1 deletion src/components/AccountDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const AccountDetails = ({ assetId, accountId }: AccountDetailsProps) => {
<RelatedAssets assetId={assetId} />
<AssetAccounts assetId={assetId} accountId={isUtxoAccountId(accountId) ? '' : accountId} />
<EarnOpportunities assetId={assetId} accountId={accountId} />
<AssetTransactionHistory limit={10} assetId={assetId} accountId={accountId} />
<AssetTransactionHistory limit={10} accountId={accountId} />
</Stack>
<Flex flexDir='column' flex='1 1 0%' width='full' maxWidth={flexMaxWidth} gap={4}>
<MultiHopTrade display={displayMdBlock} />
Expand Down
23 changes: 12 additions & 11 deletions src/components/TransactionHistory/AssetTransactionHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ArrowForwardIcon } from '@chakra-ui/icons'
import { Button, Card, CardHeader, Heading } from '@chakra-ui/react'
import type { AccountId, AssetId, ChainId } from '@shapeshiftoss/caip'
import { type AccountId, type AssetId, type ChainId, fromAccountId } from '@shapeshiftoss/caip'
import { useMemo } from 'react'
import { useTranslate } from 'react-polyglot'
import { useLocation } from 'react-router'
Expand Down Expand Up @@ -29,21 +29,22 @@ export const AssetTransactionHistory: React.FC<AssetTransactionHistoryProps> = (
}) => {
const translate = useTranslate()
const location = useLocation()
const generatedPath = `${location.pathname}/transactions`
const {
state: { wallet },
} = useWallet()

const wallet = useWallet().state.wallet
const asset = useAppSelector(state => selectAssetById(state, assetId ?? ''))
const chainId: ChainId = asset?.chainId ?? ''

const filter = useMemo(() => ({ assetId, accountId }), [assetId, accountId])
const chainId: ChainId = (() => {
if (asset) return asset.chainId
if (accountId) return fromAccountId(accountId).chainId
return ''
})()

const isSnapInstalled = useIsSnapInstalled()
const walletSupportsChain = useWalletSupportsChain({ chainId, wallet, isSnapInstalled })

const filter = useMemo(() => ({ assetId, accountId }), [assetId, accountId])
const txIds = useAppSelector(state => selectTxIdsByFilter(state, filter))

if (!assetId) return null
if (!walletSupportsChain) return null
if (!chainId || !walletSupportsChain) return null

return (
<Card variant='dashboard'>
Expand All @@ -61,7 +62,7 @@ export const AssetTransactionHistory: React.FC<AssetTransactionHistoryProps> = (
size='sm'
colorScheme='blue'
as={NavLink}
to={generatedPath}
to={`${location.pathname}/transactions`}
rightIcon={arrowForwardIcon}
>
{translate('common.seeAll')}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Accounts/AccountTxHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const AccountTxHistory: React.FC = () => {

return !feeAsset ? null : (
<Main titleComponent={assetHeader}>
<AssetTransactionHistory assetId={feeAssetId} accountId={accountId} useCompactMode={false} />
<AssetTransactionHistory accountId={accountId} useCompactMode={false} />
</Main>
)
}
2 changes: 2 additions & 0 deletions src/state/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { clearAssets } from './clearAssets'
import { clearNfts } from './clearNfts'
import { clearOpportunities } from './clearOpportunities'
import { clearPortfolio } from './clearPortfolio'
import { clearTxHistory } from './clearTxHistory'

export const migrations = {
0: clearOpportunities,
Expand All @@ -14,4 +15,5 @@ export const migrations = {
7: clearPortfolio,
8: clearOpportunities,
9: clearAssets,
10: clearTxHistory,
}
78 changes: 46 additions & 32 deletions src/state/slices/txHistorySlice/txHistorySlice.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { createSlice } from '@reduxjs/toolkit'
import { createApi } from '@reduxjs/toolkit/dist/query/react'
import type { AccountId, AssetId } from '@shapeshiftoss/caip'
import { ASSET_NAMESPACE, ethChainId, fromAccountId, isNft, toAssetId } from '@shapeshiftoss/caip'
import {
ASSET_NAMESPACE,
ethChainId,
fromAccountId,
gnosisChainId,
isNft,
polygonChainId,
toAssetId,
} from '@shapeshiftoss/caip'
import type { Transaction } from '@shapeshiftoss/chain-adapters'
import type { UtxoAccountType } from '@shapeshiftoss/types'
import difference from 'lodash/difference'
import identity from 'lodash/identity'
import orderBy from 'lodash/orderBy'
import { PURGE } from 'redux-persist'
import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingleton'
Expand Down Expand Up @@ -255,48 +261,56 @@ export const txHistoryApi = createApi({
const results: TransactionsByAccountId = {}
await Promise.all(
accountIds.map(async accountId => {
results[accountId] = []

const { chainId, account: pubkey } = fromAccountId(accountId)
const adapter = getChainAdapterManager().get(chainId)
if (!adapter)
return {
error: {
data: `getAllTxHistory: no adapter available for chainId ${chainId}`,
status: 400,
},
}

let currentCursor: string = ''
if (!adapter) {
const data = `getAllTxHistory: no adapter available for chainId ${chainId}`
return { error: { data, status: 400 } }
}

try {
let currentCursor = ''
do {
const pageSize = (() => {
switch (chainId) {
case gnosisChainId:
case polygonChainId:
return 10
default:
return 100
}
})()

const { cursor, transactions } = await adapter.getTxHistory({
cursor: currentCursor,
pubkey,
pageSize: 100,
pageSize,
})

currentCursor = cursor

const state = getState() as State
const txState = state.txHistory.txs

const existingTxIndexes = Object.values(
txState?.byAccountIdAssetId?.[accountId] ?? {},
).flatMap(identity)

// freshly fetched - unchained returns latest txs first
const fetchedTxIndexes: TxId[] = transactions.map(tx =>
serializeTxIndex(accountId, tx.txid, tx.address, tx.data),
)
// diff the two - if we haven't seen any of these txs before, upsert them
const diffedTxIds = difference(fetchedTxIndexes, existingTxIndexes)
if (diffedTxIds.length) {
// new txs to return
results[accountId] = transactions
} else {
// we've previously fetched all txs for this account, don't keep paginating
break
const txsById = state.txHistory.txs.byId

const newTxs: Transaction[] = []
for (const tx of transactions) {
if (txsById[serializeTxIndex(accountId, tx.txid, tx.address, tx.data)]) break
newTxs.push(tx)
}

results[accountId].push(...newTxs)

/**
* We have run into a transaction that already exists in the store, stop fetching more history
*
* TODO: An edge case exists if there was an error fetching transaction history after at least one page was fetched.
* We will think we ran into the latest existing transaction in the store and stop fetching,
* but all transaction history after the failed response on the initial `getAllTxHistory` call will still be missing.
*/
if (newTxs.length < pageSize) break

currentCursor = cursor
} while (currentCursor)
} catch (err) {
console.error(err)
Expand Down

0 comments on commit 1d207ef

Please sign in to comment.