Skip to content

Commit beac938

Browse files
authored
feat: evict account from cache when it's stale (#49)
* chore: rename transactions card in asset * chore: some naming tweaks and setting up structure * feat: evict account from cache when it's stale
1 parent 89069fc commit beac938

File tree

7 files changed

+115
-39
lines changed

7 files changed

+115
-39
lines changed

src/features/accounts/components/account-activity-tabs.tsx

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,66 @@
11
import { OverflowAutoTabsContent, Tabs, TabsList, TabsTrigger } from '@/features/common/components/tabs'
22
import { cn } from '@/features/common/utils'
33
import { useMemo } from 'react'
4+
import { AccountAssetHeld } from './account-assets-held'
5+
import { Account } from '../models'
6+
7+
const accountLiveTransactionsTabId = 'live-transactions'
8+
const accountHistoricalTransactionsTabId = 'historical-transactions'
9+
const accountHeldAssetsTabId = 'held-assets'
10+
const accountCreatedAssetsTabId = 'created-assets'
11+
const accountCreatedApplicationsTabId = 'created-applications'
12+
const accountOptedApplicationsTabId = 'opted-applications'
413

5-
const accountVisualTransactionTabId = 'visual'
6-
const accountVisualAssetsTabId = 'table'
7-
const accountVisualCreatedAssetsTabId = 'created-assets'
8-
const accountVisualCreatedApplicationsTabId = 'created-applications'
9-
const accountVisualOptedApplicationsTabId = 'opted-applications'
1014
export const accountDetailsLabel = 'View account Details'
11-
export const accountVisualGraphTabLabel = 'Transactions'
12-
export const accountVisualAssetsTabLabel = 'Assets'
13-
export const accountVisualCreatedAssetsTabLabel = 'Created Assets'
14-
export const accountVisualCreatedApplicationsTabLabel = 'Created Applications'
15-
export const accountVisualOptedApplicationsTabLabel = 'Opted Applications'
15+
export const accountLiveTransactionsTabLabel = 'Live Transactions'
16+
export const accountHistoricalTransactionsTabLabel = 'Historical Transactions'
17+
export const accountHeldAssetsTabLabel = 'Held Assets'
18+
export const accountCreatedAssetsTabLabel = 'Created Assets'
19+
export const accountCreatedApplicationsTabLabel = 'Created Applications'
20+
export const accountOptedApplicationsTabLabel = 'Opted Applications'
21+
22+
type Props = {
23+
account: Account
24+
}
1625

