feat(xc-admin-frontend): instructions summary in proposal page + improve ui in proposal row + refactor the code (#1478)

* refactor: move proposals to a folder

* refactor: use @images instead of relative paths

* refactor: split proposals into multiple files

* refactor: add type for proposal status

* refactor: add eslint and fix errors

* refactor: fix eslint errors

* refactor: fix eslint

* refactor: fix prettier

* refactor: remove any

* refactor: Proposals.tsx

* feat: add basic instructions summary

* feat: add unknown instruction

* fix: revert package-lock.json

* fix: update package-lock.json

* fix: pre-commit

* fix: ts error

* fix: remove message buffer dependency

* fix: revert back the cluster default

* feat: add support for different types of instructions

* feat: add transaction index to proposal row

* feat: improve the proposal row ui

* fix: display bigint properly (#1499)

---------

Co-authored-by: guibescos <59208140+guibescos@users.noreply.github.com>
This commit is contained in:
Keyvan Khademi 2024-04-23 13:24:44 -07:00 committed by GitHub
parent d05df508a8
commit b110bbca5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1231 additions and 658 deletions

View File

@ -1 +1,3 @@
node_modules
tailwind.config.js
next.config.js

View File

@ -1,3 +1,14 @@
{
"extends": "next/core-web-vitals"
"extends": [
"plugin:@typescript-eslint/recommended",
"next/core-web-vitals",
"prettier"
],
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
]
}
}

View File

