Commit 5298a5ce authored by Justin Domingue's avatar Justin Domingue Committed by GitHub

feat: handle chain id in subgraph api (#1938)

* build dynamic subgraph url based on chain id

* reset api state (query cache) on chain id change

* removed dependency on rtk-query/graphql

* add error message
parent 4d3073d2
...@@ -21,6 +21,7 @@ export enum ApplicationModal { ...@@ -21,6 +21,7 @@ export enum ApplicationModal {
ARBITRUM_OPTIONS, ARBITRUM_OPTIONS,
} }
export const updateChainId = createAction<{ chainId: number | null }>('application/updateChainId')
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('application/updateBlockNumber') export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('application/updateBlockNumber')
export const setOpenModal = createAction<ApplicationModal | null>('application/setOpenModal') export const setOpenModal = createAction<ApplicationModal | null>('application/setOpenModal')
export const addPopup = export const addPopup =
......
import { createStore, Store } from 'redux' import { createStore, Store } from 'redux'
import { addPopup, ApplicationModal, removePopup, setOpenModal, updateBlockNumber } from './actions' import { addPopup, ApplicationModal, removePopup, setOpenModal, updateBlockNumber, updateChainId } from './actions'
import reducer, { ApplicationState } from './reducer' import reducer, { ApplicationState } from './reducer'
describe('application reducer', () => { describe('application reducer', () => {
...@@ -7,6 +7,7 @@ describe('application reducer', () => { ...@@ -7,6 +7,7 @@ describe('application reducer', () => {
beforeEach(() => { beforeEach(() => {
store = createStore(reducer, { store = createStore(reducer, {
chainId: null,
popupList: [], popupList: [],
blockNumber: { blockNumber: {
[1]: 3, [1]: 3,
...@@ -51,6 +52,16 @@ describe('application reducer', () => { ...@@ -51,6 +52,16 @@ describe('application reducer', () => {
}) })
}) })
describe('updateChainId', () => {
it('updates chain id', () => {
expect(store.getState().chainId).toEqual(null)
store.dispatch(updateChainId({ chainId: 1 }))
expect(store.getState().chainId).toEqual(1)
})
})
describe('updateBlockNumber', () => { describe('updateBlockNumber', () => {
it('updates block number', () => { it('updates block number', () => {
store.dispatch(updateBlockNumber({ chainId: 1, blockNumber: 4 })) store.dispatch(updateBlockNumber({ chainId: 1, blockNumber: 4 }))
......
import { createReducer, nanoid } from '@reduxjs/toolkit' import { createReducer, nanoid } from '@reduxjs/toolkit'
import { addPopup, PopupContent, removePopup, updateBlockNumber, ApplicationModal, setOpenModal } from './actions' import {
addPopup,
PopupContent,
removePopup,
updateBlockNumber,
ApplicationModal,
setOpenModal,
updateChainId,
} from './actions'
type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }> type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
export interface ApplicationState { export interface ApplicationState {
// used by RTK-Query to build dynamic subgraph urls
readonly chainId: number | null
readonly blockNumber: { readonly [chainId: number]: number } readonly blockNumber: { readonly [chainId: number]: number }
readonly popupList: PopupList readonly popupList: PopupList
readonly openModal: ApplicationModal | null readonly openModal: ApplicationModal | null
} }
const initialState: ApplicationState = { const initialState: ApplicationState = {
chainId: null,
blockNumber: {}, blockNumber: {},
popupList: [], popupList: [],
openModal: null, openModal: null,
...@@ -17,6 +28,10 @@ const initialState: ApplicationState = { ...@@ -17,6 +28,10 @@ const initialState: ApplicationState = {
export default createReducer(initialState, (builder) => export default createReducer(initialState, (builder) =>
builder builder
.addCase(updateChainId, (state, action) => {
const { chainId } = action.payload
state.chainId = chainId
})
.addCase(updateBlockNumber, (state, action) => { .addCase(updateBlockNumber, (state, action) => {
const { chainId, blockNumber } = action.payload const { chainId, blockNumber } = action.payload
if (typeof state.blockNumber[chainId] !== 'number') { if (typeof state.blockNumber[chainId] !== 'number') {
......
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useAppDispatch } from 'state/hooks' import { api } from 'state/data/slice'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { supportedChainId } from 'utils/supportedChainId'
import useDebounce from '../../hooks/useDebounce' import useDebounce from '../../hooks/useDebounce'
import useIsWindowVisible from '../../hooks/useIsWindowVisible' import useIsWindowVisible from '../../hooks/useIsWindowVisible'
import { useActiveWeb3React } from '../../hooks/web3' import { useActiveWeb3React } from '../../hooks/web3'
import { updateBlockNumber } from './actions' import { updateBlockNumber, updateChainId } from './actions'
function useQueryCacheInvalidator() {
const chainId = useAppSelector((state) => state.application.chainId)
const dispatch = useAppDispatch()
useEffect(() => {
console.log('chanid changed')
dispatch(api.util.resetApiState())
}, [chainId, dispatch])
}
export default function Updater(): null { export default function Updater(): null {
const { library, chainId } = useActiveWeb3React() const { library, chainId } = useActiveWeb3React()
...@@ -16,6 +28,8 @@ export default function Updater(): null { ...@@ -16,6 +28,8 @@ export default function Updater(): null {
blockNumber: null, blockNumber: null,
}) })
useQueryCacheInvalidator()
const blockNumberCallback = useCallback( const blockNumberCallback = useCallback(
(blockNumber: number) => { (blockNumber: number) => {
setState((state) => { setState((state) => {
...@@ -53,5 +67,11 @@ export default function Updater(): null { ...@@ -53,5 +67,11 @@ export default function Updater(): null {
dispatch(updateBlockNumber({ chainId: debouncedState.chainId, blockNumber: debouncedState.blockNumber })) dispatch(updateBlockNumber({ chainId: debouncedState.chainId, blockNumber: debouncedState.blockNumber }))
}, [windowVisible, dispatch, debouncedState.blockNumber, debouncedState.chainId]) }, [windowVisible, dispatch, debouncedState.blockNumber, debouncedState.chainId])
useEffect(() => {
dispatch(
updateChainId({ chainId: debouncedState.chainId ? supportedChainId(debouncedState.chainId) ?? null : null })
)
}, [dispatch, debouncedState.chainId])
return null return null
} }
import { createApi } from '@reduxjs/toolkit/query/react' import { createApi } from '@reduxjs/toolkit/query/react'
import { gql, GraphQLClient } from 'graphql-request' import { ClientError, gql, GraphQLClient } from 'graphql-request'
import { FeeAmount } from '@uniswap/v3-sdk' import { FeeAmount } from '@uniswap/v3-sdk'
import { reduce } from 'lodash' import { reduce } from 'lodash'
import { graphqlRequestBaseQuery } from '@rtk-query/graphql-request-base-query'
import { FeeTierDistribution, PoolTVL } from './types' import { FeeTierDistribution, PoolTVL } from './types'
import { SupportedChainId } from 'constants/chains'
import { AppState } from 'state'
import { BaseQueryApi, BaseQueryFn } from '@reduxjs/toolkit/dist/query/baseQueryTypes'
import { DocumentNode } from 'graphql'
export const UNISWAP_V3_GRAPH_URL = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3' export const UNISWAP_V3_GRAPH_URL = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
export const graphqlRequestBaseQuery = (): BaseQueryFn<
{ document: string | DocumentNode; variables?: any },
unknown,
Pick<ClientError, 'name' | 'message' | 'stack'>,
Partial<Pick<ClientError, 'request' | 'response'>>
> => {
return async ({ document, variables }, { getState }: BaseQueryApi) => {
try {
const chainId = (getState() as AppState).application.chainId
let client: GraphQLClient | null = null
switch (chainId) {
case SupportedChainId.MAINNET:
client = new GraphQLClient(UNISWAP_V3_GRAPH_URL)
break
default:
return {
error: {
name: 'UnsupportedChainId',
message: `Subgraph queries again ChainId ${chainId} are not supported.`,
stack: '',
},
}
}
return { data: await client.request(document, variables), meta: {} }
} catch (error) {
if (error instanceof ClientError) {
const { name, message, stack, request, response } = error
return { error: { name, message, stack }, meta: { request, response } }
}
throw error
}
}
}
export const client = new GraphQLClient(UNISWAP_V3_GRAPH_URL) export const client = new GraphQLClient(UNISWAP_V3_GRAPH_URL)
export const api = createApi({ export const api = createApi({
reducerPath: 'dataApi', reducerPath: 'dataApi',
baseQuery: graphqlRequestBaseQuery({ client }), baseQuery: graphqlRequestBaseQuery(),
endpoints: (builder) => ({ endpoints: (builder) => ({
getFeeTierDistribution: builder.query<FeeTierDistribution, { token0: string; token1: string }>({ getFeeTierDistribution: builder.query<FeeTierDistribution, { token0: string; token1: string }>({
query: ({ token0, token1 }) => ({ query: ({ token0, token1 }) => ({
......
...@@ -3240,13 +3240,6 @@ ...@@ -3240,13 +3240,6 @@
estree-walker "^1.0.1" estree-walker "^1.0.1"
picomatch "^2.2.2" picomatch "^2.2.2"
"@rtk-query/graphql-request-base-query@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@rtk-query/graphql-request-base-query/-/graphql-request-base-query-1.0.3.tgz#34f5cd65ac98187ade3044174228bde83d9eccb6"
integrity sha512-oL/cE4Nm3GXjpVpTaFD+THwY71Tpk2XjuwYDEL8xk8gkfsx3GWbKRgcBX7x8xnUNcKobHXejjxkw9n1k/RpAbA==
dependencies:
graphql-request "^3.4.0"
"@samverschueren/stream-to-observable@^0.3.0": "@samverschueren/stream-to-observable@^0.3.0":
version "0.3.1" version "0.3.1"
resolved "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz" resolved "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment