diff --git a/components/Members/AddMemberForm.tsx b/components/Members/AddMemberForm.tsx index 672de73e3..9c087b803 100644 --- a/components/Members/AddMemberForm.tsx +++ b/components/Members/AddMemberForm.tsx @@ -21,6 +21,7 @@ import AddMemberIcon from '@components/AddMemberIcon' import { ArrowCircleDownIcon, ArrowCircleUpIcon, + RefreshIcon, } from '@heroicons/react/outline' import useCreateProposal from '@hooks/useCreateProposal' import { AssetAccount } from '@utils/uiTypes/assets' @@ -33,6 +34,8 @@ import { useRealmQuery } from '@hooks/queries/realm' import { DEFAULT_GOVERNANCE_PROGRAM_VERSION } from '@components/instructions/tools' import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext' import {useVoteByCouncilToggle} from "@hooks/useVoteByCouncilToggle"; +import { resolveDomain } from '@utils/domains' +import debounce from 'lodash/debounce' interface AddMemberForm extends Omit { description: string @@ -95,6 +98,44 @@ const AddMemberForm: FC<{ close: () => void; mintAccount: AssetAccount }> = ({ // note the lack of space is not a typo const proposalTitle = `Add ${govpop}member ${abbrevAddress}` + const [isResolvingDomain, setIsResolvingDomain] = useState(false) + + const resolveDomainDebounced = useMemo( + () => + debounce(async (domain: string) => { + try { + console.log('Attempting to resolve domain:', domain) + const resolved = await resolveDomain(connection.current, domain) + console.log('Domain resolved to:', resolved?.toBase58() || 'null') + + if (resolved) { + handleSetForm({ + value: resolved.toBase58(), + propertyName: 'destinationAccount', + }) + } + } catch (error) { + console.error('Error resolving domain:', error) + } finally { + setIsResolvingDomain(false) + } + }, 500), + [connection] + ) + + const handleDestinationAccountChange = async (event) => { + const value = event.target.value + handleSetForm({ + value, + propertyName: 'destinationAccount', + }) + + if (value.includes('.')) { + setIsResolvingDomain(true) + resolveDomainDebounced(value) + } + } + const setAmount = (event) => { const value = event.target.value @@ -264,23 +305,25 @@ const AddMemberForm: FC<{ close: () => void; mintAccount: AssetAccount }> = ({

Add new member to {realmInfo?.displayName}

