Skip to content
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

Merged
merged 19 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fac0260
Remove a bunch of graphql / apollo stuff from package.json
adamgall Feb 3, 2025
5c2d4a4
Install urql and graphql packages
adamgall Feb 3, 2025
d35d015
First pass through of replacing apollo with urql
adamgall Feb 3, 2025
daa7538
Remove some more apollo specific configs
adamgall Feb 4, 2025
fa72e20
Add more Decent and Sablier config data to the network config files
adamgall Feb 4, 2025
fb5481b
Refactor GraphQL client creation and remove URQL provider
adamgall Feb 4, 2025
6208a14
Merge branch 'develop' into ENG-61_update-subgraph-endpoints
adamgall Feb 4, 2025
2380ba3
Remove network-only request policy from GraphQL queries
adamgall Feb 4, 2025
992ea20
Refactor GraphQL queries and client management
adamgall Feb 4, 2025
1548b20
Update GraphQL query names and imports across multiple files
adamgall Feb 4, 2025
9ef3dbb
Rename GraphQL client creation functions for clarity
adamgall Feb 4, 2025
b3e7cae
Rename subgraph configuration type and update network configs
adamgall Feb 4, 2025
a4d2cac
Remove version from TheGraphConfig and update subgraph URLs
adamgall Feb 4, 2025
003cfc1
Merge branch 'develop' into ENG-61_update-subgraph-endpoints
adamgall Feb 4, 2025
a851c43
Update Sepolia Decent Subgraph ID
adamgall Feb 4, 2025
624b344
Rename Subgraph API Key Environment Variable
adamgall Feb 4, 2025
95004a5
Remove redundant build script for Cloudflare
adamgall Feb 4, 2025
406b343
Add type definitions and improve GraphQL query type safety
adamgall Feb 5, 2025
3b3066f
Merge branch 'develop' into ENG-61_update-subgraph-endpoints
adamgall Feb 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ VITE_APP_INFURA_IPFS_API_KEY=""
VITE_APP_INFURA_IPFS_API_SECRET=""
# index.html and WalletConnect metadata
VITE_APP_SITE_URL="http://localhost:3000"
# TheGraph API key
VITE_APP_THEGRAPH_API_KEY=""
# WalletConnect Cloud Project ID
VITE_APP_WALLET_CONNECT_PROJECT_ID=""

Expand Down
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ yarn-error.log*
# translation script outputs
*.csv

# GraphQL generated files
/.graphclient

# Wrangler
/.wrangler
.dev.vars
13 changes: 0 additions & 13 deletions .graphclientrc.yml

This file was deleted.

