import { Program } from '@coral-xyz/anchor' import { Dialog, Menu, Transition } from '@headlessui/react' import { PythOracle } from '@pythnetwork/client/lib/anchor' import * as Label from '@radix-ui/react-label' import { PublicKey, TransactionInstruction } from '@solana/web3.js' import SquadsMesh from '@sqds/mesh' import axios from 'axios' import { Fragment, useContext, useEffect, useState } from 'react' import toast from 'react-hot-toast' import { createDetermisticPriceStoreInitializePublisherInstruction, getMaximumNumberOfPublishers, getMultisigCluster, isPriceStorePublisherInitialized, isRemoteCluster, mapKey, PRICE_FEED_MULTISIG, } from '@pythnetwork/xc-admin-common' import { ClusterContext } from '../contexts/ClusterContext' import { usePythContext } from '../contexts/PythContext' import { ProductRawConfig } from '../hooks/usePyth' import Arrow from '@images/icons/down.inline.svg' import { capitalizeFirstLetter } from '../utils/capitalizeFirstLetter' import Spinner from './common/Spinner' import CloseIcon from './icons/CloseIcon' const assetTypes = [ 'All', 'Crypto', 'Equity', 'FX', 'Metal', 'Rates', 'Commodities', ] const PermissionDepermissionKey = ({ isPermission, pythProgramClient, readOnlySquads, proposerServerUrl, }: { isPermission: boolean pythProgramClient?: Program readOnlySquads: SquadsMesh proposerServerUrl: string }) => { const [publisherKey, setPublisherKey] = useState( 'JTmFx5zX9mM94itfk2nQcJnQQDPjcv4UPD7SYj6xDCV' ) const [selectedAssetType, setSelectedAssetType] = useState('All') const [isModalOpen, setIsModalOpen] = useState(false) const [isSubmitButtonLoading, setIsSubmitButtonLoading] = useState(false) const [priceAccounts, setPriceAccounts] = useState([]) const { cluster } = useContext(ClusterContext) const { rawConfig, dataIsLoading, connection } = usePythContext() // get current input value const handleChange = (event: any) => { setSelectedAssetType(event.target.value) setIsModalOpen(true) } const closeModal = () => { setIsModalOpen(false) } const onKeyChange = (event: React.SyntheticEvent) => { const { currentTarget: { value }, } = event setPublisherKey(value) } const handleSubmitButton = async () => { if (pythProgramClient) { const instructions: TransactionInstruction[] = [] const multisigAuthority = readOnlySquads.getAuthorityPDA( PRICE_FEED_MULTISIG[getMultisigCluster(cluster)], 1 ) const isRemote: boolean = isRemoteCluster(cluster) const fundingAccount = isRemote ? mapKey(multisigAuthority) : multisigAuthority const publisherPublicKey = new PublicKey(publisherKey) for (const priceAccount of priceAccounts) { if (isPermission) { instructions.push( await pythProgramClient.methods .addPublisher(publisherPublicKey) .accounts({ fundingAccount, priceAccount: priceAccount, }) .instruction() ) } else { instructions.push( await pythProgramClient.methods .delPublisher(publisherPublicKey) .accounts({ fundingAccount, priceAccount: priceAccount, }) .instruction() ) } } if (isPermission) { if ( !connection || !(await isPriceStorePublisherInitialized( connection, publisherPublicKey )) ) { instructions.push( await createDetermisticPriceStoreInitializePublisherInstruction( fundingAccount, publisherPublicKey ) ) } } setIsSubmitButtonLoading(true) try { const response = await axios.post(proposerServerUrl + '/api/propose', { instructions, cluster, }) const { proposalPubkey } = response.data toast.success(`Proposal sent! 🚀 Proposal Pubkey: ${proposalPubkey}`) setIsSubmitButtonLoading(false) closeModal() } catch (error: any) { if (error.response) { toast.error(capitalizeFirstLetter(error.response.data)) } else { toast.error(capitalizeFirstLetter(error.message)) } setIsSubmitButtonLoading(false) } } } useEffect(() => { if (!dataIsLoading) { const res: PublicKey[] = [] rawConfig.mappingAccounts[0].products.map((product: ProductRawConfig) => { const publisherExists = product.priceAccounts[0].publishers.find( (p) => p.toBase58() === publisherKey ) !== undefined if ( (selectedAssetType === 'All' || product.metadata.asset_type === selectedAssetType) && ((isPermission && product.priceAccounts[0].publishers.length < getMaximumNumberOfPublishers(cluster) && !publisherExists) || (!isPermission && publisherExists)) ) { res.push(product.priceAccounts[0].address) } }) setPriceAccounts(res) } }, [rawConfig, dataIsLoading, selectedAssetType, isPermission, publisherKey]) return ( <> {({ open }) => ( <> {isPermission ? 'Permission Key' : 'Depermission Key'} {assetTypes.map((a) => ( ))} )} setIsModalOpen(false)} >
{isPermission ? 'Permission' : 'Depermission'} Publisher Key
Asset Type: {selectedAssetType}
Key
) } export default PermissionDepermissionKey