Skip to content

Commit

Permalink
Delegation rework pt 2 (#1860)
Browse files Browse the repository at this point in the history
  • Loading branch information
asktree authored Oct 18, 2023
1 parent 585366b commit 9ebeb51
Show file tree
Hide file tree
Showing 15 changed files with 415 additions and 199 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 @@ -274,7 +403,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
10 changes: 0 additions & 10 deletions components/GovernancePower/GovernancePowerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,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 @@ -58,10 +52,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>
) : (
<div className="flex flex-col gap-2">
{realmConfig?.account.communityTokenConfig.tokenType ===
Expand Down
20 changes: 3 additions & 17 deletions components/SelectPrimaryDelegators.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { DisplayAddress } from '@cardinal/namespaces-components'
import Select from '@components/inputs/Select'
import useWalletOnePointOh from '@hooks/useWalletOnePointOh'
import { useTokenOwnerRecordsDelegatedToUser } from '@hooks/queries/tokenOwnerRecord'
import { useSelectedDelegatorStore } from 'stores/useSelectedDelegatorStore'
import { PublicKey } from '@solana/web3.js'
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'
import { useRealmQuery } from '@hooks/queries/realm'
import { useMemo } from 'react'
import { ProgramAccount, TokenOwnerRecord } from '@solana/spl-governance'
import { capitalize } from '@utils/helpers'
import { abbreviateAddress } from '@utils/formatting'

const YOUR_WALLET_VALUE = 'Your wallet'

Expand Down Expand Up @@ -104,7 +103,6 @@ function PrimaryDelegatorSelect({
kind: 'community' | 'council'
tors: ProgramAccount<TokenOwnerRecord>[]
}) {
const connection = useLegacyConnectionContext()
return (
<div className="flex space-x-4 items-center mt-4">
<div className="bg-bkg-1 px-4 py-2 justify-between rounded-md w-full">
Expand All @@ -118,13 +116,7 @@ function PrimaryDelegatorSelect({
componentLabel={
selectedDelegator ? (
<div className="relative">
<DisplayAddress
connection={connection.current}
address={selectedDelegator}
height="12px"
width="100px"
dark={true}
/>
{abbreviateAddress(selectedDelegator)}
<div className="absolute bg-bkg-1 bottom-0 left-0 w-full h-full opacity-0 " />
</div>
) : (
Expand All @@ -141,13 +133,7 @@ function PrimaryDelegatorSelect({
value={delegatedTor.account.governingTokenOwner.toBase58()}
>
<div className="relative">
<DisplayAddress
connection={connection.current}
address={delegatedTor.account.governingTokenOwner}
height="12px"
width="100px"
dark={true}
/>
{abbreviateAddress(delegatedTor.account.governingTokenOwner)}
<div className="absolute bg-bkg-1 bottom-0 left-0 w-full h-full opacity-0 " />
</div>
</Select.Option>
Expand Down
Loading

1 comment on commit 9ebeb51

@vercel
Copy link

@vercel vercel bot commented on 9ebeb51 Oct 18, 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
app.realms.today
governance-ui-solana-labs.vercel.app

Please sign in to comment.