Skip to content

Commit

Permalink
Delegator rework for vanilla + ux adjustment (#1912)
Browse files Browse the repository at this point in the history
  • Loading branch information
asktree authored Nov 9, 2023
1 parent 2b6bff5 commit c1ed6a6
Show file tree
Hide file tree
Showing 16 changed files with 489 additions and 230 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ yarn-error.log*

# Sentry
.sentryclirc

.vscode/settings.json
12 changes: 0 additions & 12 deletions .vscode/settings.json

This file was deleted.

169 changes: 149 additions & 20 deletions actions/castVote.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
Connection,
Keypair,
PublicKey,
Transaction,
TransactionInstruction,
} from '@solana/web3.js'
import {
Expand All @@ -14,6 +14,7 @@ import {
VoteKind,
VoteType,
withPostChatMessage,
withCreateTokenOwnerRecord,
} from '@solana/spl-governance'
import { ProgramAccount } from '@solana/spl-governance'
import { RpcContext } from '@solana/spl-governance'
Expand All @@ -28,11 +29,15 @@ import {
SequenceType,
txBatchesToInstructionSetWithSigners,
} from '@utils/sendTransactions'
import { sendTransaction } from '@utils/send'
import { calcCostOfNftVote, checkHasEnoughSolToVote } from '@tools/nftVoteCalc'
import useNftProposalStore from 'NftVotePlugin/NftProposalStore'
import { HeliumVsrClient } from 'HeliumVotePlugin/sdk/client'
import { NftVoterClient } from '@utils/uiTypes/NftVoterClient'
import { fetchRealmByPubkey } from '@hooks/queries/realm'
import { fetchProposalByPubkeyQuery } from '@hooks/queries/proposal'
import { findPluginName } from '@hooks/queries/governancePower'
import { DELEGATOR_BATCH_VOTE_SUPPORT_BY_PLUGIN } from '@constants/flags'
import { fetchTokenOwnerRecordByPubkey } from '@hooks/queries/tokenOwnerRecord'
import { fetchProgramVersion } from '@hooks/queries/useProgramVersionQuery'

const getVetoTokenMint = (
Expand All @@ -50,6 +55,86 @@ const getVetoTokenMint = (
return vetoTokenMint
}

const createDelegatorVote = async ({
connection,
realmPk,
proposalPk,
tokenOwnerRecordPk,
userPk,
vote,
}: {
connection: Connection
realmPk: PublicKey
proposalPk: PublicKey
tokenOwnerRecordPk: PublicKey
userPk: PublicKey
vote: Vote
}) => {
//
const realm = (await fetchRealmByPubkey(connection, realmPk)).result
if (!realm) throw new Error()
const proposal = (await fetchProposalByPubkeyQuery(connection, proposalPk))
.result
if (!proposal) throw new Error()

const programVersion = await fetchProgramVersion(connection, realm.owner)

const castVoteIxs: TransactionInstruction[] = []
await withCastVote(
castVoteIxs,
realm.owner,
programVersion,
realm.pubkey,
proposal.account.governance,
proposal.pubkey,
proposal.account.tokenOwnerRecord,
tokenOwnerRecordPk,
userPk,
proposal.account.governingTokenMint,
vote,
userPk
//plugin?.voterWeightPk,
//plugin?.maxVoterWeightRecord
)
return castVoteIxs
}

const createTokenOwnerRecordIfNeeded = async ({
connection,
realmPk,
tokenOwnerRecordPk,
payer,
governingTokenMint,
}: {
connection: Connection
realmPk: PublicKey
tokenOwnerRecordPk: PublicKey
payer: PublicKey
governingTokenMint: PublicKey
}) => {
const realm = await fetchRealmByPubkey(connection, realmPk)
if (!realm.result) throw new Error()
const version = await fetchProgramVersion(connection, realm.result.owner)

const tokenOwnerRecord = await fetchTokenOwnerRecordByPubkey(
connection,
tokenOwnerRecordPk
)
if (tokenOwnerRecord.result) return []
// create token owner record
const ixs: TransactionInstruction[] = []
await withCreateTokenOwnerRecord(
ixs,
realm.result.owner,
version,
realmPk,
payer,
governingTokenMint,
payer
)
return ixs
}

export async function castVote(
{ connection, wallet, programId, walletPubkey }: RpcContext,
realm: ProgramAccount<Realm>,
Expand All @@ -60,9 +145,9 @@ export async function castVote(
votingPlugin?: VotingClient,
runAfterConfirmation?: (() => void) | null,
voteWeights?: number[],
_additionalTokenOwnerRecords?: []
additionalTokenOwnerRecords?: PublicKey[]
) {
const signers: Keypair[] = []
const chatMessageSigners: Keypair[] = []

const createCastNftVoteTicketIxs: TransactionInstruction[] = []
const createPostMessageTicketIxs: TransactionInstruction[] = []
Expand All @@ -81,7 +166,6 @@ export async function castVote(
tokenOwnerRecord,
createCastNftVoteTicketIxs
)
console.log('PLUGIN IXS', pluginCastVoteIxs)

const isMulti =
proposal.account.voteType !== VoteType.SINGLE_CHOICE &&
Expand Down Expand Up @@ -153,6 +237,25 @@ export async function castVote(
plugin?.maxVoterWeightRecord
)

const delegatorCastVoteAtoms =
additionalTokenOwnerRecords &&
DELEGATOR_BATCH_VOTE_SUPPORT_BY_PLUGIN[
findPluginName(votingPlugin?.client?.program.programId)
]
? await Promise.all(
additionalTokenOwnerRecords.map((tokenOwnerRecordPk) =>
createDelegatorVote({
connection,
realmPk: realm.pubkey,
proposalPk: proposal.pubkey,
tokenOwnerRecordPk,
userPk: walletPubkey,
vote,
})
)
)
: []

const pluginPostMessageIxs: TransactionInstruction[] = []
const postMessageIxs: TransactionInstruction[] = []
if (message) {
Expand All @@ -165,7 +268,7 @@ export async function castVote(

await withPostChatMessage(
postMessageIxs,
signers,
chatMessageSigners,
GOVERNANCE_CHAT_PROGRAM_ID,
programId,
realm.pubkey,
Expand All @@ -182,22 +285,48 @@ export async function castVote(

const isNftVoter = votingPlugin?.client instanceof NftVoterClient
const isHeliumVoter = votingPlugin?.client instanceof HeliumVsrClient
const tokenOwnerRecordIxs = await createTokenOwnerRecordIfNeeded({
connection,
realmPk: realm.pubkey,
tokenOwnerRecordPk: tokenOwnerRecord,
payer,
governingTokenMint: tokenMint,
})

if (!isNftVoter && !isHeliumVoter) {
const transaction = new Transaction()
transaction.add(
...[
...pluginCastVoteIxs,
...castVoteIxs,
...pluginPostMessageIxs,
...postMessageIxs,
]
const batch1 = [
...tokenOwnerRecordIxs,
...pluginCastVoteIxs,
...castVoteIxs,
...pluginPostMessageIxs,
...postMessageIxs,
]
// chunk size chosen conservatively. "Atoms" refers to atomic clusters of instructions (namely, updatevoterweight? + vote)
const delegatorBatches = chunks(delegatorCastVoteAtoms, 2).map((x) =>
x.flat()
)
const actions = [batch1, ...delegatorBatches].map((ixs) => ({
instructionsSet: ixs.map((ix) => ({
transactionInstruction: ix,
signers: chatMessageSigners.filter((kp) =>
ix.keys.find((key) => key.isSigner && key.pubkey.equals(kp.publicKey))
),
})),
sequenceType: SequenceType.Parallel,
}))

await sendTransaction({ transaction, wallet, connection, signers })
if (runAfterConfirmation) {
runAfterConfirmation()
}
await sendTransactionsV3({
connection,
wallet,
transactionInstructions: actions,
callbacks: {
afterAllTxConfirmed: () => {
if (runAfterConfirmation) {
runAfterConfirmation()
}
},
},
})
}

// we need to chunk instructions
Expand All @@ -217,7 +346,7 @@ export async function castVote(
return {
instructionsSet: txBatchesToInstructionSetWithSigners(
txBatch,
message ? [[], signers] : [],
message ? [[], chatMessageSigners] : [], // seeing signer related bugs when posting chat? This is likely culprit
batchIdx
),
sequenceType: SequenceType.Sequential,
Expand Down Expand Up @@ -276,7 +405,7 @@ export async function castVote(
return {
instructionsSet: txBatchesToInstructionSetWithSigners(
txBatch,
message ? [[], signers] : [],
message ? [[], chatMessageSigners] : [], // seeing signer related bugs when posting chat? This is likely culprit
batchIdx
),
sequenceType: SequenceType.Sequential,
Expand Down
27 changes: 3 additions & 24 deletions components/GovernancePower/GovernancePowerCard.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import { ChevronRightIcon } from '@heroicons/react/solid'
import {
determineVotingPowerType,
useGovernancePowerAsync,
} from '@hooks/queries/governancePower'
import { useGovernancePowerAsync } from '@hooks/queries/governancePower'
import { useRealmConfigQuery } from '@hooks/queries/realmConfig'
import useSelectedRealmPubkey from '@hooks/selectedRealm/useSelectedRealmPubkey'
import useQueryContext from '@hooks/useQueryContext'
import useWalletOnePointOh from '@hooks/useWalletOnePointOh'
import { GoverningTokenType } from '@solana/spl-governance'
import { useConnection } from '@solana/wallet-adapter-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useAsync } from 'react-async-hook'
import GovernancePowerForRole from './GovernancePowerForRole'
import { Deposit } from './Vanilla/Deposit'

const GovernancePowerTitle = () => {
const { symbol } = useRouter().query
Expand All @@ -36,7 +29,7 @@ const GovernancePowerTitle = () => {
</div>
)
}

/*
// TODO: refactor deposit components to their own generic DepositForRole component
const VanillaDeposit = ({ role }: { role: 'community' | 'council' }) => {
const { connection } = useConnection()
Expand All @@ -49,7 +42,7 @@ const VanillaDeposit = ({ role }: { role: 'community' | 'council' }) => {
}, [connection, realmPk, role])
return kind === 'vanilla' ? <Deposit role={role} /> : <></>
}
} */

const GovernancePowerCard = () => {
const connected = useWalletOnePointOh()?.connected ?? false
Expand All @@ -59,12 +52,6 @@ const GovernancePowerCard = () => {

const bothLoading = communityPower.loading && councilPower.loading

const bothZero =
communityPower.result !== undefined &&
councilPower.result !== undefined &&
communityPower.result.isZero() &&
councilPower.result.isZero()

const realmConfig = useRealmConfigQuery().data?.result

return (
Expand All @@ -79,14 +66,6 @@ const GovernancePowerCard = () => {
<div className="h-12 mb-4 rounded-lg animate-pulse bg-bkg-3" />
<div className="h-10 rounded-lg animate-pulse bg-bkg-3" />
</>
) : bothZero ? (
<div className={'text-xs text-white/50 mt-8'}>
You do not have any governance power in this dao
<div className="flex flex-col">
<VanillaDeposit role="community" />
<VanillaDeposit role="council" />
</div>
</div>
) : (
<div className="flex flex-col gap-2">
{realmConfig?.account.communityTokenConfig.tokenType ===
Expand Down
Loading

1 comment on commit c1ed6a6

@vercel
Copy link

@vercel vercel bot commented on c1ed6a6 Nov 9, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

governance-ui – ./

governance-ui-git-main-solana-labs.vercel.app
governance-ui-solana-labs.vercel.app
app.realms.today

Please sign in to comment.