[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:
parent
c59c00333a
commit
f1ead1ca7a
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -59,6 +59,10 @@ const usePyth = (): PythHookData => {
|
|||
setError(null)
|
||||
}, [urlsIndex, cluster])
|
||||
|
||||
useEffect(() => {
|
||||
setUrlsIndex(0)
|
||||
}, [cluster])
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
const urls = pythClusterApiUrls(cluster)
|
||||
|
|
Loading…
Reference in New Issue