import { AnchorProvider, Program } from '@coral-xyz/anchor' import { getPythProgramKeyForCluster, pythOracleProgram, } from '@pythnetwork/client' import { PythOracle } from '@pythnetwork/client/lib/anchor' import { useWallet } from '@solana/wallet-adapter-react' import { WalletModalButton } from '@solana/wallet-adapter-react-ui' import { PublicKey } from '@solana/web3.js' import { createColumnHelper, flexRender, getCoreRowModel, useReactTable, } from '@tanstack/react-table' import copy from 'copy-to-clipboard' import { useContext, useEffect, useState } from 'react' import toast from 'react-hot-toast' import { BPF_UPGRADABLE_LOADER, getMultisigCluster, isRemoteCluster, mapKey, UPGRADE_MULTISIG, MultisigVault, } from '@pythnetwork/xc-admin-common' import { ClusterContext } from '../../contexts/ClusterContext' import { useMultisigContext } from '../../contexts/MultisigContext' import { usePythContext } from '../../contexts/PythContext' import CopyIcon from '@images/icons/copy.inline.svg' import { capitalizeFirstLetter } from '../../utils/capitalizeFirstLetter' import ClusterSwitch from '../ClusterSwitch' import Modal from '../common/Modal' import Spinner from '../common/Spinner' import EditButton from '../EditButton' import Loadbar from '../loaders/Loadbar' import { Wallet } from '@coral-xyz/anchor/dist/cjs/provider' interface UpdatePermissionsProps { account: PermissionAccount pubkey: string newPubkey?: string } const DEFAULT_DATA: UpdatePermissionsProps[] = [ { account: 'Master Authority', pubkey: new PublicKey(0).toBase58(), }, { account: 'Data Curation Authority', pubkey: new PublicKey(0).toBase58(), }, { account: 'Security Authority', pubkey: new PublicKey(0).toBase58(), }, ] const columnHelper = createColumnHelper() const defaultColumns = [ columnHelper.accessor('account', { cell: (info) => info.getValue(), header: () => Account, }), columnHelper.accessor('pubkey', { cell: (props) => { const pubkey = props.getValue() return ( <>
{ copy(pubkey) }} > {pubkey} {pubkey.slice(0, 6) + '...' + pubkey.slice(-6)} {' '}
) }, header: () => Public Key, }), ] type PermissionAccount = | 'Master Authority' | 'Data Curation Authority' | 'Security Authority' interface PermissionAccountInfo { prev: string new: string } const UpdatePermissions = () => { const [data, setData] = useState(() => [...DEFAULT_DATA]) const [columns, setColumns] = useState(() => [...defaultColumns]) const [pubkeyChanges, setPubkeyChanges] = useState>>() const [finalPubkeyChanges, setFinalPubkeyChanges] = useState>() const [editable, setEditable] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false) const [isSendProposalButtonLoading, setIsSendProposalButtonLoading] = useState(false) const { cluster } = useContext(ClusterContext) const { isLoading: isMultisigLoading, walletSquads } = useMultisigContext() const { rawConfig, dataIsLoading, connection } = usePythContext() const { connected } = useWallet() const [pythProgramClient, setPythProgramClient] = useState>() useEffect(() => { if (rawConfig.permissionAccount) { const masterAuthority = rawConfig.permissionAccount.masterAuthority.toBase58() const dataCurationAuthority = rawConfig.permissionAccount.dataCurationAuthority.toBase58() const securityAuthority = rawConfig.permissionAccount.securityAuthority.toBase58() setData([ { account: 'Master Authority', pubkey: masterAuthority, }, { account: 'Data Curation Authority', pubkey: dataCurationAuthority, }, { account: 'Security Authority', pubkey: securityAuthority, }, ]) } else { setData([...DEFAULT_DATA]) } }, [rawConfig]) const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), }) const backfillPubkeyChanges = () => { const newPubkeyChanges: Record = { 'Master Authority': { prev: data[0].pubkey, new: data[0].pubkey, }, 'Data Curation Authority': { prev: data[1].pubkey, new: data[1].pubkey, }, 'Security Authority': { prev: data[2].pubkey, new: data[2].pubkey, }, } if (pubkeyChanges) { Object.keys(pubkeyChanges).forEach((key) => { newPubkeyChanges[key as PermissionAccount] = pubkeyChanges[ key as PermissionAccount ] as PermissionAccountInfo }) } return newPubkeyChanges } const handleEditButtonClick = () => { const nextState = !editable if (nextState) { const newColumns = [ ...defaultColumns, columnHelper.accessor('newPubkey', { cell: (info) => info.getValue(), header: () => New Public Key, }), ] setColumns(newColumns) } else { if (pubkeyChanges && Object.keys(pubkeyChanges).length > 0) { openModal() setFinalPubkeyChanges(backfillPubkeyChanges()) } else { setColumns(defaultColumns) } } setEditable(nextState) } const openModal = () => { setIsModalOpen(true) } const closeModal = () => { setIsModalOpen(false) } // check if pubkey is valid const isValidPubkey = (pubkey: string) => { try { new PublicKey(pubkey) return true } catch (e) { return false } } const handleEditPubkey = ( e: any, account: PermissionAccount, prevPubkey: string ) => { const newPubkey = e.target.textContent if (isValidPubkey(newPubkey) && newPubkey !== prevPubkey) { setPubkeyChanges({ ...pubkeyChanges, [account]: { prev: prevPubkey, new: newPubkey, }, }) } else { // delete account from pubkeyChanges if it exists if (pubkeyChanges && pubkeyChanges[account]) { delete pubkeyChanges[account] } setPubkeyChanges(pubkeyChanges) } } const handleSendProposalButtonClick = () => { if (pythProgramClient && finalPubkeyChanges && walletSquads) { const programDataAccount = PublicKey.findProgramAddressSync( [pythProgramClient?.programId.toBuffer()], BPF_UPGRADABLE_LOADER )[0] const multisigAuthority = walletSquads.getAuthorityPDA( UPGRADE_MULTISIG[getMultisigCluster(cluster)], 1 ) pythProgramClient?.methods .updPermissions( new PublicKey(finalPubkeyChanges['Master Authority'].new), new PublicKey(finalPubkeyChanges['Data Curation Authority'].new), new PublicKey(finalPubkeyChanges['Security Authority'].new) ) .accounts({ upgradeAuthority: isRemoteCluster(cluster) ? mapKey(multisigAuthority) : multisigAuthority, programDataAccount, }) .instruction() .then(async (instruction) => { if (!isMultisigLoading) { setIsSendProposalButtonLoading(true) try { const vault = new MultisigVault( walletSquads.wallet as Wallet, getMultisigCluster(cluster), walletSquads, UPGRADE_MULTISIG[getMultisigCluster(cluster)] ) const proposalPubkey = ( await vault.proposeInstructions([instruction], cluster) )[0] toast.success( `Proposal sent! 🚀 Proposal Pubkey: ${proposalPubkey}` ) setIsSendProposalButtonLoading(false) } catch (e: any) { toast.error(capitalizeFirstLetter(e.message)) setIsSendProposalButtonLoading(false) } } }) } } const ModalContent = ({ changes }: { changes: any }) => { return ( <> {Object.keys(changes).length > 0 ? (
{Object.keys(changes).map((key) => { return ( changes[key].prev !== changes[key].new && ( <>
{key} {changes[key].prev} → {changes[key].new}
) ) })}
) : (

No proposed changes.

)} {Object.keys(changes).length > 0 ? ( !connected ? (
) : ( ) ) : null} ) } // create anchor wallet when connected useEffect(() => { if (connected && walletSquads && connection) { const provider = new AnchorProvider( connection, walletSquads.wallet as Wallet, AnchorProvider.defaultOptions() ) setPythProgramClient( pythOracleProgram(getPythProgramKeyForCluster(cluster), provider) ) } }, [connection, connected, cluster, walletSquads]) return (
} />

Update Permissions

{dataIsLoading ? (
) : (
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} ))} {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( ))} ))}
{header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )}
handleEditPubkey( e, cell.row.original.account, cell.row.original.pubkey ) } contentEditable={ cell.column.id === 'newPubkey' && editable ? true : false } suppressContentEditableWarning={true} className={ cell.column.id === 'account' ? 'py-3 pl-4 pr-2 xl:pl-14' : 'items-center py-3 pl-1 pr-4' } > {flexRender( cell.column.columnDef.cell, cell.getContext() )}
)}
) } export default UpdatePermissions