From 1f22ec75318edf3dd60cbc85ef98b2002372a56d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Brzezi=C5=84ski?= Date: Sat, 25 Feb 2023 00:47:20 +0100 Subject: [PATCH] set mint authority instruction (#1426) * set new authority * fix * fix --- components/instructions/programs/splToken.tsx | 69 ++++++- hooks/useGovernanceAssets.ts | 5 +- .../instructions/SetMintAuthroity.tsx | 172 ++++++++++++++++++ pages/dao/[symbol]/proposal/new.tsx | 2 + utils/uiTypes/proposalCreationTypes.ts | 1 + 5 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 pages/dao/[symbol]/proposal/components/instructions/SetMintAuthroity.tsx diff --git a/components/instructions/programs/splToken.tsx b/components/instructions/programs/splToken.tsx index b7465464b2..97e1215a6f 100644 --- a/components/instructions/programs/splToken.tsx +++ b/components/instructions/programs/splToken.tsx @@ -1,9 +1,14 @@ import { Connection, PublicKey } from '@solana/web3.js' -import { AccountMetaData } from '@solana/spl-governance' +import { AccountMetaData, SYSTEM_PROGRAM_ID } from '@solana/spl-governance' import { tryGetMint, tryGetTokenAccount } from '../../../utils/tokens' import BN from 'bn.js' import { getMintDecimalAmountFromNatural } from '@tools/sdk/units' import tokenPriceService from '@utils/services/tokenPrice' +import { TokenInstruction } from '@solendprotocol/solend-sdk/dist/instructions/instruction' +import { AuthorityType } from '@solana/spl-token' +import { struct, u8 } from 'buffer-layout' +import { publicKey } from '@coral-xyz/borsh' + export interface TokenMintMetadata { name: string } @@ -84,6 +89,68 @@ export const SPL_TOKEN_INSTRUCTIONS = { ) }, }, + 6: { + name: 'Token: Set Mint Authority', + accounts: [{ name: 'Mint', important: true }, { name: 'Mint Authority' }], + getDataUI: async ( + connection: Connection, + data: Uint8Array + //accounts: AccountMetaData[] + ) => { + interface SetAuthorityInstructionData { + instruction: TokenInstruction.SetAuthority + authorityType: AuthorityType + newAuthorityOption: 1 | 0 + newAuthority: PublicKey + } + const authorityTypes = [ + 'MintTokens', + 'FreezeAccount', + 'AccountOwner', + 'CloseAccount', + ] + const setAuthorityInstructionData = struct( + [ + u8('instruction'), + u8('authorityType'), + u8('newAuthorityOption'), + publicKey('newAuthority'), + ] + ) + let authorityParams: SetAuthorityInstructionData | null = null + try { + authorityParams = setAuthorityInstructionData.decode( + Buffer.from(data) + ) + } catch (e) { + console.log(e) + } + + return ( + <> + {authorityParams ? ( +
+
+ New authority:{' '} + {authorityParams.newAuthority.equals(SYSTEM_PROGRAM_ID) + ? 'None' + : authorityParams.newAuthority.toBase58()} +
+
+ New authority option: {authorityParams.newAuthorityOption} +
+
+ Authority type:{' '} + {authorityTypes[authorityParams.authorityType]} +
+
+ ) : ( +
{JSON.stringify(data)}
+ )} + + ) + }, + }, 7: { name: 'Token: MintTo', accounts: [ diff --git a/hooks/useGovernanceAssets.ts b/hooks/useGovernanceAssets.ts index e26efb4afb..f7e671586c 100644 --- a/hooks/useGovernanceAssets.ts +++ b/hooks/useGovernanceAssets.ts @@ -373,7 +373,10 @@ export default function useGovernanceAssets() { name: 'Withdraw validator stake', packageId: PackageEnum.Common, }, - + [Instructions.SetMintAuthority]: { + name: 'Set Mint Authority', + packageId: PackageEnum.Common, + }, /* ██████ ██ ██ █████ ██ ███████ ██ ███ ██ █████ ███ ██ ██████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ████ ██ ██ ██ diff --git a/pages/dao/[symbol]/proposal/components/instructions/SetMintAuthroity.tsx b/pages/dao/[symbol]/proposal/components/instructions/SetMintAuthroity.tsx new file mode 100644 index 0000000000..0469bb2da5 --- /dev/null +++ b/pages/dao/[symbol]/proposal/components/instructions/SetMintAuthroity.tsx @@ -0,0 +1,172 @@ +import React, { useContext, useEffect, useState } from 'react' +import useWalletStore from 'stores/useWalletStore' +import { UiInstruction } from 'utils/uiTypes/proposalCreationTypes' +import { NewProposalContext } from '../../new' +import { + Governance, + serializeInstructionToBase64, +} from '@solana/spl-governance' +import { ProgramAccount } from '@solana/spl-governance' +import useGovernanceAssets from 'hooks/useGovernanceAssets' +import GovernedAccountSelect from '../GovernedAccountSelect' +import { validateInstruction } from 'utils/instructionTools' +import { AccountType, AssetAccount } from '@utils/uiTypes/assets' +import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token' +import Switch from '@components/Switch' +import Input from '@components/inputs/Input' +import { validatePubkey } from '@utils/formValidation' +import * as yup from 'yup' +import { PublicKey } from '@solana/web3.js' + +type Form = { + governedAccount: AssetAccount | null + setAuthorityToNone: boolean + mintAuthority: string +} + +const SetMintAuthority = ({ + index, + governance, +}: { + index: number + governance: ProgramAccount | null +}) => { + const { assetAccounts } = useGovernanceAssets() + const mintGovernancesWithMintInfo = assetAccounts.filter( + (x) => x.type === AccountType.MINT + ) + const shouldBeGoverned = !!(index !== 0 && governance) + + const [form, setForm] = useState
({ + governedAccount: null, + setAuthorityToNone: false, + mintAuthority: '', + }) + const wallet = useWalletStore((s) => s.current) + const [formErrors, setFormErrors] = useState({}) + + const { handleSetInstructions } = useContext(NewProposalContext) + const handleSetForm = ({ propertyName, value }) => { + setFormErrors({}) + setForm({ ...form, [propertyName]: value }) + } + + async function getInstruction(): Promise { + const isValid = await validateInstruction({ schema, form, setFormErrors }) + let serializedInstruction = '' + if ( + isValid && + form.governedAccount?.governance.account && + wallet?.publicKey + ) { + const ix = Token.createSetAuthorityInstruction( + TOKEN_PROGRAM_ID, + form.governedAccount.extensions.mint!.publicKey!, + form.setAuthorityToNone ? null : new PublicKey(form.mintAuthority), + 'MintTokens', + form.governedAccount.extensions.mint!.account.mintAuthority!, + [] + ) + + serializedInstruction = serializeInstructionToBase64(ix) + } + const obj: UiInstruction = { + serializedInstruction: serializedInstruction, + isValid, + governance: form.governedAccount?.governance, + } + return obj + } + + useEffect(() => { + handleSetInstructions( + { governedAccount: form.governedAccount?.governance, getInstruction }, + index + ) + // eslint-disable-next-line react-hooks/exhaustive-deps -- TODO please fix, it can cause difficult bugs. You might wanna check out https://bobbyhadz.com/blog/react-hooks-exhaustive-deps for info. -@asktree + }, [form]) + + const schema = yup.object().shape({ + mintAuthority: yup + .string() + .test( + 'mintAuthorityTest', + 'Invalid pubkey', + async function (val: string) { + console.log(val) + if (val) { + try { + await validatePubkey(form.mintAuthority) + return true + } catch (e) { + return this.createError({ + message: `${e}`, + }) + } + } else if (!form.setAuthorityToNone) { + return this.createError({ + message: `Pubkey is required`, + }) + } + return true + } + ), + governedAccount: yup + .object() + .nullable() + .required('Program governed account is required'), + }) + + useEffect(() => { + handleSetForm({ + value: '', + propertyName: 'mintAuthority', + }) + }, [form.setAuthorityToNone]) + return ( + <> + { + handleSetForm({ value, propertyName: 'governedAccount' }) + }} + value={form.governedAccount} + error={formErrors['governedAccount']} + shouldBeGoverned={shouldBeGoverned} + governance={governance} + type="mint" + > +
+
Set mint authority to none
+
+ { + handleSetForm({ + value: checked, + propertyName: 'setAuthorityToNone', + }) + }} + /> +
+
+ {!form.setAuthorityToNone && ( + + handleSetForm({ + value: evt.target.value, + propertyName: 'mintAuthority', + }) + } + error={formErrors['mintAuthority']} + /> + )} + + ) +} + +export default SetMintAuthority diff --git a/pages/dao/[symbol]/proposal/new.tsx b/pages/dao/[symbol]/proposal/new.tsx index ac9869e315..6781fe8f42 100644 --- a/pages/dao/[symbol]/proposal/new.tsx +++ b/pages/dao/[symbol]/proposal/new.tsx @@ -126,6 +126,7 @@ import PsyFinanceClaimUnderlyingPostExpiration from './components/instructions/P import PsyFinanceExerciseOption from './components/instructions/PsyFinance/ExerciseOption' import RevokeGoverningTokens from './components/instructions/SplGov/RevokeGoverningTokens' import PreviousRouteBtn from '@components/PreviousRouteBtn' +import SetMintAuthority from './components/instructions/SetMintAuthroity' const TITLE_LENGTH_LIMIT = 130 @@ -533,6 +534,7 @@ const New = () => { [Instructions.AddServiceToDID]: AddServiceToDID, [Instructions.RemoveServiceFromDID]: RemoveServiceFromDID, [Instructions.RevokeGoverningTokens]: RevokeGoverningTokens, + [Instructions.SetMintAuthority]: SetMintAuthority, }), [governance?.pubkey.toBase58()] ) diff --git a/utils/uiTypes/proposalCreationTypes.ts b/utils/uiTypes/proposalCreationTypes.ts index c1f2246f72..4367916180 100644 --- a/utils/uiTypes/proposalCreationTypes.ts +++ b/utils/uiTypes/proposalCreationTypes.ts @@ -644,6 +644,7 @@ export enum Instructions { AddServiceToDID, RemoveServiceFromDID, RevokeGoverningTokens, + SetMintAuthority, } export type createParams = [