17-
export function AccountActivityTabs() {
26+
export function AccountActivityTabs({ account }: Props) {
1827
const tabs = useMemo(
1928
() => [
2029
{
21-
id: accountVisualTransactionTabId,
22-
label: accountDetailsLabel,
30+
id: accountLiveTransactionsTabId,
31+
label: accountLiveTransactionsTabLabel,
2332
children: '',
2433
},
2534
{
26-
id: accountVisualAssetsTabId,
27-
label: accountVisualAssetsTabLabel,
35+
id: accountHistoricalTransactionsTabId,
36+
label: accountHistoricalTransactionsTabLabel,
2837
children: '',
2938
},
3039
{
31-
id: accountVisualCreatedAssetsTabId,
32-
label: accountVisualCreatedAssetsTabLabel,
40+
id: accountHeldAssetsTabId,
41+
label: accountHeldAssetsTabLabel,
42+
children: <AccountAssetHeld address={account.address} />,
43+
},
44+
{
45+
id: accountCreatedAssetsTabId,
46+
label: accountCreatedAssetsTabLabel,
3347
children: '',
3448
},
3549
{
36-
id: accountVisualCreatedApplicationsTabId,
37-
label: accountVisualCreatedApplicationsTabLabel,
50+
id: accountCreatedApplicationsTabId,
51+
label: accountCreatedApplicationsTabLabel,
3852
children: '',
3953
},
4054
{
41-
id: accountVisualOptedApplicationsTabId,
42-
label: accountVisualOptedApplicationsTabLabel,
55+
id: accountOptedApplicationsTabId,
56+
label: accountOptedApplicationsTabLabel,
4357
children: '',
4458
},
4559
],
46-
[]
60+
[account.address]
4761
)
4862
return (
49-
<Tabs defaultValue={accountVisualTransactionTabId}>
63+
<Tabs defaultValue={accountLiveTransactionsTabId}>
5064
<TabsList aria-label={accountDetailsLabel}>
5165
{tabs.map((tab) => (
5266
<TabsTrigger key={tab.id} className={cn('data-[state=active]:border-primary data-[state=active]:border-b-2 w-44')} value={tab.id}>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Address } from '../data/types'
2+
3+
type Props = {
4+
address: Address
5+
}
6+
7+
export function AccountAssetHeld({ address }: Props) {
8+
return <>Assets Held for {address}</>
9+
}

src/features/accounts/components/account-details.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function AccountDetails({ account }: Props) {
1919
<CardContent className={cn('text-sm space-y-2')}>
2020
<h1 className={cn('text-2xl text-primary font-bold')}>{activityLabel}</h1>
2121
<div className={cn('border-solid border-2 border-border grid')}>
22-
<AccountActivityTabs />
22+
<AccountActivityTabs account={account} />
2323
</div>
2424
</CardContent>
2525
</Card>

src/features/assets/components/asset-details.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
assetNameLabel,
2626
assetReserveLabel,
2727
assetTotalSupplyLabel,
28-
assetTransactionsLabel,
28+
assetActivityLabel,
2929
assetUnitNameLabel,
3030
assetUrlLabel,
3131
} from './labels'
@@ -162,9 +162,9 @@ export function AssetDetails({ asset }: Props) {
162162

163163
<Card className={cn('p-4')}>
164164
<CardContent className={cn('text-sm space-y-2')}>
165-
<h1 className={cn('text-2xl text-primary font-bold')}>{assetTransactionsLabel}</h1>
165+
<h1 className={cn('text-2xl text-primary font-bold')}>{assetActivityLabel}</h1>
166166
<Tabs defaultValue={assetLiveTransactionsTabId}>
167-
<TabsList aria-label={assetTransactionsLabel}>
167+
<TabsList aria-label={assetActivityLabel}>
168168
<TabsTrigger
169169
className={cn('data-[state=active]:border-primary data-[state=active]:border-b-2 w-48')}
170170
value={assetLiveTransactionsTabId}

src/features/assets/components/labels.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const assetMetadataLabel = 'Asset Metadata'
2222

2323
export const assetJsonLabel = 'Asset JSON'
2424

25-
export const assetTransactionsLabel = 'Asset Transactions'
25+
export const assetActivityLabel = 'Activity'
2626
export const assetLiveTransactionsTabId = 'live-transactions'
2727
export const assetLiveTransactionsTabLabel = 'Live Transactions'
2828
export const assetHistoricalTransactionsTabId = 'historical-transactions'

src/features/assets/pages/asset-page.test.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
assetReserveLabel,
1818
assetTotalSupplyLabel,
1919
assetTraitsLabel,
20-
assetTransactionsLabel,
20+
assetActivityLabel,
2121
assetUrlLabel,
2222
} from '../components/labels'
2323
import { useParams } from 'react-router-dom'
@@ -149,9 +149,9 @@ describe('asset-page', () => {
149149
],
150150
})
151151

152-
const transactionTabList = component.getByRole('tablist', { name: assetTransactionsLabel })
153-
expect(transactionTabList).toBeTruthy()
154-
expect(transactionTabList.children.length).toBe(2)
152+
const activityTabList = component.getByRole('tablist', { name: assetActivityLabel })
153+
expect(activityTabList).toBeTruthy()
154+
expect(activityTabList.children.length).toBe(2)
155155
})
156156
}
157157
)
@@ -614,8 +614,8 @@ describe('asset-page', () => {
614614
const assetTraitsCard = component.queryByText(assetTraitsLabel)
615615
expect(assetTraitsCard).toBeNull()
616616

617-
const transactionTabList = component.queryByRole('tablist', { name: assetTransactionsLabel })
618-
expect(transactionTabList).toBeNull()
617+
const activityTabList = component.queryByRole('tablist', { name: assetActivityLabel })
618+
expect(activityTabList).toBeNull()
619619
})
620620
}
621621
)

