@@ -6,7 +6,7 @@ import { asTransactionSummary } from '@/features/transactions/mappers'
66import { atomEffect } from 'jotai-effect'
77import { AlgorandSubscriber } from '@algorandfoundation/algokit-subscriber'
88import { algod } from '@/features/common/data'
9- import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
9+ import { ApplicationOnComplete , TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
1010import { BlockResult , Round } from './types'
1111import { assetMetadataResultsAtom } from '@/features/assets/data'
1212import algosdk from 'algosdk'
@@ -17,6 +17,9 @@ import { BlockSummary } from '../models'
1717import { blockResultsAtom , addStateExtractedFromBlocksAtom , syncedRoundAtom } from './block-result'
1818import { GroupId , GroupResult } from '@/features/groups/data/types'
1919import { 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
2124const 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