Ver código fonte

[xc-admin] remove unused tabs (#596)

Daniel Chew 2 anos atrás
pai
commit
e841989fa2

+ 0 - 391
governance/xc_admin/packages/xc_admin_frontend/components/tabs/AddRemovePublishers.tsx

@@ -1,391 +0,0 @@
-import { AnchorProvider, Program, Wallet } from '@coral-xyz/anchor'
-import {
-  getPythProgramKeyForCluster,
-  pythOracleProgram,
-} from '@pythnetwork/client'
-import { PythOracle } from '@pythnetwork/client/lib/anchor'
-import { useAnchorWallet, useWallet } from '@solana/wallet-adapter-react'
-import { WalletModalButton } from '@solana/wallet-adapter-react-ui'
-import { PublicKey, TransactionInstruction } from '@solana/web3.js'
-import { Fragment, useContext, useEffect, useState } from 'react'
-import toast from 'react-hot-toast'
-import { getMultisigCluster, proposeInstructions } from 'xc_admin_common'
-import { ClusterContext } from '../../contexts/ClusterContext'
-import { usePythContext } from '../../contexts/PythContext'
-import { PRICE_FEED_MULTISIG, useMultisig } from '../../hooks/useMultisig'
-import { capitalizeFirstLetter } from '../../utils/capitalizeFirstLetter'
-import ClusterSwitch from '../ClusterSwitch'
-import Modal from '../common/Modal'
-import Spinner from '../common/Spinner'
-import Loadbar from '../loaders/Loadbar'
-
-interface SymbolToPublisherKeys {
-  [key: string]: PublicKey[]
-}
-
-interface PublishersInfo {
-  prev: string[]
-  new: string[]
-}
-
-let symbolToPriceAccountKeyMapping: Record<string, string> = {}
-
-const AddRemovePublishers = () => {
-  const [data, setData] = useState<SymbolToPublisherKeys>({})
-  const [publisherChanges, setPublisherChanges] =
-    useState<Record<string, PublishersInfo>>()
-  const [isModalOpen, setIsModalOpen] = useState(false)
-  const [isSendProposalButtonLoading, setIsSendProposalButtonLoading] =
-    useState(false)
-  const { cluster } = useContext(ClusterContext)
-  const anchorWallet = useAnchorWallet()
-  const { isLoading: isMultisigLoading, squads } = useMultisig(
-    anchorWallet as Wallet
-  )
-  const { rawConfig, dataIsLoading, connection } = usePythContext()
-  const { connected } = useWallet()
-  const [pythProgramClient, setPythProgramClient] =
-    useState<Program<PythOracle>>()
-
-  const openModal = () => {
-    setIsModalOpen(true)
-  }
-
-  const closeModal = () => {
-    setIsModalOpen(false)
-  }
-
-  useEffect(() => {
-    if (!dataIsLoading && rawConfig && rawConfig.mappingAccounts.length > 0) {
-      let symbolToPublisherKeysMapping: SymbolToPublisherKeys = {}
-      rawConfig.mappingAccounts.map((mappingAccount) => {
-        mappingAccount.products.map((product) => {
-          const priceAccount = product.priceAccounts.find(
-            (priceAccount) =>
-              priceAccount.address.toBase58() === product.metadata.price_account
-          )
-          if (priceAccount) {
-            symbolToPublisherKeysMapping[product.metadata.symbol] =
-              priceAccount.publishers
-            symbolToPriceAccountKeyMapping[product.metadata.symbol] =
-              priceAccount.address.toBase58()
-          }
-        })
-      })
-      symbolToPublisherKeysMapping = sortData(symbolToPublisherKeysMapping)
-      setData(symbolToPublisherKeysMapping)
-    }
-  }, [rawConfig, dataIsLoading])
-
-  const sortData = (data: SymbolToPublisherKeys) => {
-    let sortedSymbolToPublisherKeysMapping: SymbolToPublisherKeys = {}
-    // sort symbolToPublisherKeysMapping by symbol
-    sortedSymbolToPublisherKeysMapping = JSON.parse(
-      JSON.stringify(data, Object.keys(data).sort())
-    )
-    // sort symbolToPublisherKeysMapping by publisher keys
-    Object.keys(sortedSymbolToPublisherKeysMapping).forEach((key) => {
-      // sort publisher keys and make them each of type PublicKey because JSON.stringify makes them of type string
-      sortedSymbolToPublisherKeysMapping[key] =
-        sortedSymbolToPublisherKeysMapping[key]
-          .sort()
-          .map((publisherKey) => new PublicKey(publisherKey))
-    })
-    return sortedSymbolToPublisherKeysMapping
-  }
-
-  // function to download json file
-  const handleDownloadJsonButtonClick = () => {
-    const dataStr =
-      'data:text/json;charset=utf-8,' +
-      encodeURIComponent(JSON.stringify(data, null, 2))
-    const downloadAnchor = document.createElement('a')
-    downloadAnchor.setAttribute('href', dataStr)
-    downloadAnchor.setAttribute('download', 'publishers.json')
-    document.body.appendChild(downloadAnchor) // required for firefox
-    downloadAnchor.click()
-    downloadAnchor.remove()
-  }
-
-  // function to upload json file and update publisherChanges state
-  const handleUploadJsonButtonClick = () => {
-    const uploadAnchor = document.createElement('input')
-    uploadAnchor.setAttribute('type', 'file')
-    uploadAnchor.setAttribute('accept', '.json')
-    uploadAnchor.addEventListener('change', (e) => {
-      const file = (e.target as HTMLInputElement).files![0]
-      const reader = new FileReader()
-      reader.onload = (e) => {
-        if (e.target) {
-          const fileData = e.target.result
-          if (!isValidJson(fileData as string)) return
-          const fileDataParsed = sortData(JSON.parse(fileData as string))
-          const changes: Record<string, PublishersInfo> = {}
-          Object.keys(fileDataParsed).forEach((symbol) => {
-            if (
-              JSON.stringify(data[symbol]) !==
-              JSON.stringify(fileDataParsed[symbol])
-            ) {
-              changes[symbol] = { prev: [], new: [] }
-              changes[symbol].prev = data[symbol].map((p: PublicKey) =>
-                p.toBase58()
-              )
-              changes[symbol].new = fileDataParsed[symbol].map((p: PublicKey) =>
-                p.toBase58()
-              )
-            }
-          })
-          setPublisherChanges(changes)
-          openModal()
-        }
-      }
-      reader.readAsText(file)
-    })
-    document.body.appendChild(uploadAnchor) // required for firefox
-    uploadAnchor.click()
-    uploadAnchor.remove()
-  }
-
-  // check if uploaded json is valid json
-  const isValidJson = (json: string) => {
-    try {
-      JSON.parse(json)
-    } catch (e: any) {
-      toast.error(capitalizeFirstLetter(e.message))
-      return false
-    }
-    // check if json keys are existing products
-    const jsonParsed = JSON.parse(json)
-    const jsonSymbols = Object.keys(jsonParsed)
-    const existingSymbols = Object.keys(data)
-    // check that jsonSymbols is equal to existingSymbols no matter the order
-    if (
-      JSON.stringify(jsonSymbols.sort()) !==
-      JSON.stringify(existingSymbols.sort())
-    ) {
-      toast.error('Symbols in json file do not match existing symbols!')
-      return false
-    }
-    return true
-  }
-
-  const handleSendProposalButtonClick = async () => {
-    if (pythProgramClient && publisherChanges) {
-      const instructions: TransactionInstruction[] = []
-      Object.keys(publisherChanges).forEach((symbol) => {
-        const { prev, new: newPublisherKeys } = publisherChanges[symbol]
-        // prev and new are arrays of publisher pubkeys
-        // check if there are any new publishers to add by comparing prev and new
-        const publisherKeysToAdd = newPublisherKeys.filter(
-          (newPublisher) => !prev.includes(newPublisher)
-        )
-        // check if there are any publishers to remove by comparing prev and new
-        const publisherKeysToRemove = prev.filter(
-          (prevPublisher) => !newPublisherKeys.includes(prevPublisher)
-        )
-        // add instructions to add new publishers
-        publisherKeysToAdd.forEach((publisherKey) => {
-          pythProgramClient.methods
-            .addPublisher(new PublicKey(publisherKey))
-            .accounts({
-              fundingAccount: squads?.getAuthorityPDA(
-                PRICE_FEED_MULTISIG[getMultisigCluster(cluster)],
-                1
-              ),
-              priceAccount: new PublicKey(
-                symbolToPriceAccountKeyMapping[symbol]
-              ),
-            })
-            .instruction()
-            .then((instruction) => instructions.push(instruction))
-        })
-        // add instructions to remove publishers
-        publisherKeysToRemove.forEach((publisherKey) => {
-          pythProgramClient.methods
-            .delPublisher(new PublicKey(publisherKey))
-            .accounts({
-              fundingAccount: squads?.getAuthorityPDA(
-                PRICE_FEED_MULTISIG[getMultisigCluster(cluster)],
-                1
-              ),
-              priceAccount: new PublicKey(
-                symbolToPriceAccountKeyMapping[symbol]
-              ),
-            })
-            .instruction()
-            .then((instruction) => instructions.push(instruction))
-        })
-      })
-      if (!isMultisigLoading && squads) {
-        setIsSendProposalButtonLoading(true)
-        try {
-          const proposalPubkey = await proposeInstructions(
-            squads,
-            PRICE_FEED_MULTISIG[getMultisigCluster(cluster)],
-            instructions,
-            false
-          )
-          toast.success(`Proposal sent! 🚀 Proposal Pubkey: ${proposalPubkey}`)
-          setIsSendProposalButtonLoading(false)
-        } catch (e: any) {
-          toast.error(capitalizeFirstLetter(e.message))
-          setIsSendProposalButtonLoading(false)
-        }
-      }
-    }
-  }
-
-  const ModalContent = ({ changes }: { changes: any }) => {
-    return (
-      <>
-        {Object.keys(changes).length > 0 ? (
-          <table className="mb-10 w-full table-auto bg-darkGray text-left">
-            <thead>
-              <tr>
-                <th className="base16 py-8 pl-6 pr-2 font-semibold lg:pl-6">
-                  Description
-                </th>
-                <th className="base16 py-8 pl-1 pr-2 font-semibold lg:pl-6">
-                  ID
-                </th>
-              </tr>
-            </thead>
-            {Object.keys(changes).map((key) => {
-              const publisherKeysToAdd = changes[key].new.filter(
-                (newPublisher: string) =>
-                  !changes[key].prev.includes(newPublisher)
-              )
-              const publisherKeysToRemove = changes[key].prev.filter(
-                (prevPublisher: string) =>
-                  !changes[key].new.includes(prevPublisher)
-              )
-              return (
-                changes[key].prev !== changes[key].new && (
-                  <tbody>
-                    <Fragment key={key}>
-                      <tr>
-                        <td className="py-3 pl-6 pr-1 lg:pl-6">Product</td>
-                        <td className="py-3 pl-1 pr-8 lg:pl-6">{key}</td>
-                      </tr>
-                      {publisherKeysToAdd.length > 0 && (
-                        <tr>
-                          <td className="py-3 pl-6 pr-1 lg:pl-6">
-                            Add Publisher(s)
-                          </td>
-                          <td className="py-3 pl-1 pr-8 lg:pl-6">
-                            {publisherKeysToAdd.map((publisherKey: string) => (
-                              <span key={publisherKey} className="block">
-                                {publisherKey}
-                              </span>
-                            ))}
-                          </td>
-                        </tr>
-                      )}
-                      {publisherKeysToRemove.length > 0 && (
-                        <tr>
-                          <td className="py-3 pl-6 pr-1 lg:pl-6">
-                            Remove Publisher(s)
-                          </td>
-                          <td className="py-3 pl-1 pr-8 lg:pl-6">
-                            {publisherKeysToRemove.map(
-                              (publisherKey: string) => (
-                                <span key={publisherKey} className="block">
-                                  {publisherKey}
-                                </span>
-                              )
-                            )}
-                          </td>
-                        </tr>
-                      )}
-                    </Fragment>
-                  </tbody>
-                )
-              )
-            })}
-          </table>
-        ) : (
-          <p className="mb-8 leading-6">No proposed changes.</p>
-        )}
-        {Object.keys(changes).length > 0 ? (
-          !connected ? (
-            <div className="flex justify-center">
-              <WalletModalButton className="action-btn text-base" />
-            </div>
-          ) : (
-            <button
-              className="action-btn text-base"
-              onClick={handleSendProposalButtonClick}
-            >
-              {isSendProposalButtonLoading ? <Spinner /> : 'Send Proposal'}
-            </button>
-          )
-        ) : null}
-      </>
-    )
-  }
-
-  // create anchor wallet when connected
-  useEffect(() => {
-    if (connected) {
-      const provider = new AnchorProvider(
-        connection,
-        anchorWallet as Wallet,
-        AnchorProvider.defaultOptions()
-      )
-      setPythProgramClient(
-        pythOracleProgram(getPythProgramKeyForCluster(cluster), provider)
-      )
-    }
-  }, [anchorWallet, connection, connected, cluster])
-
-  return (
-    <div className="relative">
-      <Modal
-        isModalOpen={isModalOpen}
-        setIsModalOpen={setIsModalOpen}
-        closeModal={closeModal}
-        content={<ModalContent changes={publisherChanges} />}
-      />
-      <div className="container flex flex-col items-center justify-between lg:flex-row">
-        <div className="mb-4 w-full text-left lg:mb-0">
-          <h1 className="h1 mb-4">Add/Remove Publishers</h1>
-        </div>
-      </div>
-      <div className="container min-h-[50vh]">
-        <div className="flex justify-between">
-          <div className="mb-4 md:mb-0">
-            <ClusterSwitch />
-          </div>
-        </div>
-        <div className="relative mt-6">
-          {dataIsLoading ? (
-            <div className="mt-3">
-              <Loadbar theme="light" />
-            </div>
-          ) : (
-            <div className="flex items-center space-x-4">
-              <div className="mb-10">
-                <button
-                  className="action-btn text-base"
-                  onClick={handleDownloadJsonButtonClick}
-                >
-                  Download JSON
-                </button>
-              </div>
-              <div className="mb-10">
-                <button
-                  className="action-btn text-base"
-                  onClick={handleUploadJsonButtonClick}
-                >
-                  Upload JSON
-                </button>
-              </div>
-            </div>
-          )}
-        </div>
-      </div>
-    </div>
-  )
-}
-
-export default AddRemovePublishers

+ 0 - 361
governance/xc_admin/packages/xc_admin_frontend/components/tabs/MinPublishers.tsx

@@ -1,361 +0,0 @@
-import { AnchorProvider, Program, Wallet } from '@coral-xyz/anchor'
-import {
-  getPythProgramKeyForCluster,
-  pythOracleProgram,
-} from '@pythnetwork/client'
-import { PythOracle } from '@pythnetwork/client/lib/anchor'
-import { useAnchorWallet, useWallet } from '@solana/wallet-adapter-react'
-import { WalletModalButton } from '@solana/wallet-adapter-react-ui'
-import { TransactionInstruction } from '@solana/web3.js'
-import {
-  createColumnHelper,
-  flexRender,
-  getCoreRowModel,
-  useReactTable,
-} from '@tanstack/react-table'
-import { useContext, useEffect, useState } from 'react'
-import toast from 'react-hot-toast'
-import { getMultisigCluster, proposeInstructions } from 'xc_admin_common'
-import { ClusterContext } from '../../contexts/ClusterContext'
-import { usePythContext } from '../../contexts/PythContext'
-import { PRICE_FEED_MULTISIG, useMultisig } from '../../hooks/useMultisig'
-import { capitalizeFirstLetter } from '../../utils/capitalizeFirstLetter'
-import ClusterSwitch from '../ClusterSwitch'
-import Modal from '../common/Modal'
-import Spinner from '../common/Spinner'
-import EditButton from '../EditButton'
-import Loadbar from '../loaders/Loadbar'
-
-interface MinPublishersProps {
-  symbol: string
-  minPublishers: number
-  newMinPublishers?: number
-}
-
-interface MinPublishersInfo {
-  prev: number
-  new: number
-}
-
-const columnHelper = createColumnHelper<MinPublishersProps>()
-
-const defaultColumns = [
-  columnHelper.accessor('symbol', {
-    cell: (info) => info.getValue(),
-    header: () => <span>Symbol</span>,
-  }),
-  columnHelper.accessor('minPublishers', {
-    cell: (props) => {
-      const minPublishers = props.getValue()
-      return <span className="mr-2">{minPublishers}</span>
-    },
-    header: () => <span>Min Publishers</span>,
-  }),
-]
-
-const MinPublishers = () => {
-  const [data, setData] = useState<MinPublishersProps[]>([])
-  const [columns, setColumns] = useState(() => [...defaultColumns])
-  const [minPublishersChanges, setMinPublishersChanges] =
-    useState<Record<string, MinPublishersInfo>>()
-  const [editable, setEditable] = useState(false)
-  const [isModalOpen, setIsModalOpen] = useState(false)
-  const [isSendProposalButtonLoading, setIsSendProposalButtonLoading] =
-    useState(false)
-  const { cluster } = useContext(ClusterContext)
-  const anchorWallet = useAnchorWallet()
-  const { isLoading: isMultisigLoading, squads } = useMultisig(
-    anchorWallet as Wallet
-  )
-  const { rawConfig, dataIsLoading, connection } = usePythContext()
-  const { connected } = useWallet()
-  const [pythProgramClient, setPythProgramClient] =
-    useState<Program<PythOracle>>()
-
-  const openModal = () => {
-    setIsModalOpen(true)
-  }
-
-  const closeModal = () => {
-    setIsModalOpen(false)
-  }
-
-  const handleEditButtonClick = () => {
-    const nextState = !editable
-    if (nextState) {
-      const newColumns = [
-        ...defaultColumns,
-        columnHelper.accessor('newMinPublishers', {
-          cell: (info) => info.getValue(),
-          header: () => <span>New Min Publishers</span>,
-        }),
-      ]
-      setColumns(newColumns)
-    } else {
-      if (
-        minPublishersChanges &&
-        Object.keys(minPublishersChanges).length > 0
-      ) {
-        openModal()
-        setMinPublishersChanges(minPublishersChanges)
-      } else {
-        setColumns(defaultColumns)
-      }
-    }
-
-    setEditable(nextState)
-  }
-
-  const handleEditMinPublishers = (
-    e: any,
-    symbol: string,
-    prevMinPublishers: number
-  ) => {
-    const newMinPublishers = Number(e.target.textContent)
-    if (prevMinPublishers !== newMinPublishers) {
-      setMinPublishersChanges({
-        ...minPublishersChanges,
-        [symbol]: {
-          prev: prevMinPublishers,
-          new: newMinPublishers,
-        },
-      })
-    } else {
-      // delete symbol from minPublishersChanges if it exists
-      if (minPublishersChanges && minPublishersChanges[symbol]) {
-        delete minPublishersChanges[symbol]
-      }
-      setMinPublishersChanges(minPublishersChanges)
-    }
-  }
-
-  useEffect(() => {
-    if (!dataIsLoading && rawConfig && rawConfig.mappingAccounts.length > 0) {
-      const minPublishersData: MinPublishersProps[] = []
-      rawConfig.mappingAccounts
-        .sort(
-          (mapping1, mapping2) =>
-            mapping2.products.length - mapping1.products.length
-        )[0]
-        .products.sort((product1, product2) =>
-          product1.metadata.symbol.localeCompare(product2.metadata.symbol)
-        )
-        .map((product) =>
-          product.priceAccounts.map((priceAccount) => {
-            minPublishersData.push({
-              symbol: product.metadata.symbol,
-              minPublishers: priceAccount.minPub,
-            })
-          })
-        )
-      setData(minPublishersData)
-    }
-  }, [setData, rawConfig, dataIsLoading])
-
-  const table = useReactTable({
-    data,
-    columns,
-    getCoreRowModel: getCoreRowModel(),
-  })
-
-  const handleSendProposalButtonClick = async () => {
-    if (pythProgramClient && minPublishersChanges) {
-      const instructions: TransactionInstruction[] = []
-      Object.keys(minPublishersChanges).forEach((symbol) => {
-        const { prev, new: newMinPublishers } = minPublishersChanges[symbol]
-        const priceAccountPubkey = rawConfig.mappingAccounts
-          .sort(
-            (mapping1, mapping2) =>
-              mapping2.products.length - mapping1.products.length
-          )[0]
-          .products.find((product) => product.metadata.symbol === symbol)!
-          .priceAccounts.find(
-            (priceAccount) => priceAccount.minPub === prev
-          )!.address
-
-        pythProgramClient.methods
-          .setMinPub(newMinPublishers, [0, 0, 0])
-          .accounts({
-            priceAccount: priceAccountPubkey,
-            fundingAccount: squads?.getAuthorityPDA(
-              PRICE_FEED_MULTISIG[getMultisigCluster(cluster)],
-              1
-            ),
-          })
-          .instruction()
-          .then((instruction) => instructions.push(instruction))
-      })
-      if (!isMultisigLoading && squads) {
-        setIsSendProposalButtonLoading(true)
-        try {
-          const proposalPubkey = await proposeInstructions(
-            squads,
-            PRICE_FEED_MULTISIG[getMultisigCluster(cluster)],
-            instructions,
-            false
-          )
-          toast.success(`Proposal sent! 🚀 Proposal Pubkey: ${proposalPubkey}`)
-          setIsSendProposalButtonLoading(false)
-        } catch (e: any) {
-          toast.error(capitalizeFirstLetter(e.message))
-          setIsSendProposalButtonLoading(false)
-        }
-      }
-    }
-  }
-
-  const ModalContent = ({ changes }: { changes: any }) => {
-    return (
-      <>
-        {Object.keys(changes).length > 0 ? (
-          <div className="mb-10">
-            {Object.keys(changes).map((key) => {
-              return (
-                changes[key].prev !== changes[key].new && (
-                  <>
-                    <div
-                      key={key}
-                      className="mb-4 flex items-center justify-between"
-                    >
-                      <span className="pr-4 text-left font-bold">{key}</span>
-                      <span className="mr-2">
-                        {changes[key].prev} &rarr; {changes[key].new}
-                      </span>
-                    </div>
-                  </>
-                )
-              )
-            })}
-          </div>
-        ) : (
-          <p className="mb-8 leading-6">No proposed changes.</p>
-        )}
-        {Object.keys(changes).length > 0 ? (
-          !connected ? (
-            <div className="flex justify-center">
-              <WalletModalButton className="action-btn text-base" />
-            </div>
-          ) : (
-            <button
-              className="action-btn text-base"
-              onClick={handleSendProposalButtonClick}
-            >
-              {isSendProposalButtonLoading ? <Spinner /> : 'Send Proposal'}
-            </button>
-          )
-        ) : null}
-      </>
-    )
-  }
-
-  // create anchor wallet when connected
-  useEffect(() => {
-    if (connected) {
-      const provider = new AnchorProvider(
-        connection,
-        anchorWallet as Wallet,
-        AnchorProvider.defaultOptions()
-      )
-      setPythProgramClient(
-        pythOracleProgram(getPythProgramKeyForCluster(cluster), provider)
-      )
-    }
-  }, [anchorWallet, connection, connected, cluster])
-
-  return (
-    <div className="relative">
-      <Modal
-        isModalOpen={isModalOpen}
-        setIsModalOpen={setIsModalOpen}
-        closeModal={closeModal}
-        content={<ModalContent changes={minPublishersChanges} />}
-      />
-      <div className="container flex flex-col items-center justify-between lg:flex-row">
-        <div className="mb-4 w-full text-left lg:mb-0">
-          <h1 className="h1 mb-4">Min Publishers</h1>
-        </div>
-      </div>
-      <div className="container min-h-[50vh]">
-        <div className="flex justify-between">
-          <div className="mb-4 md:mb-0">
-            <ClusterSwitch />
-          </div>
-          <div className="mb-4 md:mb-0">
-            <EditButton editable={editable} onClick={handleEditButtonClick} />
-          </div>
-        </div>
-        <div className="table-responsive relative mt-6">
-          {dataIsLoading ? (
-            <div className="mt-3">
-              <Loadbar theme="light" />
-            </div>
-          ) : (
-            <div className="table-responsive mb-10">
-              <table className="w-full table-auto bg-darkGray text-left">
-                <thead>
-                  {table.getHeaderGroups().map((headerGroup) => (
-                    <tr key={headerGroup.id}>
-                      {headerGroup.headers.map((header) => (
-                        <th
-                          key={header.id}
-                          className={
-                            header.column.id === 'symbol'
-                              ? 'base16 pt-8 pb-6 pl-4 pr-2 font-semibold opacity-60 xl:pl-14'
-                              : 'base16 pt-8 pb-6 pl-1 pr-2 font-semibold opacity-60'
-                          }
-                        >
-                          {header.isPlaceholder
-                            ? null
-                            : flexRender(
-                                header.column.columnDef.header,
-                                header.getContext()
-                              )}
-                        </th>
-                      ))}
-                    </tr>
-                  ))}
-                </thead>
-                <tbody>
-                  {table.getRowModel().rows.map((row) => (
-                    <tr key={row.id} className="border-t border-beige-300">
-                      {row.getVisibleCells().map((cell) => (
-                        <td
-                          key={cell.id}
-                          onBlur={(e) =>
-                            handleEditMinPublishers(
-                              e,
-                              cell.row.original.symbol,
-                              cell.row.original.minPublishers
-                            )
-                          }
-                          contentEditable={
-                            cell.column.id === 'newMinPublishers' && editable
-                              ? true
-                              : false
-                          }
-                          suppressContentEditableWarning={true}
-                          className={
-                            cell.column.id === 'symbol'
-                              ? 'py-3 pl-4 pr-2 xl:pl-14'
-                              : 'items-center py-3 pl-1 pr-4'
-                          }
-                        >
-                          {flexRender(
-                            cell.column.columnDef.cell,
-                            cell.getContext()
-                          )}
-                        </td>
-                      ))}
-                    </tr>
-                  ))}
-                </tbody>
-              </table>
-            </div>
-          )}
-        </div>
-      </div>
-    </div>
-  )
-}
-
-export default MinPublishers

+ 0 - 362
governance/xc_admin/packages/xc_admin_frontend/components/tabs/UpdateProductMetadata.tsx

@@ -1,362 +0,0 @@
-import { AnchorProvider, Program, Wallet } from '@coral-xyz/anchor'
-import {
-  getPythProgramKeyForCluster,
-  Product,
-  pythOracleProgram,
-} from '@pythnetwork/client'
-import { PythOracle } from '@pythnetwork/client/lib/anchor'
-import { useAnchorWallet, useWallet } from '@solana/wallet-adapter-react'
-import { WalletModalButton } from '@solana/wallet-adapter-react-ui'
-import { PublicKey, TransactionInstruction } from '@solana/web3.js'
-import { useContext, useEffect, useState } from 'react'
-import toast from 'react-hot-toast'
-import { getMultisigCluster, proposeInstructions } from 'xc_admin_common'
-import { ClusterContext } from '../../contexts/ClusterContext'
-import { usePythContext } from '../../contexts/PythContext'
-import { PRICE_FEED_MULTISIG, useMultisig } from '../../hooks/useMultisig'
-import { capitalizeFirstLetter } from '../../utils/capitalizeFirstLetter'
-import ClusterSwitch from '../ClusterSwitch'
-import Modal from '../common/Modal'
-import Spinner from '../common/Spinner'
-import Loadbar from '../loaders/Loadbar'
-
-interface SymbolToProductMetadata {
-  [key: string]: Product
-}
-
-interface ProductMetadataInfo {
-  prev: Product
-  new: Product
-}
-
-const symbolToProductAccountKeyMapping: Record<string, PublicKey> = {}
-
-const UpdateProductMetadata = () => {
-  const [data, setData] = useState<SymbolToProductMetadata>({})
-  const [productMetadataChanges, setProductMetadataChanges] =
-    useState<Record<string, ProductMetadataInfo>>()
-  const [isModalOpen, setIsModalOpen] = useState(false)
-  const [isSendProposalButtonLoading, setIsSendProposalButtonLoading] =
-    useState(false)
-  const { cluster } = useContext(ClusterContext)
-  const anchorWallet = useAnchorWallet()
-  const { isLoading: isMultisigLoading, squads } = useMultisig(
-    anchorWallet as Wallet
-  )
-  const { rawConfig, dataIsLoading, connection } = usePythContext()
-  const { connected } = useWallet()
-  const [pythProgramClient, setPythProgramClient] =
-    useState<Program<PythOracle>>()
-
-  const openModal = () => {
-    setIsModalOpen(true)
-  }
-
-  const closeModal = () => {
-    setIsModalOpen(false)
-  }
-
-  useEffect(() => {
-    if (!dataIsLoading && rawConfig && rawConfig.mappingAccounts.length > 0) {
-      const symbolToProductMetadataMapping: SymbolToProductMetadata = {}
-      rawConfig.mappingAccounts
-        .sort(
-          (mapping1, mapping2) =>
-            mapping2.products.length - mapping1.products.length
-        )[0]
-        .products.map((product) => {
-          symbolToProductAccountKeyMapping[product.metadata.symbol] =
-            product.address
-          // create copy of product.metadata to avoid mutating the original product.metadata
-          symbolToProductMetadataMapping[product.metadata.symbol] = {
-            ...product.metadata,
-          }
-          // these fields are immutable and should not be updated
-          delete symbolToProductMetadataMapping[product.metadata.symbol].address
-          delete symbolToProductMetadataMapping[product.metadata.symbol].symbol
-          delete symbolToProductMetadataMapping[product.metadata.symbol]
-            .price_account
-        })
-      setData(sortData(symbolToProductMetadataMapping))
-    }
-  }, [rawConfig, dataIsLoading])
-
-  const sortData = (data: SymbolToProductMetadata) => {
-    const sortedSymbolToProductMetadataMapping: SymbolToProductMetadata = {}
-    Object.keys(data)
-      .sort()
-      .forEach((key) => {
-        const sortedInnerData: any = {}
-        Object.keys(data[key])
-          .sort()
-          .forEach((innerKey) => {
-            sortedInnerData[innerKey] = data[key][innerKey]
-          })
-        sortedSymbolToProductMetadataMapping[key] = sortedInnerData
-      })
-
-    return sortedSymbolToProductMetadataMapping
-  }
-
-  // function to download json file
-  const handleDownloadJsonButtonClick = () => {
-    const dataStr =
-      'data:text/json;charset=utf-8,' +
-      encodeURIComponent(JSON.stringify(data, null, 2))
-    const downloadAnchor = document.createElement('a')
-    downloadAnchor.setAttribute('href', dataStr)
-    downloadAnchor.setAttribute('download', 'products.json')
-    document.body.appendChild(downloadAnchor) // required for firefox
-    downloadAnchor.click()
-    downloadAnchor.remove()
-  }
-
-  // function to upload json file and update productMetadataChanges state
-  const handleUploadJsonButtonClick = () => {
-    const uploadAnchor = document.createElement('input')
-    uploadAnchor.setAttribute('type', 'file')
-    uploadAnchor.setAttribute('accept', '.json')
-    uploadAnchor.addEventListener('change', (e) => {
-      const file = (e.target as HTMLInputElement).files![0]
-      const reader = new FileReader()
-      reader.onload = (e) => {
-        if (e.target) {
-          const fileData = e.target.result
-          if (!isValidJson(fileData as string)) return
-          const fileDataParsed = sortData(JSON.parse(fileData as string))
-          const changes: Record<string, ProductMetadataInfo> = {}
-          Object.keys(fileDataParsed).forEach((symbol) => {
-            if (
-              JSON.stringify(data[symbol]) !==
-              JSON.stringify(fileDataParsed[symbol])
-            ) {
-              changes[symbol] = {
-                prev: data[symbol],
-                new: fileDataParsed[symbol],
-              }
-            }
-          })
-          setProductMetadataChanges(changes)
-          openModal()
-        }
-      }
-      reader.readAsText(file)
-    })
-    document.body.appendChild(uploadAnchor) // required for firefox
-    uploadAnchor.click()
-    uploadAnchor.remove()
-  }
-
-  // check if uploaded json is valid json
-  const isValidJson = (json: string) => {
-    try {
-      JSON.parse(json)
-    } catch (e: any) {
-      toast.error(capitalizeFirstLetter(e.message))
-      return false
-    }
-    // check if json keys are existing products
-    const jsonParsed = JSON.parse(json)
-    const jsonSymbols = Object.keys(jsonParsed)
-    const existingSymbols = Object.keys(data)
-    // check that jsonSymbols is equal to existingSymbols no matter the order
-    if (
-      JSON.stringify(jsonSymbols.sort()) !==
-      JSON.stringify(existingSymbols.sort())
-    ) {
-      toast.error('Symbols in json file do not match existing symbols!')
-      return false
-    }
-
-    let isValid = true
-    // check that the keys of the values of json are equal to the keys of the values of data
-    jsonSymbols.forEach((symbol) => {
-      const jsonKeys = Object.keys(jsonParsed[symbol])
-      const existingKeys = Object.keys(data[symbol])
-      if (
-        JSON.stringify(jsonKeys.sort()) !== JSON.stringify(existingKeys.sort())
-      ) {
-        toast.error(
-          `Keys in json file do not match existing keys for symbol ${symbol}!`
-        )
-        isValid = false
-      }
-    })
-    return isValid
-  }
-
-  const handleSendProposalButtonClick = async () => {
-    if (pythProgramClient && productMetadataChanges) {
-      const instructions: TransactionInstruction[] = []
-      Object.keys(productMetadataChanges).forEach((symbol) => {
-        const { prev, new: newProductMetadata } = productMetadataChanges[symbol]
-        // prev and new are json object of metadata
-        // check if there are any new metadata by comparing prev and new values
-        if (JSON.stringify(prev) !== JSON.stringify(newProductMetadata)) {
-          pythProgramClient.methods
-            .updProduct(newProductMetadata)
-            .accounts({
-              fundingAccount: squads?.getAuthorityPDA(
-                PRICE_FEED_MULTISIG[getMultisigCluster(cluster)],
-                1
-              ),
-              productAccount: symbolToProductAccountKeyMapping[symbol],
-            })
-            .instruction()
-            .then((instruction) => instructions.push(instruction))
-        }
-      })
-
-      if (!isMultisigLoading && squads) {
-        setIsSendProposalButtonLoading(true)
-        try {
-          const proposalPubkey = await proposeInstructions(
-            squads,
-            PRICE_FEED_MULTISIG[getMultisigCluster(cluster)],
-            instructions,
-            false
-          )
-          toast.success(`Proposal sent! 🚀 Proposal Pubkey: ${proposalPubkey}`)
-          setIsSendProposalButtonLoading(false)
-        } catch (e: any) {
-          toast.error(capitalizeFirstLetter(e.message))
-          setIsSendProposalButtonLoading(false)
-        }
-      }
-    }
-  }
-
-  const ModalContent = ({ changes }: { changes: any }) => {
-    return (
-      <>
-        {Object.keys(changes).length > 0 ? (
-          <table className="mb-10 w-full table-auto bg-darkGray text-left">
-            {Object.keys(changes).map((key) => {
-              const { prev, new: newProductMetadata } = changes[key]
-              const diff = Object.keys(prev).filter(
-                (k) => prev[k] !== newProductMetadata[k]
-              )
-              return (
-                <tbody key={key}>
-                  <tr>
-                    <td
-                      className="base16 py-4 pl-6 pr-2 font-bold lg:pl-6"
-                      colSpan={2}
-                    >
-                      {key}
-                    </td>
-                  </tr>
-                  {diff.map((k) => (
-                    <tr key={k}>
-                      <td className="base16 py-4 pl-6 pr-2 lg:pl-6">
-                        {k
-                          .split('_')
-                          .map((word) => capitalizeFirstLetter(word))
-                          .join(' ')}
-                      </td>
-                      <td className="base16 py-4 pl-1 pr-2 lg:pl-6">
-                        <s>{prev[k]}</s>
-                        <br />
-                        {newProductMetadata[k]}
-                      </td>
-                    </tr>
-                  ))}
-                  {/* add a divider only if its not the last item */}
-                  {Object.keys(changes).indexOf(key) !==
-                  Object.keys(changes).length - 1 ? (
-                    <tr>
-                      <td className="base16 py-4 pl-6 pr-6" colSpan={2}>
-                        <hr className="border-gray-700" />
-                      </td>
-                    </tr>
-                  ) : null}
-                </tbody>
-              )
-            })}
-          </table>
-        ) : (
-          <p className="mb-8 leading-6">No proposed changes.</p>
-        )}
-        {Object.keys(changes).length > 0 ? (
-          !connected ? (
-            <div className="flex justify-center">
-              <WalletModalButton className="action-btn text-base" />
-            </div>
-          ) : (
-            <button
-              className="action-btn text-base"
-              onClick={handleSendProposalButtonClick}
-            >
-              {isSendProposalButtonLoading ? <Spinner /> : 'Send Proposal'}
-            </button>
-          )
-        ) : null}
-      </>
-    )
-  }
-
-  // create anchor wallet when connected
-  useEffect(() => {
-    if (connected) {
-      const provider = new AnchorProvider(
-        connection,
-        anchorWallet as Wallet,
-        AnchorProvider.defaultOptions()
-      )
-      setPythProgramClient(
-        pythOracleProgram(getPythProgramKeyForCluster(cluster), provider)
-      )
-    }
-  }, [anchorWallet, connection, connected, cluster])
-
-  return (
-    <div className="relative">
-      <Modal
-        isModalOpen={isModalOpen}
-        setIsModalOpen={setIsModalOpen}
-        closeModal={closeModal}
-        content={<ModalContent changes={productMetadataChanges} />}
-      />
-      <div className="container flex flex-col items-center justify-between lg:flex-row">
-        <div className="mb-4 w-full text-left lg:mb-0">
-          <h1 className="h1 mb-4">Update Product Metadata</h1>
-        </div>
-      </div>
-      <div className="container min-h-[50vh]">
-        <div className="flex justify-between">
-          <div className="mb-4 md:mb-0">
-            <ClusterSwitch />
-          </div>
-        </div>
-        <div className="relative mt-6">
-          {dataIsLoading ? (
-            <div className="mt-3">
-              <Loadbar theme="light" />
-            </div>
-          ) : (
-            <div className="flex items-center space-x-4">
-              <div className="mb-10">
-                <button
-                  className="action-btn text-base"
-                  onClick={handleDownloadJsonButtonClick}
-                >
-                  Download JSON
-                </button>
-              </div>
-              <div className="mb-10">
-                <button
-                  className="action-btn text-base"
-                  onClick={handleUploadJsonButtonClick}
-                >
-                  Upload JSON
-                </button>
-              </div>
-            </div>
-          )}
-        </div>
-      </div>
-    </div>
-  )
-}
-
-export default UpdateProductMetadata

+ 0 - 28
governance/xc_admin/packages/xc_admin_frontend/pages/index.tsx

@@ -3,12 +3,9 @@ import type { NextPage } from 'next'
 import { useRouter } from 'next/router'
 import { useEffect, useState } from 'react'
 import Layout from '../components/layout/Layout'
-import AddRemovePublishers from '../components/tabs/AddRemovePublishers'
 import General from '../components/tabs/General'
-import MinPublishers from '../components/tabs/MinPublishers'
 import Proposals from '../components/tabs/Proposals'
 import UpdatePermissions from '../components/tabs/UpdatePermissions'
-import UpdateProductMetadata from '../components/tabs/UpdateProductMetadata'
 import { MultisigContextProvider } from '../contexts/MultisigContext'
 import { PythContextProvider } from '../contexts/PythContext'
 import { classNames } from '../utils/classNames'
@@ -19,27 +16,11 @@ const TAB_INFO = {
     description: 'General panel for the program.',
     queryString: 'general',
   },
-  MinPublishers: {
-    title: 'Min Publishers',
-    description:
-      'Set the minimum number of publishers required to publish a price.',
-    queryString: 'min-publishers',
-  },
   UpdatePermissions: {
     title: 'Update Permissions',
     description: 'Update the permissions of the program.',
     queryString: 'update-permissions',
   },
-  AddRemovePublishers: {
-    title: 'Add/Remove Publishers',
-    description: 'Add or remove publishers from price feeds.',
-    queryString: 'add-remove-publishers',
-  },
-  UpdateProductMetadata: {
-    title: 'Update Product Metadata',
-    description: 'Update the metadata of a product.',
-    queryString: 'update-product-metadata',
-  },
   Proposals: {
     title: 'Proposals',
     description: 'View and vote on proposals.',
@@ -114,18 +95,9 @@ const Home: NextPage = () => {
           {tabInfoArray[currentTabIndex].queryString ===
           TAB_INFO.General.queryString ? (
             <General />
-          ) : tabInfoArray[currentTabIndex].queryString ===
-            TAB_INFO.MinPublishers.queryString ? (
-            <MinPublishers />
           ) : tabInfoArray[currentTabIndex].queryString ===
             TAB_INFO.UpdatePermissions.queryString ? (
             <UpdatePermissions />
-          ) : tabInfoArray[currentTabIndex].queryString ===
-            TAB_INFO.AddRemovePublishers.queryString ? (
-            <AddRemovePublishers />
-          ) : tabInfoArray[currentTabIndex].queryString ===
-            TAB_INFO.UpdateProductMetadata.queryString ? (
-            <UpdateProductMetadata />
           ) : tabInfoArray[currentTabIndex].queryString ===
             TAB_INFO.Proposals.queryString ? (
             <Proposals />