[xc-admin] add verified/unverified tags for individual proposals (#584)

* fix urlsIndex not being reset to 0 when changing cluster

* fix proposals accept/reject buttons not centered when on mobile view

* add verified tag to individual proposals

* fix pre-commit error

* fix color scheme

* address comments
This commit is contained in:
Daniel Chew 2023-02-14 13:32:18 +09:00 committed by GitHub
parent c59c00333a
commit f1ead1ca7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 148 additions and 82 deletions

View File

@ -0,0 +1,23 @@
import copy from 'copy-to-clipboard'
import CopyIcon from '../../images/icons/copy.inline.svg'
const CopyPubkey: React.FC<{
pubkey: string
}> = ({ pubkey }) => {
return (
<div
className="-ml-1 inline-flex cursor-pointer items-center px-1 hover:bg-dark hover:text-white active:bg-darkGray3"
onClick={() => {
copy(pubkey)
}}
>
<span className="mr-2 hidden xl:block">{pubkey}</span>
<span className="mr-2 xl:hidden">
{pubkey.slice(0, 6) + '...' + pubkey.slice(-6)}
</span>{' '}
<CopyIcon className="shrink-0" />
</div>
)
}
export default CopyPubkey

View File

@ -1,5 +1,4 @@
import { BN } from '@coral-xyz/anchor'
import { useWallet } from '@solana/wallet-adapter-react'
import { AccountMeta, PublicKey } from '@solana/web3.js'
import { getIxPDA } from '@sqds/mesh'
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
@ -29,6 +28,7 @@ import { useMultisigContext } from '../../contexts/MultisigContext'
import CopyIcon from '../../images/icons/copy.inline.svg'
import { capitalizeFirstLetter } from '../../utils/capitalizeFirstLetter'
import ClusterSwitch from '../ClusterSwitch'
import CopyPubkey from '../common/CopyPubkey'
import Loadbar from '../loaders/Loadbar'
const ProposalRow = ({
@ -65,22 +65,19 @@ const ProposalRow = ({
}
>
<div className="flex justify-between p-4">
<div>{proposal.publicKey.toBase58()}</div>
<div
className={
status === 'active'
? 'text-[#E6DAFE]'
: status === 'executed'
? 'text-[#1FC3D7]'
: status === 'cancelled'
? 'text-[#FFA7A0]'
: status === 'rejected'
? 'text-[#F86B86]'
: ''
}
>
<strong>{status}</strong>
<div>
{' '}
<span className="mr-2 hidden sm:block">
{proposal.publicKey.toBase58()}
</span>
<span className="mr-2 sm:hidden">
{proposal.publicKey.toBase58().slice(0, 6) +
'...' +
proposal.publicKey.toBase58().slice(-6)}
</span>{' '}
</div>
<StatusTag proposalStatus={status} />
</div>
</div>
)
@ -88,7 +85,7 @@ const ProposalRow = ({
const SignerTag = () => {
return (
<div className="flex items-center justify-center rounded-full bg-darkGray4 py-1 px-2 text-xs">
<div className="flex items-center justify-center rounded-full bg-[#605D72] py-1 px-2 text-xs">
Signer
</div>
)
@ -102,6 +99,26 @@ const WritableTag = () => {
)
}
const StatusTag = ({ proposalStatus }: { proposalStatus: string }) => {
return (
<div
className={`flex items-center justify-center rounded-full ${
proposalStatus === 'active'
? 'bg-[#3C3299]'
: proposalStatus === 'executed'
? 'bg-[#1C1E5D]'
: proposalStatus === 'cancelled'
? 'bg-[#C4428F]'
: proposalStatus === 'rejected'
? 'bg-[#CF6E42]'
: 'bg-pythPurple'
} py-1 px-2 text-xs`}
>
{proposalStatus}
</div>
)
}
const Proposal = ({
proposal,
multisig,
@ -114,6 +131,7 @@ const Proposal = ({
>([])
const [isProposalInstructionsLoading, setIsProposalInstructionsLoading] =
useState(false)
const [isVerified, setIsVerified] = useState(false)
const { cluster } = useContext(ClusterContext)
const { squads, isLoading: isMultisigLoading } = useMultisigContext()
@ -141,6 +159,30 @@ const Proposal = ({
})
proposalIxs.push(parsedInstruction)
}
setIsVerified(
proposalIxs.every(
(ix) =>
ix instanceof PythMultisigInstruction ||
(ix instanceof WormholeMultisigInstruction &&
ix.name === 'postMessage' &&
ix.governanceAction instanceof ExecutePostedVaa &&
ix.governanceAction.instructions.every((remoteIx) => {
const innerMultisigParser = MultisigParser.fromCluster(
getRemoteCluster(cluster)
)
const parsedRemoteInstruction =
innerMultisigParser.parseInstruction({
programId: remoteIx.programId,
data: remoteIx.data as Buffer,
keys: remoteIx.keys as AccountMeta[],
})
return (
parsedRemoteInstruction instanceof PythMultisigInstruction
)
}) &&
ix.governanceAction.targetChainId === 'pythnet')
)
)
setProposalInstructions(proposalIxs)
setIsProposalInstructionsLoading(false)
}
@ -200,23 +242,32 @@ const Proposal = ({
!isProposalInstructionsLoading ? (
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3 my-2 space-y-4 bg-[#1E1B2F] p-4 lg:col-span-2">
<h4 className="h4 font-semibold">Info</h4>
<div className="flex justify-between">
<h4 className="h4 font-semibold">Info</h4>
<div
className={`flex items-center justify-center rounded-full py-1 px-2 text-xs ${
isVerified ? 'bg-[#187B51]' : 'bg-[#8D2D41]'
}`}
>
{isVerified ? 'Verified' : 'Unverified'}
</div>
</div>
<hr className="border-gray-700" />
<div className="flex justify-between">
<div>Status</div>
<div>{Object.keys(proposal.status)[0]}</div>
<StatusTag proposalStatus={proposalStatus} />
</div>
<div className="flex justify-between">
<div>Proposal</div>
<div>{proposal.publicKey.toBase58()}</div>
<CopyPubkey pubkey={proposal.publicKey.toBase58()} />
</div>
<div className="flex justify-between">
<div>Creator</div>
<div>{proposal.creator.toBase58()}</div>
<CopyPubkey pubkey={proposal.creator.toBase58()} />
</div>
<div className="flex justify-between">
<div>Multisig</div>
<div>{proposal.ms.toBase58()}</div>
<CopyPubkey pubkey={proposal.ms.toBase58()} />
</div>
</div>
<div className="col-span-3 my-2 space-y-4 bg-[#1E1B2F] p-4 lg:col-span-1">
@ -239,7 +290,7 @@ const Proposal = ({
</div>
</div>
{proposalStatus === 'active' ? (
<div className="flex items-center justify-between px-8 pt-3">
<div className="flex items-center justify-center space-x-8 pt-3">
<button
className="action-btn text-base"
onClick={handleClickApprove}
@ -338,15 +389,19 @@ const Proposal = ({
className="flex justify-between border-t border-beige-300 py-3"
>
<div>{key}</div>
<div className="max-w-sm break-all">
{instruction.args[key] instanceof PublicKey
? instruction.args[key].toBase58()
: typeof instruction.args[key] === 'string'
? instruction.args[key]
: instruction.args[key] instanceof Uint8Array
? instruction.args[key].toString('hex')
: JSON.stringify(instruction.args[key])}
</div>
{instruction.args[key] instanceof PublicKey ? (
<CopyPubkey
pubkey={instruction.args[key].toBase58()}
/>
) : (
<div className="max-w-sm break-all">
{typeof instruction.args[key] === 'string'
? instruction.args[key]
: instruction.args[key] instanceof Uint8Array
? instruction.args[key].toString('hex')
: JSON.stringify(instruction.args[key])}
</div>
)}
</div>
))}
</div>
@ -385,32 +440,11 @@ const Proposal = ({
{instruction.accounts.named[key].isWritable ? (
<WritableTag />
) : null}
<div
className="-ml-1 inline-flex cursor-pointer items-center px-1 hover:bg-dark hover:text-white active:bg-darkGray3"
onClick={() => {
copy(
instruction.accounts.named[
key
].pubkey.toBase58()
)
}}
>
<span className="mr-2 hidden xl:block">
{instruction.accounts.named[
key
].pubkey.toBase58()}
</span>
<span className="mr-2 xl:hidden">
{instruction.accounts.named[key].pubkey
.toBase58()
.slice(0, 6) +
'...' +
instruction.accounts.named[key].pubkey
.toBase58()
.slice(-6)}
</span>{' '}
<CopyIcon className="shrink-0" />
</div>
<CopyPubkey
pubkey={instruction.accounts.named[
key
].pubkey.toBase58()}
/>
</div>
</div>
</>
@ -428,11 +462,13 @@ const Proposal = ({
className="flex justify-between"
>
<div>Program ID</div>
<div>{instruction.instruction.programId.toBase58()}</div>
<CopyPubkey
pubkey={instruction.instruction.programId.toBase58()}
/>
</div>
<div key={`${index}_data`} className="flex justify-between">
<div>Data</div>
<div>
<div className="max-w-sm break-all">
{instruction.instruction.data.length > 0
? instruction.instruction.data.toString('hex')
: 'No data'}
@ -552,27 +588,31 @@ const Proposal = ({
className="flex justify-between border-t border-beige-300 py-3"
>
<div>{key}</div>
<div className="max-w-sm break-all">
{parsedInstruction.args[
key
] instanceof PublicKey
? parsedInstruction.args[
key
].toBase58()
: typeof parsedInstruction.args[
key
] === 'string'
? parsedInstruction.args[key]
: parsedInstruction.args[
key
] instanceof Uint8Array
? parsedInstruction.args[
key
].toString('hex')
: JSON.stringify(
parsedInstruction.args[key]
)}
</div>
{parsedInstruction.args[
key
] instanceof PublicKey ? (
<CopyPubkey
pubkey={parsedInstruction.args[
key
].toBase58()}
/>
) : (
<div className="max-w-sm break-all">
{typeof parsedInstruction.args[
key
] === 'string'
? parsedInstruction.args[key]
: parsedInstruction.args[
key
] instanceof Uint8Array
? parsedInstruction.args[
key
].toString('hex')
: JSON.stringify(
parsedInstruction.args[key]
)}
</div>
)}
</div>
)
)}
@ -774,7 +814,6 @@ const Proposals = () => {
priceFeedMultisigProposals,
isLoading: isMultisigLoading,
} = useMultisigContext()
const { connected } = useWallet()
const handleClickBackToPriceFeeds = () => {
delete router.query.proposal

View File

@ -59,6 +59,10 @@ const usePyth = (): PythHookData => {
setError(null)
}, [urlsIndex, cluster])
useEffect(() => {
setUrlsIndex(0)
}, [cluster])
useEffect(() => {
let cancelled = false
const urls = pythClusterApiUrls(cluster)