security modal actions (#409)
This commit is contained in:
parent
cafd08189f
commit
478ac38c9e
|
@ -0,0 +1,228 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import { ModalProps } from '../../types/modal'
|
||||
import Modal from '../shared/Modal'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { Bank, Group } from '@blockworks-foundation/mango-v4'
|
||||
import { AccountMeta, PublicKey, Transaction } from '@solana/web3.js'
|
||||
import {
|
||||
MANGO_DAO_SECURITY_WALLET_GOVERNANCE,
|
||||
MANGO_GOVERNANCE_PROGRAM,
|
||||
MANGO_SECURITY_COUNCIL_MINT,
|
||||
MANGO_SECURITY_COUNCIL_WALLET,
|
||||
MANGO_SECURITY_REALM_PK,
|
||||
} from 'utils/governance/constants'
|
||||
import { createProposal } from 'utils/governance/instructions/createProposal'
|
||||
import { notify } from 'utils/notifications'
|
||||
import Button from '@components/shared/Button'
|
||||
import { getAllProposals } from '@solana/spl-governance'
|
||||
import useSecurityCouncilDao from 'hooks/useSecurityCouncilDao'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import Select from '@components/forms/Select'
|
||||
import { abbreviateAddress } from 'utils/formatting'
|
||||
|
||||
const SecurityCouncilModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
bank,
|
||||
group,
|
||||
}: ModalProps & {
|
||||
bank: Bank
|
||||
group: Group
|
||||
}) => {
|
||||
const client = mangoStore((s) => s.client)
|
||||
//do not deconstruct wallet is used for anchor to sign
|
||||
const wallet = useWallet()
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
const fee = mangoStore((s) => s.priorityFee)
|
||||
const { queries, currentDelegate, setDelegate, currentVoter } =
|
||||
useSecurityCouncilDao()
|
||||
const [proposing, setProposing] = useState(false)
|
||||
|
||||
//0 no borrows
|
||||
//1 no borrows no deposits
|
||||
const proposeReduceOnly = useCallback(
|
||||
async (bank: Bank, mode: number) => {
|
||||
if (!currentVoter) {
|
||||
notify({
|
||||
type: 'error',
|
||||
description: 'No vote record found',
|
||||
title: 'No vote record found',
|
||||
})
|
||||
}
|
||||
const mintInfo = group!.mintInfosMapByTokenIndex.get(bank.tokenIndex)!
|
||||
setProposing(true)
|
||||
const proposalTx = []
|
||||
|
||||
const walletSigner = wallet as never
|
||||
const ix = await client!.program.methods
|
||||
.tokenEdit(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
mode,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
.accounts({
|
||||
group: group!.publicKey,
|
||||
oracle: bank.oracle,
|
||||
admin: MANGO_SECURITY_COUNCIL_WALLET,
|
||||
mintInfo: mintInfo.publicKey,
|
||||
fallbackOracle: bank.fallbackOracle,
|
||||
})
|
||||
.remainingAccounts([
|
||||
{
|
||||
pubkey: bank.publicKey,
|
||||
isWritable: true,
|
||||
isSigner: false,
|
||||
} as AccountMeta,
|
||||
])
|
||||
.instruction()
|
||||
|
||||
proposalTx.push(ix)
|
||||
|
||||
try {
|
||||
setProposing(true)
|
||||
const simTransaction = new Transaction({ feePayer: wallet.publicKey })
|
||||
simTransaction.add(...proposalTx)
|
||||
const simulation = await connection.simulateTransaction(simTransaction)
|
||||
if (!simulation.value.err) {
|
||||
const proposals = await getAllProposals(
|
||||
connection,
|
||||
MANGO_GOVERNANCE_PROGRAM,
|
||||
MANGO_SECURITY_REALM_PK,
|
||||
)
|
||||
const index = proposals ? Object.values(proposals).length : 0
|
||||
const proposalAddress = await createProposal(
|
||||
connection,
|
||||
client,
|
||||
walletSigner,
|
||||
MANGO_DAO_SECURITY_WALLET_GOVERNANCE,
|
||||
currentVoter!,
|
||||
`Edit ${bank.name} token`,
|
||||
'',
|
||||
index,
|
||||
proposalTx,
|
||||
null,
|
||||
fee,
|
||||
MANGO_SECURITY_COUNCIL_MINT,
|
||||
MANGO_SECURITY_REALM_PK,
|
||||
)
|
||||
window.open(
|
||||
`https://dao.mango.markets/dao/AQbsV8b3Yv3UHUmd62hw9vFmNTHgBTdiLfaWzwmVfXB2/proposal/${proposalAddress.toBase58()}`,
|
||||
'_blank',
|
||||
)
|
||||
} else {
|
||||
throw simulation.value.logs
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
notify({
|
||||
title: 'Error during proposal creation',
|
||||
description: `${e}`,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
setProposing(false)
|
||||
},
|
||||
[client, connection, currentVoter, fee, group, wallet],
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
panelClassNames="!max-w-[300px] min-h-[300px]"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
<h3 className="mb-6">Token: {bank.name}</h3>
|
||||
{queries.data?.delegatedAccounts.length ? (
|
||||
<div className="flex items-start">
|
||||
<p className="mr-2">delegate</p>
|
||||
<Select
|
||||
value={
|
||||
currentDelegate
|
||||
? abbreviateAddress(
|
||||
new PublicKey(
|
||||
queries.data.delegatedAccounts.find(
|
||||
(x) => x.pubkey.toBase58() === currentDelegate,
|
||||
)!.account.governingTokenOwner!,
|
||||
),
|
||||
)
|
||||
: 'none'
|
||||
}
|
||||
onChange={(selected) => {
|
||||
setDelegate(selected)
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<Select.Option value={''}>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
none
|
||||
</div>
|
||||
</Select.Option>
|
||||
{queries.data.delegatedAccounts.map((x) => (
|
||||
<Select.Option
|
||||
key={x.pubkey.toBase58()}
|
||||
value={x.pubkey.toBase58()}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
{abbreviateAddress(x.account.governingTokenOwner)}
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mt-6 space-y-3">
|
||||
<Button onClick={() => proposeReduceOnly(bank, 1)} disabled={proposing}>
|
||||
{proposing ? (
|
||||
<Loading className="w-5"></Loading>
|
||||
) : (
|
||||
'No borrows no deposits'
|
||||
)}
|
||||
</Button>
|
||||
<Button onClick={() => proposeReduceOnly(bank, 2)} disabled={proposing}>
|
||||
{proposing ? <Loading className="w-5"></Loading> : 'No borrows'}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default SecurityCouncilModal
|
|
@ -0,0 +1,98 @@
|
|||
import { PublicKey } from '@metaplex-foundation/js'
|
||||
import {
|
||||
ProgramAccount,
|
||||
TokenOwnerRecord,
|
||||
getGovernanceAccounts,
|
||||
getTokenOwnerRecord,
|
||||
getTokenOwnerRecordAddress,
|
||||
pubkeyFilter,
|
||||
} from '@solana/spl-governance'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { Connection } from '@solana/web3.js'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
MANGO_GOVERNANCE_PROGRAM,
|
||||
MANGO_SECURITY_COUNCIL_MINT,
|
||||
MANGO_SECURITY_REALM_PK,
|
||||
} from 'utils/governance/constants'
|
||||
|
||||
export default function useSecurityCouncilDao() {
|
||||
const wallet = useWallet()
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
const [currentDelegate, setDelegate] = useState<string>('')
|
||||
const [currentVoter, setVoter] =
|
||||
useState<ProgramAccount<TokenOwnerRecord> | null>(null)
|
||||
|
||||
const query = useQuery(
|
||||
['security-council', wallet.publicKey, connection.rpcEndpoint],
|
||||
async () => {
|
||||
const walletPk = wallet.publicKey!
|
||||
const delegatedAccounts = await fetchDelegatedAccounts(
|
||||
walletPk,
|
||||
connection,
|
||||
MANGO_GOVERNANCE_PROGRAM,
|
||||
)
|
||||
const tokenOwnerRecordPk = await getTokenOwnerRecordAddress(
|
||||
MANGO_GOVERNANCE_PROGRAM,
|
||||
MANGO_SECURITY_REALM_PK,
|
||||
MANGO_SECURITY_COUNCIL_MINT,
|
||||
walletPk,
|
||||
)
|
||||
return {
|
||||
delegatedAccounts: delegatedAccounts.filter((x) =>
|
||||
x.account.realm.equals(MANGO_SECURITY_REALM_PK),
|
||||
),
|
||||
tokenOwnerRecordPk,
|
||||
}
|
||||
},
|
||||
{
|
||||
cacheTime: 1000 * 60 * 10,
|
||||
staleTime: 1000 * 60,
|
||||
retry: 2,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled: !!wallet.publicKey,
|
||||
},
|
||||
)
|
||||
useEffect(() => {
|
||||
const updateVoter = async () => {
|
||||
setDelegate(currentDelegate)
|
||||
try {
|
||||
const tokenOwnerRecord = await getTokenOwnerRecord(
|
||||
connection,
|
||||
currentDelegate
|
||||
? new PublicKey(currentDelegate)
|
||||
: query.data!.tokenOwnerRecordPk,
|
||||
)
|
||||
setVoter(tokenOwnerRecord)
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (e) {}
|
||||
}
|
||||
if (query.data?.tokenOwnerRecordPk || currentDelegate) {
|
||||
updateVoter()
|
||||
}
|
||||
}, [query.data?.tokenOwnerRecordPk, currentDelegate])
|
||||
return {
|
||||
currentDelegate,
|
||||
currentVoter,
|
||||
setDelegate,
|
||||
queries: query,
|
||||
}
|
||||
}
|
||||
|
||||
const fetchDelegatedAccounts = async (
|
||||
wallet: PublicKey,
|
||||
connection: Connection,
|
||||
programId: PublicKey,
|
||||
) => {
|
||||
const governanceDelegateOffset = 122
|
||||
|
||||
const accounts = await getGovernanceAccounts(
|
||||
connection,
|
||||
programId,
|
||||
TokenOwnerRecord,
|
||||
[pubkeyFilter(governanceDelegateOffset, wallet)!],
|
||||
)
|
||||
return accounts
|
||||
}
|
|
@ -47,6 +47,7 @@ import {
|
|||
getProposedKey,
|
||||
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import SecurityCouncilModal from '@components/modals/SecurityCouncilModal'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
|
@ -703,6 +704,9 @@ const BankDisclosure = ({
|
|||
const [openedSuggestedModal, setOpenedSuggestedModal] = useState<
|
||||
string | null
|
||||
>(null)
|
||||
const [securityModalOpen, setSecurityModalOpen] = useState<string | null>(
|
||||
null,
|
||||
)
|
||||
|
||||
const { currentTier, suggestedTierKey } = getSuggestedAndCurrentTier(
|
||||
bank,
|
||||
|
@ -956,6 +960,25 @@ const BankDisclosure = ({
|
|||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="my-3 flex">
|
||||
<Button
|
||||
className="ml-auto"
|
||||
onClick={() => setSecurityModalOpen(bank.mint.toBase58())}
|
||||
size="small"
|
||||
>
|
||||
Security council
|
||||
{securityModalOpen === bank.mint.toBase58() && (
|
||||
<SecurityCouncilModal
|
||||
group={group}
|
||||
bank={bank}
|
||||
isOpen={securityModalOpen === bank.mint.toBase58()}
|
||||
onClose={() => setSecurityModalOpen(null)}
|
||||
></SecurityCouncilModal>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<KeyValuePair
|
||||
label="Mint"
|
||||
value={<ExplorerLink address={bank.mint.toString()} />}
|
||||
|
|
|
@ -3,6 +3,17 @@ import { PublicKey } from '@solana/web3.js'
|
|||
export const MANGO_REALM_PK = new PublicKey(
|
||||
'DPiH3H3c7t47BMxqTxLsuPQpEC6Kne8GA9VXbxpnZxFE',
|
||||
)
|
||||
|
||||
export const MANGO_SECURITY_REALM_PK = new PublicKey(
|
||||
'AQbsV8b3Yv3UHUmd62hw9vFmNTHgBTdiLfaWzwmVfXB2',
|
||||
)
|
||||
export const MANGO_SECURITY_COUNCIL_MINT = new PublicKey(
|
||||
'HCZ4rgExnFkACvdbtnuMKFRn5ZELuQYyLKWyPuwRaCGS',
|
||||
)
|
||||
|
||||
export const MANGO_SECURITY_COUNCIL_WALLET = new PublicKey(
|
||||
'B89yKk4eaQqxSFnDQStTpmCqSdxofqk2RaaacCPVxuvD',
|
||||
)
|
||||
export const MANGO_GOVERNANCE_PROGRAM = new PublicKey(
|
||||
'GqTPL6qRf5aUuqscLh8Rg2HTxPUXfhhAXDptTLhp1t2J',
|
||||
)
|
||||
|
@ -20,6 +31,9 @@ export const MANGO_MINT = new PublicKey(
|
|||
export const MANGO_DAO_WALLET_GOVERNANCE = new PublicKey(
|
||||
'7zGXUAeUkY9pEGfApsY26amibvqsf2dmty1cbtxHdfaQ',
|
||||
)
|
||||
export const MANGO_DAO_SECURITY_WALLET_GOVERNANCE = new PublicKey(
|
||||
'GChgcmMjyPH7B3sHY6ZoHGxXJhW2u5TkwQLxeDoU5JeF',
|
||||
)
|
||||
|
||||
export const MANGO_DAO_FAST_LISTING_GOVERNANCE = new PublicKey(
|
||||
'7D6tGmaMyC8i73Q8X2Fec2S1Zb5rkyai6pctdMqHpHWT',
|
||||
|
|
|
@ -39,14 +39,17 @@ export const createProposal = async (
|
|||
descriptionLink: string,
|
||||
proposalIndex: number,
|
||||
proposalInstructions: TransactionInstruction[],
|
||||
client: VsrClient,
|
||||
client: VsrClient | null,
|
||||
fee: number,
|
||||
mint?: PublicKey,
|
||||
realm?: PublicKey,
|
||||
) => {
|
||||
const instructions: TransactionInstruction[] = []
|
||||
const walletPk = wallet.publicKey!
|
||||
const governanceAuthority = walletPk
|
||||
const signatory = walletPk
|
||||
const payer = walletPk
|
||||
let vsrVoterWeightPk: PublicKey | undefined = undefined
|
||||
|
||||
// Changed this because it is misbehaving on my local validator setup.
|
||||
const programVersion = await getGovernanceProgramVersion(
|
||||
|
@ -59,30 +62,33 @@ export const createProposal = async (
|
|||
const options = ['Approve']
|
||||
const useDenyOption = true
|
||||
|
||||
const { updateVoterWeightRecordIx, voterWeightPk } =
|
||||
await updateVoterWeightRecord(
|
||||
client,
|
||||
tokenOwnerRecord.account.governingTokenOwner,
|
||||
)
|
||||
instructions.push(updateVoterWeightRecordIx)
|
||||
if (client) {
|
||||
const { updateVoterWeightRecordIx, voterWeightPk } =
|
||||
await updateVoterWeightRecord(
|
||||
client,
|
||||
tokenOwnerRecord.account.governingTokenOwner,
|
||||
)
|
||||
vsrVoterWeightPk = voterWeightPk
|
||||
instructions.push(updateVoterWeightRecordIx)
|
||||
}
|
||||
|
||||
const proposalAddress = await withCreateProposal(
|
||||
instructions,
|
||||
MANGO_GOVERNANCE_PROGRAM,
|
||||
programVersion,
|
||||
MANGO_REALM_PK,
|
||||
realm || MANGO_REALM_PK,
|
||||
governance,
|
||||
tokenOwnerRecord.pubkey,
|
||||
name,
|
||||
descriptionLink,
|
||||
new PublicKey(MANGO_MINT),
|
||||
mint || new PublicKey(MANGO_MINT),
|
||||
governanceAuthority,
|
||||
proposalIndex,
|
||||
voteType,
|
||||
options,
|
||||
useDenyOption,
|
||||
payer,
|
||||
voterWeightPk,
|
||||
vsrVoterWeightPk,
|
||||
)
|
||||
|
||||
await withAddSignatory(
|
||||
|
@ -125,7 +131,7 @@ export const createProposal = async (
|
|||
insertInstructions, // SingOff proposal needs to be executed after inserting instructions hence we add it to insertInstructions
|
||||
MANGO_GOVERNANCE_PROGRAM,
|
||||
programVersion,
|
||||
MANGO_REALM_PK,
|
||||
realm || MANGO_REALM_PK,
|
||||
governance,
|
||||
proposalAddress,
|
||||
signatory,
|
||||
|
@ -137,6 +143,7 @@ export const createProposal = async (
|
|||
|
||||
const transactions: Transaction[] = []
|
||||
const latestBlockhash = await connection.getLatestBlockhash('processed')
|
||||
|
||||
for (const chunk of txChunks) {
|
||||
const tx = new Transaction()
|
||||
tx.add(createComputeBudgetIx(fee))
|
||||
|
@ -146,7 +153,6 @@ export const createProposal = async (
|
|||
tx.feePayer = payer
|
||||
transactions.push(tx)
|
||||
}
|
||||
|
||||
const signedTransactions = await wallet.signAllTransactions(transactions)
|
||||
for (const tx of signedTransactions) {
|
||||
await sendTxAndConfirm(
|
||||
|
|
Loading…
Reference in New Issue