mirror of https://github.com/certusone/oyster.git
Governance: Create mint instruction (#121)
* feat: setup CreateMintInstruction * fix: change instruction and enum layout to avoid braking changes
This commit is contained in:
parent
3db71073f7
commit
ac8d8e21c0
|
@ -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<PublicKey> => {
|
||||
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(
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -6,4 +6,5 @@ export enum GoverningTokenType {
|
|||
export enum GovernanceType {
|
||||
Account,
|
||||
Program,
|
||||
Mint,
|
||||
}
|
||||
|
|
|
@ -40,15 +40,21 @@ export const GovernanceError: Record<number, string> = [
|
|||
'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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
CancelProposalArgs,
|
||||
CastVoteArgs,
|
||||
CreateAccountGovernanceArgs,
|
||||
CreateMintGovernanceArgs,
|
||||
CreateProgramGovernanceArgs,
|
||||
CreateProposalArgs,
|
||||
CreateRealmArgs,
|
||||
|
@ -126,6 +127,17 @@ export const GOVERNANCE_SCHEMA = new Map<any, any>([
|
|||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
CreateMintGovernanceArgs,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['instruction', 'u8'],
|
||||
['config', GovernanceConfig],
|
||||
['transferMintAuthority', 'u8'],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
CreateProposalArgs,
|
||||
{
|
||||
|
|
|
@ -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 };
|
||||
};
|
|
@ -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 = () => {
|
|||
>
|
||||
<Col flex="auto" xxl={15} xs={24} className="proposals-container">
|
||||
<div className="proposals-header">
|
||||
{governedMint ? (
|
||||
{governance?.info.isMintGovernance() ? (
|
||||
<TokenIcon
|
||||
mintAddress={governance?.info.config.governedAccount}
|
||||
size={60}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { PlusCircleOutlined } from '@ant-design/icons';
|
||||
import { ExplorerLink, ParsedAccount, useMint, utils } from '@oyster/common';
|
||||
import { ExplorerLink, ParsedAccount, utils } from '@oyster/common';
|
||||
import { Token } from '@solana/spl-token';
|
||||
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||
import {
|
||||
|
@ -33,9 +33,8 @@ const InstructionInput = ({
|
|||
const [instruction, setInstruction] = useState('');
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// We don't support MintGovernance account yet, but we can check the governed account type here
|
||||
const mint = useMint(governance.info.config.governedAccount);
|
||||
const creatorsEnabled = mint || governance.info.isProgramGovernance();
|
||||
const creatorsEnabled =
|
||||
governance.info.isMintGovernance() || governance.info.isProgramGovernance();
|
||||
|
||||
const updateInstruction = (instruction: string) => {
|
||||
setInstruction(instruction);
|
||||
|
@ -86,7 +85,7 @@ const InstructionInput = ({
|
|||
governance={governance}
|
||||
></UpgradeProgramForm>
|
||||
)}
|
||||
{mint && (
|
||||
{governance.info.isMintGovernance() && (
|
||||
<MintToForm
|
||||
form={form}
|
||||
onCreateInstruction={onCreateInstruction}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ParsedAccount, TokenIcon, useMint } from '@oyster/common';
|
||||
import { ParsedAccount, TokenIcon } from '@oyster/common';
|
||||
import { Avatar, Badge } from 'antd';
|
||||
import React from 'react';
|
||||
import { Governance, ProposalState } from '../../models/accounts';
|
||||
|
@ -10,8 +10,6 @@ export function GovernanceBadge({
|
|||
}: {
|
||||
governance: ParsedAccount<Governance>;
|
||||
}) {
|
||||
// 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({
|
|||
}
|
||||
>
|
||||
<div style={{ width: 55, height: 45 }}>
|
||||
{governedMint ? (
|
||||
{governance.info.isMintGovernance() ? (
|
||||
<TokenIcon
|
||||
mintAddress={governance.info.config.governedAccount}
|
||||
size={40}
|
||||
|
|
|
@ -38,7 +38,7 @@ export function RegisterGovernance({
|
|||
maxVotingTime: number;
|
||||
yesVoteThresholdPercentage: number;
|
||||
governedAccountAddress: string;
|
||||
transferUpgradeAuthority: boolean;
|
||||
transferAuthority: boolean;
|
||||
}) => {
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<Form.Item label={LABELS.GOVERNANCE_OVER} name="governanceType">
|
||||
|
@ -88,22 +88,30 @@ export function RegisterGovernance({
|
|||
<Radio.Button value={GovernanceType.Program}>
|
||||
{LABELS.PROGRAM}
|
||||
</Radio.Button>
|
||||
<Radio.Button value={GovernanceType.Mint}>{LABELS.MINT}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<AccountFormItem
|
||||
name="governedAccountAddress"
|
||||
label={
|
||||
governanceType === GovernanceType.Account
|
||||
? LABELS.ACCOUNT_ADDRESS
|
||||
: LABELS.PROGRAM_ID_LABEL
|
||||
governanceType === GovernanceType.Program
|
||||
? LABELS.PROGRAM_ID_LABEL
|
||||
: governanceType === GovernanceType.Mint
|
||||
? LABELS.MINT_ADDRESS_LABEL
|
||||
: LABELS.ACCOUNT_ADDRESS
|
||||
}
|
||||
></AccountFormItem>
|
||||
|
||||
{governanceType === GovernanceType.Program && (
|
||||
{(governanceType === GovernanceType.Program ||
|
||||
governanceType === GovernanceType.Mint) && (
|
||||
<Form.Item
|
||||
name="transferUpgradeAuthority"
|
||||
label={LABELS.TRANSFER_UPGRADE_AUTHORITY}
|
||||
name="transferAuthority"
|
||||
label={
|
||||
governanceType === GovernanceType.Program
|
||||
? LABELS.TRANSFER_UPGRADE_AUTHORITY
|
||||
: LABELS.TRANSFER_MINT_AUTHORITY
|
||||
}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox></Checkbox>
|
||||
|
|
Loading…
Reference in New Issue