diff --git a/frontend/pages/create_election_governance.tsx b/frontend/pages/create_election_governance.tsx new file mode 100644 index 00000000..067f50c2 --- /dev/null +++ b/frontend/pages/create_election_governance.tsx @@ -0,0 +1,50 @@ +import type { NextPage } from 'next' +import Layout from '../components/Layout' +import SEO from '../components/SEO' +import { StakeAccount } from '@pythnetwork/staking' +import { useEffect, useState } from 'react' +import toast from 'react-hot-toast' +import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter' +import { useStakeConnection } from 'hooks/useStakeConnection' +import { useStakeAccounts } from 'hooks/useStakeAccounts' + +const CreateElectionGovernance: NextPage = () => { + const { data: stakeConnection } = useStakeConnection() + const { data: stakeAccounts } = useStakeAccounts() + const [selectedStakeAccount, setSelectStakeAccount] = useState() + + useEffect(() => { + if (stakeAccounts && stakeAccounts.length > 0) + setSelectStakeAccount(stakeAccounts[0]) + }, [stakeAccounts]) + + const createElectionGovernance = async () => { + if (stakeConnection && selectedStakeAccount) + try { + await stakeConnection.createElectionGovernance(selectedStakeAccount) + toast.success('Successfully created election governance') + } catch (err) { + toast.error(capitalizeFirstLetter(err.message)) + } + } + + return ( + + + {stakeConnection ? ( +

+ +

+ ) : ( +

Please connect wallet

+ )} +
+ ) +} + +export default CreateElectionGovernance diff --git a/staking/app/StakeConnection.ts b/staking/app/StakeConnection.ts index 6ee7b170..149d7e8d 100644 --- a/staking/app/StakeConnection.ts +++ b/staking/app/StakeConnection.ts @@ -34,11 +34,19 @@ import { batchInstructions } from "./transaction"; import { PythBalance } from "./pythBalance"; import { getTokenOwnerRecordAddress, + GovernanceConfig, + PROGRAM_VERSION, PROGRAM_VERSION_V2, + VoteThreshold, + VoteThresholdType, + VoteTipping, + withCreateGovernance, withCreateTokenOwnerRecord, } from "@solana/spl-governance"; import { + EPOCH_DURATION, GOVERNANCE_ADDRESS, + REALM_ID, STAKING_ADDRESS, WALLET_TESTER_ADDRESS, } from "./constants"; @@ -1068,6 +1076,64 @@ export class StakeConnection { timeOfFirstStake, }; } + + // This is a helper to create the election governance from the UI. + // The address is hardcoded so it can only be run once. + public async createElectionGovernance(stakeAccount: StakeAccount) { + const governanceConfig = new GovernanceConfig({ + communityVoteThreshold: new VoteThreshold({ + type: VoteThresholdType.YesVotePercentage, + value: 1, // 1%, irrelevant since the proposals won't be executed + }), + minCommunityTokensToCreateProposal: new BN( + wasm.Constants.MAX_VOTER_WEIGHT().toString() + ).div(new BN(20000)), // 0.5 basis points of the staked supply + minInstructionHoldUpTime: 0, // irrelevant since the proposals won't be executed + baseVotingTime: EPOCH_DURATION, // Is equal to 1 Pyth epoch + communityVoteTipping: VoteTipping.Disabled, // Let it run for the full duration + minCouncilTokensToCreateProposal: new BN(1), // Not used since we don't have a council + + // V3 + councilVoteThreshold: new VoteThreshold({ + type: VoteThresholdType.Disabled, + }), + councilVetoVoteThreshold: new VoteThreshold({ + type: VoteThresholdType.Disabled, + }), + communityVetoVoteThreshold: new VoteThreshold({ + type: VoteThresholdType.Disabled, + }), + councilVoteTipping: VoteTipping.Disabled, // Not used since we don't have a council + votingCoolOffTime: 0, + depositExemptProposalCount: 100, + }); + + const tx = new Transaction(); + + const { voterWeightAccount, maxVoterWeightRecord } = + await this.withUpdateVoterWeight(tx.instructions, stakeAccount, { + createGovernance: {}, + }); + await withCreateGovernance( + tx.instructions, + GOVERNANCE_ADDRESS(), + PROGRAM_VERSION, + REALM_ID, + new PublicKey("6oXTdojyfDS8m5VtTaYB9xRCxpKGSvKJFndLUPV3V3wT"), // this seed is the authority of the pythian multisig + governanceConfig, + await getTokenOwnerRecordAddress( + GOVERNANCE_ADDRESS(), + REALM_ID, + this.config.pythTokenMint, + this.userPublicKey() + ), + this.userPublicKey(), + this.userPublicKey(), + voterWeightAccount + ); + + await this.provider.sendAndConfirm(tx); + } } export interface BalanceSummary {