src/features/blocks/data/latest-blocks.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { asTransactionSummary } from '@/features/transactions/mappers'
66
import { atomEffect } from 'jotai-effect'
77
import { AlgorandSubscriber } from '@algorandfoundation/algokit-subscriber'
88
import { algod } from '@/features/common/data'
9-
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
9+
import { ApplicationOnComplete, TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
1010
import { BlockResult, Round } from './types'
1111
import { assetMetadataResultsAtom } from '@/features/assets/data'
1212
import algosdk from 'algosdk'
@@ -17,6 +17,9 @@ import { BlockSummary } from '../models'
1717
import { blockResultsAtom, addStateExtractedFromBlocksAtom, syncedRoundAtom } from './block-result'
1818
import { GroupId, GroupResult } from '@/features/groups/data/types'
1919
import { AssetId } from '@/features/assets/data/types'
20+
import { BalanceChangeRole } from '@algorandfoundation/algokit-subscriber/types/subscription'
21+
import { accountResultsAtom } from '@/features/accounts/data'
22+
import { Address } from '@/features/accounts/data/types'
2023

2124
const maxBlocksToDisplay = 5
2225

@@ -100,15 +103,20 @@ const subscribeToBlocksEffect = atomEffect((get, set) => {
100103
return
101104
}
102105

103-
const [blockTransactionIds, transactionResults, groupResults, staleAssetIds] = result.subscribedTransactions.reduce(
106+
const [blockTransactionIds, transactionResults, groupResults, staleAssetIds, staleAddresses] = result.subscribedTransactions.reduce(
104107
(acc, t) => {
105108
if (!t.parentTransactionId && t['confirmed-round'] != null) {
106109
const round = t['confirmed-round']
107-
// Filter out filtersMatched and balanceChanges, as we don't need them
108-
const { filtersMatched, balanceChanges, ...transaction } = t
110+
// Remove filtersMatched, balanceChanges and arc28Events, as we don't need to store them in the transaction
111+
const { filtersMatched: _filtersMatched, balanceChanges, arc28Events: _arc28Events, ...transaction } = t
109112

113+
// Accumulate transaction ids by round
110114
acc[0].set(round, (acc[0].get(round) ?? []).concat(transaction.id))
115+
116+
// Accumulate transactions
111117
acc[1].push(transaction)
118+
119+
// Accumulate group results
112120
if (t.group) {
113121
const roundTime = transaction['round-time']
114122
const group: GroupResult = acc[2].get(t.group) ?? {
@@ -120,16 +128,48 @@ const subscribeToBlocksEffect = atomEffect((get, set) => {
120128
group.transactionIds.push(t.id)
121129
acc[2].set(t.group, group)
122130
}
131+
132+
// Accumulate stale asset ids
123133
const staleAssetIds = flattenTransactionResult(t)
124134
.filter((t) => t['tx-type'] === algosdk.TransactionType.acfg)
125135
.map((t) => t['asset-config-transaction']!['asset-id'])
126136
.filter(distinct((x) => x))
127137
.filter(isDefined) // We ignore asset create transactions because they aren't in the atom
128138
acc[3].push(...staleAssetIds)
139+
140+
// Accumulate stale addresses
141+
const addressesStaleDueToBalanceChanges =
142+
balanceChanges
143+
?.filter((bc) => {
144+
const isAssetOptIn =
145+
bc.amount === 0n &&
146+
bc.assetId !== 0 &&
147+
bc.roles.includes(BalanceChangeRole.Sender) &&
148+
bc.roles.includes(BalanceChangeRole.Receiver)
149+
const isNonZeroAmount = bc.amount !== 0n // Can either be negative (decreased balance) or positive (increased balance)
150+
return isAssetOptIn || isNonZeroAmount
151+
})
152+
.map((bc) => bc.address)
153+
.filter(distinct((x) => x)) ?? []
154+
const addressesStaleDueToAppChanges = flattenTransactionResult(t)
155+
.filter((t) => {
156+
if (t['tx-type'] !== algosdk.TransactionType.appl) {
157+
return false
158+
}
159+
const appCallTransaction = t['application-transaction']!
160+
const isAppCreate =
161+
appCallTransaction['on-completion'] === ApplicationOnComplete.noop && !appCallTransaction['application-id']
162+
const isAppOptIn = appCallTransaction['on-completion'] === ApplicationOnComplete.optin && appCallTransaction['application-id']
163+
return isAppCreate || isAppOptIn
164+
})
165+
.map((t) => t.sender)
166+
.filter(distinct((x) => x))
167+
const staleAddresses = Array.from(new Set(addressesStaleDueToBalanceChanges.concat(addressesStaleDueToAppChanges)))
168+
acc[4].push(...staleAddresses)
129169
}
130170
return acc
131171
},
132-
[new Map(), [], new Map(), []] as [Map<Round, string[]>, TransactionResult[], Map<GroupId, GroupResult>, AssetId[]]
172+
[new Map(), [], new Map(), [], []] as [Map<Round, string[]>, TransactionResult[], Map<GroupId, GroupResult>, AssetId[], Address[]]
133173
)
134174

135175
const blockResults = result.blockMetadata.map((b) => {
@@ -161,6 +201,19 @@ const subscribeToBlocksEffect = atomEffect((get, set) => {
161201
})
162202
}
163203

204+
if (staleAddresses.length > 0) {
205+
const currentAccountResults = get.peek(accountResultsAtom)
206+
const addressesToRemove = staleAddresses.filter((staleAddress) => currentAccountResults.has(staleAddress))
207+
208+
set(accountResultsAtom, (prev) => {
209+
const next = new Map(prev)
210+
addressesToRemove.forEach((address) => {
211+
next.delete(address)
212+
})
213+
return next
214+
})
215+
}
216+
164217
set(addStateExtractedFromBlocksAtom, blockResults, transactionResults, Array.from(groupResults.values()))
165218

166219
set(liveTransactionIdsAtom, (prev) => {

0 commit comments

Comments
 (0)