3,926 changes: 239 additions & 3,687 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 3 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
"private": true,
"dependencies": {
"@amplitude/analytics-browser": "^2.11.1",
"@apollo/client": "^3.7.10",
"@chakra-ui/anatomy": "^2.2.2",
"@chakra-ui/react": "^2.8.2",
"@cloudflare/workers-types": "^4.20241230.0",
"@fontsource/space-mono": "^5.0.19",
"@fractal-framework/fractal-contracts": "^1.4.2",
"@graphprotocol/client-apollo": "^1.0.16",
"@hatsprotocol/modules-sdk": "^1.4.0",
"@hatsprotocol/sdk-v1-core": "^0.9.0",
"@hatsprotocol/sdk-v1-subgraph": "^1.0.0",
Expand All @@ -32,6 +30,7 @@
"evm-proxy-detection": "^1.1.0",
"formik": "^2.2.9",
"framer-motion": "^6.5.1",
"graphql": "^16.10.0",
"hono": "^4.6.16",
"i18next": "^23.10.1",
"i18next-browser-languagedetector": "^6.1.5",
Expand All @@ -50,6 +49,7 @@
"react-router-dom": "^6.22.0",
"remark-gfm": "^4.0.0",
"sonner": "^1.5.0",
"urql": "^4.2.1",
"use-between": "^1.3.5",
"viem": "^2.13.1",
"vite": "^5.3.4",
Expand All @@ -66,11 +66,7 @@
"dev": "vite --force",
"preview": "vite preview",
"build": "vite build && vite build --mode page-function",
"build:cf": "vite build && vite build --mode page-function",
"graphql:build": "graphclient build",
"graphql:dev-server": "graphclient serve-dev",
"test": "vitest --dir=test",
"postinstall": "npm run graphql:build"
Comment on lines -69 to -73
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

"test": "vitest --dir=test"
},
"engines": {
"node": "23.6.0",
Expand All @@ -89,7 +85,6 @@
]
},
"devDependencies": {
"@graphprotocol/client-cli": "^2.2.20",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.2.1",
"@types/lodash.camelcase": "^4.3.9",
Expand Down
53 changes: 33 additions & 20 deletions src/components/DaoHierarchy/DaoHierarchyNode.tsx
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, DAOQueryResponse } 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';
Expand All @@ -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,
Expand All @@ -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({
Expand Down Expand Up @@ -127,21 +120,38 @@ 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<DAOQueryResponse>(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,
Expand All @@ -152,9 +162,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({
Expand All @@ -166,9 +177,11 @@ export function DaoHierarchyNode({
setHierarchyNode(cachedNode);
return;
}

loadDao(safeAddress).then(_node => {
if (!_node) {
setErrorLoading(true);
return;
}
setValue(
{
Expand All @@ -181,8 +194,8 @@ export function DaoHierarchyNode({
setHierarchyNode(_node);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Comment on lines -184 to -185
Copy link
Contributor

Choose a reason for hiding this comment

The 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 (
Expand Down
8 changes: 4 additions & 4 deletions src/components/ui/page/Global/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const useUserTracking = () => {
const useUpdateFavoritesCache = (onFavoritesUpdated: () => void) => {
const { favoritesList } = useAccountFavorites();
const { getEnsName } = useNetworkEnsNameAsync();

useEffect(() => {
(async () => {
const favoriteNames = await Promise.all(
Expand All @@ -55,10 +56,7 @@ const useUpdateFavoritesCache = (onFavoritesUpdated: () => void) => {

const networkConfig = getNetworkConfig(favoriteChain.id);

return Promise.all([
favorite,
getSafeName(networkConfig.subgraph, favorite.address, getEnsName),
]);
return Promise.all([favorite, getSafeName(networkConfig, favorite.address, getEnsName)]);
}),
);

Expand Down Expand Up @@ -91,6 +89,8 @@ const useUpdateFavoritesCache = (onFavoritesUpdated: () => void) => {
}
})();
}, [favoritesList, getEnsName, onFavoritesUpdated]);

return null;
};

export function Global() {
Expand Down
26 changes: 24 additions & 2 deletions src/graphql/DAO.graphql → src/graphql/DAOQueries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
query DAOQuery($safeAddress: Bytes) {
// Define the base DAO structure that can be nested
interface DAONode {
id: string;
address: string;
parentAddress: string | null;
name: string | null;
snapshotENS: string | null;
}

// Define the recursive hierarchy structure
interface DAOHierarchyNode extends DAONode {
hierarchy: DAOHierarchyNode[];
}

// Define the top-level DAO response structure
export interface DAOQueryResponse {
daos: (DAONode & {
hierarchy: DAOHierarchyNode[];
proposalTemplatesHash: string | null;
})[];
}

export const DAOQuery = `query DAOQuery($safeAddress: Bytes) {
daos(where: { id: $safeAddress }) {
id
address
Expand Down Expand Up @@ -39,4 +61,4 @@ query DAOQuery($safeAddress: Bytes) {
}
proposalTemplatesHash
}
}
}`;
118 changes: 118 additions & 0 deletions src/graphql/SnapshotQueries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Address } from 'viem';
import { SnapshotPlugin, SnapshotProposalType } from '../types';

// Types for Extended Snapshot Proposal
interface Strategy {
name: string;
network: string;
params: Record<string, unknown>;
}

export interface ExtendedSnapshotProposalResponse {
proposal: {
snapshot: number;
type: SnapshotProposalType;
quorum: number;
privacy: string;
strategies: Strategy[];
plugins: SnapshotPlugin[];
choices: string[];
ipfs: string;
};
}

export interface SnapshotVote {
id: string;
voter: Address;
vp: number;
vp_by_strategy: number[];
vp_state: string;
created: number;
choice: number | Record<string, number>;
}

export interface SnapshotProposalVotesResponse {
votes: SnapshotVote[];
}

// Types for User Voting Weight
interface VotingPower {
vp: number;
vp_by_strategy: number[];
vp_state: string;
}

export interface UserVotingWeightResponse {
vp: VotingPower;
}

// Types for Proposals Query
interface SnapshotProposal {
id: string;
title: string;
body: string;
start: number;
end: number;
state: string;
author: Address;
}

export interface ProposalsResponse {
proposals: SnapshotProposal[];
}

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
start
end
state
author
}
}`;
Loading