refactor state management

This commit is contained in:
Maximilian Schneider 2021-08-24 05:16:47 +02:00
parent 01e5d50464
commit 55c3598088
3 changed files with 216 additions and 177 deletions

67
hooks/useRealm.tsx Normal file
View File

@ -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,
}
}

View File

@ -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 = () => {
</p>
<p>
in Governance:{' '}
{realmTokenRecord
? realmTokenRecord.info.governingTokenDepositAmount.toNumber()
{ownTokenRecord
? ownTokenRecord.info.governingTokenDepositAmount.toNumber()
: 'N/A'}
</p>
<p>Proposals:</p>
{Object.entries(realmProposals).map(([k, v]) => (
<div className="m-10 p-4 border" key={k}>
<Link href={`/proposal/${k}`}>
<a>
<h3>{v.info.name}</h3>
<p>{v.info.descriptionLink}</p>
<p>
{moment.unix(v.info.votingCompletedAt.toNumber()).fromNow()}
</p>
<p>{ProposalStateLabels[v.info.state]}</p>
<p>Votes {JSON.stringify(votes[k])}</p>
<p>
{`Yes Threshold: ${
governances[v.info.governance.toBase58()]?.info.config
.voteThresholdPercentage.value
}%`}
</p>
</a>
</Link>
</div>
))}
{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]) => (
<div className="m-10 p-4 border" key={k}>
<Link href={`/proposal/${k}`}>
<a>
<h3>{v.info.name}</h3>
<p>{v.info.descriptionLink}</p>
{v.info.votingCompletedAt ? (
<p>
Ended{' '}
{moment
.unix(v.info.votingCompletedAt.toNumber())
.fromNow()}
</p>
) : (
<p>
Created{' '}
{moment.unix(v.info.votingAt.toNumber()).fromNow()}
</p>
)}
<p>{ProposalStateLabels[v.info.state]}</p>
<p>Votes {JSON.stringify(votes[k])}</p>
<p>
{`Yes Threshold: ${
governances[v.info.governance.toBase58()]?.info.config
.voteThresholdPercentage.value
}%`}
</p>
</a>
</Link>
</div>
))}
</div>
</>
)

View File

@ -44,11 +44,17 @@ interface WalletStore extends State {
endpoint: string
}
current: WalletAdapter | undefined
governances: { [governance: string]: ParsedAccount<Governance> }
proposals: { [proposal: string]: ParsedAccount<Proposal> }
realms: { [realm: string]: ParsedAccount<Realm> }
tokenRecords: { [owner: string]: ParsedAccount<TokenOwnerRecord> }
votes: { [proposal: string]: { yes: number; no: number } }
selectedRealm: {
realm?: ParsedAccount<Realm>
mint?: MintAccount
governances: { [governance: string]: ParsedAccount<Governance> }
proposals: { [proposal: string]: ParsedAccount<Proposal> }
tokenRecords: { [owner: string]: ParsedAccount<TokenOwnerRecord> }
votes: { [proposal: string]: { yes: number; no: number } }
}
selectedProposal: any
providerUrl: string
tokenAccounts: ProgramAccount<TokenAccount>[]
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<WalletStore>((set, get) => ({
connected: false,
connection: {
@ -65,14 +83,20 @@ const useWalletStore = create<WalletStore>((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<WalletStore>((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<WalletStore>((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<Realm>(
programId,
@ -114,95 +137,96 @@ const useWalletStore = create<WalletStore>((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<Governance>(
programId,
endpoint,
Governance,
getAccountTypes(Governance),
[pubkeyFilter(1, realmId)]
),
getGovernanceAccounts<TokenOwnerRecord>(
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<TokenOwnerRecord>(
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<Governance>(
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<Proposal>(
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<VoteRecord>(
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<WalletStore>((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<WalletStore>((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