From 55c3598088089befb9f02beb7c8807c411e83c0d Mon Sep 17 00:00:00 2001
From: Maximilian Schneider
Date: Tue, 24 Aug 2021 05:16:47 +0200
Subject: [PATCH] refactor state management
---
hooks/useRealm.tsx | 67 +++++++++++++++
pages/dao/[symbol].tsx | 151 +++++++++++---------------------
stores/useWalletStore.tsx | 175 +++++++++++++++++++++-----------------
3 files changed, 216 insertions(+), 177 deletions(-)
create mode 100644 hooks/useRealm.tsx
diff --git a/hooks/useRealm.tsx b/hooks/useRealm.tsx
new file mode 100644
index 0000000..62348a8
--- /dev/null
+++ b/hooks/useRealm.tsx
@@ -0,0 +1,67 @@
+import { PublicKey } from '@solana/web3.js'
+import { useEffect, useMemo } from 'react'
+import { RealmInfo } from '../@types/types'
+import useWalletStore from '../stores/useWalletStore'
+
+export const REALMS: RealmInfo[] = [
+ {
+ symbol: 'MNGO',
+ programId: new PublicKey('GqTPL6qRf5aUuqscLh8Rg2HTxPUXfhhAXDptTLhp1t2J'),
+ realmId: new PublicKey('DPiH3H3c7t47BMxqTxLsuPQpEC6Kne8GA9VXbxpnZxFE'),
+ },
+]
+
+export default function useRealm(symbol: string) {
+ const { fetchAllRealms, fetchRealm } = useWalletStore((s) => s.actions)
+ const connected = useWalletStore((s) => s.connected)
+ const wallet = useWalletStore((s) => s.current)
+ const tokenAccounts = useWalletStore((s) => s.tokenAccounts)
+ const {
+ realm,
+ mint,
+ governances,
+ proposals,
+ tokenRecords,
+ votes,
+ } = useWalletStore((s) => s.selectedRealm)
+
+ const realmInfo = useMemo(() => REALMS.find((r) => r.symbol === symbol), [
+ symbol,
+ ])
+
+ useEffect(() => {
+ const fetch = async () => {
+ if (realmInfo) {
+ await fetchAllRealms(realmInfo.programId)
+ fetchRealm(realmInfo.programId, realmInfo.realmId)
+ }
+ }
+ fetch()
+ }, [realmInfo])
+
+ const realmTokenAccount = useMemo(
+ () =>
+ realm &&
+ tokenAccounts.find((a) =>
+ a.account.mint.equals(realm.info.communityMint)
+ ),
+ [realm, tokenAccounts]
+ )
+
+ const ownTokenRecord = useMemo(
+ () => wallet?.connected && tokenRecords[wallet.publicKey.toBase58()],
+ [tokenRecords, wallet, connected]
+ )
+
+ return {
+ realm,
+ realmInfo,
+ mint,
+ governances,
+ proposals,
+ tokenRecords,
+ votes,
+ realmTokenAccount,
+ ownTokenRecord,
+ }
+}
diff --git a/pages/dao/[symbol].tsx b/pages/dao/[symbol].tsx
index 847d1df..2604cee 100644
--- a/pages/dao/[symbol].tsx
+++ b/pages/dao/[symbol].tsx
@@ -1,19 +1,8 @@
-import { PublicKey } from '@solana/web3.js'
import { useRouter } from 'next/router'
import Link from 'next/link'
-import { useMemo } from 'react'
-import { useEffect } from 'react'
-import { RealmInfo } from '../../@types/types'
import useWalletStore from '../../stores/useWalletStore'
import moment from 'moment'
-
-export const REALMS: RealmInfo[] = [
- {
- symbol: 'MNGO',
- programId: new PublicKey('GqTPL6qRf5aUuqscLh8Rg2HTxPUXfhhAXDptTLhp1t2J'),
- realmId: new PublicKey('DPiH3H3c7t47BMxqTxLsuPQpEC6Kne8GA9VXbxpnZxFE'),
- },
-]
+import useRealm from '../../hooks/useRealm'
export const ProposalStateLabels = {
0: 'Draft',
@@ -31,68 +20,18 @@ const DAO = () => {
const router = useRouter()
const { symbol } = router.query
- const { fetchRealm } = useWalletStore((s) => s.actions)
- const connected = useWalletStore((s) => s.connected)
const wallet = useWalletStore((s) => s.current)
- const realms = useWalletStore((s) => s.realms)
- const governances = useWalletStore((s) => s.governances)
- const proposals = useWalletStore((s) => s.proposals)
- const votes = useWalletStore((s) => s.votes)
- const tokenAccounts = useWalletStore((s) => s.tokenAccounts)
- const tokenRecords = useWalletStore((s) => s.tokenRecords)
-
- const realmInfo = useMemo(() => REALMS.find((r) => r.symbol === symbol), [
- symbol,
- ])
-
- useEffect(() => {
- if (realmInfo) {
- fetchRealm(realmInfo.programId, realmInfo.realmId)
- }
- }, [realmInfo])
-
- const realm = useMemo(() => {
- return realmInfo && realms[realmInfo.realmId.toBase58()]
- }, [realmInfo, realms])
-
- const realmGovernances = useMemo(() => {
- return realmInfo
- ? Object.fromEntries(
- Object.entries(governances).filter(([_k, v]) =>
- v.info.realm.equals(realmInfo.realmId)
- )
- )
- : {}
- }, [realmInfo, governances])
-
- const realmProposals = useMemo(() => {
- return Object.fromEntries(
- Object.entries(proposals)
- .filter(
- ([_k, v]) =>
- Object.keys(realmGovernances).includes(
- v.info.governance.toBase58()
- ) && v.info.votingAtSlot
- )
- .sort(
- (a, b) =>
- b[1].info.votingAt.toNumber() - a[1].info.votingAt.toNumber()
- )
- )
- }, [realmGovernances, proposals])
-
- const realmTokenAccount = useMemo(
- () =>
- realm &&
- tokenAccounts.find((a) =>
- a.account.mint.equals(realm.info.communityMint)
- ),
- [realm, tokenAccounts]
- )
+ const {
+ governances,
+ proposals,
+ votes,
+ realmTokenAccount,
+ ownTokenRecord,
+ } = useRealm(symbol as string)
+ // DEBUG print remove
console.log(
- 'governance page tokenAccounts',
- tokenAccounts,
+ 'governance page tokenAccount',
realmTokenAccount && realmTokenAccount.publicKey.toBase58()
)
@@ -101,14 +40,9 @@ const DAO = () => {
wallet?.connected && wallet.publicKey.toBase58()
)
- const realmTokenRecord = useMemo(
- () => wallet?.connected && tokenRecords[wallet.publicKey.toBase58()],
- [tokenRecords, wallet, connected]
- )
-
console.log(
'governance page tokenRecord',
- wallet?.connected && realmTokenRecord
+ wallet?.connected && ownTokenRecord
)
return (
@@ -123,32 +57,49 @@ const DAO = () => {
in Governance:{' '}
- {realmTokenRecord
- ? realmTokenRecord.info.governingTokenDepositAmount.toNumber()
+ {ownTokenRecord
+ ? ownTokenRecord.info.governingTokenDepositAmount.toNumber()
: 'N/A'}
Proposals:
- {Object.entries(realmProposals).map(([k, v]) => (
-
- ))}
+ {Object.entries(proposals)
+ .filter(([_k, v]) => v.info.votingAt)
+ .sort(
+ (a, b) =>
+ b[1].info.votingAt.toNumber() - a[1].info.votingAt.toNumber()
+ )
+ .map(([k, v]) => (
+
+ ))}
>
)
diff --git a/stores/useWalletStore.tsx b/stores/useWalletStore.tsx
index 175d99e..46fe8c4 100644
--- a/stores/useWalletStore.tsx
+++ b/stores/useWalletStore.tsx
@@ -44,11 +44,17 @@ interface WalletStore extends State {
endpoint: string
}
current: WalletAdapter | undefined
- governances: { [governance: string]: ParsedAccount }
- proposals: { [proposal: string]: ParsedAccount }
+
realms: { [realm: string]: ParsedAccount }
- tokenRecords: { [owner: string]: ParsedAccount }
- votes: { [proposal: string]: { yes: number; no: number } }
+ selectedRealm: {
+ realm?: ParsedAccount
+ mint?: MintAccount
+ governances: { [governance: string]: ParsedAccount }
+ proposals: { [proposal: string]: ParsedAccount }
+ tokenRecords: { [owner: string]: ParsedAccount }
+ votes: { [proposal: string]: { yes: number; no: number } }
+ }
+ selectedProposal: any
providerUrl: string
tokenAccounts: ProgramAccount[]
mints: { [pubkey: string]: MintAccount }
@@ -56,6 +62,18 @@ interface WalletStore extends State {
actions: any
}
+function mapKeys(xs: any, mapFn: (k: string) => any) {
+ return Object.keys(xs).map(mapFn)
+}
+
+function mapEntries(xs: any, mapFn: (kv: any[]) => any[]) {
+ return Object.fromEntries(Object.entries(xs).map(mapFn))
+}
+
+function merge(...os) {
+ return Object.assign({}, ...os)
+}
+
const useWalletStore = create((set, get) => ({
connected: false,
connection: {
@@ -65,14 +83,20 @@ const useWalletStore = create((set, get) => ({
endpoint: ENDPOINT.url,
},
current: null,
- governances: {},
- proposals: {},
realms: {},
- tokenRecords: {},
- votes: {},
+ selectedRealm: {
+ realm: null,
+ mint: null,
+ governances: {},
+ proposals: {},
+ tokenRecords: {},
+ votes: {},
+ },
+ selectedProposal: {},
providerUrl: DEFAULT_PROVIDER.url,
tokenAccounts: [],
mints: {},
+ set: (fn) => set(produce(fn)),
actions: {
async fetchWalletTokenAccounts() {
const connection = get().connection.current
@@ -90,7 +114,7 @@ const useWalletStore = create((set, get) => ({
console.log(
'fetchWalletTokenAccounts',
connected,
- ownedTokenAccounts.map((t) => t.publicKey.toBase58())
+ ownedTokenAccounts.map((t) => t.account.mint.toBase58())
)
set((state) => {
@@ -102,11 +126,10 @@ const useWalletStore = create((set, get) => ({
})
}
},
- async fetchRealm(programId: PublicKey, realmId: PublicKey) {
+ async fetchAllRealms(programId: PublicKey) {
const connection = get().connection.current
const endpoint = get().connection.endpoint
-
- console.log('fetchRealm', programId.toBase58(), realmId.toBase58())
+ const set = get().set
const realms = await getGovernanceAccounts(
programId,
@@ -114,95 +137,96 @@ const useWalletStore = create((set, get) => ({
Realm,
getAccountTypes(Realm)
)
- console.log('fetchRealm realms', realms[realmId.toBase58()])
set((s) => {
- s.realms = Object.assign({}, s.realms, realms)
+ s.realms = realms
})
- const realmMint = await getMint(
- connection,
- realms[realmId.toBase58()].info.communityMint
+ const mints = await Promise.all(
+ Object.values(realms).map((r) =>
+ getMint(connection, r.info.communityMint)
+ )
)
+ set((s) => {
+ s.mints = Object.fromEntries(
+ mints.map((m) => [m.publicKey.toBase58(), m.account])
+ )
+ })
+ },
+ async fetchRealm(programId: PublicKey, realmId: PublicKey) {
+ const endpoint = get().connection.endpoint
+ const realms = get().realms
+ const mints = get().mints
+ const realm = realms[realmId.toBase58()]
+ const realmMintPk = realm.info.communityMint
+ const realmMint = mints[realmMintPk.toBase58()]
+ const set = get().set
+
+ console.log('fetchRealm', programId.toBase58(), realmId.toBase58())
+
+ const [governances, tokenRecords] = await Promise.all([
+ getGovernanceAccounts(
+ programId,
+ endpoint,
+ Governance,
+ getAccountTypes(Governance),
+ [pubkeyFilter(1, realmId)]
+ ),
+
+ getGovernanceAccounts(
+ programId,
+ endpoint,
+ TokenOwnerRecord,
+ getAccountTypes(TokenOwnerRecord),
+ [pubkeyFilter(1, realmId), pubkeyFilter(1 + 32, realmMintPk)]
+ ),
+ ])
+
+ const tokenRecordsByOwner = mapEntries(tokenRecords, ([_k, v]) => [
+ v.info.governingTokenOwner.toBase58(),
+ v,
+ ])
+
console.log('fetchRealm mint', realmMint)
-
- set(
- (s) =>
- (s.mints = Object.assign(
- {},
- s.mints,
- Object.fromEntries([
- [realmMint.publicKey.toBase58(), realmMint.account],
- ])
- ))
- )
-
- const tokenRecords = await getGovernanceAccounts(
- programId,
- endpoint,
- TokenOwnerRecord,
- getAccountTypes(TokenOwnerRecord),
- [pubkeyFilter(1, realmId), pubkeyFilter(1 + 32, realmMint.publicKey)]
- )
- const tokenRecordsByOwner = Object.fromEntries(
- Object.entries(tokenRecords).map(([_k, v]) => [
- v.info.governingTokenOwner.toBase58(),
- v,
- ])
- )
- console.log('fetchRealm tokenOwners', tokenRecordsByOwner)
-
- set((s) => {
- s.tokenRecords = Object.assign({}, s.tokenRecords, tokenRecordsByOwner)
- })
-
- const governances = await getGovernanceAccounts(
- programId,
- endpoint,
- Governance,
- getAccountTypes(Governance),
- [pubkeyFilter(1, realmId)]
- )
console.log('fetchRealm governances', governances)
+ console.log('fetchRealm tokenRecords', tokenRecordsByOwner)
set((s) => {
- s.governances = Object.assign({}, s.governances, governances)
+ s.selectedRealm.realm = realm
+ s.selectedRealm.mint = realmMint
+ s.selectedRealm.governances = governances
+ s.selectedRealm.tokenRecords = tokenRecordsByOwner
})
- const governanceIds = Object.keys(governances).map(
- (k) => new PublicKey(k)
- )
-
const proposalsByGovernance = await Promise.all(
- governanceIds.map((governanceId) => {
+ mapKeys(governances, (g) => {
return getGovernanceAccounts(
programId,
endpoint,
Proposal,
getAccountTypes(Proposal),
- [pubkeyFilter(1, governanceId)]
+ [pubkeyFilter(1, new PublicKey(g))]
)
})
)
- const proposals = Object.assign({}, ...proposalsByGovernance)
+
+ const proposals = merge(...proposalsByGovernance)
console.log('fetchRealm proposals', proposals)
set((s) => {
- s.proposals = Object.assign({}, s.proposals, proposals)
+ s.selectedRealm.proposals = proposals
})
- const proposalIds = Object.keys(proposals).map((k) => new PublicKey(k))
-
const votesByProposal = await Promise.all(
- proposalIds.map(async (proposalId) => {
+ mapKeys(proposals, async (p) => {
const voteRecords = await getGovernanceAccounts(
programId,
endpoint,
VoteRecord,
getAccountTypes(VoteRecord),
- [pubkeyFilter(1, proposalId)]
+ [pubkeyFilter(1, new PublicKey(p))]
)
const precision = Math.pow(10, 6)
@@ -212,7 +236,7 @@ const useWalletStore = create((set, get) => ({
.filter((v) => !!v)
.reduce((acc, cur) => acc.add(cur), new BN(0))
.mul(new BN(precision))
- .div(realmMint.account.supply)
+ .div(realmMint.supply)
.toNumber() *
(100 / precision)
@@ -222,26 +246,23 @@ const useWalletStore = create((set, get) => ({
.filter((v) => !!v)
.reduce((acc, cur) => acc.add(cur), new BN(0))
.mul(new BN(precision))
- .div(realmMint.account.supply)
+ .div(realmMint.supply)
.toNumber() *
(100 / precision)
- return [proposalId.toBase58(), { yes, no }]
+ return [p, { yes, no }]
})
)
+ // TODO: cache votes of finished proposals permanently in local storage to avoid recounting
+
console.log('fetchRealm votes', votesByProposal)
set((s) => {
- s.votes = Object.assign(
- {},
- s.votes,
- Object.fromEntries(votesByProposal)
- )
+ s.selectedRealm.votes = Object.fromEntries(votesByProposal)
})
},
},
- set: (fn) => set(produce(fn)),
}))
export default useWalletStore