diff --git a/packages/governance/src/actions/registerGovernance.ts b/packages/governance/src/actions/registerGovernance.ts index 9af6d46..8c15b9e 100644 --- a/packages/governance/src/actions/registerGovernance.ts +++ b/packages/governance/src/actions/registerGovernance.ts @@ -5,6 +5,7 @@ import { GovernanceType } from '../models/enums'; import { GovernanceConfig } from '../models/accounts'; import { withCreateProgramGovernance } from '../models/withCreateProgramGovernance'; import { sendTransactionWithNotifications } from '../tools/transactions'; +import { withCreateMintGovernance } from '../models/withCreateMintGovernance'; export const registerGovernance = async ( connection: Connection, @@ -12,34 +13,55 @@ export const registerGovernance = async ( governanceType: GovernanceType, realm: PublicKey, config: GovernanceConfig, - transferUpgradeAuthority?: boolean, + transferAuthority?: boolean, ): Promise => { let instructions: TransactionInstruction[] = []; let governanceAddress; - if (governanceType === GovernanceType.Account) { - governanceAddress = ( - await withCreateAccountGovernance( - instructions, - realm, - config, - wallet.publicKey, - ) - ).governanceAddress; - } else if (governanceType === GovernanceType.Program) { - governanceAddress = ( - await withCreateProgramGovernance( - instructions, - realm, - config, - transferUpgradeAuthority!, - wallet.publicKey, - wallet.publicKey, - ) - ).governanceAddress; - } else { - throw new Error(`Governance type ${governanceType} is not supported yet.`); + switch (governanceType) { + case GovernanceType.Account: { + governanceAddress = ( + await withCreateAccountGovernance( + instructions, + realm, + config, + wallet.publicKey, + ) + ).governanceAddress; + break; + } + case GovernanceType.Program: { + governanceAddress = ( + await withCreateProgramGovernance( + instructions, + realm, + config, + transferAuthority!, + wallet.publicKey, + wallet.publicKey, + ) + ).governanceAddress; + break; + } + case GovernanceType.Mint: { + governanceAddress = ( + await withCreateMintGovernance( + instructions, + realm, + config, + transferAuthority!, + wallet.publicKey, + wallet.publicKey, + ) + ).governanceAddress; + break; + } + default: { + throw new Error( + `Governance type ${governanceType} is not supported yet.`, + ); + } } await sendTransactionWithNotifications( diff --git a/packages/governance/src/constants/labels.ts b/packages/governance/src/constants/labels.ts index bb90bce..82cefab 100644 --- a/packages/governance/src/constants/labels.ts +++ b/packages/governance/src/constants/labels.ts @@ -109,11 +109,13 @@ export const LABELS = { REGISTER_GOVERNANCE: 'Register Governance', GOVERNANCE_OVER: 'governance over', PROGRAM: 'Program', + MINT: 'Mint', REGISTER: 'Register', REGISTERING: 'Registering', PROGRAM_ID_LABEL: 'program id', + MINT_ADDRESS_LABEL: 'mint address', ACCOUNT_ADDRESS: 'account address', MIN_TOKENS_TO_CREATE_PROPOSAL: 'min tokens to create proposal', @@ -121,6 +123,7 @@ export const LABELS = { MAX_VOTING_TIME: 'max voting time (slots)', TRANSFER_UPGRADE_AUTHORITY: 'transfer upgrade authority', + TRANSFER_MINT_AUTHORITY: 'transfer mint authority', PROGRAM_ID: 'Program ID', INSTRUCTION: 'Instruction', diff --git a/packages/governance/src/models/accounts.ts b/packages/governance/src/models/accounts.ts index 34c5ce8..6e1149e 100644 --- a/packages/governance/src/models/accounts.ts +++ b/packages/governance/src/models/accounts.ts @@ -15,6 +15,7 @@ export enum GovernanceAccountType { SignatoryRecord = 6, VoteRecord = 7, ProposalInstruction = 8, + MintGovernance = 9, } export interface GovernanceAccount { @@ -48,6 +49,7 @@ export function getAccountTypes(accountClass: GovernanceAccountClass) { return [ GovernanceAccountType.AccountGovernance, GovernanceAccountType.ProgramGovernance, + GovernanceAccountType.MintGovernance, ]; default: throw Error(`${accountClass} account is not supported`); @@ -108,6 +110,10 @@ export class Governance { return this.accountType === GovernanceAccountType.ProgramGovernance; } + isMintGovernance() { + return this.accountType === GovernanceAccountType.MintGovernance; + } + constructor(args: { accountType: number; config: GovernanceConfig; diff --git a/packages/governance/src/models/enums.ts b/packages/governance/src/models/enums.ts index bd21d03..6d68bc0 100644 --- a/packages/governance/src/models/enums.ts +++ b/packages/governance/src/models/enums.ts @@ -6,4 +6,5 @@ export enum GoverningTokenType { export enum GovernanceType { Account, Program, + Mint, } diff --git a/packages/governance/src/models/errors.ts b/packages/governance/src/models/errors.ts index db02f38..52794a0 100644 --- a/packages/governance/src/models/errors.ts +++ b/packages/governance/src/models/errors.ts @@ -40,15 +40,21 @@ export const GovernanceError: Record = [ 'Proposal voting time expired', // ProposalVotingTimeExpired 'Invalid Signatory Mint', // InvalidSignatoryMint 'Invalid account owner', // InvalidAccountOwner + "Account doesn't exist", // AccountDoesNotExist 'Invalid Account type', // InvalidAccountType 'Proposal does not belong to the given Governance', // InvalidGovernanceForProposal 'Proposal does not belong to given Governing Mint', // InvalidGoverningMintForProposal + 'Current mint authority must sign transaction', // MintAuthorityMustSign + 'Invalid mint authority', // InvalidMintAuthority + 'Mint has no authority', // MintHasNoAuthority 'Invalid Token account owner', // SplTokenAccountWithInvalidOwner 'Invalid Mint account owner', // SplTokenMintWithInvalidOwner 'Token Account is not initialized', // SplTokenAccountNotInitialized + "Token Account doesn't exist", // SplTokenAccountDoesNotExist 'Token account data is invalid', // SplTokenInvalidTokenAccountData 'Token mint account data is invalid', // SplTokenInvalidMintAccountData 'Token Mint account is not initialized', // SplTokenMintNotInitialized + "Token Mint account doesn't exist", // SplTokenMintDoesNotExist 'Invalid ProgramData account address', // InvalidProgramDataAccountAddress 'Invalid ProgramData account Data', // InvalidProgramDataAccountData "Provided upgrade authority doesn't match current program upgrade authority", // InvalidUpgradeAuthority diff --git a/packages/governance/src/models/instructions.ts b/packages/governance/src/models/instructions.ts index 0303307..df57738 100644 --- a/packages/governance/src/models/instructions.ts +++ b/packages/governance/src/models/instructions.ts @@ -9,6 +9,7 @@ export enum GovernanceInstruction { SetGovernanceDelegate = 3, // -- CreateAccountGovernance = 4, CreateProgramGovernance = 5, + CreateProposal = 6, AddSignatory = 7, RemoveSignatory = 8, @@ -21,6 +22,8 @@ export enum GovernanceInstruction { FinalizeVote = 14, RelinquishVote = 15, ExecuteInstruction = 16, + + CreateMintGovernance = 17, } export class CreateRealmArgs { @@ -67,6 +70,21 @@ export class CreateProgramGovernanceArgs { } } +export class CreateMintGovernanceArgs { + instruction: GovernanceInstruction = + GovernanceInstruction.CreateMintGovernance; + config: GovernanceConfig; + transferMintAuthority: boolean; + + constructor(args: { + config: GovernanceConfig; + transferMintAuthority: boolean; + }) { + this.config = args.config; + this.transferMintAuthority = !!args.transferMintAuthority; + } +} + export class CreateProposalArgs { instruction: GovernanceInstruction = GovernanceInstruction.CreateProposal; name: string; diff --git a/packages/governance/src/models/serialisation.ts b/packages/governance/src/models/serialisation.ts index 5795071..6ab1611 100644 --- a/packages/governance/src/models/serialisation.ts +++ b/packages/governance/src/models/serialisation.ts @@ -11,6 +11,7 @@ import { CancelProposalArgs, CastVoteArgs, CreateAccountGovernanceArgs, + CreateMintGovernanceArgs, CreateProgramGovernanceArgs, CreateProposalArgs, CreateRealmArgs, @@ -126,6 +127,17 @@ export const GOVERNANCE_SCHEMA = new Map([ ], }, ], + [ + CreateMintGovernanceArgs, + { + kind: 'struct', + fields: [ + ['instruction', 'u8'], + ['config', GovernanceConfig], + ['transferMintAuthority', 'u8'], + ], + }, + ], [ CreateProposalArgs, { diff --git a/packages/governance/src/models/withCreateMintGovernance.ts b/packages/governance/src/models/withCreateMintGovernance.ts new file mode 100644 index 0000000..13b59ff --- /dev/null +++ b/packages/governance/src/models/withCreateMintGovernance.ts @@ -0,0 +1,89 @@ +import { utils } from '@oyster/common'; +import { + PublicKey, + SYSVAR_RENT_PUBKEY, + TransactionInstruction, +} from '@solana/web3.js'; +import { GOVERNANCE_SCHEMA } from './serialisation'; +import { serialize } from 'borsh'; +import { GovernanceConfig } from './accounts'; +import { CreateMintGovernanceArgs } from './instructions'; + +export const withCreateMintGovernance = async ( + instructions: TransactionInstruction[], + realm: PublicKey, + config: GovernanceConfig, + transferMintAuthority: boolean, + mintAuthority: PublicKey, + payer: PublicKey, +): Promise<{ governanceAddress: PublicKey }> => { + const PROGRAM_IDS = utils.programIds(); + + const args = new CreateMintGovernanceArgs({ + config, + transferMintAuthority, + }); + const data = Buffer.from(serialize(GOVERNANCE_SCHEMA, args)); + + const [mintGovernanceAddress] = await PublicKey.findProgramAddress( + [ + Buffer.from('mint-governance'), + realm.toBuffer(), + config.governedAccount.toBuffer(), + ], + PROGRAM_IDS.governance.programId, + ); + + const keys = [ + { + pubkey: realm, + isWritable: false, + isSigner: false, + }, + { + pubkey: mintGovernanceAddress, + isWritable: true, + isSigner: false, + }, + { + pubkey: config.governedAccount, + isWritable: true, + isSigner: false, + }, + { + pubkey: mintAuthority, + isWritable: false, + isSigner: true, + }, + { + pubkey: payer, + isWritable: false, + isSigner: true, + }, + { + pubkey: PROGRAM_IDS.token, + isWritable: false, + isSigner: false, + }, + { + pubkey: PROGRAM_IDS.system, + isWritable: false, + isSigner: false, + }, + { + pubkey: SYSVAR_RENT_PUBKEY, + isWritable: false, + isSigner: false, + }, + ]; + + instructions.push( + new TransactionInstruction({ + keys, + programId: PROGRAM_IDS.governance.programId, + data, + }), + ); + + return { governanceAddress: mintGovernanceAddress }; +}; diff --git a/packages/governance/src/views/governance/GovernanceView.tsx b/packages/governance/src/views/governance/GovernanceView.tsx index c044c1a..12444e5 100644 --- a/packages/governance/src/views/governance/GovernanceView.tsx +++ b/packages/governance/src/views/governance/GovernanceView.tsx @@ -6,12 +6,7 @@ import { useGovernance, useProposalsByGovernance } from '../../hooks/apiHooks'; import './style.less'; // Don't remove this line, it will break dark mode if you do due to weird transpiling conditions import { StateBadge } from '../proposal/components/StateBadge'; import { useHistory } from 'react-router-dom'; -import { - ExplorerLink, - TokenIcon, - useConnectionConfig, - useMint, -} from '@oyster/common'; +import { ExplorerLink, TokenIcon, useConnectionConfig } from '@oyster/common'; import { AddNewProposal } from './NewProposal'; import { useKeyParam } from '../../hooks/useKeyParam'; import { Proposal, ProposalState } from '../../models/accounts'; @@ -39,8 +34,6 @@ export const GovernanceView = () => { const mint = communityTokenMint?.toBase58() || ''; - // We don't support MintGovernance account yet, but we can check the governed account type here - const governedMint = useMint(governance?.info.config.governedAccount); const color = governance?.info.isProgramGovernance() ? 'green' : 'gray'; const proposalItems = useMemo(() => { @@ -78,7 +71,7 @@ export const GovernanceView = () => { >
- {governedMint ? ( + {governance?.info.isMintGovernance() ? ( { setInstruction(instruction); @@ -86,7 +85,7 @@ const InstructionInput = ({ governance={governance} > )} - {mint && ( + {governance.info.isMintGovernance() && ( ; }) { - // We don't support MintGovernance account yet, but we can check the governed account type here - const governedMint = useMint(governance.info.config.governedAccount); const proposals = useProposalsByGovernance(governance?.pubkey); const color = governance.info.isProgramGovernance() ? 'green' : 'gray'; @@ -22,7 +20,7 @@ export function GovernanceBadge({ } >
- {governedMint ? ( + {governance.info.isMintGovernance() ? ( { const config = new GovernanceConfig({ realm: realmKey, @@ -54,7 +54,7 @@ export function RegisterGovernance({ values.governanceType, realmKey, config, - values.transferUpgradeAuthority, + values.transferAuthority, ); }; @@ -77,7 +77,7 @@ export function RegisterGovernance({ onComplete={onComplete} initialValues={{ governanceType: GovernanceType.Account, - transferUpgradeAuthority: true, + transferAuthority: true, }} > @@ -88,22 +88,30 @@ export function RegisterGovernance({ {LABELS.PROGRAM} + {LABELS.MINT} - {governanceType === GovernanceType.Program && ( + {(governanceType === GovernanceType.Program || + governanceType === GovernanceType.Mint) && (