Skip to content

Commit

Permalink
[pyth] add mesh instructions (solana-labs#2164)
Browse files Browse the repository at this point in the history
  • Loading branch information
guibescos authored Mar 1, 2024
1 parent 8f8427e commit 06ff1ea
Show file tree
Hide file tree
Showing 10 changed files with 513 additions and 2 deletions.
27 changes: 26 additions & 1 deletion hooks/useGovernanceAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ export default function useGovernanceAssets() {
name: 'Solend',
image: '/img/solend.png',
},
[PackageEnum.Squads]: {
name: 'Squads',
image: '/img/squads.png',
},
[PackageEnum.Switchboard]: {
name: 'Switchboard',
image: '/img/switchboard.png',
Expand Down Expand Up @@ -748,7 +752,28 @@ export default function useGovernanceAssets() {
name: 'Withdraw Funds',
packageId: PackageEnum.Solend,
},

/*
███████ ██████ ██ ██ █████ ██████ ███████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██
███████ ██ ██ ██ ██ ███████ ██ ██ ███████
██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ██
███████ ██████ ██████ ██ ██ ██████ ███████
*/
[Instructions.SquadsMeshAddMember]: {
name: 'Mesh Add Member',
packageId: PackageEnum.Squads,
isVisible: true,
},
[Instructions.SquadsMeshChangeThresholdMember]: {
name: 'Mesh Change Threshold',
packageId: PackageEnum.Squads,
isVisible: true,
},
[Instructions.SquadsMeshRemoveMember]: {
name: 'Mesh Remove Member',
packageId: PackageEnum.Squads,
isVisible: true,
},
/*
███████ ██ ██ ██ ████████ ██████ ██ ██ ██████ ██████ █████ ██████ ██████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"@solana/web3.js": "1.78.2",
"@solendprotocol/solend-sdk": "0.5.5",
"@sqds/iframe-adapter": "1.0.16",
"@sqds/mesh": "1.0.6",
"@switchboard-xyz/sbv2-lite": "0.2.4",
"@switchboard-xyz/solana.js": "3.1.5",
"@tailwindcss/forms": "0.5.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { useContext, useEffect, useState } from 'react'
import * as yup from 'yup'
import { isFormValid, validatePubkey } from '@utils/formValidation'
import { UiInstruction } from '@utils/uiTypes/proposalCreationTypes'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import { Governance } from '@solana/spl-governance'
import { ProgramAccount } from '@solana/spl-governance'
import { serializeInstructionToBase64 } from '@solana/spl-governance'
import { MESH_PROGRAM_ID, MeshEditMemberForm } from './common'
import useWalletOnePointOh from '@hooks/useWalletOnePointOh'
import InstructionForm, { InstructionInput } from '../FormCreator'
import { InstructionInputType } from '../inputInstructionType'
import { NewProposalContext } from '../../../new'
import Squads from '@sqds/mesh'
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'
import { PublicKey } from '@solana/web3.js'
import { Wallet } from '@coral-xyz/anchor'

const MeshAddMember = ({
index,
governance,
}: {
index: number
governance: ProgramAccount<Governance> | null
}) => {
const wallet = useWalletOnePointOh()
const connection = useLegacyConnectionContext()
const { assetAccounts } = useGovernanceAssets()
const shouldBeGoverned = !!(index !== 0 && governance)
const [form, setForm] = useState<MeshEditMemberForm>({
governedAccount: null,
vault: '',
member: '',
})
const [formErrors, setFormErrors] = useState({})
const { handleSetInstructions } = useContext(NewProposalContext)

const validateInstruction = async (): Promise<boolean> => {
const { isValid, validationErrors } = await isFormValid(schema, form)
setFormErrors(validationErrors)
return isValid
}
async function getInstruction(): Promise<UiInstruction> {
const isValid = await validateInstruction()

if (
isValid &&
form.governedAccount?.governance?.account &&
wallet?.publicKey
) {
const squads = new Squads({
connection: connection.current,
wallet: {} as Wallet,
multisigProgramId: MESH_PROGRAM_ID,
})
const instruction = await squads.buildAddMember(
new PublicKey(form.vault),
form.governedAccount.governance.pubkey,
new PublicKey(form.member)
)
return {
serializedInstruction: serializeInstructionToBase64(instruction),
isValid,
governance: form.governedAccount?.governance,
chunkBy: 1,
}
} else {
return {
serializedInstruction: '',
isValid,
governance: form.governedAccount?.governance,
chunkBy: 1,
}
}
}

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({
governedAccount: yup
.object()
.nullable()
.required('Program governed account is required'),
vault: yup
.string()
.required('Vault is required')
.test('is-vault-valid', 'Invalid Vault Account', function (val: string) {
return val ? validatePubkey(val) : true
}),
member: yup
.string()
.required('Member is required')
.test(
'is-member-valid',
'Invalid Member Account',
function (val: string) {
return val ? validatePubkey(val) : true
}
),
})
const inputs: InstructionInput[] = [
{
label: 'Governance',
initialValue: form.governedAccount,
name: 'governedAccount',
type: InstructionInputType.GOVERNED_ACCOUNT,
shouldBeGoverned: shouldBeGoverned as any,
governance: governance,
options: assetAccounts,
},
{
label: 'Vault',
initialValue: form.vault,
type: InstructionInputType.INPUT,
inputType: 'text',
name: 'vault',
},
{
label: 'Member',
initialValue: form.member,
type: InstructionInputType.INPUT,
inputType: 'text',
name: 'member',
},
]

return (
<>
{form && (
<InstructionForm
outerForm={form}
setForm={setForm}
inputs={inputs}
setFormErrors={setFormErrors}
formErrors={formErrors}
></InstructionForm>
)}
</>
)
}

export default MeshAddMember
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { useContext, useEffect, useState } from 'react'
import * as yup from 'yup'
import { isFormValid, validatePubkey } from '@utils/formValidation'
import { UiInstruction } from '@utils/uiTypes/proposalCreationTypes'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import { Governance } from '@solana/spl-governance'
import { ProgramAccount } from '@solana/spl-governance'
import { serializeInstructionToBase64 } from '@solana/spl-governance'
import { MESH_PROGRAM_ID } from './common'
import useWalletOnePointOh from '@hooks/useWalletOnePointOh'
import InstructionForm, { InstructionInput } from '../FormCreator'
import { InstructionInputType } from '../inputInstructionType'
import { NewProposalContext } from '../../../new'
import Squads from '@sqds/mesh'
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'
import { PublicKey } from '@solana/web3.js'
import { Wallet } from '@coral-xyz/anchor'
import { AssetAccount } from '@utils/uiTypes/assets'

export interface MeshChangeThresholdMemberForm {
governedAccount: AssetAccount | null
vault: string
newThreshold: number
}

const MeshChangeThresholdMember = ({
index,
governance,
}: {
index: number
governance: ProgramAccount<Governance> | null
}) => {
const wallet = useWalletOnePointOh()
const connection = useLegacyConnectionContext()
const { assetAccounts } = useGovernanceAssets()
const shouldBeGoverned = !!(index !== 0 && governance)
const [form, setForm] = useState<MeshChangeThresholdMemberForm>({
governedAccount: null,
vault: '',
newThreshold: 0,
})
const [formErrors, setFormErrors] = useState({})
const { handleSetInstructions } = useContext(NewProposalContext)

const validateInstruction = async (): Promise<boolean> => {
const { isValid, validationErrors } = await isFormValid(schema, form)
setFormErrors(validationErrors)
return isValid
}
async function getInstruction(): Promise<UiInstruction> {
const isValid = await validateInstruction()

if (
isValid &&
form.governedAccount?.governance?.account &&
wallet?.publicKey
) {
const squads = new Squads({
connection: connection.current,
wallet: {} as Wallet,
multisigProgramId: MESH_PROGRAM_ID,
})
const instruction = await squads.buildChangeThresholdMember(
new PublicKey(form.vault),
form.governedAccount.governance.pubkey,
form.newThreshold
)
return {
serializedInstruction: serializeInstructionToBase64(instruction),
isValid,
governance: form.governedAccount?.governance,
chunkBy: 1,
}
} else {
return {
serializedInstruction: '',
isValid,
governance: form.governedAccount?.governance,
chunkBy: 1,
}
}
}

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({
governedAccount: yup
.object()
.nullable()
.required('Program governed account is required'),
vault: yup
.string()
.required('Vault is required')
.test('is-vault-valid', 'Invalid Vault Account', function (val: string) {
return val ? validatePubkey(val) : true
}),
newThreshold: yup
.number()
.required('New threshold is required')
.test(
'is-threshold-valid',
"New threshold can't be 0",
function (val: number) {
return val > 0
}
),
})
const inputs: InstructionInput[] = [
{
label: 'Governance',
initialValue: form.governedAccount,
name: 'governedAccount',
type: InstructionInputType.GOVERNED_ACCOUNT,
shouldBeGoverned: shouldBeGoverned as any,
governance: governance,
options: assetAccounts,
},
{
label: 'Vault',
initialValue: form.vault,
type: InstructionInputType.INPUT,
inputType: 'text',
name: 'vault',
},
{
label: 'New threshold',
initialValue: form.newThreshold,
type: InstructionInputType.INPUT,
inputType: 'number',
name: 'newThreshold',
},
]

return (
<>
{form && (
<InstructionForm
outerForm={form}
setForm={setForm}
inputs={inputs}
setFormErrors={setFormErrors}
formErrors={formErrors}
></InstructionForm>
)}
</>
)
}

export default MeshChangeThresholdMember
Loading

0 comments on commit 06ff1ea

Please sign in to comment.