Skip to content

Commit f1ead1c

Browse files
authored
[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
1 parent c59c003 commit f1ead1c

File tree

3 files changed

+148
-82
lines changed

3 files changed

+148
-82
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import copy from 'copy-to-clipboard'
2+
import CopyIcon from '../../images/icons/copy.inline.svg'
3+
4+
const CopyPubkey: React.FC<{
5+
pubkey: string
6+
}> = ({ pubkey }) => {
7+
return (
8+
<div
9+
className="-ml-1 inline-flex cursor-pointer items-center px-1 hover:bg-dark hover:text-white active:bg-darkGray3"
10+
onClick={() => {
11+
copy(pubkey)
12+
}}
13+
>
14+
<span className="mr-2 hidden xl:block">{pubkey}</span>
15+
<span className="mr-2 xl:hidden">
16+
{pubkey.slice(0, 6) + '...' + pubkey.slice(-6)}
17+
</span>{' '}
18+
<CopyIcon className="shrink-0" />
19+
</div>
20+
)
21+
}
22+
23+
export default CopyPubkey

governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals.tsx

Lines changed: 121 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { BN } from '@coral-xyz/anchor'
2-
import { useWallet } from '@solana/wallet-adapter-react'
32
import { AccountMeta, PublicKey } from '@solana/web3.js'
43
import { getIxPDA } from '@sqds/mesh'
54
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
@@ -29,6 +28,7 @@ import { useMultisigContext } from '../../contexts/MultisigContext'
2928
import CopyIcon from '../../images/icons/copy.inline.svg'
3029
import { capitalizeFirstLetter } from '../../utils/capitalizeFirstLetter'
3130
import ClusterSwitch from '../ClusterSwitch'
31+
import CopyPubkey from '../common/CopyPubkey'
3232
import Loadbar from '../loaders/Loadbar'
3333

3434
const ProposalRow = ({
@@ -65,30 +65,27 @@ const ProposalRow = ({
6565
}
6666
>
6767
<div className="flex justify-between p-4">
68-
<div>{proposal.publicKey.toBase58()}</div>
69-
<div
70-
className={
71-
status === 'active'
72-
? 'text-[#E6DAFE]'
73-
: status === 'executed'
74-
? 'text-[#1FC3D7]'
75-
: status === 'cancelled'
76-
? 'text-[#FFA7A0]'
77-
: status === 'rejected'
78-
? 'text-[#F86B86]'
79-
: ''
80-
}
81-
>
82-
<strong>{status}</strong>
68+
<div>
69+
{' '}
70+
<span className="mr-2 hidden sm:block">
71+
{proposal.publicKey.toBase58()}
72+
</span>
73+
<span className="mr-2 sm:hidden">
74+
{proposal.publicKey.toBase58().slice(0, 6) +
75+
'...' +
76+
proposal.publicKey.toBase58().slice(-6)}
77+
</span>{' '}
8378
</div>
79+
80+
<StatusTag proposalStatus={status} />
8481
</div>
8582
</div>
8683
)
8784
}
8885

8986
const SignerTag = () => {
9087
return (
91-
<div className="flex items-center justify-center rounded-full bg-darkGray4 py-1 px-2 text-xs">
88+
<div className="flex items-center justify-center rounded-full bg-[#605D72] py-1 px-2 text-xs">
9289
Signer
9390
</div>
9491
)
@@ -102,6 +99,26 @@ const WritableTag = () => {
10299
)
103100
}
104101

102+
const StatusTag = ({ proposalStatus }: { proposalStatus: string }) => {
103+
return (
104+
<div
105+
className={`flex items-center justify-center rounded-full ${
106+
proposalStatus === 'active'
107+
? 'bg-[#3C3299]'
108+
: proposalStatus === 'executed'
109+
? 'bg-[#1C1E5D]'
110+
: proposalStatus === 'cancelled'
111+
? 'bg-[#C4428F]'
112+
: proposalStatus === 'rejected'
113+
? 'bg-[#CF6E42]'
114+
: 'bg-pythPurple'
115+
} py-1 px-2 text-xs`}
116+
>
117+
{proposalStatus}
118+
</div>
119+
)
120+
}
121+
105122
const Proposal = ({
106123
proposal,
107124
multisig,
@@ -114,6 +131,7 @@ const Proposal = ({
114131
>([])
115132
const [isProposalInstructionsLoading, setIsProposalInstructionsLoading] =
116133
useState(false)
134+
const [isVerified, setIsVerified] = useState(false)
117135
const { cluster } = useContext(ClusterContext)
118136
const { squads, isLoading: isMultisigLoading } = useMultisigContext()
119137

@@ -141,6 +159,30 @@ const Proposal = ({
141159
})
142160
proposalIxs.push(parsedInstruction)
143161
}
162+
setIsVerified(
163+
proposalIxs.every(
164+
(ix) =>
165+
ix instanceof PythMultisigInstruction ||
166+
(ix instanceof WormholeMultisigInstruction &&
167+
ix.name === 'postMessage' &&
168+
ix.governanceAction instanceof ExecutePostedVaa &&
169+
ix.governanceAction.instructions.every((remoteIx) => {
170+
const innerMultisigParser = MultisigParser.fromCluster(
171+
getRemoteCluster(cluster)
172+
)
173+
const parsedRemoteInstruction =
174+
innerMultisigParser.parseInstruction({
175+
programId: remoteIx.programId,
176+
data: remoteIx.data as Buffer,
177+
keys: remoteIx.keys as AccountMeta[],
178+
})
179+
return (
180+
parsedRemoteInstruction instanceof PythMultisigInstruction
181+
)
182+
}) &&
183+
ix.governanceAction.targetChainId === 'pythnet')
184+
)
185+
)
144186
setProposalInstructions(proposalIxs)
145187
setIsProposalInstructionsLoading(false)
146188
}
@@ -200,23 +242,32 @@ const Proposal = ({
200242
!isProposalInstructionsLoading ? (
201243
<div className="grid grid-cols-3 gap-4">
202244
<div className="col-span-3 my-2 space-y-4 bg-[#1E1B2F] p-4 lg:col-span-2">
203-
<h4 className="h4 font-semibold">Info</h4>
245+
<div className="flex justify-between">
246+
<h4 className="h4 font-semibold">Info</h4>
247+
<div
248+
className={`flex items-center justify-center rounded-full py-1 px-2 text-xs ${
249+
isVerified ? 'bg-[#187B51]' : 'bg-[#8D2D41]'
250+
}`}
251+
>
252+
{isVerified ? 'Verified' : 'Unverified'}
253+
</div>
254+
</div>
204255
<hr className="border-gray-700" />
205256
<div className="flex justify-between">
206257
<div>Status</div>
207-
<div>{Object.keys(proposal.status)[0]}</div>
258+
<StatusTag proposalStatus={proposalStatus} />
208259
</div>
209260
<div className="flex justify-between">
210261
<div>Proposal</div>
211-
<div>{proposal.publicKey.toBase58()}</div>
262+
<CopyPubkey pubkey={proposal.publicKey.toBase58()} />
212263
</div>
213264
<div className="flex justify-between">
214265
<div>Creator</div>
215-
<div>{proposal.creator.toBase58()}</div>
266+
<CopyPubkey pubkey={proposal.creator.toBase58()} />
216267
</div>
217268
<div className="flex justify-between">
218269
<div>Multisig</div>
219-
<div>{proposal.ms.toBase58()}</div>
270+
<CopyPubkey pubkey={proposal.ms.toBase58()} />
220271
</div>
221272
</div>
222273
<div className="col-span-3 my-2 space-y-4 bg-[#1E1B2F] p-4 lg:col-span-1">
@@ -239,7 +290,7 @@ const Proposal = ({
239290
</div>
240291
</div>
241292
{proposalStatus === 'active' ? (
242-
<div className="flex items-center justify-between px-8 pt-3">
293+
<div className="flex items-center justify-center space-x-8 pt-3">
243294
<button
244295
className="action-btn text-base"
245296
onClick={handleClickApprove}
@@ -338,15 +389,19 @@ const Proposal = ({
338389
className="flex justify-between border-t border-beige-300 py-3"
339390
>
340391
<div>{key}</div>
341-
<div className="max-w-sm break-all">
342-
{instruction.args[key] instanceof PublicKey
343-
? instruction.args[key].toBase58()
344-
: typeof instruction.args[key] === 'string'
345-
? instruction.args[key]
346-
: instruction.args[key] instanceof Uint8Array
347-
? instruction.args[key].toString('hex')
348-
: JSON.stringify(instruction.args[key])}
349-
</div>
392+
{instruction.args[key] instanceof PublicKey ? (
393+
<CopyPubkey
394+
pubkey={instruction.args[key].toBase58()}
395+
/>
396+
) : (
397+
<div className="max-w-sm break-all">
398+
{typeof instruction.args[key] === 'string'
399+
? instruction.args[key]
400+
: instruction.args[key] instanceof Uint8Array
401+
? instruction.args[key].toString('hex')
402+
: JSON.stringify(instruction.args[key])}
403+
</div>
404+
)}
350405
</div>
351406
))}
352407
</div>
@@ -385,32 +440,11 @@ const Proposal = ({
385440
{instruction.accounts.named[key].isWritable ? (
386441
<WritableTag />
387442
) : null}
388-
<div
389-
className="-ml-1 inline-flex cursor-pointer items-center px-1 hover:bg-dark hover:text-white active:bg-darkGray3"
390-
onClick={() => {
391-
copy(
392-
instruction.accounts.named[
393-
key
394-
].pubkey.toBase58()
395-
)
396-
}}
397-
>
398-
<span className="mr-2 hidden xl:block">
399-
{instruction.accounts.named[
400-
key
401-
].pubkey.toBase58()}
402-
</span>
403-
<span className="mr-2 xl:hidden">
404-
{instruction.accounts.named[key].pubkey
405-
.toBase58()
406-
.slice(0, 6) +
407-
'...' +
408-
instruction.accounts.named[key].pubkey
409-
.toBase58()
410-
.slice(-6)}
411-
</span>{' '}
412-
<CopyIcon className="shrink-0" />
413-
</div>
443+
<CopyPubkey
444+
pubkey={instruction.accounts.named[
445+
key
446+
].pubkey.toBase58()}
447+
/>
414448
</div>
415449
</div>
416450
</>
@@ -428,11 +462,13 @@ const Proposal = ({
428462
className="flex justify-between"
429463
>
430464
<div>Program ID</div>
431-
<div>{instruction.instruction.programId.toBase58()}</div>
465+
<CopyPubkey
466+
pubkey={instruction.instruction.programId.toBase58()}
467+
/>
432468
</div>
433469
<div key={`${index}_data`} className="flex justify-between">
434470
<div>Data</div>
435-
<div>
471+
<div className="max-w-sm break-all">
436472
{instruction.instruction.data.length > 0
437473
? instruction.instruction.data.toString('hex')
438474
: 'No data'}
@@ -552,27 +588,31 @@ const Proposal = ({
552588
className="flex justify-between border-t border-beige-300 py-3"
553589
>
554590
<div>{key}</div>
555-
<div className="max-w-sm break-all">
556-
{parsedInstruction.args[
557-
key
558-
] instanceof PublicKey
559-
? parsedInstruction.args[
560-
key
561-
].toBase58()
562-
: typeof parsedInstruction.args[
563-
key
564-
] === 'string'
565-
? parsedInstruction.args[key]
566-
: parsedInstruction.args[
567-
key
568-
] instanceof Uint8Array
569-
? parsedInstruction.args[
570-
key
571-
].toString('hex')
572-
: JSON.stringify(
573-
parsedInstruction.args[key]
574-
)}
575-
</div>
591+
{parsedInstruction.args[
592+
key
593+
] instanceof PublicKey ? (
594+
<CopyPubkey
595+
pubkey={parsedInstruction.args[
596+
key
597+
].toBase58()}
598+
/>
599+
) : (
600+
<div className="max-w-sm break-all">
601+
{typeof parsedInstruction.args[
602+
key
603+
] === 'string'
604+
? parsedInstruction.args[key]
605+
: parsedInstruction.args[
606+
key
607+
] instanceof Uint8Array
608+
? parsedInstruction.args[
609+
key
610+
].toString('hex')
611+
: JSON.stringify(
612+
parsedInstruction.args[key]
613+
)}
614+
</div>
615+
)}
576616
</div>
577617
)
578618
)}
@@ -774,7 +814,6 @@ const Proposals = () => {
774814
priceFeedMultisigProposals,
775815
isLoading: isMultisigLoading,
776816
} = useMultisigContext()
777-
const { connected } = useWallet()
778817

779818
const handleClickBackToPriceFeeds = () => {
780819
delete router.query.proposal

governance/xc_admin/packages/xc_admin_frontend/hooks/usePyth.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ const usePyth = (): PythHookData => {
5959
setError(null)
6060
}, [urlsIndex, cluster])
6161

62+
useEffect(() => {
63+
setUrlsIndex(0)
64+
}, [cluster])
65+
6266
useEffect(() => {
6367
let cancelled = false
6468
const urls = pythClusterApiUrls(cluster)

0 commit comments

Comments
 (0)