import { useWallet } from '@solana/wallet-adapter-react' import { AccountMeta, PublicKey, SystemProgram, TransactionInstruction, } from '@solana/web3.js' import SquadsMesh from '@sqds/mesh' import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types' import { type ReactNode, Fragment, useContext, useEffect, useState, } from 'react' import toast from 'react-hot-toast' import { AnchorMultisigInstruction, ExecutePostedVaa, MultisigInstruction, MultisigParser, PythMultisigInstruction, WormholeMultisigInstruction, getManyProposalsInstructions, getMultisigCluster, getProgramName, } from '@pythnetwork/xc-admin-common' import { ClusterContext } from '../../../contexts/ClusterContext' import { useMultisigContext } from '../../../contexts/MultisigContext' import { usePythContext } from '../../../contexts/PythContext' import { capitalizeFirstLetter } from '../../../utils/capitalizeFirstLetter' import { ParsedAccountPubkeyRow, SignerTag, WritableTag, } from '../../InstructionViews/AccountUtils' import { WormholeInstructionView } from '../../InstructionViews/WormholeInstructionView' import CopyText from '../../common/CopyText' import Spinner from '../../common/Spinner' import Loadbar from '../../loaders/Loadbar' import { Wallet } from '@coral-xyz/anchor' import { PythCluster, getPythProgramKeyForCluster } from '@pythnetwork/client' import { TransactionBuilder, sendTransactions } from '@pythnetwork/solana-utils' import { getMappingCluster, isPubkey } from '../../InstructionViews/utils' import { StatusTag } from './StatusTag' import { getProposalStatus } from './utils' import VerifiedIcon from '@images/icons/verified.inline.svg' import VotedIcon from '@images/icons/voted.inline.svg' import WarningIcon from '@images/icons/warning.inline.svg' import * as Tooltip from '@radix-ui/react-tooltip' import { InstructionsSummary } from './InstructionsSummary' const IconWithTooltip = ({ icon, tooltipText, }: { icon: ReactNode tooltipText: string }) => { return (
{icon} {tooltipText}
) } const VerifiedIconWithTooltip = () => { return ( } tooltipText="The instructions in this proposal are verified." /> ) } const UnverifiedIconWithTooltip = () => { return ( } tooltipText="Be careful! The instructions in this proposal are not verified." /> ) } const VotedIconWithTooltip = () => { return ( } tooltipText="You have voted on this proposal." /> ) } const AccountList = ({ listName, accounts, }: { listName: string accounts: PublicKey[] }) => { const { multisigSignerKeyToNameMapping } = usePythContext() return (

{listName}: {accounts.length}


{accounts.map((pubkey, idx) => (
Key {idx + 1}{' '} {pubkey.toBase58() in multisigSignerKeyToNameMapping && `(${multisigSignerKeyToNameMapping[pubkey.toBase58()]})`}
))}
) } export const Proposal = ({ proposal, multisig, }: { proposal?: TransactionAccount multisig?: MultisigAccount }) => { const [instructions, setInstructions] = useState([]) const [isTransactionLoading, setIsTransactionLoading] = useState(false) const { cluster: contextCluster } = useContext(ClusterContext) const multisigCluster = getMultisigCluster(contextCluster) const targetClusters: (PythCluster | 'unknown')[] = [] instructions.map((ix) => { if (!(ix instanceof WormholeMultisigInstruction)) { targetClusters.push(multisigCluster) } else if ( ix instanceof WormholeMultisigInstruction && ix.governanceAction instanceof ExecutePostedVaa ) { ix.governanceAction.instructions.map((ix) => { const remoteClusters: PythCluster[] = [ 'pythnet', 'pythtest-conformance', 'pythtest-crosschain', ] for (const remoteCluster of remoteClusters) { if ( multisigCluster === getMultisigCluster(remoteCluster) && (ix.programId.equals(getPythProgramKeyForCluster(remoteCluster)) || ix.programId.equals(SystemProgram.programId)) ) { targetClusters.push(remoteCluster) } } }) } else { targetClusters.push('unknown') } }) const uniqueTargetCluster = new Set(targetClusters).size === 1 const cluster = uniqueTargetCluster && targetClusters[0] !== 'unknown' ? targetClusters[0] : contextCluster const { walletSquads: squads, isLoading: isMultisigLoading, refreshData, readOnlySquads, } = useMultisigContext() const { priceAccountKeyToSymbolMapping, productAccountKeyToSymbolMapping, publisherKeyToNameMapping, } = usePythContext() const publisherKeyToNameMappingCluster = publisherKeyToNameMapping[getMappingCluster(cluster)] const { publicKey: signerPublicKey } = useWallet() const proposalStatus = getProposalStatus(proposal, multisig) const verified = proposal && Object.keys(proposal.status)[0] !== 'draft' && instructions.length > 0 && instructions.every( (ix) => ix instanceof PythMultisigInstruction || (ix instanceof WormholeMultisigInstruction && ix.name === 'postMessage' && ix.governanceAction instanceof ExecutePostedVaa && ix.governanceAction.instructions.every((remoteIx) => { const innerMultisigParser = MultisigParser.fromCluster(cluster) const parsedRemoteInstruction = innerMultisigParser.parseInstruction({ programId: remoteIx.programId, data: remoteIx.data as Buffer, keys: remoteIx.keys as AccountMeta[], }) return ( parsedRemoteInstruction instanceof PythMultisigInstruction || parsedRemoteInstruction instanceof AnchorMultisigInstruction ) }) && ix.governanceAction.targetChainId === 'pythnet') ) const voted = proposal && signerPublicKey && (proposal.approved.some( (p) => p.toBase58() === signerPublicKey.toBase58() ) || proposal.cancelled.some( (p) => p.toBase58() === signerPublicKey.toBase58() ) || proposal.rejected.some( (p) => p.toBase58() === signerPublicKey.toBase58() )) useEffect(() => { let isCancelled = false const fetchInstructions = async () => { if (proposal) { const proposalInstructions = ( await getManyProposalsInstructions(readOnlySquads, [proposal]) )[0] const multisigParser = MultisigParser.fromCluster( getMultisigCluster(cluster) ) const parsedInstructions = proposalInstructions.map((ix) => multisigParser.parseInstruction({ programId: ix.programId, data: ix.data as Buffer, keys: ix.keys as AccountMeta[], }) ) if (!isCancelled) setInstructions(parsedInstructions) } else { if (!isCancelled) setInstructions([]) } } fetchInstructions().catch(console.error) return () => { isCancelled = true } }, [cluster, proposal, readOnlySquads]) const handleClick = async ( instructionGenerator: ( squad: SquadsMesh, vaultKey: PublicKey, proposalKey: PublicKey ) => Promise, msg: string ) => { if (proposal && squads) { try { setIsTransactionLoading(true) const instruction = await instructionGenerator( squads, proposal.ms, proposal.publicKey ) const builder = new TransactionBuilder( squads.wallet.publicKey, squads.connection ) builder.addInstruction({ instruction, signers: [], computeUnits: 20000, }) const transactions = builder.buildLegacyTransactions({ computeUnitPriceMicroLamports: 150000, tightComputeBudget: true, }) await sendTransactions( transactions, squads.connection, squads.wallet as Wallet ) if (refreshData) await refreshData().fetchData() toast.success(msg) } catch (e: any) { toast.error(capitalizeFirstLetter(e.message)) } finally { setIsTransactionLoading(false) } } } const handleClickApprove = async () => { await handleClick( async ( squad: SquadsMesh, vaultKey: PublicKey, proposalKey: PublicKey ): Promise => { return await squad.buildApproveTransaction(vaultKey, proposalKey) }, `Approved proposal ${proposal?.publicKey.toBase58()}` ) } const handleClickReject = async () => { await handleClick( async ( squad: SquadsMesh, vaultKey: PublicKey, proposalKey: PublicKey ): Promise => { return await squad.buildRejectTransaction(vaultKey, proposalKey) }, `Rejected proposal ${proposal?.publicKey.toBase58()}` ) } const handleClickExecute = async () => { await handleClick( async ( squad: SquadsMesh, vaultKey: PublicKey, proposalKey: PublicKey ): Promise => { return await squad.buildExecuteTransaction(proposalKey) }, `Executed proposal ${proposal?.publicKey.toBase58()}` ) } const handleClickCancel = async () => { await handleClick( async ( squad: SquadsMesh, vaultKey: PublicKey, proposalKey: PublicKey ): Promise => { return await squad.buildCancelTransaction(vaultKey, proposalKey) }, `Cancelled proposal ${proposal?.publicKey.toBase58()}` ) } if (!proposal || !multisig || isMultisigLoading) return (
) return (

Multisig network: {multisigCluster}

{uniqueTargetCluster ? `Target network: ${targetClusters[0]}` : targetClusters.length == 0 ? '' : `Multiple target networks detected ${targetClusters.join(' ')}`}

Info

{verified ? ( ) : ( )} {voted && }

Status
Proposal
Creator
Multisig

Results


Confirmed
{proposal.approved.length}
{proposalStatus === 'active' || proposalStatus === 'rejected' ? (
Rejected
{proposal.rejected.length}
) : (
Cancelled
{proposal.cancelled.length}
)}
Threshold
{multisig.threshold}/{multisig.keys.length}
{proposalStatus === 'active' ? (
) : proposalStatus === 'executeReady' ? (
) : null}
{proposal.approved.length > 0 && ( )} {proposal.rejected.length > 0 && ( )} {proposal.cancelled.length > 0 && ( )}

Total Instructions: {instructions.length}


Summary


{instructions?.map((instruction, index) => (

Instruction {index + 1}

Program
{getProgramName(instruction.program)}
{
Instruction Name
{instruction.name}
} {instruction instanceof WormholeMultisigInstruction && instruction.governanceAction ? ( <>
Target Chain
{instruction.governanceAction.targetChainId}
) : null} {instruction instanceof WormholeMultisigInstruction ? null : (
Arguments
{Object.keys(instruction.args).length > 0 ? (
Key
Value
{Object.keys(instruction.args).map((key, index) => (
{key}
{instruction.args[key] instanceof PublicKey ? ( ) : typeof instruction.args[key] === 'string' && isPubkey(instruction.args[key]) ? ( ) : (
{typeof instruction.args[key] === 'string' ? instruction.args[key] : instruction.args[key] instanceof Uint8Array ? instruction.args[key].toString() : typeof instruction.args[key] === 'bigint' ? instruction.args[key].toString() : JSON.stringify(instruction.args[key])}
)}
{key === 'pub' && instruction.args[key].toBase58() in publisherKeyToNameMappingCluster ? ( ) : null}
))}
) : (
No arguments
)}
)} {instruction instanceof WormholeMultisigInstruction && ( )} {!(instruction instanceof WormholeMultisigInstruction) ? (
Accounts
{Object.keys(instruction.accounts.named).length > 0 ? (
Account
Pubkey
{Object.keys(instruction.accounts.named).map( (key, index) => ( <>
{key}
{instruction.accounts.named[key].isSigner ? ( ) : null} {instruction.accounts.named[key].isWritable ? ( ) : null}
{key === 'priceAccount' && instruction.accounts.named[key].pubkey.toBase58() in priceAccountKeyToSymbolMapping ? ( ) : key === 'productAccount' && instruction.accounts.named[key].pubkey.toBase58() in productAccountKeyToSymbolMapping ? ( ) : null} ) )}
) : (
No arguments
)}
) : null} {index !== instructions.length - 1 ? (
) : null}
))}
) }