Przeglądaj źródła

fix(xc-admin-frontend): fixed build

benduran 3 tygodni temu
rodzic
commit
5db359797e
26 zmienionych plików z 327 dodań i 321 usunięć
  1. 1 0
      governance/xc_admin/packages/xc_admin_frontend/@types/next-seo.config/index.d.ts
  2. 1 1
      governance/xc_admin/packages/xc_admin_frontend/components/ClusterSwitch.tsx
  3. 11 11
      governance/xc_admin/packages/xc_admin_frontend/components/InstructionViews/WormholeInstructionView.tsx
  4. 10 10
      governance/xc_admin/packages/xc_admin_frontend/components/InstructionViews/utils.ts
  5. 2 2
      governance/xc_admin/packages/xc_admin_frontend/components/PermissionDepermissionKey.tsx
  6. 1 1
      governance/xc_admin/packages/xc_admin_frontend/components/common/Modal.tsx
  7. 1 1
      governance/xc_admin/packages/xc_admin_frontend/components/layout/MobileMenu.tsx
  8. 7 7
      governance/xc_admin/packages/xc_admin_frontend/components/programs/PythCore.tsx
  9. 6 6
      governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/InstructionsSummary.tsx
  10. 21 21
      governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/Proposal.tsx
  11. 5 5
      governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/ProposalRow.tsx
  12. 1 1
      governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/Proposals.tsx
  13. 1 1
      governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/StatusTag.tsx
  14. 52 52
      governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/utils.ts
  15. 2 2
      governance/xc_admin/packages/xc_admin_frontend/components/tabs/UpdatePermissions.tsx
  16. 2 2
      governance/xc_admin/packages/xc_admin_frontend/contexts/ClusterContext.tsx
  17. 1 1
      governance/xc_admin/packages/xc_admin_frontend/contexts/MultisigContext.tsx
  18. 1 1
      governance/xc_admin/packages/xc_admin_frontend/contexts/ProgramContext.tsx
  19. 11 11
      governance/xc_admin/packages/xc_admin_frontend/contexts/PythContext.tsx
  20. 82 82
      governance/xc_admin/packages/xc_admin_frontend/hooks/useMultisig.ts
  21. 58 56
      governance/xc_admin/packages/xc_admin_frontend/hooks/usePyth.ts
  22. 10 10
      governance/xc_admin/packages/xc_admin_frontend/next-seo.config.js
  23. 3 3
      governance/xc_admin/packages/xc_admin_frontend/pages/_app.tsx
  24. 5 5
      governance/xc_admin/packages/xc_admin_frontend/pages/index.tsx
  25. 4 1
      governance/xc_admin/packages/xc_admin_frontend/tsconfig.json
  26. 28 28
      governance/xc_admin/packages/xc_admin_frontend/utils/pythClusterApiUrl.ts

+ 1 - 0
governance/xc_admin/packages/xc_admin_frontend/@types/next-seo.config/index.d.ts

@@ -0,0 +1 @@
+declare module "*next-seo.config.js";

+ 1 - 1
governance/xc_admin/packages/xc_admin_frontend/components/ClusterSwitch.tsx

@@ -3,7 +3,7 @@ import { useRouter } from 'next/router'
 import { Fragment, useCallback, useContext, useEffect } from 'react'
 import { ClusterContext, DEFAULT_CLUSTER } from '../contexts/ClusterContext'
 import Arrow from '@images/icons/down.inline.svg'
