@@ -7,48 +7,96 @@ import useDebounce from '../../hooks/useDebounce'
77import chunkArray from '../../utils/chunkArray'
88import { useBlockNumber } from '../application/hooks'
99import { AppDispatch , AppState } from '../index'
10- import { parseCallKey , updateMulticallResults } from './actions'
10+ import {
11+ errorFetchingMulticallResults ,
12+ fetchingMulticallResults ,
13+ parseCallKey ,
14+ updateMulticallResults
15+ } from './actions'
1116
1217// chunk calls so we do not exceed the gas limit
1318const CALL_CHUNK_SIZE = 250
1419
20+ /**
21+ * From the current all listeners state, return each call key mapped to the
22+ * minimum number of blocks per fetch. This is how often each key must be fetched.
23+ * @param allListeners the all listeners state
24+ * @param chainId the current chain id
25+ */
26+ function activeListeningKeys (
27+ allListeners : AppState [ 'multicall' ] [ 'callListeners' ] ,
28+ chainId ?: number
29+ ) : { [ callKey : string ] : number } {
30+ if ( ! allListeners || ! chainId ) return { }
31+ const listeners = allListeners [ chainId ]
32+ if ( ! listeners ) return { }
33+
34+ return Object . keys ( listeners ) . reduce < { [ callKey : string ] : number } > ( ( memo , callKey ) => {
35+ const keyListeners = listeners [ callKey ]
36+
37+ memo [ callKey ] = Object . keys ( keyListeners )
38+ . filter ( key => keyListeners [ parseInt ( key ) ] > 0 )
39+ . reduce ( ( previousMin , current ) => {
40+ return Math . min ( previousMin , parseInt ( current ) )
41+ } , Infinity )
42+ return memo
43+ } , { } )
44+ }
45+
1546export default function Updater ( ) {
1647 const dispatch = useDispatch < AppDispatch > ( )
1748 const state = useSelector < AppState , AppState [ 'multicall' ] > ( state => state . multicall )
49+ // wait for listeners to settle before triggering updates
50+ const debouncedListeners = useDebounce ( state . callListeners , 100 )
1851 const latestBlockNumber = useBlockNumber ( )
1952 const { chainId } = useActiveWeb3React ( )
2053 const multicallContract = useMulticallContract ( )
2154
22- const listeningKeys = useMemo ( ( ) => {
23- if ( ! chainId || ! state . callListeners [ chainId ] ) return [ ]
24- return Object . keys ( state . callListeners [ chainId ] ) . filter ( callKey => state . callListeners [ chainId ] [ callKey ] > 0 )
25- } , [ state . callListeners , chainId ] )
26-
27- const debouncedResults = useDebounce ( state . callResults , 20 )
28- const debouncedListeningKeys = useDebounce ( listeningKeys , 20 )
55+ const listeningKeys : { [ callKey : string ] : number } = useMemo ( ( ) => {
56+ return activeListeningKeys ( debouncedListeners , chainId )
57+ } , [ debouncedListeners , chainId ] )
2958
3059 const unserializedOutdatedCallKeys = useMemo ( ( ) => {
31- if ( ! chainId || ! debouncedResults [ chainId ] ) return debouncedListeningKeys
32- if ( ! latestBlockNumber ) return [ ]
60+ // wait for these before fetching any data
61+ if ( ! chainId || ! latestBlockNumber ) return [ ]
62+ // no results at all, load everything
63+ if ( ! state . callResults [ chainId ] ) return Object . keys ( listeningKeys )
64+
65+ return Object . keys ( listeningKeys ) . filter ( callKey => {
66+ const blocksPerFetch = listeningKeys [ callKey ]
67+
68+ const data = state . callResults [ chainId ] [ callKey ]
69+ // no data, must fetch
70+ if ( ! data ) return true
3371
34- return debouncedListeningKeys . filter ( key => {
35- const data = debouncedResults [ chainId ] [ key ]
36- return ! data || data . blockNumber < latestBlockNumber
72+ // already fetching it
73+ if ( data . fetchingBlockNumber && data . fetchingBlockNumber >= latestBlockNumber + blocksPerFetch ) return false
74+
75+ // data block number is older than blocksPerFetch blocks
76+ return data . blockNumber <= latestBlockNumber - blocksPerFetch
3777 } )
38- } , [ chainId , debouncedResults , debouncedListeningKeys , latestBlockNumber ] )
78+ } , [ chainId , state . callResults , listeningKeys , latestBlockNumber ] )
3979
4080 const serializedOutdatedCallKeys = useMemo ( ( ) => JSON . stringify ( unserializedOutdatedCallKeys . sort ( ) ) , [
4181 unserializedOutdatedCallKeys
4282 ] )
4383
4484 useEffect ( ( ) => {
85+ if ( ! latestBlockNumber || ! chainId || ! multicallContract ) return
86+
4587 const outdatedCallKeys : string [ ] = JSON . parse ( serializedOutdatedCallKeys )
46- if ( ! multicallContract || ! chainId || outdatedCallKeys . length === 0 ) return
88+ if ( outdatedCallKeys . length === 0 ) return
4789 const calls = outdatedCallKeys . map ( key => parseCallKey ( key ) )
4890
4991 const chunkedCalls = chunkArray ( calls , CALL_CHUNK_SIZE )
5092
51- console . debug ( 'Firing off chunked calls' , chunkedCalls )
93+ dispatch (
94+ fetchingMulticallResults ( {
95+ calls,
96+ chainId,
97+ fetchingBlockNumber : latestBlockNumber
98+ } )
99+ )
52100
53101 chunkedCalls . forEach ( ( chunk , index ) =>
54102 multicallContract
@@ -73,9 +121,16 @@ export default function Updater() {
73121 } )
74122 . catch ( ( error : any ) => {
75123 console . error ( 'Failed to fetch multicall chunk' , chunk , chainId , error )
124+ dispatch (
125+ errorFetchingMulticallResults ( {
126+ calls : chunk ,
127+ chainId,
128+ fetchingBlockNumber : latestBlockNumber
129+ } )
130+ )
76131 } )
77132 )
78- } , [ chainId , multicallContract , dispatch , serializedOutdatedCallKeys ] )
133+ } , [ chainId , multicallContract , dispatch , serializedOutdatedCallKeys , latestBlockNumber ] )
79134
80135 return null
81136}
0 commit comments