Xc admin frontend refactor (#964)
* Remove fetching proposal internals on page load Without the internals we can not show the verified/voted icons in the proposals page, therefore we have to remove them * Refactor squads creation based on wallet Previously there was a logic to use a separate wallet for proposeSquads but now it is removed and we can unify vote/propose squads * Disable propose button if no action and show hint * Expose refresh functionality on multisig proposals * Fetch instructions within proposal and calculate voted/verified properties inside * Extract WormholeInstructionView as a separate component Moved some name mappings to pythContext so we don't need prop drilling * Add support for parsing governance instructions + minor refactors * Add ability to show / approve upgrade proposals * fix buttons overflow * Use the actual targetChainId instead of relying on the cluster context for instruction visualization * Do not fetch the data again if the multisigCluster remains the same --------- Co-authored-by: Daniel Chew <cctdaniel@outlook.com>
This commit is contained in:
parent
0e5d7d0470
commit
f595d61ccd
|
@ -0,0 +1,34 @@
|
||||||
|
export const SignerTag = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex max-h-[22px] max-w-[74px] items-center justify-center rounded-full bg-[#605D72] py-1 px-2 text-xs">
|
||||||
|
Signer
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WritableTag = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex max-h-[22px] max-w-[74px] items-center justify-center rounded-full bg-offPurple py-1 px-2 text-xs">
|
||||||
|
Writable
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ParsedAccountPubkeyRow = ({
|
||||||
|
mapping,
|
||||||
|
title,
|
||||||
|
pubkey,
|
||||||
|
}: {
|
||||||
|
mapping: { [key: string]: string }
|
||||||
|
title: string
|
||||||
|
pubkey: string
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-between pb-3">
|
||||||
|
<div className="max-w-[80px] break-words sm:max-w-none sm:break-normal">
|
||||||
|
⤷ {title}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2 sm:flex sm:space-x-2">{mapping[pubkey]}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,451 @@
|
||||||
|
import {
|
||||||
|
AptosAuthorizeUpgradeContract,
|
||||||
|
AuthorizeGovernanceDataSourceTransfer,
|
||||||
|
CosmosUpgradeContract,
|
||||||
|
EvmSetWormholeAddress,
|
||||||
|
EvmUpgradeContract,
|
||||||
|
ExecutePostedVaa,
|
||||||
|
MessageBufferMultisigInstruction,
|
||||||
|
MultisigParser,
|
||||||
|
PythGovernanceAction,
|
||||||
|
PythMultisigInstruction,
|
||||||
|
RequestGovernanceDataSourceTransfer,
|
||||||
|
SetDataSources,
|
||||||
|
SetFee,
|
||||||
|
SetValidPeriod,
|
||||||
|
UnrecognizedProgram,
|
||||||
|
WormholeMultisigInstruction,
|
||||||
|
} from 'xc_admin_common'
|
||||||
|
import { AccountMeta, PublicKey } from '@solana/web3.js'
|
||||||
|
import CopyPubkey from '../common/CopyPubkey'
|
||||||
|
import { useContext } from 'react'
|
||||||
|
import { ClusterContext } from '../../contexts/ClusterContext'
|
||||||
|
import { ParsedAccountPubkeyRow, SignerTag, WritableTag } from './AccountUtils'
|
||||||
|
import { usePythContext } from '../../contexts/PythContext'
|
||||||
|
|
||||||
|
import { getMappingCluster, isPubkey } from './utils'
|
||||||
|
|
||||||
|
const GovernanceInstructionView = ({
|
||||||
|
instruction,
|
||||||
|
actionName,
|
||||||
|
content,
|
||||||
|
}: {
|
||||||
|
instruction: PythGovernanceAction
|
||||||
|
actionName: string
|
||||||
|
content: JSX.Element
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>Action: {actionName}</div>
|
||||||
|
<div>Chain Id: {instruction.targetChainId}</div>
|
||||||
|
{content}
|
||||||
|
<div>
|
||||||
|
Raw payload hex:{' '}
|
||||||
|
<CopyPubkey pubkey={instruction.encode().toString('hex')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export const WormholeInstructionView = ({
|
||||||
|
instruction,
|
||||||
|
}: {
|
||||||
|
instruction: WormholeMultisigInstruction
|
||||||
|
}) => {
|
||||||
|
const { cluster } = useContext(ClusterContext)
|
||||||
|
const {
|
||||||
|
priceAccountKeyToSymbolMapping,
|
||||||
|
productAccountKeyToSymbolMapping,
|
||||||
|
publisherKeyToNameMapping,
|
||||||
|
} = usePythContext()
|
||||||
|
const publisherKeyToNameMappingCluster =
|
||||||
|
publisherKeyToNameMapping[getMappingCluster(cluster)]
|
||||||
|
const governanceAction = instruction.governanceAction
|
||||||
|
return (
|
||||||
|
<div className="col-span-4 my-2 space-y-4 bg-darkGray2 p-4 lg:col-span-3">
|
||||||
|
<h4 className="h4">Wormhole Instructions</h4>
|
||||||
|
<hr className="border-[#E6DAFE] opacity-30" />
|
||||||
|
{!governanceAction && (
|
||||||
|
<>
|
||||||
|
<div>Unknown message</div>
|
||||||
|
<div>Raw hex payload:</div>
|
||||||
|
<div>{(instruction.args.payload as Buffer).toString('hex')}</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{governanceAction instanceof ExecutePostedVaa &&
|
||||||
|
governanceAction.instructions.map((innerInstruction, index) => {
|
||||||
|
const multisigParser = MultisigParser.fromCluster(cluster)
|
||||||
|
const parsedInstruction = multisigParser.parseInstruction({
|
||||||
|
programId: innerInstruction.programId,
|
||||||
|
data: innerInstruction.data as Buffer,
|
||||||
|
keys: innerInstruction.keys as AccountMeta[],
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div key={`${index}_program`} className="flex justify-between">
|
||||||
|
<div>Program</div>
|
||||||
|
<div>
|
||||||
|
{parsedInstruction instanceof PythMultisigInstruction
|
||||||
|
? 'Pyth Oracle'
|
||||||
|
: parsedInstruction instanceof WormholeMultisigInstruction
|
||||||
|
? 'Wormhole'
|
||||||
|
: parsedInstruction instanceof
|
||||||
|
MessageBufferMultisigInstruction
|
||||||
|
? 'Message Buffer'
|
||||||
|
: 'Unknown'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
key={`${index}_instructionName`}
|
||||||
|
className="flex justify-between"
|
||||||
|
>
|
||||||
|
<div>Instruction Name</div>
|
||||||
|
<div>
|
||||||
|
{parsedInstruction instanceof PythMultisigInstruction ||
|
||||||
|
parsedInstruction instanceof WormholeMultisigInstruction ||
|
||||||
|
parsedInstruction instanceof MessageBufferMultisigInstruction
|
||||||
|
? parsedInstruction.name
|
||||||
|
: 'Unknown'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
key={`${index}_arguments`}
|
||||||
|
className="grid grid-cols-4 justify-between"
|
||||||
|
>
|
||||||
|
<div>Arguments</div>
|
||||||
|
{parsedInstruction instanceof PythMultisigInstruction ||
|
||||||
|
parsedInstruction instanceof WormholeMultisigInstruction ||
|
||||||
|
parsedInstruction instanceof
|
||||||
|
MessageBufferMultisigInstruction ? (
|
||||||
|
Object.keys(parsedInstruction.args).length > 0 ? (
|
||||||
|
<div className="col-span-4 mt-2 bg-[#444157] p-4 lg:col-span-3 lg:mt-0">
|
||||||
|
<div className="base16 flex justify-between pt-2 pb-6 font-semibold opacity-60">
|
||||||
|
<div>Key</div>
|
||||||
|
<div>Value</div>
|
||||||
|
</div>
|
||||||
|
{Object.keys(parsedInstruction.args).map((key, index) => (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex justify-between border-t border-beige-300 py-3"
|
||||||
|
>
|
||||||
|
<div>{key}</div>
|
||||||
|
{parsedInstruction.args[key] instanceof
|
||||||
|
PublicKey ? (
|
||||||
|
<CopyPubkey
|
||||||
|
pubkey={parsedInstruction.args[key].toBase58()}
|
||||||
|
/>
|
||||||
|
) : typeof instruction.args[key] === 'string' &&
|
||||||
|
isPubkey(instruction.args[key]) ? (
|
||||||
|
<CopyPubkey
|
||||||
|
pubkey={parsedInstruction.args[key]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<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>
|
||||||
|
{key === 'pub' &&
|
||||||
|
parsedInstruction.args[key].toBase58() in
|
||||||
|
publisherKeyToNameMappingCluster ? (
|
||||||
|
<ParsedAccountPubkeyRow
|
||||||
|
key={`${index}_${parsedInstruction.args[
|
||||||
|
key
|
||||||
|
].toBase58()}`}
|
||||||
|
mapping={publisherKeyToNameMappingCluster}
|
||||||
|
title="publisher"
|
||||||
|
pubkey={parsedInstruction.args[key].toBase58()}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="col-span-3 text-right">No arguments</div>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className="col-span-3 text-right">Unknown</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{parsedInstruction instanceof PythMultisigInstruction ||
|
||||||
|
parsedInstruction instanceof WormholeMultisigInstruction ||
|
||||||
|
parsedInstruction instanceof MessageBufferMultisigInstruction ? (
|
||||||
|
<div
|
||||||
|
key={`${index}_accounts`}
|
||||||
|
className="grid grid-cols-4 justify-between"
|
||||||
|
>
|
||||||
|
<div>Accounts</div>
|
||||||
|
{Object.keys(parsedInstruction.accounts.named).length > 0 ? (
|
||||||
|
<div className="col-span-4 mt-2 bg-[#444157] p-4 lg:col-span-3 lg:mt-0">
|
||||||
|
<div className="base16 flex justify-between pt-2 pb-6 font-semibold opacity-60">
|
||||||
|
<div>Account</div>
|
||||||
|
<div>Pubkey</div>
|
||||||
|
</div>
|
||||||
|
{Object.keys(parsedInstruction.accounts.named).map(
|
||||||
|
(key, index) => (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex justify-between border-t border-beige-300 py-3"
|
||||||
|
>
|
||||||
|
<div className="max-w-[80px] break-words sm:max-w-none sm:break-normal">
|
||||||
|
{key}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2 sm:flex sm:space-y-0 sm:space-x-2">
|
||||||
|
<div className="flex items-center space-x-2 sm:ml-2">
|
||||||
|
{parsedInstruction.accounts.named[key]
|
||||||
|
.isSigner ? (
|
||||||
|
<SignerTag />
|
||||||
|
) : null}
|
||||||
|
{parsedInstruction.accounts.named[key]
|
||||||
|
.isWritable ? (
|
||||||
|
<WritableTag />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<CopyPubkey
|
||||||
|
pubkey={parsedInstruction.accounts.named[
|
||||||
|
key
|
||||||
|
].pubkey.toBase58()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{key === 'priceAccount' &&
|
||||||
|
parsedInstruction.accounts.named[
|
||||||
|
key
|
||||||
|
].pubkey.toBase58() in
|
||||||
|
priceAccountKeyToSymbolMapping ? (
|
||||||
|
<ParsedAccountPubkeyRow
|
||||||
|
key="priceAccountPubkey"
|
||||||
|
mapping={priceAccountKeyToSymbolMapping}
|
||||||
|
title="symbol"
|
||||||
|
pubkey={parsedInstruction.accounts.named[
|
||||||
|
key
|
||||||
|
].pubkey.toBase58()}
|
||||||
|
/>
|
||||||
|
) : key === 'productAccount' &&
|
||||||
|
parsedInstruction.accounts.named[
|
||||||
|
key
|
||||||
|
].pubkey.toBase58() in
|
||||||
|
productAccountKeyToSymbolMapping ? (
|
||||||
|
<ParsedAccountPubkeyRow
|
||||||
|
key="productAccountPubkey"
|
||||||
|
mapping={productAccountKeyToSymbolMapping}
|
||||||
|
title="symbol"
|
||||||
|
pubkey={parsedInstruction.accounts.named[
|
||||||
|
key
|
||||||
|
].pubkey.toBase58()}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
{parsedInstruction.accounts.remaining.map(
|
||||||
|
(accountMeta, index) => (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
key="rem-{index}"
|
||||||
|
className="flex justify-between border-t border-beige-300 py-3"
|
||||||
|
>
|
||||||
|
<div className="max-w-[80px] break-words sm:max-w-none sm:break-normal">
|
||||||
|
Remaining {index + 1}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2 sm:flex sm:space-y-0 sm:space-x-2">
|
||||||
|
<div className="flex items-center space-x-2 sm:ml-2">
|
||||||
|
{accountMeta.isSigner ? <SignerTag /> : null}
|
||||||
|
{accountMeta.isWritable ? (
|
||||||
|
<WritableTag />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<CopyPubkey
|
||||||
|
pubkey={accountMeta.pubkey.toBase58()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>No accounts</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : parsedInstruction instanceof UnrecognizedProgram ? (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
key={`${index}_programId`}
|
||||||
|
className="flex justify-between"
|
||||||
|
>
|
||||||
|
<div>Program ID</div>
|
||||||
|
<div>
|
||||||
|
{parsedInstruction.instruction.programId.toBase58()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div key={`${index}_data`} className="flex justify-between">
|
||||||
|
<div>Data</div>
|
||||||
|
<div>
|
||||||
|
{parsedInstruction.instruction.data.length > 0
|
||||||
|
? parsedInstruction.instruction.data.toString('hex')
|
||||||
|
: 'No data'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
key={`${index}_keys`}
|
||||||
|
className="grid grid-cols-4 justify-between"
|
||||||
|
>
|
||||||
|
<div>Keys</div>
|
||||||
|
<div className="col-span-4 mt-2 bg-darkGray4 p-4 lg:col-span-3 lg:mt-0">
|
||||||
|
<div className="base16 flex justify-between pt-2 pb-6 font-semibold opacity-60">
|
||||||
|
<div>Key #</div>
|
||||||
|
<div>Pubkey</div>
|
||||||
|
</div>
|
||||||
|
{parsedInstruction.instruction.keys.map((key, index) => (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex justify-between border-t border-beige-300 py-3"
|
||||||
|
>
|
||||||
|
<div>Key {index + 1}</div>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
{key.isSigner ? <SignerTag /> : null}
|
||||||
|
{key.isWritable ? <WritableTag /> : null}
|
||||||
|
<CopyPubkey pubkey={key.pubkey.toBase58()} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{governanceAction instanceof EvmUpgradeContract && (
|
||||||
|
<GovernanceInstructionView
|
||||||
|
instruction={governanceAction}
|
||||||
|
actionName={governanceAction.action}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
Address:
|
||||||
|
<CopyPubkey pubkey={'0x' + governanceAction.address} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{governanceAction instanceof CosmosUpgradeContract && (
|
||||||
|
<GovernanceInstructionView
|
||||||
|
instruction={governanceAction}
|
||||||
|
actionName={governanceAction.action}
|
||||||
|
content={<div>Code id:{governanceAction.codeId.toString()}</div>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{governanceAction instanceof AptosAuthorizeUpgradeContract && (
|
||||||
|
<GovernanceInstructionView
|
||||||
|
instruction={governanceAction}
|
||||||
|
actionName={governanceAction.action}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
Package hash:
|
||||||
|
<CopyPubkey pubkey={governanceAction.hash} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{governanceAction instanceof SetFee && (
|
||||||
|
<GovernanceInstructionView
|
||||||
|
instruction={governanceAction}
|
||||||
|
actionName={governanceAction.action}
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
New Fee Value: {governanceAction.newFeeValue.toString()}
|
||||||
|
</div>
|
||||||
|
<div>New Fee Expo: {governanceAction.newFeeExpo.toString()}</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{governanceAction instanceof SetDataSources && (
|
||||||
|
<GovernanceInstructionView
|
||||||
|
instruction={governanceAction}
|
||||||
|
actionName={governanceAction.actionName}
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
{governanceAction.dataSources.map((dataSource, idx) => (
|
||||||
|
<div key={idx}>
|
||||||
|
Datasource #{idx + 1}:
|
||||||
|
<ul className="px-4">
|
||||||
|
<li>Emitter Chain: {dataSource.emitterChain}</li>
|
||||||
|
<li>
|
||||||
|
Emitter Address:{' '}
|
||||||
|
<CopyPubkey pubkey={'0x' + dataSource.emitterAddress} />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{governanceAction instanceof EvmSetWormholeAddress && (
|
||||||
|
<GovernanceInstructionView
|
||||||
|
instruction={governanceAction}
|
||||||
|
actionName={governanceAction.action}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
New Wormhole Address:
|
||||||
|
<CopyPubkey pubkey={'0x' + governanceAction.address} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{governanceAction instanceof SetValidPeriod && (
|
||||||
|
<GovernanceInstructionView
|
||||||
|
instruction={governanceAction}
|
||||||
|
actionName={governanceAction.action}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
New Valid Period: {governanceAction.newValidPeriod.toString()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{governanceAction instanceof RequestGovernanceDataSourceTransfer && (
|
||||||
|
<GovernanceInstructionView
|
||||||
|
instruction={governanceAction}
|
||||||
|
actionName={governanceAction.action}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
Governance Data Source Index:{' '}
|
||||||
|
{governanceAction.governanceDataSourceIndex}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{governanceAction instanceof AuthorizeGovernanceDataSourceTransfer && (
|
||||||
|
<GovernanceInstructionView
|
||||||
|
instruction={governanceAction}
|
||||||
|
actionName={governanceAction.actionName}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
Claim Vaa hex:{' '}
|
||||||
|
<CopyPubkey pubkey={governanceAction.claimVaa.toString('hex')} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { PublicKey } from '@solana/web3.js'
|
||||||
|
|
||||||
|
export const getMappingCluster = (cluster: string) => {
|
||||||
|
if (cluster === 'mainnet-beta' || cluster === 'pythnet') {
|
||||||
|
return 'pythnet'
|
||||||
|
} else {
|
||||||
|
return 'pythtest'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a string is a pubkey
|
||||||
|
export const isPubkey = (str: string) => {
|
||||||
|
try {
|
||||||
|
new PublicKey(str)
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
|
@ -778,14 +778,18 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
|
||||||
) : (
|
) : (
|
||||||
<p className="mb-8 leading-6">No proposed changes.</p>
|
<p className="mb-8 leading-6">No proposed changes.</p>
|
||||||
)}
|
)}
|
||||||
{Object.keys(changes).length > 0 ? (
|
{Object.keys(changes).length > 0 && (
|
||||||
<button
|
<>
|
||||||
className="action-btn text-base"
|
<button
|
||||||
onClick={handleSendProposalButtonClick}
|
className="action-btn text-base"
|
||||||
>
|
onClick={handleSendProposalButtonClick}
|
||||||
{isSendProposalButtonLoading ? <Spinner /> : 'Send Proposal'}
|
disabled={isSendProposalButtonLoading || !proposeSquads}
|
||||||
</button>
|
>
|
||||||
) : null}
|
{isSendProposalButtonLoading ? <Spinner /> : 'Send Proposal'}
|
||||||
|
</button>
|
||||||
|
{!proposeSquads && <div>Please connect your wallet</div>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,25 +1,10 @@
|
||||||
import { Wallet } from '@coral-xyz/anchor'
|
|
||||||
import SquadsMesh from '@sqds/mesh'
|
import SquadsMesh from '@sqds/mesh'
|
||||||
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
|
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
|
||||||
import React, { createContext, useContext, useMemo } from 'react'
|
import React, { createContext, useContext, useMemo } from 'react'
|
||||||
import { MultisigInstruction } from 'xc_admin_common'
|
import { MultisigInstruction } from 'xc_admin_common'
|
||||||
import { useMultisig } from '../hooks/useMultisig'
|
import { useMultisig, MultisigHookData } from '../hooks/useMultisig'
|
||||||
|
|
||||||
// TODO: fix any
|
const MultisigContext = createContext<MultisigHookData>({
|
||||||
interface MultisigContextProps {
|
|
||||||
isLoading: boolean
|
|
||||||
error: any // TODO: fix any
|
|
||||||
proposeSquads: SquadsMesh | undefined
|
|
||||||
voteSquads: SquadsMesh | undefined
|
|
||||||
upgradeMultisigAccount: MultisigAccount | undefined
|
|
||||||
priceFeedMultisigAccount: MultisigAccount | undefined
|
|
||||||
upgradeMultisigProposals: TransactionAccount[]
|
|
||||||
priceFeedMultisigProposals: TransactionAccount[]
|
|
||||||
allProposalsIxsParsed: MultisigInstruction[][]
|
|
||||||
setpriceFeedMultisigProposals: any
|
|
||||||
}
|
|
||||||
|
|
||||||
const MultisigContext = createContext<MultisigContextProps>({
|
|
||||||
upgradeMultisigAccount: undefined,
|
upgradeMultisigAccount: undefined,
|
||||||
priceFeedMultisigAccount: undefined,
|
priceFeedMultisigAccount: undefined,
|
||||||
upgradeMultisigProposals: [],
|
upgradeMultisigProposals: [],
|
||||||
|
@ -29,6 +14,8 @@ const MultisigContext = createContext<MultisigContextProps>({
|
||||||
error: null,
|
error: null,
|
||||||
proposeSquads: undefined,
|
proposeSquads: undefined,
|
||||||
voteSquads: undefined,
|
voteSquads: undefined,
|
||||||
|
refreshData: undefined,
|
||||||
|
connection: undefined,
|
||||||
setpriceFeedMultisigProposals: () => {},
|
setpriceFeedMultisigProposals: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -36,12 +23,11 @@ export const useMultisigContext = () => useContext(MultisigContext)
|
||||||
|
|
||||||
interface MultisigContextProviderProps {
|
interface MultisigContextProviderProps {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
wallet: Wallet
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MultisigContextProvider: React.FC<
|
export const MultisigContextProvider: React.FC<
|
||||||
MultisigContextProviderProps
|
MultisigContextProviderProps
|
||||||
> = ({ children, wallet }) => {
|
> = ({ children }) => {
|
||||||
const {
|
const {
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
|
@ -53,7 +39,9 @@ export const MultisigContextProvider: React.FC<
|
||||||
priceFeedMultisigProposals,
|
priceFeedMultisigProposals,
|
||||||
allProposalsIxsParsed,
|
allProposalsIxsParsed,
|
||||||
setpriceFeedMultisigProposals,
|
setpriceFeedMultisigProposals,
|
||||||
} = useMultisig(wallet)
|
refreshData,
|
||||||
|
connection,
|
||||||
|
} = useMultisig()
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -67,6 +55,8 @@ export const MultisigContextProvider: React.FC<
|
||||||
error,
|
error,
|
||||||
proposeSquads,
|
proposeSquads,
|
||||||
voteSquads,
|
voteSquads,
|
||||||
|
refreshData,
|
||||||
|
connection,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
proposeSquads,
|
proposeSquads,
|
||||||
|
@ -79,6 +69,8 @@ export const MultisigContextProvider: React.FC<
|
||||||
priceFeedMultisigProposals,
|
priceFeedMultisigProposals,
|
||||||
allProposalsIxsParsed,
|
allProposalsIxsParsed,
|
||||||
setpriceFeedMultisigProposals,
|
setpriceFeedMultisigProposals,
|
||||||
|
refreshData,
|
||||||
|
connection,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,24 @@
|
||||||
import React, { createContext, useContext, useMemo } from 'react'
|
import React, {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
import usePyth from '../hooks/usePyth'
|
import usePyth from '../hooks/usePyth'
|
||||||
import { RawConfig } from '../hooks/usePyth'
|
import { RawConfig } from '../hooks/usePyth'
|
||||||
|
|
||||||
// TODO: fix any
|
// TODO: fix any
|
||||||
|
type AccountKeyToSymbol = { [key: string]: string }
|
||||||
interface PythContextProps {
|
interface PythContextProps {
|
||||||
rawConfig: RawConfig
|
rawConfig: RawConfig
|
||||||
dataIsLoading: boolean
|
dataIsLoading: boolean
|
||||||
error: any
|
error: any
|
||||||
connection: any
|
connection: any
|
||||||
|
priceAccountKeyToSymbolMapping: AccountKeyToSymbol
|
||||||
|
productAccountKeyToSymbolMapping: AccountKeyToSymbol
|
||||||
|
publisherKeyToNameMapping: Record<string, Record<string, string>>
|
||||||
|
multisigSignerKeyToNameMapping: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
const PythContext = createContext<PythContextProps>({
|
const PythContext = createContext<PythContextProps>({
|
||||||
|
@ -15,20 +26,47 @@ const PythContext = createContext<PythContextProps>({
|
||||||
dataIsLoading: true,
|
dataIsLoading: true,
|
||||||
error: null,
|
error: null,
|
||||||
connection: null,
|
connection: null,
|
||||||
|
priceAccountKeyToSymbolMapping: {},
|
||||||
|
productAccountKeyToSymbolMapping: {},
|
||||||
|
publisherKeyToNameMapping: {},
|
||||||
|
multisigSignerKeyToNameMapping: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const usePythContext = () => useContext(PythContext)
|
export const usePythContext = () => useContext(PythContext)
|
||||||
|
|
||||||
interface PythContextProviderProps {
|
interface PythContextProviderProps {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
symbols?: string[]
|
publisherKeyToNameMapping: Record<string, Record<string, string>>
|
||||||
raw?: boolean
|
multisigSignerKeyToNameMapping: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PythContextProvider: React.FC<PythContextProviderProps> = ({
|
export const PythContextProvider: React.FC<PythContextProviderProps> = ({
|
||||||
children,
|
children,
|
||||||
|
publisherKeyToNameMapping,
|
||||||
|
multisigSignerKeyToNameMapping,
|
||||||
}) => {
|
}) => {
|
||||||
const { isLoading, error, connection, rawConfig } = usePyth()
|
const { isLoading, error, connection, rawConfig } = usePyth()
|
||||||
|
const [
|
||||||
|
productAccountKeyToSymbolMapping,
|
||||||
|
setProductAccountKeyToSymbolMapping,
|
||||||
|
] = useState<AccountKeyToSymbol>({})
|
||||||
|
const [priceAccountKeyToSymbolMapping, setPriceAccountKeyToSymbolMapping] =
|
||||||
|
useState<AccountKeyToSymbol>({})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading) {
|
||||||
|
const productAccountMapping: AccountKeyToSymbol = {}
|
||||||
|
const priceAccountMapping: AccountKeyToSymbol = {}
|
||||||
|
rawConfig.mappingAccounts.map((acc) =>
|
||||||
|
acc.products.map((prod) => {
|
||||||
|
productAccountMapping[prod.address.toBase58()] = prod.metadata.symbol
|
||||||
|
priceAccountMapping[prod.priceAccounts[0].address.toBase58()] =
|
||||||
|
prod.metadata.symbol
|
||||||
|
})
|
||||||
|
)
|
||||||
|
setProductAccountKeyToSymbolMapping(productAccountMapping)
|
||||||
|
setPriceAccountKeyToSymbolMapping(priceAccountMapping)
|
||||||
|
}
|
||||||
|
}, [rawConfig, isLoading])
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -36,8 +74,19 @@ export const PythContextProvider: React.FC<PythContextProviderProps> = ({
|
||||||
dataIsLoading: isLoading,
|
dataIsLoading: isLoading,
|
||||||
error,
|
error,
|
||||||
connection,
|
connection,
|
||||||
|
priceAccountKeyToSymbolMapping,
|
||||||
|
productAccountKeyToSymbolMapping,
|
||||||
|
publisherKeyToNameMapping,
|
||||||
|
multisigSignerKeyToNameMapping,
|
||||||
}),
|
}),
|
||||||
[rawConfig, isLoading, error, connection]
|
[
|
||||||
|
rawConfig,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
connection,
|
||||||
|
publisherKeyToNameMapping,
|
||||||
|
multisigSignerKeyToNameMapping,
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
return <PythContext.Provider value={value}>{children}</PythContext.Provider>
|
return <PythContext.Provider value={value}>{children}</PythContext.Provider>
|
||||||
|
|
|
@ -1,35 +1,20 @@
|
||||||
import { Wallet } from '@coral-xyz/anchor'
|
|
||||||
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
|
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
|
||||||
import { getPythProgramKeyForCluster } from '@pythnetwork/client'
|
|
||||||
import { useAnchorWallet } from '@solana/wallet-adapter-react'
|
import { useAnchorWallet } from '@solana/wallet-adapter-react'
|
||||||
import {
|
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
|
||||||
AccountMeta,
|
|
||||||
Cluster,
|
|
||||||
Connection,
|
|
||||||
Keypair,
|
|
||||||
PublicKey,
|
|
||||||
} from '@solana/web3.js'
|
|
||||||
import SquadsMesh from '@sqds/mesh'
|
import SquadsMesh from '@sqds/mesh'
|
||||||
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
|
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
|
||||||
import { useContext, useEffect, useState } from 'react'
|
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
ExecutePostedVaa,
|
|
||||||
getManyProposalsInstructions,
|
|
||||||
getMultisigCluster,
|
getMultisigCluster,
|
||||||
getProposals,
|
getProposals,
|
||||||
isRemoteCluster,
|
|
||||||
MultisigInstruction,
|
MultisigInstruction,
|
||||||
MultisigParser,
|
|
||||||
PRICE_FEED_MULTISIG,
|
PRICE_FEED_MULTISIG,
|
||||||
PythMultisigInstruction,
|
|
||||||
UnrecognizedProgram,
|
|
||||||
UPGRADE_MULTISIG,
|
UPGRADE_MULTISIG,
|
||||||
WormholeMultisigInstruction,
|
|
||||||
} from 'xc_admin_common'
|
} from 'xc_admin_common'
|
||||||
import { ClusterContext } from '../contexts/ClusterContext'
|
import { ClusterContext } from '../contexts/ClusterContext'
|
||||||
import { pythClusterApiUrls } from '../utils/pythClusterApiUrl'
|
import { pythClusterApiUrls } from '../utils/pythClusterApiUrl'
|
||||||
|
|
||||||
interface MultisigHookData {
|
export interface MultisigHookData {
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
error: any // TODO: fix any
|
error: any // TODO: fix any
|
||||||
proposeSquads: SquadsMesh | undefined
|
proposeSquads: SquadsMesh | undefined
|
||||||
|
@ -39,6 +24,8 @@ interface MultisigHookData {
|
||||||
upgradeMultisigProposals: TransactionAccount[]
|
upgradeMultisigProposals: TransactionAccount[]
|
||||||
priceFeedMultisigProposals: TransactionAccount[]
|
priceFeedMultisigProposals: TransactionAccount[]
|
||||||
allProposalsIxsParsed: MultisigInstruction[][]
|
allProposalsIxsParsed: MultisigInstruction[][]
|
||||||
|
connection?: Connection
|
||||||
|
refreshData?: () => { fetchData: () => Promise<void>; cancel: () => void }
|
||||||
setpriceFeedMultisigProposals: React.Dispatch<
|
setpriceFeedMultisigProposals: React.Dispatch<
|
||||||
React.SetStateAction<TransactionAccount[]>
|
React.SetStateAction<TransactionAccount[]>
|
||||||
>
|
>
|
||||||
|
@ -52,7 +39,8 @@ const getSortedProposals = async (
|
||||||
return proposals.sort((a, b) => b.transactionIndex - a.transactionIndex)
|
return proposals.sort((a, b) => b.transactionIndex - a.transactionIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMultisig = (wallet: Wallet): MultisigHookData => {
|
export const useMultisig = (): MultisigHookData => {
|
||||||
|
const wallet = useAnchorWallet()
|
||||||
const { cluster } = useContext(ClusterContext)
|
const { cluster } = useContext(ClusterContext)
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
|
@ -69,14 +57,11 @@ export const useMultisig = (wallet: Wallet): MultisigHookData => {
|
||||||
const [allProposalsIxsParsed, setAllProposalsIxsParsed] = useState<
|
const [allProposalsIxsParsed, setAllProposalsIxsParsed] = useState<
|
||||||
MultisigInstruction[][]
|
MultisigInstruction[][]
|
||||||
>([])
|
>([])
|
||||||
const [proposeSquads, setProposeSquads] = useState<SquadsMesh>()
|
const [squads, setSquads] = useState<SquadsMesh | undefined>()
|
||||||
const [voteSquads, setVoteSquads] = useState<SquadsMesh>()
|
|
||||||
const anchorWallet = useAnchorWallet()
|
|
||||||
|
|
||||||
const [urlsIndex, setUrlsIndex] = useState(0)
|
const [urlsIndex, setUrlsIndex] = useState(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true)
|
|
||||||
setError(null)
|
setError(null)
|
||||||
}, [urlsIndex, cluster])
|
}, [urlsIndex, cluster])
|
||||||
|
|
||||||
|
@ -84,39 +69,34 @@ export const useMultisig = (wallet: Wallet): MultisigHookData => {
|
||||||
setUrlsIndex(0)
|
setUrlsIndex(0)
|
||||||
}, [cluster])
|
}, [cluster])
|
||||||
|
|
||||||
useEffect(() => {
|
const multisigCluster = useMemo(() => getMultisigCluster(cluster), [cluster])
|
||||||
const urls = pythClusterApiUrls(getMultisigCluster(cluster))
|
|
||||||
const connection = new Connection(urls[urlsIndex].rpcUrl, {
|
const connection = useMemo(() => {
|
||||||
|
const urls = pythClusterApiUrls(multisigCluster)
|
||||||
|
return new Connection(urls[urlsIndex].rpcUrl, {
|
||||||
commitment: 'confirmed',
|
commitment: 'confirmed',
|
||||||
wsEndpoint: urls[urlsIndex].wsUrl,
|
wsEndpoint: urls[urlsIndex].wsUrl,
|
||||||
})
|
})
|
||||||
|
}, [urlsIndex, multisigCluster])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (wallet) {
|
if (wallet) {
|
||||||
setProposeSquads(
|
setSquads(
|
||||||
new SquadsMesh({
|
new SquadsMesh({
|
||||||
connection,
|
connection,
|
||||||
wallet,
|
wallet,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
setSquads(undefined)
|
||||||
}
|
}
|
||||||
if (anchorWallet) {
|
}, [wallet, urlsIndex, cluster, connection])
|
||||||
setVoteSquads(
|
|
||||||
new SquadsMesh({
|
|
||||||
connection,
|
|
||||||
wallet: anchorWallet as Wallet,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, [wallet, urlsIndex, cluster, anchorWallet])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const refreshData = useCallback(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
const urls = pythClusterApiUrls(getMultisigCluster(cluster))
|
|
||||||
const connection = new Connection(urls[urlsIndex].rpcUrl, {
|
|
||||||
commitment: 'confirmed',
|
|
||||||
wsEndpoint: urls[urlsIndex].wsUrl,
|
|
||||||
})
|
|
||||||
|
|
||||||
;(async () => {
|
const fetchData = async () => {
|
||||||
|
setIsLoading(true)
|
||||||
try {
|
try {
|
||||||
// mock wallet to allow users to view proposals without connecting their wallet
|
// mock wallet to allow users to view proposals without connecting their wallet
|
||||||
const readOnlySquads = new SquadsMesh({
|
const readOnlySquads = new SquadsMesh({
|
||||||
|
@ -125,15 +105,13 @@ export const useMultisig = (wallet: Wallet): MultisigHookData => {
|
||||||
})
|
})
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
setUpgradeMultisigAccount(
|
setUpgradeMultisigAccount(
|
||||||
await readOnlySquads.getMultisig(
|
await readOnlySquads.getMultisig(UPGRADE_MULTISIG[multisigCluster])
|
||||||
UPGRADE_MULTISIG[getMultisigCluster(cluster)]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
setpriceFeedMultisigAccount(
|
setpriceFeedMultisigAccount(
|
||||||
await readOnlySquads.getMultisig(
|
await readOnlySquads.getMultisig(
|
||||||
PRICE_FEED_MULTISIG[getMultisigCluster(cluster)]
|
PRICE_FEED_MULTISIG[multisigCluster]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -142,67 +120,18 @@ export const useMultisig = (wallet: Wallet): MultisigHookData => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
setUpgradeMultisigProposals(
|
const upgradeProposals = await getSortedProposals(
|
||||||
await getSortedProposals(
|
readOnlySquads,
|
||||||
readOnlySquads,
|
UPGRADE_MULTISIG[multisigCluster]
|
||||||
UPGRADE_MULTISIG[getMultisigCluster(cluster)]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
setUpgradeMultisigProposals(upgradeProposals)
|
||||||
try {
|
try {
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
const sortedPriceFeedMultisigProposals = await getSortedProposals(
|
const sortedPriceFeedMultisigProposals = await getSortedProposals(
|
||||||
readOnlySquads,
|
readOnlySquads,
|
||||||
PRICE_FEED_MULTISIG[getMultisigCluster(cluster)]
|
PRICE_FEED_MULTISIG[multisigCluster]
|
||||||
)
|
)
|
||||||
const allProposalsIxs = await getManyProposalsInstructions(
|
setpriceFeedMultisigProposals(sortedPriceFeedMultisigProposals)
|
||||||
readOnlySquads,
|
|
||||||
sortedPriceFeedMultisigProposals
|
|
||||||
)
|
|
||||||
const multisigParser = MultisigParser.fromCluster(
|
|
||||||
getMultisigCluster(cluster)
|
|
||||||
)
|
|
||||||
const parsedAllProposalsIxs = allProposalsIxs.map((ixs) =>
|
|
||||||
ixs.map((ix) =>
|
|
||||||
multisigParser.parseInstruction({
|
|
||||||
programId: ix.programId,
|
|
||||||
data: ix.data as Buffer,
|
|
||||||
keys: ix.keys as AccountMeta[],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
const proposalsRes: TransactionAccount[] = []
|
|
||||||
const instructionsRes: MultisigInstruction[][] = []
|
|
||||||
// filter proposals for respective devnet/pythtest and mainnet-beta/pythnet clusters
|
|
||||||
parsedAllProposalsIxs.map((ixs, idx) => {
|
|
||||||
// pythtest/pythnet proposals
|
|
||||||
if (
|
|
||||||
isRemoteCluster(cluster) &&
|
|
||||||
ixs.length > 0 &&
|
|
||||||
ixs.some(
|
|
||||||
(ix) =>
|
|
||||||
ix instanceof WormholeMultisigInstruction &&
|
|
||||||
ix.governanceAction instanceof ExecutePostedVaa &&
|
|
||||||
ix.governanceAction.instructions.some((ix) =>
|
|
||||||
ix.programId.equals(getPythProgramKeyForCluster(cluster))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
proposalsRes.push(sortedPriceFeedMultisigProposals[idx])
|
|
||||||
instructionsRes.push(ixs)
|
|
||||||
}
|
|
||||||
// devnet/testnet/mainnet-beta proposals
|
|
||||||
if (
|
|
||||||
!isRemoteCluster(cluster) &&
|
|
||||||
(ixs.length === 0 ||
|
|
||||||
ixs.some((ix) => ix instanceof PythMultisigInstruction) ||
|
|
||||||
ixs.some((ix) => ix instanceof UnrecognizedProgram))
|
|
||||||
) {
|
|
||||||
proposalsRes.push(sortedPriceFeedMultisigProposals[idx])
|
|
||||||
instructionsRes.push(ixs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setAllProposalsIxsParsed(instructionsRes)
|
|
||||||
setpriceFeedMultisigProposals(proposalsRes)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
setAllProposalsIxsParsed([])
|
setAllProposalsIxsParsed([])
|
||||||
|
@ -213,6 +142,7 @@ export const useMultisig = (wallet: Wallet): MultisigHookData => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
|
const urls = pythClusterApiUrls(multisigCluster)
|
||||||
if (urlsIndex === urls.length - 1) {
|
if (urlsIndex === urls.length - 1) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
setError(e)
|
setError(e)
|
||||||
|
@ -225,23 +155,32 @@ export const useMultisig = (wallet: Wallet): MultisigHookData => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
}
|
||||||
|
const cancel = () => {
|
||||||
return () => {
|
|
||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [urlsIndex, cluster])
|
|
||||||
|
return { cancel, fetchData }
|
||||||
|
}, [multisigCluster, urlsIndex, connection])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { cancel, fetchData } = refreshData()
|
||||||
|
fetchData()
|
||||||
|
return cancel
|
||||||
|
}, [refreshData])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
proposeSquads,
|
proposeSquads: squads,
|
||||||
voteSquads,
|
voteSquads: squads,
|
||||||
upgradeMultisigAccount,
|
upgradeMultisigAccount,
|
||||||
priceFeedMultisigAccount,
|
priceFeedMultisigAccount,
|
||||||
upgradeMultisigProposals,
|
upgradeMultisigProposals,
|
||||||
priceFeedMultisigProposals,
|
priceFeedMultisigProposals,
|
||||||
allProposalsIxsParsed,
|
allProposalsIxsParsed,
|
||||||
|
refreshData,
|
||||||
|
connection,
|
||||||
setpriceFeedMultisigProposals,
|
setpriceFeedMultisigProposals,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import { Wallet } from '@coral-xyz/anchor'
|
|
||||||
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
|
|
||||||
import { Tab } from '@headlessui/react'
|
import { Tab } from '@headlessui/react'
|
||||||
import { useAnchorWallet } from '@solana/wallet-adapter-react'
|
|
||||||
import { Keypair } from '@solana/web3.js'
|
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import type { GetServerSideProps, NextPage } from 'next'
|
import type { GetServerSideProps, NextPage } from 'next'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
@ -88,8 +84,6 @@ const Home: NextPage<{
|
||||||
}) => {
|
}) => {
|
||||||
const [currentTabIndex, setCurrentTabIndex] = useState(0)
|
const [currentTabIndex, setCurrentTabIndex] = useState(0)
|
||||||
const tabInfoArray = Object.values(TAB_INFO)
|
const tabInfoArray = Object.values(TAB_INFO)
|
||||||
const anchorWallet = useAnchorWallet()
|
|
||||||
const wallet = anchorWallet as Wallet
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
@ -123,8 +117,11 @@ const Home: NextPage<{
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<PythContextProvider>
|
<PythContextProvider
|
||||||
<MultisigContextProvider wallet={wallet}>
|
publisherKeyToNameMapping={publisherKeyToNameMapping}
|
||||||
|
multisigSignerKeyToNameMapping={multisigSignerKeyToNameMapping}
|
||||||
|
>
|
||||||
|
<MultisigContextProvider>
|
||||||
<div className="container relative pt-16 md:pt-20">
|
<div className="container relative pt-16 md:pt-20">
|
||||||
<div className="py-8 md:py-16">
|
<div className="py-8 md:py-16">
|
||||||
<Tab.Group
|
<Tab.Group
|
||||||
|
@ -150,20 +147,17 @@ const Home: NextPage<{
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{tabInfoArray[currentTabIndex].queryString ===
|
{tabInfoArray[currentTabIndex].queryString ===
|
||||||
TAB_INFO.General.queryString ? (
|
TAB_INFO.General.queryString && (
|
||||||
<General proposerServerUrl={proposerServerUrl} />
|
<General proposerServerUrl={proposerServerUrl} />
|
||||||
) : tabInfoArray[currentTabIndex].queryString ===
|
)}
|
||||||
TAB_INFO.UpdatePermissions.queryString ? (
|
{tabInfoArray[currentTabIndex].queryString ===
|
||||||
<UpdatePermissions />
|
TAB_INFO.UpdatePermissions.queryString && <UpdatePermissions />}
|
||||||
) : tabInfoArray[currentTabIndex].queryString ===
|
{tabInfoArray[currentTabIndex].queryString ===
|
||||||
TAB_INFO.Proposals.queryString ? (
|
TAB_INFO.Proposals.queryString && (
|
||||||
<StatusFilterProvider>
|
<StatusFilterProvider>
|
||||||
<Proposals
|
<Proposals />
|
||||||
publisherKeyToNameMapping={publisherKeyToNameMapping}
|
|
||||||
multisigSignerKeyToNameMapping={multisigSignerKeyToNameMapping}
|
|
||||||
/>
|
|
||||||
</StatusFilterProvider>
|
</StatusFilterProvider>
|
||||||
) : null}
|
)}
|
||||||
</MultisigContextProvider>
|
</MultisigContextProvider>
|
||||||
</PythContextProvider>
|
</PythContextProvider>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -278,13 +278,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialogTitle {
|
.dialogTitle {
|
||||||
@apply mb-8 text-center font-body text-[32px] leading-[1.1] lg:mb-11 lg:text-[44px] px-10;
|
@apply mb-8 px-10 text-center font-body text-[32px] leading-[1.1] lg:mb-11 lg:text-[44px];
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
@apply h-[45px] rounded-full bg-pythPurple px-8 font-mono font-semibold uppercase leading-none transition-colors hover:bg-mediumSlateBlue disabled:opacity-70 disabled:hover:bg-pythPurple;
|
@apply h-[45px] rounded-full bg-pythPurple px-8 font-mono font-semibold uppercase leading-none transition-colors hover:bg-mediumSlateBlue disabled:opacity-70 disabled:hover:bg-pythPurple;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-action-btn {
|
.sub-action-btn {
|
||||||
@apply h-[45px] rounded-full bg-darkGray2 px-8 font-mono font-semibold uppercase leading-none transition-colors hover:bg-darkGray4 disabled:opacity-70 disabled:hover:bg-darkGray2;
|
@apply h-[45px] rounded-full bg-darkGray2 px-8 font-mono font-semibold uppercase leading-none transition-colors hover:bg-darkGray4 disabled:opacity-70 disabled:hover:bg-darkGray2;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue