Skip to content

Commit

Permalink
Merge pull request #2076 from decentdao/issue/2049-read-hat-details-f…
Browse files Browse the repository at this point in the history
…rom-da-ipfs

Read hat details from IPFS and cache it, then read it from the cache
  • Loading branch information
mudrila committed Jul 4, 2024
2 parents 4e38c91 + 5380d02 commit ed4a431
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 44 deletions.
96 changes: 63 additions & 33 deletions src/hooks/DAO/loaders/useHatsTree.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,98 @@
import { Tree } from '@hatsprotocol/sdk-v1-subgraph';
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import useIPFSClient from '../../../providers/App/hooks/useIPFSClient';
import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider';
import { DecentHatsError, useRolesState } from '../../../state/useRolesState';
import { CacheExpiry, CacheKeys } from '../../utils/cache/cacheDefaults';
import { getValue, setValue } from '../../utils/cache/useLocalStorage';
import { useHatsSubgraphClient } from './useHatsSubgraphClient';

const useHatsTree = () => {
const { hatsTreeId } = useRolesState();
const { chain } = useNetworkConfig();
const hatsSubgraphClient = useHatsSubgraphClient();
const { setHatsTree } = useRolesState();
const ipfsClient = useIPFSClient();

useEffect(() => {
if (hatsTreeId === undefined || hatsTreeId === null) {
return;
}
hatsSubgraphClient
.getTree({
chainId: chain.id,
treeId: hatsTreeId,
props: {
hats: {
props: {
prettyId: true,
status: true,
createdAt: true,
details: true,
maxSupply: true,
eligibility: true,
toggle: true,
mutable: true,
levelAtLocalTree: true,
currentSupply: true,
wearers: {
props: {},
filters: {
first: 1,
async function getHatsTree() {
if (hatsTreeId === undefined || hatsTreeId === null) {
return;
}
try {
const tree = await hatsSubgraphClient.getTree({
chainId: chain.id,
treeId: hatsTreeId,
props: {
hats: {
props: {
prettyId: true,
status: true,
details: true,
wearers: {
props: {},
},
},
},
},
},
})
.then(tree => {
});
const hatsWithFetchedDetails = tree.hats
? await Promise.all(
tree.hats.map(async hat => {
const ipfsPrefix = 'ipfs://';
if (hat.details && hat.details.includes(ipfsPrefix)) {
const hash = hat.details.split(ipfsPrefix)[1];
if (hash) {
const cacheKey = {
cacheName: CacheKeys.IPFS_HASH,
hash,
chainId: chain.id,
} as const;
const cachedDetails = getValue(cacheKey);
if (cachedDetails) {
return { ...hat, details: cachedDetails };
} else {
try {
const detailsFromIpfs = await ipfsClient.cat(hash);
const jsonStringDetails = JSON.stringify(detailsFromIpfs);
setValue(cacheKey, jsonStringDetails, CacheExpiry.NEVER);
return { ...hat, details: jsonStringDetails };
} catch (e) {
// Fuck it =/
}
}
}
}
return hat;
}),
)
: tree.hats;

const treeWithFetchedDetails: Tree = { ...tree, hats: hatsWithFetchedDetails };
try {
setHatsTree(tree);
setHatsTree(treeWithFetchedDetails);
} catch (e) {
if (e instanceof DecentHatsError) {
toast(e.message);
}
}
})
.catch(() => {
} catch (e) {
setHatsTree(undefined);
const message = 'Hats Tree ID is not valid';
toast(message);
console.error({
message,
args: {
network: chain.id,
hatsTreeId: hatsTreeId,
hatsTreeId,
},
});
});
}, [chain.id, hatsSubgraphClient, hatsTreeId, setHatsTree]);
}
}

getHatsTree();
}, [chain.id, hatsSubgraphClient, hatsTreeId, setHatsTree, ipfsClient]);
};

export { useHatsTree };
9 changes: 9 additions & 0 deletions src/hooks/utils/cache/cacheDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export enum CacheKeys {
AVERAGE_BLOCK_TIME = 'Average Block Time',
PROPOSAL_CACHE = 'Proposal',
MIGRATION = 'Migration',
IPFS_HASH = 'IPFS Hash',
// indexDB keys
DECODED_TRANSACTION_PREFIX = 'decode_trans_',
MULTISIG_METADATA_PREFIX = 'm_m_',
Expand Down Expand Up @@ -63,11 +64,18 @@ export interface AverageBlockTimeCacheKey extends CacheKey {
chainId: number;
}

export interface IPFSHashCacheKey extends CacheKey {
cacheName: CacheKeys.IPFS_HASH;
hash: string;
chainId: number;
}

export type CacheKeyType =
| FavoritesCacheKey
| MasterCacheKey
| ProposalCacheKey
| AverageBlockTimeCacheKey
| IPFSHashCacheKey
| Omit<CacheKey, 'version'>;

export type CacheValue = {
Expand All @@ -81,6 +89,7 @@ type CacheKeyToValueMap = {
[CacheKeys.PROPOSAL_CACHE]: AzoriusProposal;
[CacheKeys.AVERAGE_BLOCK_TIME]: number;
[CacheKeys.MIGRATION]: number;
[CacheKeys.IPFS_HASH]: string;
};

export type CacheValueType<T extends CacheKeyType> = T extends { cacheName: infer U }
Expand Down
46 changes: 35 additions & 11 deletions src/state/useRolesState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ const getRawAdminHat = (hats: Hat[]) => {
return potentialRawAdminHats[0];
};

const getHatMetadata = (hat: Hat) => {
const metadata = {
name: '',
description: '',
};

if (hat.details) {
try {
// At this stage hat.details should be not IPFS hash but stringified data from the IPFS
const parsedDetails = JSON.parse(hat.details);
metadata.name = parsedDetails.data.name;
metadata.description = parsedDetails.data.description;
} catch (e) {}
}

return metadata;
};

const sanitize = (hatsTree: undefined | null | Tree): undefined | null | DecentTree => {
if (hatsTree === undefined || hatsTree === null) {
return hatsTree;
Expand All @@ -100,21 +118,24 @@ const sanitize = (hatsTree: undefined | null | Tree): undefined | null | DecentT
}

const rawTopHat = getRawTopHat(hatsTree.hats);
const topHatMetadata = getHatMetadata(rawTopHat);

const topHat: DecentHat = {
id: rawTopHat.id,
prettyId: rawTopHat.prettyId ?? '',
name: rawTopHat.details ?? '',
description: rawTopHat.details ?? '',
name: topHatMetadata.name,
description: topHatMetadata.description,
};

const rawAdminHat = getRawAdminHat(hatsTree.hats);

const adminHatMetadata = getHatMetadata(rawAdminHat);

const adminHat: DecentHat = {
id: rawAdminHat.id,
prettyId: rawAdminHat.prettyId ?? '',
name: rawAdminHat.details ?? '',
description: rawAdminHat.details ?? '',
name: adminHatMetadata.name,
description: adminHatMetadata.description,
};

const rawRoleHats = hatsTree.hats.filter(h => appearsExactlyNumberOfTimes(h.prettyId, '.', 2));
Expand All @@ -123,13 +144,16 @@ const sanitize = (hatsTree: undefined | null | Tree): undefined | null | DecentT
.filter(rawHat => rawHat.status === true)
.filter(h => h.wearers !== undefined && h.wearers.length === 1);

const roleHats: DecentRoleHat[] = rawRoleHatsPruned.map(rawHat => ({
id: rawHat.id,
prettyId: rawHat.prettyId ?? '',
name: rawHat.details ?? '',
description: rawHat.details ?? '',
wearer: rawHat.wearers![0].id,
}));
const roleHats: DecentRoleHat[] = rawRoleHatsPruned.map(rawHat => {
const hatMetadata = getHatMetadata(rawHat);
return {
id: rawHat.id,
prettyId: rawHat.prettyId ?? '',
name: hatMetadata.name,
description: hatMetadata.description,
wearer: rawHat.wearers![0].id,
};
});

const decentTree: DecentTree = {
topHat,
Expand Down

0 comments on commit ed4a431

Please sign in to comment.