- - handleSetForm({ - value: event.target.value, - propertyName: 'destinationAccount', - }) - } - noMaxWidth - error={formErrors['destinationAccount']} - /> +
+ + {isResolvingDomain && ( +
+ +
+ )} +
item.trim()).filter(Boolean) + console.log('Resolving items:', items) + + const results = await Promise.all( + items.map(async (item) => { + try { + // If it's already a valid address, return it + if (validateSolAddress(item)) { + console.log(`${item} is already a valid address`) + return { input: item, resolved: item } + } + + // Try to resolve as domain + console.log(`Attempting to resolve domain: ${item}`) + const resolved = await resolveDomain(connection, item) + console.log('Resolved:', resolved) + console.log(`Domain ${item} resolved to:`, resolved?.toBase58() || 'null') + return { + input: item, + resolved: resolved?.toBase58() + } + } catch (error) { + console.error(`Error resolving ${item}:`, error) + return { input: item, resolved: undefined } + } + }) + ) + + const valid: string[] = [] + const invalid: string[] = [] + + results.forEach(({ input, resolved }) => { + if (resolved && validateSolAddress(resolved)) { + valid.push(resolved) + } else { + invalid.push(input) + } + }) + + console.log('Final resolution results:', { + valid, + invalid, + results + }) + + return { valid, invalid } +} + export default function InviteMembersForm({ visible, type, @@ -146,6 +202,7 @@ export default function InviteMembersForm({ const [inviteList, setInviteList] = useState([]) const [invalidAddresses, setInvalidAddresses] = useState([]) const [lacksMintAuthority, setLackMintAuthority] = useState(false) + const { connection } = useConnection() const schema = yup.object(InviteMembersSchema) const { @@ -211,12 +268,15 @@ export default function InviteMembersForm({ onSubmit({ step: currentStep, data: values }) } - function addToAddressList(textBlock: string) { + /** + * Update the addToAddressList function to be async + */ + async function addToAddressList(textBlock: string) { if (lacksMintAuthority) { return } - const { valid, invalid } = textToAddressList(textBlock) + const { valid, invalid } = await resolveAddressList(connection, textBlock) const { unique, duplicate } = splitUniques(inviteList.concat(valid)) setInviteList(unique) setInvalidAddresses((currentList) => @@ -224,6 +284,9 @@ export default function InviteMembersForm({ ) } + /** + * Update the event handlers to handle async addToAddressList + */ function handleBlur(ev) { addToAddressList(ev.currentTarget.value) ev.currentTarget.value = '' @@ -232,13 +295,12 @@ export default function InviteMembersForm({ function handlePaste(ev: React.ClipboardEvent) { addToAddressList(ev.clipboardData.getData('text')) ev.clipboardData.clearData() - // Don't allow the paste event to populate the input field ev.preventDefault() } function handleKeyDown(ev) { if (ev.defaultPrevented) { - return // Do nothing if the event was already processed + return } if (ev.key === 'Enter') { @@ -323,7 +385,7 @@ export default function InviteMembersForm({ = ({ publicKey, height = "13", style, }) => { const { profile, loading } = useProfile(publicKey) - if (!publicKey) return <>; return loading ? (
acc.isSol), - connection.current - ).then((domainNames) => { - setDomains(domainNames) - setDomainsLoading(false) - }) + + Promise.all( + accounts + .filter((acc) => acc.isSol) + .map((account) => + fetchDomainsByPubkey(connection.current, account.pubkey) + ) + ).then((domainResults) => { + const allDomains = domainResults.flat().map(domain => ({ + name: domain.domainName?.replace('.sol', ''), + address: domain.domainAddress, + owner: domain.domainOwner, + type: domain.type + })); + + setDomains(allDomains); + setDomainsLoading(false); + }); } - // eslint-disable-next-line react-hooks/exhaustive-deps -- TODO please fix, it can cause difficult bugs. You might wanna check out https://bobbyhadz.com/blog/react-hooks-exhaustive-deps for info. -@asktree }, [ loadingGovernedAccounts, - // eslint-disable-next-line react-hooks/exhaustive-deps -- TODO please fix, it can cause difficult bugs. You might wanna check out https://bobbyhadz.com/blog/react-hooks-exhaustive-deps for info. -@asktree accounts.map((account) => account.pubkey.toBase58()).join('-'), ]) diff --git a/package.json b/package.json index 204878319..840db8f88 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@next/bundle-analyzer": "12.1.5", "@nivo/bar": "0.79.1", "@nivo/core": "0.79.0", + "@onsol/tldparser": "0.6.7", "@parcl-oss/staking": "0.0.2", "@project-serum/common": "0.0.1-beta.3", "@project-serum/serum": "0.13.65", diff --git a/pages/dao/[symbol]/proposal/components/instructions/SplGov/RevokeGoverningTokens.tsx b/pages/dao/[symbol]/proposal/components/instructions/SplGov/RevokeGoverningTokens.tsx index dc1610cb1..77b029d4e 100644 --- a/pages/dao/[symbol]/proposal/components/instructions/SplGov/RevokeGoverningTokens.tsx +++ b/pages/dao/[symbol]/proposal/components/instructions/SplGov/RevokeGoverningTokens.tsx @@ -26,6 +26,10 @@ import { NewProposalContext } from '../../../new' import useMembershipTypes from './useMembershipTypes' import { useRealmQuery } from '@hooks/queries/realm' import Tooltip from '@components/Tooltip' +import { resolveDomain } from '@utils/domains' +import { RefreshIcon } from '@heroicons/react/outline' +import debounce from 'lodash/debounce' +import { useConnection } from '@solana/wallet-adapter-react' type Form = { memberKey?: string @@ -196,6 +200,34 @@ const RevokeGoverningTokens: FC<{ governance, ]) + // Add state for domain resolution + const [isResolvingDomain, setIsResolvingDomain] = useState(false) + const {connection} = useConnection() + + // Add the debounced resolve function + const resolveDomainDebounced = useMemo( + () => + debounce(async (domain: string) => { + try { + console.log('Attempting to resolve domain:', domain) + const resolved = await resolveDomain(connection, domain) + console.log('Domain resolved to:', resolved?.toBase58() || 'null') + + if (resolved) { + setForm((prevForm) => ({ + ...prevForm, + memberKey: resolved.toBase58(), + })) + } + } catch (error) { + console.error('Error resolving domain:', error) + } finally { + setIsResolvingDomain(false) + } + }, 500), + [connection] + ) + return ( <> - setForm((p) => ({ ...p, memberKey: e.target.value }))} - error={formErrors.memberKey} - /> +
+ { + const value = e.target.value + setForm((p) => ({ ...p, memberKey: value })) + + if (value.includes('.')) { + setIsResolvingDomain(true) + resolveDomainDebounced(value) + } + }} + error={formErrors.memberKey} + /> + {isResolvingDomain && ( +
+ +
+ )} +
+ debounce(async (domain: string) => { + try { + console.log('Attempting to resolve domain:', domain) + const resolved = await resolveDomain(connection.current, domain) + console.log('Domain resolved to:', resolved?.toBase58() || 'null') + + if (resolved) { + setForm((prevForm) => ({ + ...prevForm, + member: resolved.toBase58(), + })) + } + } catch (error) { + console.error('Error resolving domain:', error) + } finally { + setIsResolvingDomain(false) + } + }, 500), + [connection] + ) const validateInstruction = async (): Promise => { const { isValid, validationErrors } = await isFormValid(schema, form) @@ -128,6 +155,18 @@ const MeshAddMember = ({ type: InstructionInputType.INPUT, inputType: 'text', name: 'member', + placeholder: 'Member wallet or domain name (e.g. domain.solana)', + additionalComponent: isResolvingDomain ? ( +
+ +
+ ) : null, + onBlur: () => { + if (form.member.includes('.')) { + setIsResolvingDomain(true) + resolveDomainDebounced(form.member) + } + } }, ] diff --git a/pages/dao/[symbol]/proposal/components/instructions/Squads/MeshRemoveMember.tsx b/pages/dao/[symbol]/proposal/components/instructions/Squads/MeshRemoveMember.tsx index e858b7d02..668d30c94 100644 --- a/pages/dao/[symbol]/proposal/components/instructions/Squads/MeshRemoveMember.tsx +++ b/pages/dao/[symbol]/proposal/components/instructions/Squads/MeshRemoveMember.tsx @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { useContext, useEffect, useState } from 'react' +import { useContext, useEffect, useState, useMemo } from 'react' import * as yup from 'yup' import { isFormValid, validatePubkey } from '@utils/formValidation' import { UiInstruction } from '@utils/uiTypes/proposalCreationTypes' @@ -16,6 +16,9 @@ import Squads from '@sqds/mesh' import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext' import { PublicKey } from '@solana/web3.js' import { Wallet } from '@coral-xyz/anchor' +import { resolveDomain } from '@utils/domains' +import { RefreshIcon } from '@heroicons/react/outline' +import debounce from 'lodash/debounce' const MeshRemoveMember = ({ index, @@ -35,6 +38,30 @@ const MeshRemoveMember = ({ }) const [formErrors, setFormErrors] = useState({}) const { handleSetInstructions } = useContext(NewProposalContext) + const [isResolvingDomain, setIsResolvingDomain] = useState(false) + + const resolveDomainDebounced = useMemo( + () => + debounce(async (domain: string) => { + try { + console.log('Attempting to resolve domain:', domain) + const resolved = await resolveDomain(connection.current, domain) + console.log('Domain resolved to:', resolved?.toBase58() || 'null') + + if (resolved) { + setForm((prevForm) => ({ + ...prevForm, + member: resolved.toBase58(), + })) + } + } catch (error) { + console.error('Error resolving domain:', error) + } finally { + setIsResolvingDomain(false) + } + }, 500), + [connection] + ) const validateInstruction = async (): Promise => { const { isValid, validationErrors } = await isFormValid(schema, form) @@ -128,6 +155,18 @@ const MeshRemoveMember = ({ type: InstructionInputType.INPUT, inputType: 'text', name: 'member', + placeholder: 'Member wallet or domain name (e.g. domain.solana)', + additionalComponent: isResolvingDomain ? ( +
+ +
+ ) : null, + onBlur: () => { + if (form.member.includes('.')) { + setIsResolvingDomain(true) + resolveDomainDebounced(form.member) + } + } }, ] diff --git a/pages/dao/[symbol]/treasury/v2/index.tsx b/pages/dao/[symbol]/treasury/v2/index.tsx index f5346ec6a..3e9e3da64 100644 --- a/pages/dao/[symbol]/treasury/v2/index.tsx +++ b/pages/dao/[symbol]/treasury/v2/index.tsx @@ -119,4 +119,4 @@ export default function Treasury() {
) -} +} \ No newline at end of file diff --git a/utils/domains.ts b/utils/domains.ts index e78b51139..67cf2b47b 100644 --- a/utils/domains.ts +++ b/utils/domains.ts @@ -6,11 +6,14 @@ import { NAME_TOKENIZER_ID, performReverseLookupBatch, } from '@bonfida/spl-name-service' +import { TldParser } from '@onsol/tldparser' import { Connection, ParsedAccountData, PublicKey } from '@solana/web3.js' interface Domain { domainName: string | undefined domainAddress: string + domainOwner: string + type: 'sns' | 'alldomains' } export const resolveDomain = async ( @@ -19,48 +22,57 @@ export const resolveDomain = async ( ) => { try { // Get the public key for the domain - const { pubkey } = await getDomainKey(domainName.replace('.sol', '')) + if (domainName.includes('.sol')) { + const { pubkey } = await getDomainKey(domainName) - // Check if the domain is an NFT - const [nftMintAddress] = await PublicKey.findProgramAddress( - [MINT_PREFIX, pubkey.toBuffer()], - NAME_TOKENIZER_ID - ) + // Check if the domain is an NFT + const [nftMintAddress] = await PublicKey.findProgramAddress( + [MINT_PREFIX, pubkey.toBuffer()], + NAME_TOKENIZER_ID + ) - const nftAccountData = await connection.getParsedAccountInfo(nftMintAddress) - - if ( - nftAccountData.value?.data && - !Buffer.isBuffer(nftAccountData.value.data) - ) { - const parsedData: ParsedAccountData = nftAccountData.value.data + const nftAccountData = await connection.getParsedAccountInfo( + nftMintAddress + ) if ( - parsedData.parsed.info.supply === '1' && - parsedData.parsed.info.isInitialized + nftAccountData.value?.data && + !Buffer.isBuffer(nftAccountData.value.data) ) { - const { value } = await connection.getTokenLargestAccounts( - nftMintAddress - ) - const nftHolder = value.find((e) => e.amount === '1')?.address + const parsedData: ParsedAccountData = nftAccountData.value.data - if (!nftHolder) return undefined + if ( + parsedData.parsed.info.supply === '1' && + parsedData.parsed.info.isInitialized + ) { + const { value } = await connection.getTokenLargestAccounts( + nftMintAddress + ) + const nftHolder = value.find((e) => e.amount === '1')?.address - const holderInfo = await connection.getAccountInfo(nftHolder) + if (!nftHolder) return undefined - if (!holderInfo || !holderInfo.data) { - return undefined - } + const holderInfo = await connection.getAccountInfo(nftHolder) - return new PublicKey(holderInfo.data.slice(32, 64)) + if (!holderInfo || !holderInfo.data) { + return undefined + } + + return new PublicKey(holderInfo.data.slice(32, 64)) + } } - } - // Retrieve the domain's registry information - const { registry } = await NameRegistryState.retrieve(connection, pubkey) + // Retrieve the domain's registry information + const { registry } = await NameRegistryState.retrieve(connection, pubkey) - return registry.owner + return registry.owner + } else { + const parser = new TldParser(connection) + const owner = await parser.getOwnerFromDomainTld(domainName) + return owner + } } catch (error) { + console.error('Error resolving domain:', error) return undefined } } @@ -70,16 +82,18 @@ export const fetchDomainsByPubkey = async ( pubkey: PublicKey | undefined ) => { if (!pubkey) return [] - const domains = await getAllDomains(connection, pubkey) + const sns_domains = await getAllDomains(connection, pubkey) const results: Domain[] = [] + + if (sns_domains.length > 0) { + const reverse = await performReverseLookupBatch(connection, sns_domains) - if (domains.length > 0) { - const reverse = await performReverseLookupBatch(connection, domains) - - for (let i = 0; i < domains.length; i++) { + for (let i = 0; i < sns_domains.length; i++) { results.push({ - domainAddress: domains[i].toBase58(), - domainName: reverse[i], + domainAddress: sns_domains[i].toBase58(), + domainName: reverse[i] + '.sol', + domainOwner: pubkey.toBase58(), + type: 'sns', }) } } diff --git a/yarn.lock b/yarn.lock index 4de48b7d5..1c8d2358e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -710,7 +710,7 @@ dependencies: ajv "^8.12.0" -"@coral-xyz/anchor-30@npm:@coral-xyz/anchor@0.30.1", "@coral-xyz/anchor@0.30.1", "@coral-xyz/anchor@^0.30.1", "switchboard-anchor@npm:@coral-xyz/anchor@0.30.1": +"@coral-xyz/anchor-30@npm:@coral-xyz/anchor@0.30.1": version "0.30.1" resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== @@ -756,6 +756,27 @@ superstruct "^0.15.4" toml "^3.0.0" +"@coral-xyz/anchor@0.30.1", "@coral-xyz/anchor@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" + integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== + dependencies: + "@coral-xyz/anchor-errors" "^0.30.1" + "@coral-xyz/borsh" "^0.30.1" + "@noble/hashes" "^1.3.1" + "@solana/web3.js" "^1.68.0" + bn.js "^5.1.2" + bs58 "^4.0.1" + buffer-layout "^1.2.2" + camelcase "^6.3.0" + cross-fetch "^3.1.5" + crypto-hash "^1.3.0" + eventemitter3 "^4.0.7" + pako "^2.0.3" + snake-case "^3.0.4" + superstruct "^0.15.4" + toml "^3.0.0" + "@coral-xyz/borsh@0.27.0", "@coral-xyz/borsh@^0.26.0", "@coral-xyz/borsh@^0.28.0", "@coral-xyz/borsh@^0.29.0", "@coral-xyz/borsh@^0.30.1": version "0.27.0" resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.27.0.tgz#700c647ea5262b1488957ac7fb4e8acf72c72b63" @@ -2759,6 +2780,14 @@ node-gyp "^7.1.0" read-package-json-fast "^2.0.1" +"@onsol/tldparser@0.6.7": + version "0.6.7" + resolved "https://registry.yarnpkg.com/@onsol/tldparser/-/tldparser-0.6.7.tgz#f949865b4d6d46e4c8cc22816c4cfab8a8270163" + integrity sha512-QwkRDLyC514pxeplCCXZ2kTiRcJSeUrpp+9o2XqLbePy/qzZGGG8I0UbXUKuWVD/bUL1zAm21+D+Eu30OKwcQg== + dependencies: + "@ethersproject/sha2" "^5.7.0" + "@metaplex-foundation/beet-solana" "^0.4.0" + "@parcel/watcher-android-arm64@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.0.tgz#9c93763794153e4f76920994a423b6ea3257059d" @@ -16001,7 +16030,7 @@ string-similarity@^4.0.3: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -16019,6 +16048,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -16122,7 +16160,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16150,6 +16188,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -16296,6 +16341,27 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +"switchboard-anchor@npm:@coral-xyz/anchor@0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" + integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== + dependencies: + "@coral-xyz/anchor-errors" "^0.30.1" + "@coral-xyz/borsh" "^0.30.1" + "@noble/hashes" "^1.3.1" + "@solana/web3.js" "^1.68.0" + bn.js "^5.1.2" + bs58 "^4.0.1" + buffer-layout "^1.2.2" + camelcase "^6.3.0" + cross-fetch "^3.1.5" + crypto-hash "^1.3.0" + eventemitter3 "^4.0.7" + pako "^2.0.3" + snake-case "^3.0.4" + superstruct "^0.15.4" + toml "^3.0.0" + swr@1.3.0, swr@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8" @@ -17487,7 +17553,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -17513,6 +17579,15 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"