@ -2,7 +2,8 @@ import { Menu, Transition } from '@headlessui/react'
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 Arrow from '@images/icons/down.inline.svg'
import { PythCluster } from '@pythnetwork/client'
const ClusterSwitch = ({ light }: { light?: boolean | null }) => {
const router = useRouter()
@ -27,8 +28,8 @@ const ClusterSwitch = ({ light }: { light?: boolean | null }) => {
)
useEffect(() => {
router.query && router.query.cluster
? setCluster(router.query.cluster)
router?.query?.cluster
? setCluster(router.query.cluster as PythCluster)
: setCluster(DEFAULT_CLUSTER)
}, [setCluster, router])

View File

@ -4,7 +4,7 @@ import { PythOracle } from '@pythnetwork/client/lib/anchor'
import * as Label from '@radix-ui/react-label'
import { useWallet } from '@solana/wallet-adapter-react'
import { WalletModalButton } from '@solana/wallet-adapter-react-ui'
import { Cluster, PublicKey, TransactionInstruction } from '@solana/web3.js'
import { PublicKey, TransactionInstruction } from '@solana/web3.js'
import SquadsMesh from '@sqds/mesh'
import axios from 'axios'
import { Fragment, useContext, useEffect, useState } from 'react'
@ -15,12 +15,11 @@ import {
isRemoteCluster,
mapKey,
PRICE_FEED_MULTISIG,
WORMHOLE_ADDRESS,
} from 'xc_admin_common'
import { ClusterContext } from '../contexts/ClusterContext'
import { usePythContext } from '../contexts/PythContext'
import { ProductRawConfig } from '../hooks/usePyth'
import Arrow from '../images/icons/down.inline.svg'
import Arrow from '@images/icons/down.inline.svg'
import { capitalizeFirstLetter } from '../utils/capitalizeFirstLetter'
import Spinner from './common/Spinner'
import CloseIcon from './icons/CloseIcon'
@ -83,8 +82,6 @@ const PermissionDepermissionKey = ({
1
)
const isRemote: boolean = isRemoteCluster(cluster)
const multisigCluster: Cluster | 'localnet' = getMultisigCluster(cluster)
const wormholeAddress = WORMHOLE_ADDRESS[multisigCluster]
const fundingAccount = isRemote
? mapKey(multisigAuthority)
: multisigAuthority

View File

@ -3,9 +3,10 @@ import { useRouter } from 'next/router'
import { Fragment, useCallback, useContext, useEffect } from 'react'
import {
DEFAULT_STATUS_FILTER,
type ProposalStatusFilter,
StatusFilterContext,
} from '../contexts/StatusFilterContext'
import Arrow from '../images/icons/down.inline.svg'
import Arrow from '@images/icons/down.inline.svg'
const ProposalStatusFilter = () => {
const router = useRouter()
@ -31,11 +32,11 @@ const ProposalStatusFilter = () => {
useEffect(() => {
router.query && router.query.status
? setStatusFilter(router.query.status as string)
? setStatusFilter(router.query.status as ProposalStatusFilter)
: setStatusFilter(DEFAULT_STATUS_FILTER)
}, [setStatusFilter, router])
const statuses = [
const statuses: ProposalStatusFilter[] = [
'all',
'active',
'executed',

View File

@ -1,5 +1,5 @@
import copy from 'copy-to-clipboard'
import CopyIcon from '../../images/icons/copy.inline.svg'
import CopyIcon from '@images/icons/copy.inline.svg'
const CopyText: React.FC<{
text: string

View File

@ -1,10 +1,10 @@
import Link from 'next/link'
import Discord from '../../images/icons/discord.inline.svg'
import Github from '../../images/icons/github.inline.svg'
import LinkedIn from '../../images/icons/linkedin.inline.svg'
import Telegram from '../../images/icons/telegram.inline.svg'
import Twitter from '../../images/icons/twitter.inline.svg'
import Youtube from '../../images/icons/youtube.inline.svg'
import Discord from '@images/icons/discord.inline.svg'
import Github from '@images/icons/github.inline.svg'
import LinkedIn from '@images/icons/linkedin.inline.svg'
import Telegram from '@images/icons/telegram.inline.svg'
import Twitter from '@images/icons/twitter.inline.svg'
import Youtube from '@images/icons/youtube.inline.svg'
const SocialLinks = () => {
return (

View File

@ -3,7 +3,7 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { useContext, useEffect, useState } from 'react'
import { ClusterContext, DEFAULT_CLUSTER } from '../../contexts/ClusterContext'
import Pyth from '../../images/logomark.inline.svg'
import Pyth from '@images/logomark.inline.svg'
import MobileMenu from './MobileMenu'
const WalletMultiButtonDynamic = dynamic(
@ -156,7 +156,7 @@ const Header = () => {
</div>
</div>
</header>
<MobileMenu headerState={headerState} setHeaderState={setHeaderState} />
<MobileMenu headerState={headerState} />
</>
)
}

View File

@ -4,7 +4,7 @@ import Header from './Header'
const Layout = ({ children }: { children: React.ReactNode }) => {
return (
<div className="flex flex-col min-h-screen relative overflow-hidden">
<div className="relative flex min-h-screen flex-col overflow-hidden">
<Header />
<main className="flex-grow">{children}</main>
<Footer />

View File

@ -6,15 +6,14 @@ import { useContext, useEffect, useRef } from 'react'
import { ClusterContext, DEFAULT_CLUSTER } from '../../contexts/ClusterContext'
import { BurgerState } from './Header'
import orb from '../../images/burger.png'
import orb from '@images/burger.png'
interface MenuProps {
headerState: BurgerState
setHeaderState: Function
}
const MobileMenu = ({ headerState, setHeaderState }: MenuProps) => {
let burgerMenu = useRef(null)
const MobileMenu = ({ headerState }: MenuProps) => {
const burgerMenu = useRef(null)
const router = useRouter()
const { cluster } = useContext(ClusterContext)

View File

@ -2,7 +2,7 @@ import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor'
import { AccountType, getPythProgramKeyForCluster } from '@pythnetwork/client'
import { PythOracle, pythOracleProgram } from '@pythnetwork/client/lib/anchor'
import { useWallet } from '@solana/wallet-adapter-react'
import { Cluster, PublicKey, TransactionInstruction } from '@solana/web3.js'
import { PublicKey, TransactionInstruction } from '@solana/web3.js'
import messageBuffer from 'message_buffer/idl/message_buffer.json'
import { MessageBuffer } from 'message_buffer/idl/message_buffer'
import axios from 'axios'
@ -18,7 +18,6 @@ import {
MESSAGE_BUFFER_PROGRAM_ID,
MESSAGE_BUFFER_BUFFER_SIZE,
PRICE_FEED_MULTISIG,
WORMHOLE_ADDRESS,
PRICE_FEED_OPS_KEY,
getMessageBufferAddressForPrice,
getMaximumNumberOfPublishers,
@ -44,8 +43,6 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
useState(false)
const { cluster } = useContext(ClusterContext)
const isRemote: boolean = isRemoteCluster(cluster) // Move to multisig context
const multisigCluster: Cluster | 'localnet' = getMultisigCluster(cluster) // Move to multisig context
const wormholeAddress = WORMHOLE_ADDRESS[multisigCluster] // Move to multisig context
const { isLoading: isMultisigLoading, squads } = useMultisigContext()
const { rawConfig, dataIsLoading, connection } = usePythContext()
const { connected } = useWallet()
@ -370,7 +367,7 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
}
// create add publisher instruction if there are any publishers
for (let publisherKey of newChanges.priceAccounts[0].publishers) {
for (const publisherKey of newChanges.priceAccounts[0].publishers) {
instructions.push(
await pythProgramClient.methods
.addPublisher(new PublicKey(publisherKey))
@ -525,7 +522,7 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
// add instructions to remove publishers
for (let publisherKey of publisherKeysToRemove) {
for (const publisherKey of publisherKeysToRemove) {
instructions.push(
await pythProgramClient.methods
.delPublisher(new PublicKey(publisherKey))
@ -538,7 +535,7 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
}
// add instructions to add new publishers
for (let publisherKey of publisherKeysToAdd) {
for (const publisherKey of publisherKeysToAdd) {
instructions.push(
await pythProgramClient.methods
.addPublisher(new PublicKey(publisherKey))
@ -823,7 +820,7 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
// create anchor wallet when connected
useEffect(() => {
if (connected && squads) {
if (connected && squads && connection) {
const provider = new AnchorProvider(
connection,
squads.wallet as Wallet,

View File

@ -0,0 +1,25 @@
import { PythCluster } from '@pythnetwork/client'
import { MultisigInstruction } from 'xc_admin_common'
import { getInstructionsSummary } from './utils'
export const InstructionsSummary = ({
instructions,
cluster,
}: {
instructions: MultisigInstruction[]
cluster: PythCluster
}) => {
const instructionsCount = getInstructionsSummary({ instructions, cluster })
return (
<div className="space-y-4">
{Object.entries(instructionsCount).map(([name, count]) => {
return (
<div key={name}>
{name}: {count}
</div>
)
})}
</div>
)
}

View File

@ -1,4 +1,4 @@
import * as Tooltip from '@radix-ui/react-tooltip'
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
import { useWallet } from '@solana/wallet-adapter-react'
import {
AccountMeta,
@ -7,147 +7,47 @@ import {
SystemProgram,
TransactionInstruction,
} from '@solana/web3.js'
import SquadsMesh from '@sqds/mesh'
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
import { useRouter } from 'next/router'
import { Fragment, useCallback, useContext, useEffect, useState } from 'react'
import { Fragment, useContext, useEffect, useState } from 'react'
import toast from 'react-hot-toast'
import {
AnchorMultisigInstruction,
ExecutePostedVaa,
getMultisigCluster,
MultisigInstruction,
MultisigParser,
PythMultisigInstruction,
AnchorMultisigInstruction,
WormholeMultisigInstruction,
getManyProposalsInstructions,
getMultisigCluster,
getProgramName,
} from 'xc_admin_common'
import { ClusterContext } from '../../contexts/ClusterContext'
import { useMultisigContext } from '../../contexts/MultisigContext'
import { usePythContext } from '../../contexts/PythContext'
import { StatusFilterContext } from '../../contexts/StatusFilterContext'
import VerifiedIcon from '../../images/icons/verified.inline.svg'
import WarningIcon from '../../images/icons/warning.inline.svg'
import VotedIcon from '../../images/icons/voted.inline.svg'
import { capitalizeFirstLetter } from '../../utils/capitalizeFirstLetter'
import ClusterSwitch from '../ClusterSwitch'
import CopyText from '../common/CopyText'
import Spinner from '../common/Spinner'
import Loadbar from '../loaders/Loadbar'
import ProposalStatusFilter from '../ProposalStatusFilter'
import SquadsMesh from '@sqds/mesh'
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
import { WormholeInstructionView } from '../InstructionViews/WormholeInstructionView'
import { ClusterContext } from '../../../contexts/ClusterContext'
import { useMultisigContext } from '../../../contexts/MultisigContext'
import { usePythContext } from '../../../contexts/PythContext'
import { capitalizeFirstLetter } from '../../../utils/capitalizeFirstLetter'
import {
ParsedAccountPubkeyRow,
SignerTag,
WritableTag,
} from '../InstructionViews/AccountUtils'
} from '../../InstructionViews/AccountUtils'
import { WormholeInstructionView } from '../../InstructionViews/WormholeInstructionView'
import CopyText from '../../common/CopyText'
import Spinner from '../../common/Spinner'
import Loadbar from '../../loaders/Loadbar'
import { getMappingCluster, isPubkey } from '../InstructionViews/utils'
import { getPythProgramKeyForCluster, PythCluster } from '@pythnetwork/client'
import {
DEFAULT_PRIORITY_FEE_CONFIG,
TransactionBuilder,
sendTransactions,
} from '@pythnetwork/solana-utils'
import { Wallet } from '@coral-xyz/anchor'
const ProposalRow = ({
proposal,
multisig,
}: {
proposal: TransactionAccount
multisig: MultisigAccount | undefined
}) => {
const status = getProposalStatus(proposal, multisig)
import { PythCluster, getPythProgramKeyForCluster } from '@pythnetwork/client'
import { TransactionBuilder, sendTransactions } from '@pythnetwork/solana-utils'
import { getMappingCluster, isPubkey } from '../../InstructionViews/utils'
import { StatusTag } from './StatusTag'
import { getProposalStatus } from './utils'
const router = useRouter()
const handleClickIndividualProposal = useCallback(
(proposalPubkey: string) => {
router.query.proposal = proposalPubkey
router.push(
{
pathname: router.pathname,
query: router.query,
},
undefined,
{ scroll: true }
)
},
[router]
)
return (
<div
className="my-2 max-h-[58px] cursor-pointer bg-[#1E1B2F] hover:bg-darkGray2"
onClick={() =>
handleClickIndividualProposal(proposal.publicKey.toBase58())
}
>
<div className="flex justify-between p-4">
<div className="flex">
<span className="mr-2 hidden sm:block">
{proposal.publicKey.toBase58()}
</span>
<span className="mr-2 sm:hidden">
{proposal.publicKey.toBase58().slice(0, 6) +
'...' +
proposal.publicKey.toBase58().slice(-6)}
</span>{' '}
</div>
<div className="flex space-x-2">
{proposal.approved.length > 0 && status === 'active' && (
<div>
<StatusTag
proposalStatus="executed"
text={`Approved: ${proposal.approved.length}`}
/>
</div>
)}
{proposal.rejected.length > 0 && status === 'active' && (
<div>
<StatusTag
proposalStatus="rejected"
text={`Rejected: ${proposal.rejected.length}`}
/>
</div>
)}
<div>
<StatusTag proposalStatus={status} />
</div>
</div>
</div>
</div>
)
}
const StatusTag = ({
proposalStatus,
text,
}: {
proposalStatus: string
text?: string
}) => {
return (
<div
className={`flex items-center justify-center rounded-full ${
proposalStatus === 'active'
? 'bg-[#3C3299]'
: proposalStatus === 'executed'
? 'bg-[#1B730E]'
: proposalStatus === 'cancelled'
? 'bg-[#C4428F]'
: proposalStatus === 'rejected'
? 'bg-[#CF6E42]'
: proposalStatus === 'expired'
? 'bg-[#A52A2A]'
: 'bg-pythPurple'
} py-1 px-2 text-xs`}
>
{text || proposalStatus}
</div>
)
}
import VerifiedIcon from '@images/icons/verified.inline.svg'
import VotedIcon from '@images/icons/voted.inline.svg'
import WarningIcon from '@images/icons/warning.inline.svg'
import * as Tooltip from '@radix-ui/react-tooltip'
import { InstructionsSummary } from './InstructionsSummary'
const IconWithTooltip = ({
icon,
@ -194,26 +94,11 @@ const VotedIconWithTooltip = () => {
return (
<IconWithTooltip
icon={<VotedIcon />}
tooltipText=" You have voted on this proposal."
tooltipText="You have voted on this proposal."
/>
)
}
const getProposalStatus = (
proposal: TransactionAccount | undefined,
multisig: MultisigAccount | undefined
): string => {
if (multisig && proposal) {
const onChainStatus = Object.keys(proposal.status)[0]
return proposal.transactionIndex <= multisig.msChangeIndex &&
(onChainStatus == 'active' || onChainStatus == 'draft')
? 'expired'
: onChainStatus
} else {
return 'unkwown'
}
}
const AccountList = ({
listName,
accounts,
@ -244,14 +129,12 @@ const AccountList = ({
)
}
type ProposalType = 'priceFeed' | 'governance'
const Proposal = ({
export const Proposal = ({
proposal,
multisig,
}: {
proposal: TransactionAccount | undefined
multisig: MultisigAccount | undefined
proposal?: TransactionAccount
multisig?: MultisigAccount
}) => {
const [instructions, setInstructions] = useState<MultisigInstruction[]>([])
const [isTransactionLoading, setIsTransactionLoading] = useState(false)
@ -477,9 +360,14 @@ const Proposal = ({
)
}
return proposal !== undefined &&
multisig !== undefined &&
!isMultisigLoading ? (
if (!proposal || !multisig || isMultisigLoading)
return (
<div className="mt-6">
<Loadbar theme="light" />
</div>
)
return (
<div className="grid grid-cols-3 gap-4">
<div className="col-span-3 my-2 space-y-4 bg-[#1E1B2F] p-4">
<h4 className="h4 font-semibold">
@ -599,6 +487,9 @@ const Proposal = ({
Total Instructions: {instructions.length}
</h4>
<hr className="border-gray-700" />
<h4 className="h4 text-[20px] font-semibold">Summary</h4>
<InstructionsSummary instructions={instructions} cluster={cluster} />
<hr className="border-gray-700" />
{instructions?.map((instruction, index) => (
<Fragment key={index}>
<h4 className="h4 text-[20px] font-semibold">
@ -659,6 +550,8 @@ const Proposal = ({
? instruction.args[key]
: instruction.args[key] instanceof Uint8Array
? instruction.args[key].toString('hex')
: typeof instruction.args[key] === 'bigint'
? instruction.args[key].toString()
: JSON.stringify(instruction.args[key])}
</div>
)}
@ -764,213 +657,5 @@ const Proposal = ({
))}
</div>
</div>
) : (
<div className="mt-6">
<Loadbar theme="light" />
</div>
)
}
const Proposals = () => {
const router = useRouter()
const { connected, publicKey: signerPublicKey } = useWallet()
const [currentProposal, setCurrentProposal] = useState<TransactionAccount>()
const [currentProposalPubkey, setCurrentProposalPubkey] = useState<string>()
const { cluster } = useContext(ClusterContext)
const { statusFilter } = useContext(StatusFilterContext)
const {
upgradeMultisigAccount,
priceFeedMultisigAccount,
priceFeedMultisigProposals,
upgradeMultisigProposals,
isLoading: isMultisigLoading,
refreshData,
} = useMultisigContext()
const [proposalType, setProposalType] = useState<ProposalType>('priceFeed')
const multisigAccount =
proposalType === 'priceFeed'
? priceFeedMultisigAccount
: upgradeMultisigAccount
const multisigProposals =
proposalType === 'priceFeed'
? priceFeedMultisigProposals
: upgradeMultisigProposals
const [filteredProposals, setFilteredProposals] = useState<
TransactionAccount[]
>([])
const handleClickBackToProposals = () => {
delete router.query.proposal
router.push(
{
pathname: router.pathname,
query: router.query,
},
undefined,
{ scroll: false }
)
}
useEffect(() => {
if (router.query.proposal) {
setCurrentProposalPubkey(router.query.proposal as string)
} else {
setCurrentProposalPubkey(undefined)
}
}, [router.query.proposal])
const switchProposalType = useCallback(() => {
if (proposalType === 'priceFeed') {
setProposalType('governance')
} else {
setProposalType('priceFeed')
}
}, [proposalType])
useEffect(() => {
if (currentProposalPubkey) {
const currProposal = multisigProposals.find(
(proposal) => proposal.publicKey.toBase58() === currentProposalPubkey
)
setCurrentProposal(currProposal)
if (currProposal === undefined) {
const otherProposals =
proposalType !== 'priceFeed'
? priceFeedMultisigProposals
: upgradeMultisigProposals
if (
otherProposals.findIndex(
(proposal) =>
proposal.publicKey.toBase58() === currentProposalPubkey
) !== -1
) {
switchProposalType()
}
}
}
}, [
switchProposalType,
priceFeedMultisigProposals,
proposalType,
upgradeMultisigProposals,
currentProposalPubkey,
multisigProposals,
cluster,
])
useEffect(() => {
// filter price feed multisig proposals by status
if (statusFilter === 'all') {
setFilteredProposals(multisigProposals)
} else {
setFilteredProposals(
multisigProposals.filter(
(proposal) =>
getProposalStatus(proposal, multisigAccount) === statusFilter
)
)
}
}, [statusFilter, multisigAccount, multisigProposals])
return (
<div className="relative">
<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">
{proposalType === 'priceFeed' ? 'Price Feed ' : 'Governance '}{' '}
{router.query.proposal === undefined ? 'Proposals' : 'Proposal'}
</h1>
</div>
</div>
<div className="container min-h-[50vh]">
{router.query.proposal === undefined ? (
<>
<div className="flex flex-col justify-between md:flex-row">
<div className="mb-4 flex items-center md:mb-0">
<ClusterSwitch />
</div>
<div className="flex space-x-2">
{refreshData && (
<button
disabled={isMultisigLoading}
className="sub-action-btn text-base"
onClick={() => {
const { fetchData } = refreshData()
fetchData()
}}
>
Refresh
</button>
)}
<button
disabled={isMultisigLoading}
className="action-btn text-base"
onClick={switchProposalType}
>
Show
{proposalType !== 'priceFeed'
? ' Price Feed '
: ' Governance '}
Proposals
</button>
</div>
</div>
<div className="relative mt-6">
{isMultisigLoading ? (
<div className="mt-3">
<Loadbar theme="light" />
</div>
) : (
<>
<div className="flex items-center justify-between pb-4">
<h4 className="h4">
Total Proposals: {filteredProposals.length}
</h4>
<ProposalStatusFilter />
</div>
{filteredProposals.length > 0 ? (
<div className="flex flex-col">
{filteredProposals.map((proposal, idx) => (
<ProposalRow
key={proposal.publicKey.toBase58()}
proposal={proposal}
multisig={multisigAccount}
/>
))}
</div>
) : (
<div className="mt-4">
No proposals found. If you&apos;re a member of the
multisig, you can create a proposal.
</div>
)}
</>
)}
</div>
</>
) : !isMultisigLoading && currentProposal !== undefined ? (
<>
<div
className="max-w-fit cursor-pointer bg-darkGray2 p-3 text-xs font-semibold outline-none transition-colors hover:bg-darkGray3 md:text-base"
onClick={handleClickBackToProposals}
>
&#8592; back to proposals
</div>
<div className="relative mt-6">
<Proposal proposal={currentProposal} multisig={multisigAccount} />
</div>
</>
) : (
<div className="mt-3">
<Loadbar theme="light" />
</div>
)}
</div>
</div>
)
}
export default Proposals

View File

@ -0,0 +1,191 @@
import SquadsMesh from '@sqds/mesh'
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
import { useRouter } from 'next/router'
import { useCallback, useContext, useEffect, useRef, useState } from 'react'
import { getMultisigCluster } from 'xc_admin_common'
import { ClusterContext } from '../../../contexts/ClusterContext'
import { useMultisigContext } from '../../../contexts/MultisigContext'
import { StatusTag } from './StatusTag'
import { getInstructionsSummary, getProposalStatus } from './utils'
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
import { AccountMeta, Keypair } from '@solana/web3.js'
import { MultisigParser, getManyProposalsInstructions } from 'xc_admin_common'
export const ProposalRow = ({
proposal,
multisig,
}: {
proposal: TransactionAccount
multisig: MultisigAccount | undefined
}) => {
const [time, setTime] = useState<Date>()
const [instructions, setInstructions] = useState<[string, number][]>()
const status = getProposalStatus(proposal, multisig)
const { cluster } = useContext(ClusterContext)
const { isLoading: isMultisigLoading, connection } = useMultisigContext()
const router = useRouter()
const elementRef = useRef(null)
const formattedTime = time?.toLocaleString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: false,
})
/**
* Fetch the block time of the first transaction of the proposal
* and calculates the instructions summary of the proposal
* when the proposal is in view
*/
useEffect(() => {
let isCancelled = false
const element = elementRef.current
const observer = new IntersectionObserver(async (entries) => {
if (entries[0].isIntersecting) {
if (isMultisigLoading || !connection) {
return
}
// set proposal time
if (!time) {
connection
.getConfirmedSignaturesForAddress2(proposal.publicKey)
.then((txs) => {
if (isCancelled) return
const firstBlockTime = txs?.[txs.length - 1]?.blockTime
if (firstBlockTime) {
setTime(new Date(firstBlockTime * 1000))
}
})
}
// calculate instructions summary
if (!instructions) {
const readOnlySquads = new SquadsMesh({
connection,
wallet: new NodeWallet(new Keypair()),
})
const proposalInstructions = (
await getManyProposalsInstructions(readOnlySquads, [proposal])
)[0]
const multisigParser = MultisigParser.fromCluster(
getMultisigCluster(cluster)
)
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,
cluster,
})
// show only the first two instructions
// and group the rest under 'other'
const shortSummary = Object.entries(summary).slice(0, 2)
const otherValue = Object.values(summary)
.slice(2)
.reduce((acc, curr) => acc + curr, 0)
const updatedSummary = [
...shortSummary,
...(otherValue > 0
? ([['other', otherValue]] as [string, number][])
: []),
]
if (!isCancelled) {
setInstructions(updatedSummary)
}
}
}
})
if (element) {
observer.observe(element)
}
// Clean up function
return () => {
isCancelled = true
if (element) {
observer.unobserve(element)
}
}
}, [time, cluster, proposal, connection, isMultisigLoading, instructions])
const handleClickIndividualProposal = useCallback(
(proposalPubkey: string) => {
router.query.proposal = proposalPubkey
router.push(
{
pathname: router.pathname,
query: router.query,
},
undefined,
{ scroll: true }
)
},
[router]
)
return (
<div
ref={elementRef}
className="my-2 cursor-pointer bg-[#1E1B2F] hover:bg-darkGray2"
onClick={() =>
handleClickIndividualProposal(proposal.publicKey.toBase58())
}
>
<div className="flex flex-wrap gap-4 p-4">
<div className="font-bold">{proposal.transactionIndex}</div>
<div className="flex items-center">
{formattedTime ?? (
<div className="h-5 w-48 animate-pulse rounded bg-beige-300" />
)}
</div>
<div className="flex">
<span className="mr-2">
{proposal.publicKey.toBase58().slice(0, 6) +
'...' +
proposal.publicKey.toBase58().slice(-6)}
</span>{' '}
</div>
<div className="flex flex-grow gap-4">
{instructions?.map(([name, count]) => (
<div key={name}>
{name}: {count}
</div>
))}
</div>
<div className="flex space-x-2">
{proposal.approved.length > 0 && status === 'active' && (
<div>
<StatusTag
proposalStatus="executed"
text={`Approved: ${proposal.approved.length}`}
/>
</div>
)}
{proposal.rejected.length > 0 && status === 'active' && (
<div>
<StatusTag
proposalStatus="rejected"
text={`Rejected: ${proposal.rejected.length}`}
/>
</div>
)}
<div>
<StatusTag proposalStatus={status} />
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,218 @@
import { TransactionAccount } from '@sqds/mesh/lib/types'
import { useRouter } from 'next/router'
import { useCallback, useContext, useEffect, useState } from 'react'
import { ClusterContext } from '../../../contexts/ClusterContext'
import { useMultisigContext } from '../../../contexts/MultisigContext'
import { StatusFilterContext } from '../../../contexts/StatusFilterContext'
import ClusterSwitch from '../../ClusterSwitch'
import ProposalStatusFilter from '../../ProposalStatusFilter'
import Loadbar from '../../loaders/Loadbar'
import { ProposalRow } from './ProposalRow'
import { getProposalStatus } from './utils'
import { Proposal } from './Proposal'
type ProposalType = 'priceFeed' | 'governance'
const Proposals = () => {
const router = useRouter()
const [currentProposal, setCurrentProposal] = useState<TransactionAccount>()
const [currentProposalPubkey, setCurrentProposalPubkey] = useState<string>()
const { cluster } = useContext(ClusterContext)
const { statusFilter } = useContext(StatusFilterContext)
const {
upgradeMultisigAccount,
priceFeedMultisigAccount,
priceFeedMultisigProposals,
upgradeMultisigProposals,
isLoading: isMultisigLoading,
refreshData,
} = useMultisigContext()
const [proposalType, setProposalType] = useState<ProposalType>('priceFeed')
const multisigAccount =
proposalType === 'priceFeed'
? priceFeedMultisigAccount
: upgradeMultisigAccount
const multisigProposals =
proposalType === 'priceFeed'
? priceFeedMultisigProposals
: upgradeMultisigProposals
const [filteredProposals, setFilteredProposals] = useState<
TransactionAccount[]
>([])
const handleClickBackToProposals = () => {
delete router.query.proposal
router.push(
{
pathname: router.pathname,
query: router.query,
},
undefined,
{ scroll: false }
)
}
useEffect(() => {
if (router.query.proposal) {
setCurrentProposalPubkey(router.query.proposal as string)
} else {
setCurrentProposalPubkey(undefined)
}
}, [router.query.proposal])
const switchProposalType = useCallback(() => {
if (proposalType === 'priceFeed') {
setProposalType('governance')
} else {
setProposalType('priceFeed')
}
}, [proposalType])
useEffect(() => {
if (currentProposalPubkey) {
const currProposal = multisigProposals.find(
(proposal) => proposal.publicKey.toBase58() === currentProposalPubkey
)
setCurrentProposal(currProposal)
if (currProposal === undefined) {
const otherProposals =
proposalType !== 'priceFeed'
? priceFeedMultisigProposals
: upgradeMultisigProposals
if (
otherProposals.findIndex(
(proposal) =>
proposal.publicKey.toBase58() === currentProposalPubkey
) !== -1
) {
switchProposalType()
}
}
}
}, [
switchProposalType,
priceFeedMultisigProposals,
proposalType,
upgradeMultisigProposals,
currentProposalPubkey,
multisigProposals,
cluster,
])
useEffect(() => {
// filter price feed multisig proposals by status
if (statusFilter === 'all') {
setFilteredProposals(multisigProposals)
} else {
setFilteredProposals(
multisigProposals.filter(
(proposal) =>
getProposalStatus(proposal, multisigAccount) === statusFilter
)
)
}
}, [statusFilter, multisigAccount, multisigProposals])
return (
<div className="relative">
<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">
{proposalType === 'priceFeed' ? 'Price Feed ' : 'Governance '}{' '}
{router.query.proposal === undefined ? 'Proposals' : 'Proposal'}
</h1>
</div>
</div>
<div className="container min-h-[50vh]">
{router.query.proposal === undefined ? (
<>
<div className="flex flex-col justify-between md:flex-row">
<div className="mb-4 flex items-center md:mb-0">
<ClusterSwitch />
</div>
<div className="flex space-x-2">
{refreshData && (
<button
disabled={isMultisigLoading}
className="sub-action-btn text-base"
onClick={() => {
const { fetchData } = refreshData()
fetchData()
}}
>
Refresh
</button>
)}
<button
disabled={isMultisigLoading}
className="action-btn text-base"
onClick={switchProposalType}
>
Show
{proposalType !== 'priceFeed'
? ' Price Feed '
: ' Governance '}
Proposals
</button>
</div>
</div>
<div className="relative mt-6">
{isMultisigLoading ? (
<div className="mt-3">
<Loadbar theme="light" />
</div>
) : (
<>
<div className="flex items-center justify-between pb-4">
<h4 className="h4">
Total Proposals: {filteredProposals.length}
</h4>
<ProposalStatusFilter />
</div>
{filteredProposals.length > 0 ? (
<div className="flex flex-col">
{filteredProposals.map((proposal, _idx) => (
<ProposalRow
key={proposal.publicKey.toBase58()}
proposal={proposal}
multisig={multisigAccount}
/>
))}
</div>
) : (
<div className="mt-4">
No proposals found. If you&apos;re a member of the
multisig, you can create a proposal.
</div>
)}
</>
)}
</div>
</>
) : !isMultisigLoading && currentProposal !== undefined ? (
<>
<div
className="max-w-fit cursor-pointer bg-darkGray2 p-3 text-xs font-semibold outline-none transition-colors hover:bg-darkGray3 md:text-base"
onClick={handleClickBackToProposals}
>
&#8592; back to proposals
</div>
<div className="relative mt-6">
<Proposal proposal={currentProposal} multisig={multisigAccount} />
</div>
</>
) : (
<div className="mt-3">
<Loadbar theme="light" />
</div>
)}
</div>
</div>
)
}
export default Proposals

View File

@ -0,0 +1,37 @@
import { ProposalStatus } from './utils'
const getProposalBackgroundColorClassName = (
proposalStatus: ProposalStatus
) => {
if (proposalStatus === 'active') {
return 'bg-[#3C3299]'
} else if (proposalStatus === 'executed') {
return 'bg-[#1B730E]'
} else if (proposalStatus === 'cancelled') {
return 'bg-[#C4428F]'
} else if (proposalStatus === 'rejected') {
return 'bg-[#CF6E42]'
} else if (proposalStatus === 'expired') {
return 'bg-[#A52A2A]'
} else {
return 'bg-pythPurple'
}
}
export const StatusTag = ({
proposalStatus,
text,
}: {
proposalStatus: ProposalStatus
text?: string
}) => {
return (
<div
className={`flex items-center justify-center rounded-full ${getProposalBackgroundColorClassName(
proposalStatus
)} py-1 px-2 text-xs`}
>
{text || proposalStatus}
</div>
)
}

View File

@ -0,0 +1,100 @@
import { PythCluster } from '@pythnetwork/client'
import { AccountMeta } from '@solana/web3.js'
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
import {
ExecutePostedVaa,
MultisigInstruction,
MultisigParser,
PythGovernanceActionImpl,
SetDataSources,
WormholeMultisigInstruction,
} from 'xc_admin_common'
export type ProposalStatus =
| 'active'
| 'executed'
| 'cancelled'
| 'rejected'
| 'expired'
| 'executeReady'
| 'draft'
| 'unkwown'
export const getProposalStatus = (
proposal: TransactionAccount | undefined,
multisig: MultisigAccount | undefined
): ProposalStatus => {
if (multisig && proposal) {
const onChainStatus = Object.keys(proposal.status)[0]
return proposal.transactionIndex <= multisig.msChangeIndex &&
(onChainStatus == 'active' || onChainStatus == 'draft')
? 'expired'
: (onChainStatus as ProposalStatus)
} else {
return 'unkwown'
}
}
/**
* Sorts the properties of an object by their values in ascending order.
*
* @param {Record<string, number>} obj - The object to sort. All property values should be numbers.
* @returns {Record<string, number>} A new object with the same properties as the input, but ordered such that the property with the largest numerical value comes first.
*
* @example
* const obj = { a: 2, b: 3, c: 1 };
* const sortedObj = sortObjectByValues(obj);
* console.log(sortedObj); // Outputs: { b: 3, a: 2, c: 1 }
*/
const sortObjectByValues = (obj: Record<string, number>) => {
const sortedEntries = Object.entries(obj).sort(([, a], [, b]) => b - a)
const sortedObj: Record<string, number> = {}
for (const [key, value] of sortedEntries) {
sortedObj[key] = value
}
return sortedObj
}
/**
* Returns a summary of the instructions in a list of multisig instructions.
*
* @param {MultisigInstruction[]} options.instructions - The list of multisig instructions to summarize.
* @param {PythCluster} options.cluster - The Pyth cluster to use for parsing instructions.
* @returns {Record<string, number>} A summary of the instructions, where the keys are the names of the instructions and the values are the number of times each instruction appears in the list.
*/
export const getInstructionsSummary = (options: {
instructions: MultisigInstruction[]
cluster: PythCluster
}) => {
const { instructions, cluster } = options
return sortObjectByValues(
instructions.reduce((acc, instruction) => {
if (instruction instanceof WormholeMultisigInstruction) {
const governanceAction = instruction.governanceAction
if (governanceAction instanceof ExecutePostedVaa) {
const innerInstructions = governanceAction.instructions
innerInstructions.forEach((innerInstruction) => {
const multisigParser = MultisigParser.fromCluster(cluster)
const parsedInstruction = multisigParser.parseInstruction({
programId: innerInstruction.programId,
data: innerInstruction.data as Buffer,
keys: innerInstruction.keys as AccountMeta[],
})
acc[parsedInstruction.name] = (acc[parsedInstruction.name] ?? 0) + 1
})
} else if (governanceAction instanceof PythGovernanceActionImpl) {
acc[governanceAction.action] = (acc[governanceAction.action] ?? 0) + 1
} else if (governanceAction instanceof SetDataSources) {
acc[governanceAction.actionName] =
(acc[governanceAction.actionName] ?? 0) + 1
} else {
acc['unknown'] = (acc['unknown'] ?? 0) + 1
}
} else {
acc[instruction.name] = (acc[instruction.name] ?? 0) + 1
}
return acc
}, {} as Record<string, number>)
)
}

View File

@ -21,14 +21,13 @@ import {
getMultisigCluster,
isRemoteCluster,
mapKey,
WORMHOLE_ADDRESS,
UPGRADE_MULTISIG,
MultisigVault,
} from 'xc_admin_common'
import { ClusterContext } from '../../contexts/ClusterContext'
import { useMultisigContext } from '../../contexts/MultisigContext'
import { usePythContext } from '../../contexts/PythContext'
import CopyIcon from '../../images/icons/copy.inline.svg'
import CopyIcon from '@images/icons/copy.inline.svg'
import { capitalizeFirstLetter } from '../../utils/capitalizeFirstLetter'
import ClusterSwitch from '../ClusterSwitch'
import Modal from '../common/Modal'
@ -336,7 +335,7 @@ const UpdatePermissions = () => {
// create anchor wallet when connected
useEffect(() => {
if (connected && squads) {
if (connected && squads && connection) {
const provider = new AnchorProvider(
connection,
squads.wallet as Wallet,

View File

@ -1,17 +1,17 @@
import { PythCluster } from '@pythnetwork/client/lib/cluster'
import { createContext, useMemo, useState } from 'react'
import { ReactNode, createContext, useMemo, useState } from 'react'
export const DEFAULT_CLUSTER: PythCluster = 'mainnet-beta'
export const ClusterContext = createContext<{
cluster: PythCluster
setCluster: any
setCluster: (_cluster: PythCluster) => void
}>({
cluster: DEFAULT_CLUSTER,
setCluster: {},
setCluster: () => {},
})
export const ClusterProvider = (props: any) => {
export const ClusterProvider = ({ children }: { children: ReactNode }) => {
const [cluster, setCluster] = useState<PythCluster>(DEFAULT_CLUSTER)
const contextValue = useMemo(
() => ({
@ -22,5 +22,9 @@ export const ClusterProvider = (props: any) => {
}),
[cluster]
)
return <ClusterContext.Provider {...props} value={contextValue} />
return (
<ClusterContext.Provider value={contextValue}>
{children}
</ClusterContext.Provider>
)
}

View File

@ -1,17 +1,12 @@
import SquadsMesh from '@sqds/mesh'
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
import React, { createContext, useContext, useMemo } from 'react'
import { MultisigInstruction } from 'xc_admin_common'
import { useMultisig, MultisigHookData } from '../hooks/useMultisig'
import { MultisigHookData, useMultisig } from '../hooks/useMultisig'
const MultisigContext = createContext<MultisigHookData>({
upgradeMultisigAccount: undefined,
priceFeedMultisigAccount: undefined,
upgradeMultisigProposals: [],
priceFeedMultisigProposals: [],
allProposalsIxsParsed: [],
isLoading: true,
error: null,
squads: undefined,
refreshData: undefined,
connection: undefined,
@ -28,13 +23,11 @@ export const MultisigContextProvider: React.FC<
> = ({ children }) => {
const {
isLoading,
error,
squads,
upgradeMultisigAccount,
priceFeedMultisigAccount,
upgradeMultisigProposals,
priceFeedMultisigProposals,
allProposalsIxsParsed,
refreshData,
connection,
} = useMultisig()
@ -45,9 +38,7 @@ export const MultisigContextProvider: React.FC<
priceFeedMultisigAccount,
upgradeMultisigProposals,
priceFeedMultisigProposals,
allProposalsIxsParsed,
isLoading,
error,
squads,
refreshData,
connection,
@ -55,12 +46,10 @@ export const MultisigContextProvider: React.FC<
[
squads,
isLoading,
error,
upgradeMultisigAccount,
priceFeedMultisigAccount,
upgradeMultisigProposals,
priceFeedMultisigProposals,
allProposalsIxsParsed,
refreshData,
connection,
]

View File

@ -5,16 +5,15 @@ import React, {
useMemo,
useState,
} from 'react'
import usePyth from '../hooks/usePyth'
import { usePyth } from '../hooks/usePyth'
import { RawConfig } from '../hooks/usePyth'
import { Connection } from '@solana/web3.js'
// TODO: fix any
type AccountKeyToSymbol = { [key: string]: string }
interface PythContextProps {
rawConfig: RawConfig
dataIsLoading: boolean
error: any
connection: any
connection?: Connection
priceAccountKeyToSymbolMapping: AccountKeyToSymbol
productAccountKeyToSymbolMapping: AccountKeyToSymbol
publisherKeyToNameMapping: Record<string, Record<string, string>>
@ -24,8 +23,6 @@ interface PythContextProps {
const PythContext = createContext<PythContextProps>({
rawConfig: { mappingAccounts: [] },
dataIsLoading: true,
error: null,
connection: null,
priceAccountKeyToSymbolMapping: {},
productAccountKeyToSymbolMapping: {},
publisherKeyToNameMapping: {},
@ -44,7 +41,7 @@ export const PythContextProvider: React.FC<PythContextProviderProps> = ({
publisherKeyToNameMapping,
multisigSignerKeyToNameMapping,
}) => {
const { isLoading, error, connection, rawConfig } = usePyth()
const { isLoading, connection, rawConfig } = usePyth()
const [
productAccountKeyToSymbolMapping,
setProductAccountKeyToSymbolMapping,
@ -72,7 +69,6 @@ export const PythContextProvider: React.FC<PythContextProviderProps> = ({
() => ({
rawConfig,
dataIsLoading: isLoading,
error,
connection,
priceAccountKeyToSymbolMapping,
productAccountKeyToSymbolMapping,
@ -82,7 +78,6 @@ export const PythContextProvider: React.FC<PythContextProviderProps> = ({
[
rawConfig,
isLoading,
error,
connection,
publisherKeyToNameMapping,
multisigSignerKeyToNameMapping,

View File

@ -1,27 +1,34 @@
import { createContext, useMemo, useState } from 'react'
import { ReactNode, createContext, useMemo, useState } from 'react'
import { ProposalStatus } from '../components/tabs/Proposals/utils'
export const DEFAULT_STATUS_FILTER = 'all'
export type ProposalStatusFilter = 'all' | ProposalStatus
export const StatusFilterContext = createContext<{
statusFilter: string
setStatusFilter: any
statusFilter: ProposalStatusFilter
setStatusFilter: (_statusFilter: ProposalStatusFilter) => void
}>({
statusFilter: DEFAULT_STATUS_FILTER,
setStatusFilter: {},
setStatusFilter: () => {},
})
export const StatusFilterProvider = (props: any) => {
const [statusFilter, setStatusFilter] = useState<string>(
export const StatusFilterProvider = ({ children }: { children: ReactNode }) => {
const [statusFilter, setStatusFilter] = useState<ProposalStatusFilter>(
DEFAULT_STATUS_FILTER
)
const contextValue = useMemo(
() => ({
statusFilter,
setStatusFilter: (statusFilter: string) => {
setStatusFilter: (statusFilter: ProposalStatusFilter) => {
setStatusFilter(statusFilter)
},
}),
[statusFilter]
)
return <StatusFilterContext.Provider {...props} value={contextValue} />
return (
<StatusFilterContext.Provider value={contextValue}>
{children}
</StatusFilterContext.Provider>
)
}

View File

@ -5,24 +5,21 @@ import SquadsMesh from '@sqds/mesh'
import { MultisigAccount, TransactionAccount } from '@sqds/mesh/lib/types'
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import {
getMultisigCluster,
getProposals,
MultisigInstruction,
PRICE_FEED_MULTISIG,
UPGRADE_MULTISIG,
getMultisigCluster,
getProposals,
} from 'xc_admin_common'
import { ClusterContext } from '../contexts/ClusterContext'
import { deriveWsUrl, pythClusterApiUrls } from '../utils/pythClusterApiUrl'
export interface MultisigHookData {
isLoading: boolean
error: any // TODO: fix any
squads: SquadsMesh | undefined
upgradeMultisigAccount: MultisigAccount | undefined
priceFeedMultisigAccount: MultisigAccount | undefined
upgradeMultisigProposals: TransactionAccount[]
priceFeedMultisigProposals: TransactionAccount[]
allProposalsIxsParsed: MultisigInstruction[][]
connection?: Connection
refreshData?: () => { fetchData: () => Promise<void>; cancel: () => void }
}
@ -39,7 +36,6 @@ export const useMultisig = (): MultisigHookData => {
const wallet = useAnchorWallet()
const { cluster } = useContext(ClusterContext)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
const [upgradeMultisigAccount, setUpgradeMultisigAccount] =
useState<MultisigAccount>()
const [priceFeedMultisigAccount, setPriceFeedMultisigAccount] =
@ -50,17 +46,10 @@ export const useMultisig = (): MultisigHookData => {
const [priceFeedMultisigProposals, setPriceFeedMultisigProposals] = useState<
TransactionAccount[]
>([])
const [allProposalsIxsParsed, setAllProposalsIxsParsed] = useState<
MultisigInstruction[][]
>([])
const [squads, setSquads] = useState<SquadsMesh | undefined>()
const [urlsIndex, setUrlsIndex] = useState(0)
useEffect(() => {
setError(null)
}, [urlsIndex, cluster])
useEffect(() => {
setUrlsIndex(0)
}, [cluster])
@ -132,8 +121,6 @@ export const useMultisig = (): MultisigHookData => {
if (cancelled) return
const urls = pythClusterApiUrls(multisigCluster)
if (urlsIndex === urls.length - 1) {
// @ts-ignore
setError(e)
setIsLoading(false)
console.warn(`Failed to fetch accounts`)
} else if (urlsIndex < urls.length - 1) {
@ -159,13 +146,11 @@ export const useMultisig = (): MultisigHookData => {
return {
isLoading,
error,
squads,
upgradeMultisigAccount,
priceFeedMultisigAccount,
upgradeMultisigProposals,
priceFeedMultisigProposals,
allProposalsIxsParsed,
refreshData,
connection,
}

View File

@ -15,11 +15,8 @@ import { useContext, useEffect, useRef, useState } from 'react'
import { ClusterContext } from '../contexts/ClusterContext'
import { deriveWsUrl, pythClusterApiUrls } from '../utils/pythClusterApiUrl'
const ONES = '11111111111111111111111111111111'
interface PythHookData {
isLoading: boolean
error: any // TODO: fix any
rawConfig: RawConfig
connection?: Connection
}
@ -47,17 +44,15 @@ export type PriceRawConfig = {
publishers: PublicKey[]
}
const usePyth = (): PythHookData => {
export const usePyth = (): PythHookData => {
const connectionRef = useRef<Connection>()
const { cluster } = useContext(ClusterContext)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
const [rawConfig, setRawConfig] = useState<RawConfig>({ mappingAccounts: [] })
const [urlsIndex, setUrlsIndex] = useState(0)
useEffect(() => {
setIsLoading(true)
setError(null)
}, [urlsIndex, cluster])
useEffect(() => {
@ -127,7 +122,7 @@ const usePyth = (): PythHookData => {
} else {
let priceAccountKey: string | undefined =
parsed.priceAccountKey.toBase58()
let priceAccounts = []
const priceAccounts = []
while (priceAccountKey) {
const toAdd: PriceRawConfig = priceRawConfigs[priceAccountKey]
priceAccounts.push(toAdd)
@ -196,8 +191,6 @@ const usePyth = (): PythHookData => {
} catch (e) {
if (cancelled) return
if (urlsIndex === urls.length - 1) {
// @ts-ignore
setError(e)
setIsLoading(false)
console.warn(`Failed to fetch accounts`)
} else if (urlsIndex < urls.length - 1) {
@ -213,10 +206,7 @@ const usePyth = (): PythHookData => {
return {
isLoading,
error,
connection: connectionRef.current,
rawConfig,
}
}
export default usePyth

View File

@ -1,3 +1,5 @@
const path = require('path')
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
@ -16,6 +18,11 @@ const nextConfig = {
loader: require.resolve('@svgr/webpack'),
})
config.resolve.alias = {
...config.resolve.alias,
'@images': path.resolve(__dirname, 'images/'),
}
return config
},
}

View File

@ -40,8 +40,9 @@
},
"devDependencies": {
"@svgr/webpack": "^6.3.1",
"@typescript-eslint/eslint-plugin": "^7.7.0",
"autoprefixer": "^10.4.8",
"eslint": "8.22.0",
"eslint": "8.56.0",
"eslint-config-next": "12.2.5",
"postcss": "^8.4.16",
"prettier": "^2.7.1",

View File

@ -5,7 +5,7 @@ import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import Layout from '../components/layout/Layout'
import General from '../components/tabs/General'
import Proposals from '../components/tabs/Proposals'
import Proposals from '../components/tabs/Proposals/Proposals'
import UpdatePermissions from '../components/tabs/UpdatePermissions'
import { MultisigContextProvider } from '../contexts/MultisigContext'
import { PythContextProvider } from '../contexts/PythContext'

View File

@ -13,7 +13,11 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"skipLibCheck": true
"skipLibCheck": true,
"paths": {
"@images/*": ["./images/*"],
"xc-admin-common": ["../xc_admin_common/src"]
}
},
"include": [
"next-env.d.ts",

726
package-lock.json generated
View File

@ -1459,26 +1459,6 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"express_relay/examples/easy_lend/node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"engines": {
"node": ">=10.10.0"
}
},
"express_relay/examples/easy_lend/node_modules/@humanwhocodes/object-schema": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
"dev": true
},
"express_relay/examples/easy_lend/node_modules/@noble/curves": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
@ -1817,26 +1797,6 @@
"url": "https://opencollective.com/eslint"
}
},
"express_relay/sdk/js/node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"engines": {
"node": ">=10.10.0"
}
},
"express_relay/sdk/js/node_modules/@humanwhocodes/object-schema": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
"dev": true
},
"express_relay/sdk/js/node_modules/@jest/console": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz",
@ -5069,8 +5029,9 @@
},
"devDependencies": {
"@svgr/webpack": "^6.3.1",
"@typescript-eslint/eslint-plugin": "^7.7.0",
"autoprefixer": "^10.4.8",
"eslint": "8.22.0",
"eslint": "8.56.0",
"eslint-config-next": "12.2.5",
"postcss": "^8.4.16",
"prettier": "^2.7.1",
@ -5133,6 +5094,257 @@
"@types/node": "*"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.1.tgz",
"integrity": "sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.7.1",
"@typescript-eslint/type-utils": "7.7.1",
"@typescript-eslint/utils": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"engines": {
"node": ">=16"
},
"peerDependencies": {
"typescript": ">=4.2.0"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/parser": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.1.tgz",
"integrity": "sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==",
"dev": true,
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.7.1",
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/typescript-estree": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/scope-manager": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.1.tgz",
"integrity": "sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/type-utils": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.1.tgz",
"integrity": "sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "7.7.1",
"@typescript-eslint/utils": "7.7.1",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"engines": {
"node": ">=16"
},
"peerDependencies": {
"typescript": ">=4.2.0"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/types": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.1.tgz",
"integrity": "sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==",
"dev": true,
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/typescript-estree": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.1.tgz",
"integrity": "sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"engines": {
"node": ">=16"
},
"peerDependencies": {
"typescript": ">=4.2.0"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/utils": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.1.tgz",
"integrity": "sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.15",
"@types/semver": "^7.5.8",
"@typescript-eslint/scope-manager": "7.7.1",
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/typescript-estree": "7.7.1",
"semver": "^7.6.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.56.0"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/@typescript-eslint/visitor-keys": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.1.tgz",
"integrity": "sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.7.1",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/cross-fetch": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
@ -5154,50 +5366,49 @@
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/eslint": {
"version": "8.22.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz",
"integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
"integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.3.0",
"@humanwhocodes/config-array": "^0.10.4",
"@humanwhocodes/gitignore-to-minimatch": "^1.0.2",
"ajv": "^6.10.0",
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.56.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.1.1",
"eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.3.0",
"espree": "^9.3.3",
"esquery": "^1.4.0",
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
"find-up": "^5.0.0",
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^6.0.1",
"globals": "^13.15.0",
"globby": "^11.1.0",
"grapheme-splitter": "^1.0.4",
"glob-parent": "^6.0.2",
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
"regexpp": "^3.2.0",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"strip-json-comments": "^3.1.0",
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
"text-table": "^0.2.0"
},
"bin": {
"eslint": "bin/eslint.js"
@ -5276,6 +5487,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@ -5306,6 +5529,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"governance/xc_admin/packages/xc_admin_frontend/node_modules/web3": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/web3/-/web3-4.8.0.tgz",
@ -10047,29 +10285,18 @@
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.10.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz",
"integrity": "sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==",
"dev": true,
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dependencies": {
"@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1",
"minimatch": "^3.0.4"
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"engines": {
"node": ">=10.10.0"
}
},
"node_modules/@humanwhocodes/gitignore-to-minimatch": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz",
"integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==",
"dev": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@ -10083,9 +10310,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="
},
"node_modules/@hutson/parse-repository-url": {
"version": "3.0.2",
@ -22040,9 +22267,9 @@
}
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
@ -22235,9 +22462,9 @@
}
},
"node_modules/@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
"integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw=="
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="
},
"node_modules/@types/serve-index": {
"version": "1.9.1",
@ -30391,19 +30618,6 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/eslint/node_modules/@humanwhocodes/config-array": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
"integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
"dependencies": {
"@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1",
"minimatch": "^3.0.5"
},
"engines": {
"node": ">=10.10.0"
}
},
"node_modules/eslint/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -35004,9 +35218,9 @@
]
},
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"engines": {
"node": ">= 4"
}
@ -64059,31 +64273,24 @@
}
},
"@humanwhocodes/config-array": {
"version": "0.10.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz",
"integrity": "sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==",
"dev": true,
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"requires": {
"@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1",
"minimatch": "^3.0.4"
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
}
},
"@humanwhocodes/gitignore-to-minimatch": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz",
"integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==",
"dev": true
},
"@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="
},
"@humanwhocodes/object-schema": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="
},
"@hutson/parse-repository-url": {
"version": "3.0.2",
@ -68614,23 +68821,6 @@
"strip-json-comments": "^3.1.1"
}
},
"@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true,
"requires": {
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
}
},
"@humanwhocodes/object-schema": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
"dev": true
},
"@jest/console": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz",
@ -77796,9 +77986,9 @@
}
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"@types/json5": {
"version": "0.0.29",
@ -77990,9 +78180,9 @@
}
},
"@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
"integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw=="
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="
},
"@types/serve-index": {
"version": "1.9.1",
@ -84064,23 +84254,6 @@
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true
},
"@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true,
"requires": {
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
}
},
"@humanwhocodes/object-schema": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
"dev": true
},
"@noble/curves": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
@ -84797,16 +84970,6 @@
"text-table": "^0.2.0"
},
"dependencies": {
"@humanwhocodes/config-array": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
"integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
"requires": {
"@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1",
"minimatch": "^3.0.5"
}
},
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -88921,9 +89084,9 @@
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw=="
},
"ignore-walk": {
"version": "5.0.1",
@ -107414,10 +107577,11 @@
"@types/node": "^18.11.18",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"@typescript-eslint/eslint-plugin": "^7.7.0",
"autoprefixer": "^10.4.8",
"axios": "^1.4.0",
"copy-to-clipboard": "^3.3.3",
"eslint": "8.22.0",
"eslint": "8.56.0",
"eslint-config-next": "12.2.5",
"gsap": "^3.11.4",
"next": "12.2.5",
@ -107475,6 +107639,153 @@
"@types/node": "*"
}
},
"@typescript-eslint/eslint-plugin": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.1.tgz",
"integrity": "sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==",
"dev": true,
"requires": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.7.1",
"@typescript-eslint/type-utils": "7.7.1",
"@typescript-eslint/utils": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"dependencies": {
"ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"requires": {}
}
}
},
"@typescript-eslint/parser": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.1.tgz",
"integrity": "sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==",
"dev": true,
"peer": true,
"requires": {
"@typescript-eslint/scope-manager": "7.7.1",
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/typescript-estree": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1",
"debug": "^4.3.4"
}
},
"@typescript-eslint/scope-manager": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.1.tgz",
"integrity": "sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1"
}
},
"@typescript-eslint/type-utils": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.1.tgz",
"integrity": "sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==",
"dev": true,
"requires": {
"@typescript-eslint/typescript-estree": "7.7.1",
"@typescript-eslint/utils": "7.7.1",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
"dependencies": {
"ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"requires": {}
}
}
},
"@typescript-eslint/types": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.1.tgz",
"integrity": "sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.1.tgz",
"integrity": "sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/visitor-keys": "7.7.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"dependencies": {
"minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
},
"ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"requires": {}
}
}
},
"@typescript-eslint/utils": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.1.tgz",
"integrity": "sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.15",
"@types/semver": "^7.5.8",
"@typescript-eslint/scope-manager": "7.7.1",
"@typescript-eslint/types": "7.7.1",
"@typescript-eslint/typescript-estree": "7.7.1",
"semver": "^7.6.0"
}
},
"@typescript-eslint/visitor-keys": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.1.tgz",
"integrity": "sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "7.7.1",
"eslint-visitor-keys": "^3.4.3"
}
},
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
},
"cross-fetch": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
@ -107490,50 +107801,49 @@
"dev": true
},
"eslint": {
"version": "8.22.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz",
"integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
"integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.3.0",
"@humanwhocodes/config-array": "^0.10.4",
"@humanwhocodes/gitignore-to-minimatch": "^1.0.2",
"ajv": "^6.10.0",
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.56.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.1.1",
"eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.3.0",
"espree": "^9.3.3",
"esquery": "^1.4.0",
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
"find-up": "^5.0.0",
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^6.0.1",
"globals": "^13.15.0",
"globby": "^11.1.0",
"grapheme-splitter": "^1.0.4",
"glob-parent": "^6.0.2",
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
"regexpp": "^3.2.0",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"strip-json-comments": "^3.1.0",
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
"text-table": "^0.2.0"
}
},
"ethereum-cryptography": {
@ -107586,6 +107896,15 @@
"p-locate": "^5.0.0"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@ -107604,6 +107923,15 @@
"p-limit": "^3.0.2"
}
},
"semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"web3": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/web3/-/web3-4.8.0.tgz",