-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Eng 61 update subgraph endpoints #2708
Changes from 17 commits
fac0260
5c2d4a4
d35d015
daa7538
fa72e20
fb5481b
6208a14
2380ba3
992ea20
1548b20
9ef3dbb
b3e7cae
a4d2cac
003cfc1
a851c43
624b344
95004a5
406b343
3b3066f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,18 @@ | ||
import { useLazyQuery } from '@apollo/client'; | ||
import { Center, Flex, Icon, Link, Text } from '@chakra-ui/react'; | ||
import { abis } from '@fractal-framework/fractal-contracts'; | ||
import { ArrowElbowDownRight } from '@phosphor-icons/react'; | ||
import { useCallback, useEffect, useState } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { Link as RouterLink } from 'react-router-dom'; | ||
import { Address, getContract, zeroAddress } from 'viem'; | ||
import { DAOQueryDocument } from '../../../.graphclient'; | ||
import { SENTINEL_ADDRESS } from '../../constants/common'; | ||
import { DAO_ROUTES } from '../../constants/routes'; | ||
import { createDecentSubgraphClient } from '../../graphql'; | ||
import { DAOQuery } from '../../graphql/DAOQueries'; | ||
import { useDecentModules } from '../../hooks/DAO/loaders/useDecentModules'; | ||
import useNetworkPublicClient from '../../hooks/useNetworkPublicClient'; | ||
import { CacheKeys } from '../../hooks/utils/cache/cacheDefaults'; | ||
import { setValue, getValue } from '../../hooks/utils/cache/useLocalStorage'; | ||
import { getValue, setValue } from '../../hooks/utils/cache/useLocalStorage'; | ||
import { useAddressContractType } from '../../hooks/utils/useAddressContractType'; | ||
import { useSafeAPI } from '../../providers/App/hooks/useSafeAPI'; | ||
import { useNetworkConfigStore } from '../../providers/NetworkConfig/useNetworkConfigStore'; | ||
|
@@ -31,6 +31,7 @@ import { BarLoader } from '../ui/loaders/BarLoader'; | |
* From this initial DAO info, the component will get the DAO's children | ||
* and display another DaoNode for each child, and so on for their children. | ||
*/ | ||
|
||
export function DaoHierarchyNode({ | ||
safeAddress, | ||
depth, | ||
|
@@ -43,20 +44,12 @@ export function DaoHierarchyNode({ | |
const safeApi = useSafeAPI(); | ||
const [hierarchyNode, setHierarchyNode] = useState<DaoHierarchyInfo>(); | ||
const [hasErrorLoading, setErrorLoading] = useState<boolean>(false); | ||
const { addressPrefix, subgraph, chain } = useNetworkConfigStore(); | ||
const { addressPrefix, getConfigByChainId, chain } = useNetworkConfigStore(); | ||
const publicClient = useNetworkPublicClient(); | ||
|
||
const { getAddressContractType } = useAddressContractType(); | ||
const lookupModules = useDecentModules(); | ||
|
||
const [getDAOInfo] = useLazyQuery(DAOQueryDocument, { | ||
context: { | ||
subgraphSpace: subgraph.space, | ||
subgraphSlug: subgraph.slug, | ||
subgraphVersion: subgraph.version, | ||
}, | ||
}); | ||
|
||
const getVotingStrategies = useCallback( | ||
async (azoriusModule: DecentModule) => { | ||
const azoriusContract = getContract({ | ||
|
@@ -127,21 +120,36 @@ export function DaoHierarchyNode({ | |
} | ||
try { | ||
const safe = await safeApi.getSafeInfo(_safeAddress); | ||
const graphRawNodeData = await getDAOInfo({ variables: { safeAddress: _safeAddress } }); | ||
|
||
const client = createDecentSubgraphClient(getConfigByChainId(chain.id)); | ||
const queryResult = await client.query(DAOQuery, { safeAddress: _safeAddress }); | ||
|
||
if (queryResult.error) { | ||
throw new Error('Query failed'); | ||
} | ||
|
||
if (!queryResult.data) { | ||
throw new Error('No data found'); | ||
} | ||
|
||
const modules = await lookupModules(safe.modules); | ||
const graphDAOData = graphRawNodeData.data?.daos[0]; | ||
const graphDAOData = queryResult.data.daos[0]; | ||
const azoriusModule = getAzoriusModuleFromModules(modules ?? []); | ||
const votingStrategies: DaoHierarchyStrategyType[] = azoriusModule | ||
? await getGovernanceTypes(azoriusModule) | ||
: ['MULTISIG']; | ||
if (!graphRawNodeData || !graphDAOData) { | ||
|
||
if (!graphDAOData) { | ||
throw new Error('No data found'); | ||
} | ||
|
||
return { | ||
daoName: graphDAOData.name ?? null, | ||
safeAddress: _safeAddress, | ||
parentAddress: graphDAOData.parentAddress ?? null, | ||
childAddresses: graphDAOData.hierarchy.map(child => child.address), | ||
parentAddress: graphDAOData.parentAddress as Address | null, | ||
childAddresses: graphDAOData.hierarchy.map( | ||
(child: { address: string }) => child.address as Address, | ||
), | ||
daoSnapshotENS: graphDAOData.snapshotENS ?? null, | ||
proposalTemplatesHash: graphDAOData.proposalTemplatesHash ?? null, | ||
modules, | ||
|
@@ -152,9 +160,10 @@ export function DaoHierarchyNode({ | |
return; | ||
} | ||
}, | ||
[getDAOInfo, getGovernanceTypes, lookupModules, safeApi], | ||
[getConfigByChainId, chain.id, getGovernanceTypes, lookupModules, safeApi], | ||
); | ||
|
||
// Effect to handle query result changes | ||
useEffect(() => { | ||
if (safeAddress) { | ||
const cachedNode = getValue({ | ||
|
@@ -166,9 +175,11 @@ export function DaoHierarchyNode({ | |
setHierarchyNode(cachedNode); | ||
return; | ||
} | ||
|
||
loadDao(safeAddress).then(_node => { | ||
if (!_node) { | ||
setErrorLoading(true); | ||
return; | ||
} | ||
setValue( | ||
{ | ||
|
@@ -181,8 +192,8 @@ export function DaoHierarchyNode({ | |
setHierarchyNode(_node); | ||
}); | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
Comment on lines
-184
to
-185
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👀 sweet if we don't need this anymore. tested Myosin good stress tester for org page. |
||
}, [chain.id, loadDao, safeAddress]); | ||
|
||
if (!hierarchyNode) { | ||
// node hasn't loaded yet | ||
return ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
export const ExtendedSnapshotProposalQuery = `query ExtendedSnapshotProposal($snapshotProposalId: String!) { | ||
proposal(id: $snapshotProposalId) { | ||
snapshot | ||
type | ||
quorum | ||
privacy | ||
strategies { | ||
name | ||
network | ||
params | ||
} | ||
plugins | ||
choices | ||
ipfs | ||
} | ||
}`; | ||
|
||
export const SnapshotProposalVotesQuery = `query SnapshotProposalVotes($snapshotProposalId: String!) { | ||
votes(where: { proposal: $snapshotProposalId }, first: 500) { | ||
id | ||
voter | ||
vp | ||
vp_by_strategy | ||
vp_state | ||
created | ||
choice | ||
} | ||
}`; | ||
|
||
export const UserVotingWeightQuery = `query UserVotingWeight($voter: String!, $space: String!, $proposal: String!) { | ||
vp(voter: $voter, space: $space, proposal: $proposal) { | ||
vp | ||
vp_by_strategy | ||
vp_state | ||
} | ||
}`; | ||
|
||
export const ProposalsQuery = `query Proposals($spaceIn: [String!]) { | ||
proposals( | ||
first: 50, | ||
where: { | ||
space_in: $spaceIn | ||
}, | ||
orderBy: "created", | ||
orderDirection: desc | ||
) { | ||
id | ||
title | ||
body | ||
choices | ||
start | ||
end | ||
snapshot | ||
state | ||
author | ||
space { | ||
id | ||
name | ||
} | ||
} | ||
}`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,57 @@ | ||
import { ApolloClient, InMemoryCache } from '@apollo/client'; | ||
import { GraphApolloLink } from '@graphprotocol/client-apollo'; | ||
import * as GraphClient from '../../.graphclient'; | ||
import { cacheExchange, createClient, fetchExchange } from 'urql'; | ||
import { NetworkConfig, TheGraphConfig } from '../types/network'; | ||
|
||
const graphQLClient = new ApolloClient({ | ||
link: new GraphApolloLink(GraphClient), | ||
cache: new InMemoryCache(), | ||
}); | ||
// Cache to store client instances by their unique URL | ||
const clientCache = new Map<string, ReturnType<typeof createClient>>(); | ||
|
||
export default graphQLClient; | ||
const createTheGraphClient = (config: TheGraphConfig) => { | ||
const theGraphAPIKey = import.meta.env.VITE_APP_THEGRAPH_API_KEY; | ||
|
||
const url = import.meta.env.DEV | ||
? `https://api.studio.thegraph.com/query/${config.space}/${config.slug}/version/latest` | ||
: `https://gateway.thegraph.com/api/${theGraphAPIKey}/subgraphs/id/${config.id}`; | ||
|
||
// Check if we already have a client for this URL | ||
const cachedClient = clientCache.get(url); | ||
if (cachedClient) { | ||
return cachedClient; | ||
} | ||
Comment on lines
+14
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we cache the client? looking at the documentation, and is this instead of create a context provider? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Decided to implement this lil caching mechanism (thanks for the suggestion @mudrila), specifically because I've removed the context provider. Wherever we need to utilize a graphql client, we create (or get from cache) an instance of it on the fly, instead of using the "one" that would be in a Provider context. Decided to do it like this because of how diverse our graphql clients are. Effectively the "url" property of a I didn't really do any profiling to see how expensive it is to create these different clients, but the caching layer here is so light it seemed like a simple thing to implement. |
||
|
||
// Create new client if not cached | ||
const client = createClient({ | ||
url, | ||
exchanges: [cacheExchange, fetchExchange], | ||
}); | ||
|
||
// Cache the new client | ||
clientCache.set(url, client); | ||
return client; | ||
}; | ||
|
||
export const createDecentSubgraphClient = (networkConfig: NetworkConfig) => { | ||
return createTheGraphClient(networkConfig.decentSubgraph); | ||
}; | ||
|
||
export const createSablierSubgraphClient = (networkConfig: NetworkConfig) => { | ||
return createTheGraphClient(networkConfig.sablierSubgraph); | ||
}; | ||
|
||
const SNAPSHOT_URL = 'https://hub.snapshot.org/graphql'; | ||
|
||
export const createSnapshotSubgraphClient = () => { | ||
// Check if we already have a Snapshot client | ||
const cachedClient = clientCache.get(SNAPSHOT_URL); | ||
if (cachedClient) { | ||
return cachedClient; | ||
} | ||
|
||
// Create new Snapshot client if not cached | ||
const client = createClient({ | ||
url: SNAPSHOT_URL, | ||
exchanges: [cacheExchange, fetchExchange], | ||
}); | ||
|
||
// Cache the new client | ||
clientCache.set(SNAPSHOT_URL, client); | ||
return client; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