-import { PythCluster } from '@pythnetwork/client'
+import type { PythCluster } from '@pythnetwork/client'
 
 const ClusterSwitch = ({ light }: { light?: boolean | null }) => {
   const router = useRouter()

+ 11 - 11
governance/xc_admin/packages/xc_admin_frontend/components/InstructionViews/WormholeInstructionView.tsx

@@ -6,7 +6,7 @@ import {
   EvmUpgradeContract,
   ExecutePostedVaa,
   MultisigParser,
-  PythGovernanceAction,
+  type PythGovernanceAction,
   RequestGovernanceDataSourceTransfer,
   SetDataSources,
   SetFee,
@@ -15,13 +15,13 @@ import {
   WormholeMultisigInstruction,
   getProgramName,
 } from '@pythnetwork/xc-admin-common'
-import { AccountMeta, PublicKey } from '@solana/web3.js'
+import { type AccountMeta, PublicKey } from '@solana/web3.js'
 import type { ReactNode } from 'react'
 import CopyText from '../common/CopyText'
 import { ParsedAccountPubkeyRow, SignerTag, WritableTag } from './AccountUtils'
 import { usePythContext } from '../../contexts/PythContext'
 import { getMappingCluster, isPubkey } from './utils'
-import { PythCluster } from '@pythnetwork/client'
+import type { PythCluster } from '@pythnetwork/client'
 import { lamportsToSol } from '../../utils/lamportsToSol'
 import { parseEvmExecuteCallData } from '../../utils/parseEvmExecuteCallData'
 
@@ -149,7 +149,7 @@ export const WormholeInstructionView = ({
                           )}
                         </div>
                         {key === 'pub' &&
-                        parsedInstruction.args[key].toBase58() in
+                        publisherKeyToNameMappingCluster && parsedInstruction.args[key].toBase58() in
                           publisherKeyToNameMappingCluster ? (
                           <ParsedAccountPubkeyRow
                             key={`${index}_${parsedInstruction.args[
@@ -192,25 +192,25 @@ export const WormholeInstructionView = ({
                               <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 ? (
+                                    ?.isSigner ? (
                                     <SignerTag />
                                   ) : null}
                                   {parsedInstruction.accounts.named[key]
-                                    .isWritable ? (
+                                    ?.isWritable ? (
                                     <WritableTag />
                                   ) : null}
                                 </div>
                                 <CopyText
                                   text={parsedInstruction.accounts.named[
                                     key
-                                  ].pubkey.toBase58()}
+                                  ]?.pubkey.toBase58() ?? ''}
                                 />
                               </div>
                             </div>
                             {key === 'priceAccount' &&
                             parsedInstruction.accounts.named[
                               key
-                            ].pubkey.toBase58() in
+                            ]!.pubkey.toBase58() in
                               priceAccountKeyToSymbolMapping ? (
                               <ParsedAccountPubkeyRow
                                 key="priceAccountPubkey"
@@ -218,12 +218,12 @@ export const WormholeInstructionView = ({
                                 title="symbol"
                                 pubkey={parsedInstruction.accounts.named[
                                   key
-                                ].pubkey.toBase58()}
+                                ]?.pubkey.toBase58() ?? ''}
                               />
                             ) : key === 'productAccount' &&
                               parsedInstruction.accounts.named[
                                 key
-                              ].pubkey.toBase58() in
+                              ]!.pubkey.toBase58() in
                                 productAccountKeyToSymbolMapping ? (
                               <ParsedAccountPubkeyRow
                                 key="productAccountPubkey"
@@ -231,7 +231,7 @@ export const WormholeInstructionView = ({
                                 title="symbol"
                                 pubkey={parsedInstruction.accounts.named[
                                   key
-                                ].pubkey.toBase58()}
+                                ]?.pubkey.toBase58() ?? ''}
                               />
                             ) : null}
                           </>

+ 10 - 10
governance/xc_admin/packages/xc_admin_frontend/components/InstructionViews/utils.ts

@@ -1,19 +1,19 @@
-import { PublicKey } from '@solana/web3.js'
+import { PublicKey } from "@solana/web3.js";
 
-export const getMappingCluster = (cluster: string) => {
-  if (cluster === 'mainnet-beta' || cluster === 'pythnet') {
-    return 'pythnet'
+export const getMappingCluster = (cluster: string | undefined) => {
+  if (cluster === "mainnet-beta" || cluster === "pythnet") {
+    return "pythnet";
   } else {
-    return 'pythtest'
+    return "pythtest";
   }
-}
+};
 
 // check if a string is a pubkey
 export const isPubkey = (str: string) => {
   try {
-    new PublicKey(str)
-    return true
+    new PublicKey(str);
+    return true;
   } catch (e) {
-    return false
+    return false;
   }
-}
+};

+ 2 - 2
governance/xc_admin/packages/xc_admin_frontend/components/PermissionDepermissionKey.tsx

@@ -1,6 +1,6 @@
 import { Program } from '@coral-xyz/anchor'
 import { Dialog, Menu, Transition } from '@headlessui/react'
-import { PythOracle } from '@pythnetwork/client/lib/anchor'
+import type { 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'
@@ -18,7 +18,7 @@ import {
 } from '@pythnetwork/xc-admin-common'
 import { ClusterContext } from '../contexts/ClusterContext'
 import { usePythContext } from '../contexts/PythContext'
-import { ProductRawConfig } from '../hooks/usePyth'
+import type { ProductRawConfig } from '../hooks/usePyth'
 import Arrow from '@images/icons/down.inline.svg'
 import { capitalizeFirstLetter } from '../utils/capitalizeFirstLetter'
 import Spinner from './common/Spinner'

+ 1 - 1
governance/xc_admin/packages/xc_admin_frontend/components/common/Modal.tsx

@@ -1,5 +1,5 @@
 import { Dialog, Transition } from '@headlessui/react'
-import { Dispatch, Fragment, SetStateAction } from 'react'
+import { type Dispatch, Fragment, type SetStateAction } from 'react'
 import CloseIcon from '../icons/CloseIcon'
 
 const Modal: React.FC<{

+ 1 - 1
governance/xc_admin/packages/xc_admin_frontend/components/layout/MobileMenu.tsx

@@ -4,7 +4,7 @@ import Link from 'next/link'
 import { useRouter } from 'next/router'
 import { useContext, useEffect, useRef } from 'react'
 import { ClusterContext, DEFAULT_CLUSTER } from '../../contexts/ClusterContext'
-import { BurgerState } from './Header'
+import type { BurgerState } from './Header'
 
 import orb from '@images/burger.png'
 

+ 7 - 7
governance/xc_admin/packages/xc_admin_frontend/components/programs/PythCore.tsx

@@ -1,9 +1,9 @@
-import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor'
+import { AnchorProvider, type Idl, Program } from '@coral-xyz/anchor'
 import { getPythProgramKeyForCluster } from '@pythnetwork/client'
-import { PythOracle, pythOracleProgram } from '@pythnetwork/client/lib/anchor'
+import { type PythOracle, pythOracleProgram } from '@pythnetwork/client/lib/anchor'
 import { PublicKey } from '@solana/web3.js'
 import messageBuffer from 'message_buffer/idl/message_buffer.json'
-import { MessageBuffer } from 'message_buffer/idl/message_buffer'
+import type { MessageBuffer } from 'message_buffer/idl/message_buffer'
 import axios from 'axios'
 import { useContext, useEffect, useState } from 'react'
 import toast from 'react-hot-toast'
@@ -18,9 +18,9 @@ import {
   ProgramType,
   validateUploadedConfig,
   generateInstructions,
-  DownloadableConfig,
-  DownloadableProduct,
-  DownloadablePriceAccount,
+  type DownloadableConfig,
+  type DownloadableProduct,
+  type DownloadablePriceAccount,
 } from '@pythnetwork/xc-admin-common'
 import { ClusterContext } from '../../contexts/ClusterContext'
 import { useMultisigContext } from '../../contexts/MultisigContext'
@@ -31,7 +31,7 @@ import Modal from '../common/Modal'
 import Spinner from '../common/Spinner'
 import Loadbar from '../loaders/Loadbar'
 import PermissionDepermissionKey from '../PermissionDepermissionKey'
-import { Wallet } from '@coral-xyz/anchor/dist/cjs/provider'
+import type { Wallet } from '@coral-xyz/anchor/dist/cjs/provider'
 
 interface PriceAccountMetadata extends DownloadablePriceAccount {
   [key: string]: string | string[] | number

+ 6 - 6
governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/InstructionsSummary.tsx

@@ -1,6 +1,6 @@
 import { Listbox, Transition } from '@headlessui/react'
-import { PythCluster } from '@pythnetwork/client'
-import { MultisigInstruction } from '@pythnetwork/xc-admin-common'
+import type { PythCluster } from '@pythnetwork/client'
+import type { MultisigInstruction } from '@pythnetwork/xc-admin-common'
 import { getInstructionsSummary } from './utils'
 import { getMappingCluster } from '../../InstructionViews/utils'
 import CopyText from '../../common/CopyText'
@@ -14,10 +14,10 @@ export const InstructionsSummary = ({
   cluster,
 }: {
   instructions: MultisigInstruction[]
-  cluster: PythCluster
+  cluster: PythCluster | undefined;
 }) => (
   <div className="space-y-4">
-    {getInstructionsSummary({ instructions, cluster }).map((instruction) => (
+    {cluster && getInstructionsSummary({ instructions, cluster }).map((instruction) => (
       <SummaryItem instruction={instruction} key={instruction.name} />
     ))}
   </div>
@@ -113,7 +113,7 @@ const AddRemovePublisherDetails = ({
               <KeyAndName
                 mapping={
                   groupBy === 'publisher'
-                    ? publisherKeyToName
+                    ? publisherKeyToName ?? {}
                     : priceAccountKeyToSymbolMapping
                 }
               >
@@ -127,7 +127,7 @@ const AddRemovePublisherDetails = ({
                     mapping={
                       groupBy === 'publisher'
                         ? priceAccountKeyToSymbolMapping
-                        : publisherKeyToName
+                        : publisherKeyToName ?? {}
                     }
                   >
                     {groupBy === 'publisher'

+ 21 - 21
governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/Proposal.tsx

@@ -1,12 +1,12 @@
 import { useWallet } from '@solana/wallet-adapter-react'
 import {
-  AccountMeta,
+  type AccountMeta,
   PublicKey,
   SystemProgram,
   TransactionInstruction,
 } from '@solana/web3.js'
 import SquadsMesh from '@sqds/mesh'
-import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
+import type { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
 import {
   type ReactNode,
   Fragment,
@@ -18,7 +18,7 @@ import toast from 'react-hot-toast'
 import {
   AnchorMultisigInstruction,
   ExecutePostedVaa,
-  MultisigInstruction,
+  type MultisigInstruction,
   MultisigParser,
   PythMultisigInstruction,
   WormholeMultisigInstruction,
@@ -41,7 +41,7 @@ import Spinner from '../../common/Spinner'
 import Loadbar from '../../loaders/Loadbar'
 
 import { Wallet } from '@coral-xyz/anchor'
-import { PythCluster, getPythProgramKeyForCluster } from '@pythnetwork/client'
+import { type PythCluster, getPythProgramKeyForCluster } from '@pythnetwork/client'
 import { TransactionBuilder, sendTransactions } from '@pythnetwork/solana-utils'
 import { getMappingCluster, isPubkey } from '../../InstructionViews/utils'
 import { StatusTag } from './StatusTag'
@@ -207,9 +207,9 @@ export const Proposal = ({
           ix.name === 'postMessage' &&
           ix.governanceAction instanceof ExecutePostedVaa &&
           ix.governanceAction.instructions.every((remoteIx) => {
-            const innerMultisigParser = MultisigParser.fromCluster(cluster)
+            const innerMultisigParser = cluster ? MultisigParser.fromCluster(cluster) : undefined;
             const parsedRemoteInstruction =
-              innerMultisigParser.parseInstruction({
+              innerMultisigParser?.parseInstruction({
                 programId: remoteIx.programId,
                 data: remoteIx.data as Buffer,
                 keys: remoteIx.keys as AccountMeta[],
@@ -242,16 +242,16 @@ export const Proposal = ({
         const proposalInstructions = (
           await getManyProposalsInstructions(readOnlySquads, [proposal])
         )[0]
-        const multisigParser = MultisigParser.fromCluster(
+        const multisigParser = cluster ? MultisigParser.fromCluster(
           getMultisigCluster(cluster)
-        )
-        const parsedInstructions = proposalInstructions.map((ix) =>
-          multisigParser.parseInstruction({
+        ) : undefined;
+        const parsedInstructions = (proposalInstructions?.map((ix) =>
+          multisigParser?.parseInstruction({
             programId: ix.programId,
             data: ix.data as Buffer,
             keys: ix.keys as AccountMeta[],
           })
-        )
+        ) ?? []).filter((instruction): instruction is MultisigInstruction => Boolean(instruction));
         if (!isCancelled) setInstructions(parsedInstructions)
       } else {
         if (!isCancelled) setInstructions([])
@@ -339,7 +339,7 @@ export const Proposal = ({
     await handleClick(
       async (
         squad: SquadsMesh,
-        vaultKey: PublicKey,
+        _: PublicKey,
         proposalKey: PublicKey
       ): Promise<TransactionInstruction> => {
         return await squad.buildExecuteTransaction(proposalKey)
@@ -558,7 +558,7 @@ export const Proposal = ({
                           )}
                         </div>
                         {key === 'pub' &&
-                        instruction.args[key].toBase58() in
+                        publisherKeyToNameMappingCluster && instruction.args[key].toBase58() in
                           publisherKeyToNameMappingCluster ? (
                           <ParsedAccountPubkeyRow
                             key={`${index}_${instruction.args[key].toBase58()}`}
@@ -575,7 +575,7 @@ export const Proposal = ({
                 )}
               </div>
             )}
-            {instruction instanceof WormholeMultisigInstruction && (
+            {cluster && instruction instanceof WormholeMultisigInstruction && (
               <WormholeInstructionView
                 cluster={cluster}
                 instruction={instruction}
@@ -605,22 +605,22 @@ export const Proposal = ({
                             </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">
-                                {instruction.accounts.named[key].isSigner ? (
+                                {instruction.accounts.named[key]?.isSigner ? (
                                   <SignerTag />
                                 ) : null}
-                                {instruction.accounts.named[key].isWritable ? (
+                                {instruction.accounts.named[key]?.isWritable ? (
                                   <WritableTag />
                                 ) : null}
                               </div>
                               <CopyText
                                 text={instruction.accounts.named[
                                   key
-                                ].pubkey.toBase58()}
+                                ]?.pubkey.toBase58() ?? ''}
                               />
                             </div>
                           </div>
                           {key === 'priceAccount' &&
-                          instruction.accounts.named[key].pubkey.toBase58() in
+                          instruction.accounts.named[key]!.pubkey.toBase58() in
                             priceAccountKeyToSymbolMapping ? (
                             <ParsedAccountPubkeyRow
                               key="priceAccountPubkey"
@@ -628,10 +628,10 @@ export const Proposal = ({
                               title="symbol"
                               pubkey={instruction.accounts.named[
                                 key
-                              ].pubkey.toBase58()}
+                              ]?.pubkey.toBase58() ?? ''}
                             />
                           ) : key === 'productAccount' &&
-                            instruction.accounts.named[key].pubkey.toBase58() in
+                            instruction.accounts.named[key]!.pubkey.toBase58() in
                               productAccountKeyToSymbolMapping ? (
                             <ParsedAccountPubkeyRow
                               key="productAccountPubkey"
@@ -639,7 +639,7 @@ export const Proposal = ({
                               title="symbol"
                               pubkey={instruction.accounts.named[
                                 key
-                              ].pubkey.toBase58()}
+                              ]?.pubkey.toBase58() ?? ''}
                             />
                           ) : null}
                         </>

+ 5 - 5
governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/ProposalRow.tsx

@@ -1,4 +1,4 @@
-import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
+import type { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
 import { useRouter } from 'next/router'
 import { useCallback, useContext, useEffect, useRef, useState } from 'react'
 import { getMultisigCluster } from '@pythnetwork/xc-admin-common'
@@ -7,7 +7,7 @@ import { useMultisigContext } from '../../../contexts/MultisigContext'
 import { StatusTag } from './StatusTag'
 import { getInstructionsSummary, getProposalStatus } from './utils'
 import { useWallet } from '@solana/wallet-adapter-react'
-import { AccountMeta } from '@solana/web3.js'
+import type { AccountMeta } from '@solana/web3.js'
 import {
   MultisigParser,
   getManyProposalsInstructions,
@@ -51,7 +51,7 @@ export const ProposalRow = ({
     let isCancelled = false
     const element = elementRef.current
     const observer = new IntersectionObserver(async (entries) => {
-      if (entries[0].isIntersecting) {
+      if (entries[0]?.isIntersecting) {
         if (isMultisigLoading) {
           return
         }
@@ -82,13 +82,13 @@ export const ProposalRow = ({
           const multisigParser = MultisigParser.fromCluster(
             getMultisigCluster(cluster)
           )
-          const parsedInstructions = proposalInstructions.map((ix) =>
+          const parsedInstructions = proposalInstructions?.map((ix) =>
             multisigParser.parseInstruction({
               programId: ix.programId,
               data: ix.data as Buffer,
               keys: ix.keys as AccountMeta[],
             })
-          )
+          ) ?? [];
 
           const summary = getInstructionsSummary({
             instructions: parsedInstructions,

+ 1 - 1
governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/Proposals.tsx

@@ -1,4 +1,4 @@
-import { TransactionAccount } from '@sqds/mesh/lib/types'
+import type { TransactionAccount } from '@sqds/mesh/lib/types'
 import { useRouter } from 'next/router'
 import { useContext, useEffect, useState, useMemo, Fragment } from 'react'
 import { ClusterContext } from '../../../contexts/ClusterContext'

+ 1 - 1
governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/StatusTag.tsx

@@ -1,4 +1,4 @@
-import { ProposalStatus } from './utils'
+import type { ProposalStatus } from './utils'
 
 const getProposalBackgroundColorClassName = (
   proposalStatus: ProposalStatus

+ 52 - 52
governance/xc_admin/packages/xc_admin_frontend/components/tabs/Proposals/utils.ts

@@ -1,41 +1,41 @@
-import { PythCluster } from '@pythnetwork/client'
-import { PublicKey } from '@solana/web3.js'
-import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
+import type { PythCluster } from "@pythnetwork/client";
+import { PublicKey } from "@solana/web3.js";
+import type { MultisigAccount, TransactionAccount } from "@sqds/mesh/lib/types";
 import {
   ExecutePostedVaa,
-  MultisigInstruction,
+  type MultisigInstruction,
   MultisigParser,
   PythGovernanceActionImpl,
   SetDataSources,
   WormholeMultisigInstruction,
-} from '@pythnetwork/xc-admin-common'
+} from "@pythnetwork/xc-admin-common";
 
 export const PROPOSAL_STATUSES = [
-  'active',
-  'executed',
-  'cancelled',
-  'rejected',
-  'expired',
-  'executeReady',
-  'draft',
-  'unkwown',
-] as const
-export type ProposalStatus = (typeof PROPOSAL_STATUSES)[number]
+  "active",
+  "executed",
+  "cancelled",
+  "rejected",
+  "expired",
+  "executeReady",
+  "draft",
+  "unkwown",
+] as const;
+export type ProposalStatus = (typeof PROPOSAL_STATUSES)[number];
 
 export const getProposalStatus = (
   proposal: TransactionAccount | undefined,
-  multisig: MultisigAccount | undefined
+  multisig: MultisigAccount | undefined,
 ): ProposalStatus => {
   if (multisig && proposal) {
-    const onChainStatus = Object.keys(proposal.status)[0]
+    const onChainStatus = Object.keys(proposal.status)[0];
     return proposal.transactionIndex <= multisig.msChangeIndex &&
-      (onChainStatus == 'active' || onChainStatus == 'draft')
-      ? 'expired'
-      : (onChainStatus as ProposalStatus)
+      (onChainStatus == "active" || onChainStatus == "draft")
+      ? "expired"
+      : (onChainStatus as ProposalStatus);
   } else {
-    return 'unkwown'
+    return "unkwown";
   }
-}
+};
 
 /**
  * Returns a summary of the instructions in a list of multisig instructions.
@@ -48,74 +48,74 @@ export const getInstructionsSummary = ({
   instructions,
   cluster,
 }: {
-  instructions: MultisigInstruction[]
-  cluster: PythCluster
+  instructions: MultisigInstruction[];
+  cluster: PythCluster;
 }) =>
   Object.entries(
     getInstructionSummariesByName(
       MultisigParser.fromCluster(cluster),
-      instructions
-    )
+      instructions,
+    ),
   )
     .map(([name, summaries = []]) => ({
       name,
       count: summaries.length ?? 0,
       summaries,
     }))
-    .toSorted(({ count }) => count)
+    .toSorted(({ count }) => count);
 
 const getInstructionSummariesByName = (
   parser: MultisigParser,
-  instructions: MultisigInstruction[]
+  instructions: MultisigInstruction[],
 ) =>
   Object.groupBy(
     instructions.flatMap((instruction) =>
-      getInstructionSummary(parser, instruction)
+      getInstructionSummary(parser, instruction),
     ),
-    ({ name }) => name
-  )
+    ({ name }) => name,
+  );
 
 const getInstructionSummary = (
   parser: MultisigParser,
-  instruction: MultisigInstruction
+  instruction: MultisigInstruction,
 ) => {
   if (instruction instanceof WormholeMultisigInstruction) {
-    const { governanceAction } = instruction
+    const { governanceAction } = instruction;
     if (governanceAction instanceof ExecutePostedVaa) {
       return governanceAction.instructions.map((innerInstruction) =>
-        getTransactionSummary(parser.parseInstruction(innerInstruction))
-      )
+        getTransactionSummary(parser.parseInstruction(innerInstruction)),
+      );
     } else if (governanceAction instanceof PythGovernanceActionImpl) {
-      return [{ name: governanceAction.action } as const]
+      return [{ name: governanceAction.action } as const];
     } else if (governanceAction instanceof SetDataSources) {
-      return [{ name: governanceAction.actionName } as const]
+      return [{ name: governanceAction.actionName } as const];
     } else {
-      return [{ name: 'unknown' } as const]
+      return [{ name: "unknown" } as const];
     }
   } else {
-    return [getTransactionSummary(instruction)]
+    return [getTransactionSummary(instruction)];
   }
-}
+};
 
 const getTransactionSummary = (instruction: MultisigInstruction) => {
   switch (instruction.name) {
-    case 'addPublisher':
+    case "addPublisher":
       return {
-        name: 'addPublisher',
+        name: "addPublisher",
         priceAccount:
-          instruction.accounts.named['priceAccount'].pubkey.toBase58(),
-        pub: (instruction.args['pub'] as PublicKey).toBase58(),
-      } as const
-    case 'delPublisher':
+          instruction.accounts.named["priceAccount"]?.pubkey.toBase58() ?? "",
+        pub: (instruction.args["pub"] as PublicKey).toBase58(),
+      } as const;
+    case "delPublisher":
       return {
-        name: 'delPublisher',
+        name: "delPublisher",
         priceAccount:
-          instruction.accounts.named['priceAccount'].pubkey.toBase58(),
-        pub: (instruction.args['pub'] as PublicKey).toBase58(),
-      } as const
+          instruction.accounts.named["priceAccount"]?.pubkey.toBase58() ?? "",
+        pub: (instruction.args["pub"] as PublicKey).toBase58(),
+      } as const;
     default:
       return {
         name: instruction.name,
-      } as const
+      } as const;
   }
-}
+};

+ 2 - 2
governance/xc_admin/packages/xc_admin_frontend/components/tabs/UpdatePermissions.tsx

@@ -3,7 +3,7 @@ import {
   getPythProgramKeyForCluster,
   pythOracleProgram,
 } from '@pythnetwork/client'
-import { PythOracle } from '@pythnetwork/client/lib/anchor'
+import type { PythOracle } from '@pythnetwork/client/lib/anchor'
 import { useWallet } from '@solana/wallet-adapter-react'
 import { WalletModalButton } from '@solana/wallet-adapter-react-ui'
 import { PublicKey } from '@solana/web3.js'
@@ -34,7 +34,7 @@ import Modal from '../common/Modal'
 import Spinner from '../common/Spinner'
 import EditButton from '../EditButton'
 import Loadbar from '../loaders/Loadbar'
-import { Wallet } from '@coral-xyz/anchor/dist/cjs/provider'
+import type { Wallet } from '@coral-xyz/anchor/dist/cjs/provider'
 
 interface UpdatePermissionsProps {
   account: PermissionAccount

+ 2 - 2
governance/xc_admin/packages/xc_admin_frontend/contexts/ClusterContext.tsx

@@ -1,5 +1,5 @@
-import { PythCluster } from '@pythnetwork/client/lib/cluster'
-import { ReactNode, createContext, useMemo, useState } from 'react'
+import type { PythCluster } from '@pythnetwork/client/lib/cluster'
+import { type ReactNode, createContext, useMemo, useState } from 'react'
 
 export const DEFAULT_CLUSTER: PythCluster = 'mainnet-beta'
 

+ 1 - 1
governance/xc_admin/packages/xc_admin_frontend/contexts/MultisigContext.tsx

@@ -1,5 +1,5 @@
 import React, { createContext, useContext, useMemo } from 'react'
-import { MultisigHookData, useMultisig } from '../hooks/useMultisig'
+import { type MultisigHookData, useMultisig } from '../hooks/useMultisig'
 
 const MultisigContext = createContext<MultisigHookData | undefined>(undefined)
 

+ 1 - 1
governance/xc_admin/packages/xc_admin_frontend/contexts/ProgramContext.tsx

@@ -1,4 +1,4 @@
-import { createContext, useContext, useState, ReactNode } from 'react'
+import { createContext, useContext, useState, type ReactNode } from 'react'
 import { ProgramType } from '@pythnetwork/xc-admin-common'
 
 /**

+ 11 - 11
governance/xc_admin/packages/xc_admin_frontend/contexts/PythContext.tsx

@@ -6,22 +6,22 @@ import React, {
   useState,
 } from 'react'
 import { usePyth } from '../hooks/usePyth'
-import { RawConfig } from '../hooks/usePyth'
+import type { RawConfig } from '../hooks/usePyth'
 import { Connection } from '@solana/web3.js'
 import {
-  MappingRawConfig,
-  ProductRawConfig,
+  type MappingRawConfig,
+  type ProductRawConfig,
 } from '@pythnetwork/xc-admin-common'
 
 type AccountKeyToSymbol = { [key: string]: string }
 interface PythContextProps {
-  rawConfig: RawConfig
-  dataIsLoading: boolean
-  connection?: Connection
-  priceAccountKeyToSymbolMapping: AccountKeyToSymbol
-  productAccountKeyToSymbolMapping: AccountKeyToSymbol
-  publisherKeyToNameMapping: Record<string, Record<string, string>>
-  multisigSignerKeyToNameMapping: Record<string, string>
+  rawConfig: RawConfig;
+  dataIsLoading: boolean;
+  connection?: Connection | undefined;
+  priceAccountKeyToSymbolMapping: AccountKeyToSymbol;
+  productAccountKeyToSymbolMapping: AccountKeyToSymbol;
+  publisherKeyToNameMapping: Record<string, Record<string, string>>;
+  multisigSignerKeyToNameMapping: Record<string, string>;
 }
 
 const PythContext = createContext<PythContextProps>({
@@ -69,7 +69,7 @@ export const PythContextProvider: React.FC<PythContextProviderProps> = ({
     }
   }, [rawConfig, isLoading])
 
-  const value = useMemo(
+  const value = useMemo<PythContextProps>(
     () => ({
       rawConfig,
       dataIsLoading: isLoading,

+ 82 - 82
governance/xc_admin/packages/xc_admin_frontend/hooks/useMultisig.ts

@@ -1,145 +1,145 @@
-import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
-import { useAnchorWallet } from '@solana/wallet-adapter-react'
-import { Connection, Keypair, PublicKey } from '@solana/web3.js'
-import SquadsMesh from '@sqds/mesh'
-import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
-import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
+import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
+import { useAnchorWallet } from "@solana/wallet-adapter-react";
+import { Connection, Keypair, PublicKey } from "@solana/web3.js";
+import SquadsMesh from "@sqds/mesh";
+import type { MultisigAccount, TransactionAccount } from "@sqds/mesh/lib/types";
+import { useCallback, useContext, useEffect, useMemo, useState } from "react";
 import {
   PRICE_FEED_MULTISIG,
   UPGRADE_MULTISIG,
   getMultisigCluster,
   getProposals,
-} from '@pythnetwork/xc-admin-common'
-import { ClusterContext } from '../contexts/ClusterContext'
-import { deriveWsUrl, pythClusterApiUrls } from '../utils/pythClusterApiUrl'
+} from "@pythnetwork/xc-admin-common";
+import { ClusterContext } from "../contexts/ClusterContext";
+import { deriveWsUrl, pythClusterApiUrls } from "../utils/pythClusterApiUrl";
 
 export interface MultisigHookData {
-  isLoading: boolean
-  walletSquads: SquadsMesh | undefined
-  readOnlySquads: SquadsMesh
-  upgradeMultisigAccount: MultisigAccount | undefined
-  priceFeedMultisigAccount: MultisigAccount | undefined
-  upgradeMultisigProposals: TransactionAccount[]
-  priceFeedMultisigProposals: TransactionAccount[]
-  connection: Connection
-  refreshData?: () => { fetchData: () => Promise<void>; cancel: () => void }
+  isLoading: boolean;
+  walletSquads: SquadsMesh | undefined;
+  readOnlySquads: SquadsMesh;
+  upgradeMultisigAccount: MultisigAccount | undefined;
+  priceFeedMultisigAccount: MultisigAccount | undefined;
+  upgradeMultisigProposals: TransactionAccount[];
+  priceFeedMultisigProposals: TransactionAccount[];
+  connection: Connection;
+  refreshData?: () => { fetchData: () => Promise<void>; cancel: () => void };
 }
 
 const getSortedProposals = async (
   readOnlySquads: SquadsMesh,
-  vault: PublicKey
+  vault: PublicKey,
 ): Promise<TransactionAccount[]> => {
-  const proposals = await getProposals(readOnlySquads, vault)
-  return proposals.sort((a, b) => b.transactionIndex - a.transactionIndex)
-}
+  const proposals = await getProposals(readOnlySquads, vault);
+  return proposals.sort((a, b) => b.transactionIndex - a.transactionIndex);
+};
 
 export const useMultisig = (): MultisigHookData => {
-  const wallet = useAnchorWallet()
-  const { cluster } = useContext(ClusterContext)
-  const [isLoading, setIsLoading] = useState(true)
+  const wallet = useAnchorWallet();
+  const { cluster } = useContext(ClusterContext);
+  const [isLoading, setIsLoading] = useState(true);
   const [upgradeMultisigAccount, setUpgradeMultisigAccount] =
-    useState<MultisigAccount>()
+    useState<MultisigAccount>();
   const [priceFeedMultisigAccount, setPriceFeedMultisigAccount] =
-    useState<MultisigAccount>()
+    useState<MultisigAccount>();
   const [upgradeMultisigProposals, setUpgradeMultisigProposals] = useState<
     TransactionAccount[]
-  >([])
+  >([]);
   const [priceFeedMultisigProposals, setPriceFeedMultisigProposals] = useState<
     TransactionAccount[]
-  >([])
+  >([]);
 
-  const [urlsIndex, setUrlsIndex] = useState(0)
+  const [urlsIndex, setUrlsIndex] = useState(0);
 
   useEffect(() => {
-    setUrlsIndex(0)
-  }, [cluster])
+    setUrlsIndex(0);
+  }, [cluster]);
 
-  const multisigCluster = useMemo(() => getMultisigCluster(cluster), [cluster])
+  const multisigCluster = useMemo(() => getMultisigCluster(cluster), [cluster]);
 
   const connection = useMemo(() => {
-    const urls = pythClusterApiUrls(multisigCluster)
-    return new Connection(urls[urlsIndex], {
-      commitment: 'confirmed',
-      wsEndpoint: deriveWsUrl(urls[urlsIndex]),
-    })
-  }, [urlsIndex, multisigCluster])
+    const urls = pythClusterApiUrls(multisigCluster);
+    return new Connection(urls[urlsIndex] ?? "", {
+      commitment: "confirmed",
+      wsEndpoint: deriveWsUrl(urls[urlsIndex] ?? ""),
+    });
+  }, [urlsIndex, multisigCluster]);
 
   const readOnlySquads = useMemo(() => {
     return new SquadsMesh({
       connection,
       wallet: new NodeWallet(new Keypair()),
-    })
-  }, [connection])
+    });
+  }, [connection]);
 
   const walletSquads = useMemo(() => {
-    if (!wallet) return undefined
+    if (!wallet) return undefined;
     return new SquadsMesh({
       connection,
       wallet,
-    })
-  }, [connection, wallet])
+    });
+  }, [connection, wallet]);
 
   const refreshData = useCallback(() => {
-    let cancelled = false
+    let cancelled = false;
 
     const fetchData = async () => {
-      setIsLoading(true)
+      setIsLoading(true);
       try {
-        if (cancelled) return
+        if (cancelled) return;
         const upgradeMultisigAccount = await readOnlySquads.getMultisig(
-          UPGRADE_MULTISIG[multisigCluster]
-        )
+          UPGRADE_MULTISIG[multisigCluster],
+        );
 
-        if (cancelled) return
+        if (cancelled) return;
         const priceFeedMultisigAccount = await readOnlySquads.getMultisig(
-          PRICE_FEED_MULTISIG[multisigCluster]
-        )
+          PRICE_FEED_MULTISIG[multisigCluster],
+        );
 
-        if (cancelled) return
+        if (cancelled) return;
         const upgradeProposals = await getSortedProposals(
           readOnlySquads,
-          UPGRADE_MULTISIG[multisigCluster]
-        )
+          UPGRADE_MULTISIG[multisigCluster],
+        );
 
-        if (cancelled) return
+        if (cancelled) return;
         const sortedPriceFeedMultisigProposals = await getSortedProposals(
           readOnlySquads,
-          PRICE_FEED_MULTISIG[multisigCluster]
-        )
+          PRICE_FEED_MULTISIG[multisigCluster],
+        );
 
-        setUpgradeMultisigAccount(upgradeMultisigAccount)
-        setPriceFeedMultisigAccount(priceFeedMultisigAccount)
-        setUpgradeMultisigProposals(upgradeProposals)
-        setPriceFeedMultisigProposals(sortedPriceFeedMultisigProposals)
+        setUpgradeMultisigAccount(upgradeMultisigAccount);
+        setPriceFeedMultisigAccount(priceFeedMultisigAccount);
+        setUpgradeMultisigProposals(upgradeProposals);
+        setPriceFeedMultisigProposals(sortedPriceFeedMultisigProposals);
 
-        setIsLoading(false)
+        setIsLoading(false);
       } catch (e) {
-        console.log(e)
-        if (cancelled) return
-        const urls = pythClusterApiUrls(multisigCluster)
+        console.log(e);
+        if (cancelled) return;
+        const urls = pythClusterApiUrls(multisigCluster);
         if (urlsIndex === urls.length - 1) {
-          setIsLoading(false)
-          console.warn(`Failed to fetch accounts`)
+          setIsLoading(false);
+          console.warn(`Failed to fetch accounts`);
         } else if (urlsIndex < urls.length - 1) {
-          setUrlsIndex((urlsIndex) => urlsIndex + 1)
+          setUrlsIndex((urlsIndex) => urlsIndex + 1);
           console.warn(
-            `Failed with ${urls[urlsIndex]}, trying with ${urls[urlsIndex + 1]}`
-          )
+            `Failed with ${urls[urlsIndex]}, trying with ${urls[urlsIndex + 1]}`,
+          );
         }
       }
-    }
+    };
     const cancel = () => {
-      cancelled = true
-    }
+      cancelled = true;
+    };
 
-    return { cancel, fetchData }
-  }, [readOnlySquads, multisigCluster, urlsIndex])
+    return { cancel, fetchData };
+  }, [readOnlySquads, multisigCluster, urlsIndex]);
 
   useEffect(() => {
-    const { cancel, fetchData } = refreshData()
-    fetchData()
-    return cancel
-  }, [refreshData])
+    const { cancel, fetchData } = refreshData();
+    fetchData();
+    return cancel;
+  }, [refreshData]);
 
   return {
     isLoading,
@@ -151,5 +151,5 @@ export const useMultisig = (): MultisigHookData => {
     priceFeedMultisigProposals,
     refreshData,
     connection,
-  }
-}
+  };
+};

+ 58 - 56
governance/xc_admin/packages/xc_admin_frontend/hooks/usePyth.ts

@@ -1,66 +1,68 @@
 import {
   AccountType,
-  PythCluster,
+  type PythCluster,
   getPythProgramKeyForCluster,
   parseBaseData,
-} from '@pythnetwork/client'
-import { Connection } from '@solana/web3.js'
-import { useContext, useEffect, useRef, useState } from 'react'
-import { ClusterContext } from '../contexts/ClusterContext'
-import { deriveWsUrl, pythClusterApiUrls } from '../utils/pythClusterApiUrl'
+} from "@pythnetwork/client";
+import { Connection } from "@solana/web3.js";
+import { useContext, useEffect, useRef, useState } from "react";
+import { ClusterContext } from "../contexts/ClusterContext";
+import { deriveWsUrl, pythClusterApiUrls } from "../utils/pythClusterApiUrl";
 import {
   ProgramType,
   getConfig,
-  RawConfig,
-  MappingRawConfig,
-  ProductRawConfig,
-  PriceRawConfig,
-} from '@pythnetwork/xc-admin-common'
+  type RawConfig,
+  type MappingRawConfig,
+  type ProductRawConfig,
+  type PriceRawConfig,
+} from "@pythnetwork/xc-admin-common";
 
 interface PythHookData {
-  isLoading: boolean
-  rawConfig: RawConfig
-  connection?: Connection
+  isLoading: boolean;
+  rawConfig: RawConfig;
+  connection?: Connection | undefined;
 }
 
 export const usePyth = (): PythHookData => {
-  const connectionRef = useRef<Connection | undefined>(undefined)
-  const { cluster } = useContext(ClusterContext)
-  const [isLoading, setIsLoading] = useState(true)
-  const [rawConfig, setRawConfig] = useState<RawConfig>({ mappingAccounts: [] })
-  const [urlsIndex, setUrlsIndex] = useState(0)
+  const connectionRef = useRef<Connection | undefined>(undefined);
+  const { cluster } = useContext(ClusterContext);
+  const [isLoading, setIsLoading] = useState(true);
+  const [rawConfig, setRawConfig] = useState<RawConfig>({
+    mappingAccounts: [],
+  });
+  const [urlsIndex, setUrlsIndex] = useState(0);
 
   useEffect(() => {
-    setIsLoading(true)
-  }, [urlsIndex, cluster])
+    setIsLoading(true);
+  }, [urlsIndex, cluster]);
 
   useEffect(() => {
-    setUrlsIndex(0)
-  }, [cluster])
+    setUrlsIndex(0);
+  }, [cluster]);
 
   useEffect(() => {
-    let cancelled = false
-    const urls = pythClusterApiUrls(cluster)
-    const connection = new Connection(urls[urlsIndex], {
-      commitment: 'confirmed',
-      wsEndpoint: deriveWsUrl(urls[urlsIndex]),
-    })
+    let cancelled = false;
+    const urls = pythClusterApiUrls(cluster);
+    const connection = new Connection(urls[urlsIndex] ?? "", {
+      commitment: "confirmed",
+      wsEndpoint: deriveWsUrl(urls[urlsIndex] ?? ""),
+    });
 
-    connectionRef.current = connection
-    ;(async () => {
+    connectionRef.current = connection;
+    (async () => {
       try {
         const allPythAccounts = [
           ...(await connection.getProgramAccounts(
-            getPythProgramKeyForCluster(cluster as PythCluster)
+            getPythProgramKeyForCluster(cluster as PythCluster),
           )),
-        ]
-        if (cancelled) return
+        ];
+        if (cancelled) return;
 
         // Use the functional approach to parse the accounts
         const parsedConfig = getConfig[ProgramType.PYTH_CORE]({
           accounts: allPythAccounts,
           cluster: cluster as PythCluster,
-        })
+        });
 
         // Get all account pubkeys from the parsed config
         const processedPubkeys = new Set<string>([
@@ -69,19 +71,19 @@ export const usePyth = (): PythHookData => {
             mapping.products.flatMap((prod) => [
               prod.address.toBase58(),
               ...prod.priceAccounts.map((price) => price.address.toBase58()),
-            ])
+            ]),
           ),
-        ])
+        ]);
 
         // Find accounts that weren't included in the parsed config
         const unprocessedAccounts = allPythAccounts.filter((account) => {
-          const base = parseBaseData(account.account.data)
+          const base = parseBaseData(account.account.data);
           // Skip permission accounts entirely
           if (!base || base.type === AccountType.Permission) {
-            return false
+            return false;
           }
-          return !processedPubkeys.has(account.pubkey.toBase58())
-        })
+          return !processedPubkeys.has(account.pubkey.toBase58());
+        });
 
         if (unprocessedAccounts.length > 0) {
           console.warn(
@@ -89,34 +91,34 @@ export const usePyth = (): PythHookData => {
             unprocessedAccounts.map((acc) => ({
               pubkey: acc.pubkey.toBase58(),
               type: parseBaseData(acc.account.data)?.type,
-            }))
-          )
+            })),
+          );
         }
 
-        setRawConfig(parsedConfig as RawConfig)
-        setIsLoading(false)
+        setRawConfig(parsedConfig as RawConfig);
+        setIsLoading(false);
       } catch (e) {
-        if (cancelled) return
+        if (cancelled) return;
         if (urlsIndex === urls.length - 1) {
-          setIsLoading(false)
-          console.warn(`Failed to fetch accounts`)
+          setIsLoading(false);
+          console.warn(`Failed to fetch accounts`);
         } else if (urlsIndex < urls.length - 1) {
-          setUrlsIndex((urlsIndex) => urlsIndex + 1)
+          setUrlsIndex((urlsIndex) => urlsIndex + 1);
         }
       }
-    })()
+    })();
 
     return () => {
-      cancelled = true
-    }
-  }, [urlsIndex, cluster])
+      cancelled = true;
+    };
+  }, [urlsIndex, cluster]);
 
   return {
     isLoading,
     connection: connectionRef.current,
     rawConfig,
-  }
-}
+  };
+};
 
 // Re-export the types for compatibility
-export type { RawConfig, MappingRawConfig, ProductRawConfig, PriceRawConfig }
+export type { RawConfig, MappingRawConfig, ProductRawConfig, PriceRawConfig };

+ 10 - 10
governance/xc_admin/packages/xc_admin_frontend/next-seo.config.js

@@ -1,22 +1,22 @@
 export default {
-  defaultTitle: 'Pyth Network',
-  titleTemplate: '%s | Pyth Network',
+  defaultTitle: "Pyth Network",
+  titleTemplate: "%s | Pyth Network",
   description:
-    'Pyth is building a way to deliver a decentralized, cross-chain market of verifiable data from first-party sources to any smart contract, anywhere.',
+    "Pyth is building a way to deliver a decentralized, cross-chain market of verifiable data from first-party sources to any smart contract, anywhere.",
   openGraph: {
-    type: 'website',
+    type: "website",
     images: [
       {
-        url: 'https://proposals.pyth.network/default-banner.png',
+        url: "https://proposals.pyth.network/default-banner.png",
         width: 1200,
         height: 630,
-        alt: 'Pyth Network',
-        type: 'image/png',
+        alt: "Pyth Network",
+        type: "image/png",
       },
     ],
   },
   twitter: {
-    handle: '@PythNetwork',
-    cardType: 'summary_large_image',
+    handle: "@PythNetwork",
+    cardType: "summary_large_image",
   },
-}
+};

+ 3 - 3
governance/xc_admin/packages/xc_admin_frontend/pages/_app.tsx

@@ -11,7 +11,7 @@ import {
   SolflareWalletAdapter,
   TorusWalletAdapter,
   WalletConnectWalletAdapter,
-  WalletConnectWalletAdapterConfig,
+  type WalletConnectWalletAdapterConfig,
 } from '@solana/wallet-adapter-wallets'
 import { clusterApiUrl } from '@solana/web3.js'
 import { DefaultSeo } from 'next-seo'
@@ -21,7 +21,7 @@ import { useMemo } from 'react'
 import { Toaster } from 'react-hot-toast'
 import { ClusterProvider } from '../contexts/ClusterContext'
 import { ProgramProvider } from '../contexts/ProgramContext'
-import SEO from '../next-seo.config'
+import SEO from '../next-seo.config.js'
 import '../styles/globals.css'
 import { NuqsAdapter } from 'nuqs/adapters/next/pages'
 
@@ -29,7 +29,7 @@ const walletConnectConfig: WalletConnectWalletAdapterConfig = {
   network: WalletAdapterNetwork.Mainnet,
   options: {
     relayUrl: 'wss://relay.walletconnect.com',
-    projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID,
+    projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID ?? '',
     metadata: {
       name: 'Pyth Proposals Page',
       description: 'Vote on Pyth Improvement Proposals',

+ 5 - 5
governance/xc_admin/packages/xc_admin_frontend/pages/index.tsx

@@ -100,10 +100,10 @@ const Home: NextPage<{
 
   // set current tab value when tab is clicked
   const handleChangeTab = (index: number) => {
-    if (tabInfoArray[index].queryString !== 'proposals') {
+    if (tabInfoArray[index]?.queryString !== 'proposals') {
       delete router.query.proposal
     }
-    router.query.tab = tabInfoArray[index].queryString
+    router.query.tab = tabInfoArray[index]?.queryString
     setCurrentTabIndex(index)
     router.push(
       {
@@ -157,13 +157,13 @@ const Home: NextPage<{
               </Tab.Group>
             </div>
           </div>
-          {tabInfoArray[currentTabIndex].queryString ===
+          {tabInfoArray[currentTabIndex]?.queryString ===
             TAB_INFO.General.queryString && (
             <General proposerServerUrl={proposerServerUrl} />
           )}
-          {tabInfoArray[currentTabIndex].queryString ===
+          {tabInfoArray[currentTabIndex]?.queryString ===
             TAB_INFO.UpdatePermissions.queryString && <UpdatePermissions />}
-          {tabInfoArray[currentTabIndex].queryString ===
+          {tabInfoArray[currentTabIndex]?.queryString ===
             TAB_INFO.Proposals.queryString && <Proposals />}
         </MultisigContextProvider>
       </PythContextProvider>

+ 4 - 1
governance/xc_admin/packages/xc_admin_frontend/tsconfig.json

@@ -4,7 +4,10 @@
     "paths": {
       "@images/*": ["./images/*"],
       "xc-admin-common": ["../xc_admin_common/src"]
-    }
+    },
+    "exactOptionalPropertyTypes": false,
+    "strictNullChecks": false,
+    "noImplicitAny": false
   },
   "include": [
     "next-env.d.ts",

+ 28 - 28
governance/xc_admin/packages/xc_admin_frontend/utils/pythClusterApiUrl.ts

@@ -1,54 +1,54 @@
 import {
-  PythCluster,
+  type PythCluster,
   getPythClusterApiUrl,
-} from '@pythnetwork/client/lib/cluster'
+} from "@pythnetwork/client/lib/cluster";
 
 const CLUSTER_URLS: Record<PythCluster, string[]> = {
-  'mainnet-beta': [
-    process.env.NEXT_PUBLIC_MAINNET_RPC || getPythClusterApiUrl('mainnet-beta'),
-    'https://pyth-network.rpcpool.com/' +
-      (process.env.NEXT_PUBLIC_RPC_POOL_TOKEN || ''),
-    'http://pyth-rpc1.certus.one:8899/',
-    'http://pyth-rpc2.certus.one:8899/',
-    'https://api.mainnet-beta.solana.com/',
+  "mainnet-beta": [
+    process.env.NEXT_PUBLIC_MAINNET_RPC || getPythClusterApiUrl("mainnet-beta"),
+    "https://pyth-network.rpcpool.com/" +
+      (process.env.NEXT_PUBLIC_RPC_POOL_TOKEN || ""),
+    "http://pyth-rpc1.certus.one:8899/",
+    "http://pyth-rpc2.certus.one:8899/",
+    "https://api.mainnet-beta.solana.com/",
   ],
   devnet: [
-    process.env.NEXT_PUBLIC_DEVNET_RPC || getPythClusterApiUrl('devnet'),
-    'https://api.devnet.solana.com/',
+    process.env.NEXT_PUBLIC_DEVNET_RPC || getPythClusterApiUrl("devnet"),
+    "https://api.devnet.solana.com/",
   ],
   testnet: [
-    process.env.NEXT_PUBLIC_TESTNET_RPC || getPythClusterApiUrl('testnet'),
-    'https://api.testnet.solana.com/',
+    process.env.NEXT_PUBLIC_TESTNET_RPC || getPythClusterApiUrl("testnet"),
+    "https://api.testnet.solana.com/",
   ],
-  'pythtest-conformance': [
+  "pythtest-conformance": [
     process.env.NEXT_PUBLIC_PYTHTEST_RPC ||
-      getPythClusterApiUrl('pythtest-conformance'),
-    'https://api.pythtest.pyth.network/',
+      getPythClusterApiUrl("pythtest-conformance"),
+    "https://api.pythtest.pyth.network/",
   ],
-  'pythtest-crosschain': [
+  "pythtest-crosschain": [
     process.env.NEXT_PUBLIC_PYTHTEST_RPC ||
-      getPythClusterApiUrl('pythtest-crosschain'),
-    'https://api.pythtest.pyth.network/',
+      getPythClusterApiUrl("pythtest-crosschain"),
+    "https://api.pythtest.pyth.network/",
   ],
   pythnet: [
-    process.env.NEXT_PUBLIC_PYTHNET_RPC || getPythClusterApiUrl('pythnet'),
-    'https://pythnet.rpcpool.com/',
+    process.env.NEXT_PUBLIC_PYTHNET_RPC || getPythClusterApiUrl("pythnet"),
+    "https://pythnet.rpcpool.com/",
   ],
-  localnet: ['http://localhost:8899/'],
-}
+  localnet: ["http://localhost:8899/"],
+};
 
 export function pythClusterApiUrls(cluster: PythCluster) {
   if (CLUSTER_URLS.hasOwnProperty(cluster)) {
-    return CLUSTER_URLS[cluster]
+    return CLUSTER_URLS[cluster];
   } else {
-    return []
+    return [];
   }
 }
 
 export function deriveWsUrl(httpUrl: string) {
-  if (httpUrl.startsWith('https://')) {
-    return 'wss://' + httpUrl.slice(8)
+  if (httpUrl.startsWith("https://")) {
+    return "wss://" + httpUrl.slice(8);
   } else {
-    return 'ws://' + httpUrl.slice(7)
+    return "ws://" + httpUrl.slice(7);
   }
 }