import { createReducer } from "@reduxjs/toolkit"; import { addMulticallListeners, errorFetchingMulticallResults, fetchingMulticallResults, removeMulticallListeners, toCallKey, updateMulticallResults, } from "./actions"; export interface MulticallState { callListeners?: { // on a per-chain basis [chainId: number]: { // stores for each call key the listeners' preferences [callKey: string]: { // stores how many listeners there are per each blocks per fetch preference [blocksPerFetch: number]: number; }; }; }; callResults: { [chainId: number]: { [callKey: string]: { data?: string | null; blockNumber?: number; fetchingBlockNumber?: number; }; }; }; } const initialState: MulticallState = { callResults: {}, }; export default createReducer(initialState, (builder) => builder .addCase( addMulticallListeners, ( state, { payload: { calls, chainId, options: { blocksPerFetch = 1 } = {} } } ) => { const listeners: MulticallState["callListeners"] = state.callListeners ? state.callListeners : (state.callListeners = {}); listeners[chainId] = listeners[chainId] ?? {}; calls.forEach((call) => { const callKey = toCallKey(call); listeners[chainId][callKey] = listeners[chainId][callKey] ?? {}; listeners[chainId][callKey][blocksPerFetch] = (listeners[chainId][callKey][blocksPerFetch] ?? 0) + 1; }); } ) .addCase( removeMulticallListeners, ( state, { payload: { chainId, calls, options: { blocksPerFetch = 1 } = {} } } ) => { const listeners: MulticallState["callListeners"] = state.callListeners ? state.callListeners : (state.callListeners = {}); if (!listeners[chainId]) return; calls.forEach((call) => { const callKey = toCallKey(call); if (!listeners[chainId][callKey]) return; if (!listeners[chainId][callKey][blocksPerFetch]) return; if (listeners[chainId][callKey][blocksPerFetch] === 1) { delete listeners[chainId][callKey][blocksPerFetch]; } else { listeners[chainId][callKey][blocksPerFetch]--; } }); } ) .addCase( fetchingMulticallResults, (state, { payload: { chainId, fetchingBlockNumber, calls } }) => { state.callResults[chainId] = state.callResults[chainId] ?? {}; calls.forEach((call) => { const callKey = toCallKey(call); const current = state.callResults[chainId][callKey]; if (!current) { state.callResults[chainId][callKey] = { fetchingBlockNumber, }; } else { if ((current.fetchingBlockNumber ?? 0) >= fetchingBlockNumber) return; state.callResults[chainId][ callKey ].fetchingBlockNumber = fetchingBlockNumber; } }); } ) .addCase( errorFetchingMulticallResults, (state, { payload: { fetchingBlockNumber, chainId, calls } }) => { state.callResults[chainId] = state.callResults[chainId] ?? {}; calls.forEach((call) => { const callKey = toCallKey(call); const current = state.callResults[chainId][callKey]; if (!current || typeof current.fetchingBlockNumber !== "number") return; // only should be dispatched if we are already fetching if (current.fetchingBlockNumber <= fetchingBlockNumber) { delete current.fetchingBlockNumber; current.data = null; current.blockNumber = fetchingBlockNumber; } }); } ) .addCase( updateMulticallResults, (state, { payload: { chainId, results, blockNumber } }) => { state.callResults[chainId] = state.callResults[chainId] ?? {}; Object.keys(results).forEach((callKey) => { const current = state.callResults[chainId][callKey]; if ((current?.blockNumber ?? 0) > blockNumber) return; state.callResults[chainId][callKey] = { data: results[callKey], blockNumber, }; }); } ) );