Fixes for Cosmos support (#115)

pull/116/head
J M Rossy 2 months ago committed by GitHub
parent f02dd1b75f
commit 3d0674decf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      src/components/search/SearchFilterBar.tsx
  2. 2
      src/components/search/SearchStates.tsx
  3. 4
      src/features/chains/ChainSearchModal.tsx
  4. 34
      src/features/chains/queries/useScrapedChains.ts
  5. 26
      src/features/messages/cards/TransactionCard.tsx
  6. 4
      src/features/messages/pi-queries/usePiChainMessageQuery.ts
  7. 12
      src/features/messages/queries/build.ts
  8. 33
      src/features/messages/queries/encoding.ts
  9. 35
      src/features/messages/queries/parse.ts
  10. 19
      src/features/messages/queries/useMessageQuery.ts
  11. 8
      src/store.ts

@ -1,7 +1,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import { ChainMetadata } from '@hyperlane-xyz/sdk'; import { ChainMetadata, getDomainId } from '@hyperlane-xyz/sdk';
import { trimToLength } from '@hyperlane-xyz/utils'; import { trimToLength } from '@hyperlane-xyz/utils';
import { ChevronIcon, IconButton, Popover, XIcon, useModal } from '@hyperlane-xyz/widgets'; import { ChevronIcon, IconButton, Popover, XIcon, useModal } from '@hyperlane-xyz/widgets';
@ -69,7 +69,7 @@ function ChainSelector({
: undefined; : undefined;
const onClickChain = (c: ChainMetadata) => { const onClickChain = (c: ChainMetadata) => {
onChangeValue(c.chainId.toString()); onChangeValue(getDomainId(c).toString());
close(); close();
}; };

@ -76,7 +76,7 @@ export function SearchInvalidError({
show={show} show={show}
imgSrc={SearchOffIcon} imgSrc={SearchOffIcon}
text={`Sorry, that search input is not valid. Please try ${ text={`Sorry, that search input is not valid. Please try ${
allowAddress ? 'an account addresses or ' : '' allowAddress ? 'an account address or ' : ''
}a transaction hash like 0xABC123...`} }a transaction hash like 0xABC123...`}
imgWidth={70} imgWidth={70}
/> />

@ -3,7 +3,7 @@ import { ChainSearchMenu, Modal } from '@hyperlane-xyz/widgets';
import { useMultiProvider, useStore } from '../../store'; import { useMultiProvider, useStore } from '../../store';
import { useScrapedEvmChains } from './queries/useScrapedChains'; import { useScrapedChains } from './queries/useScrapedChains';
export function ChainSearchModal({ export function ChainSearchModal({
isOpen, isOpen,
@ -17,7 +17,7 @@ export function ChainSearchModal({
showAddChainMenu?: boolean; showAddChainMenu?: boolean;
}) { }) {
const multiProvider = useMultiProvider(); const multiProvider = useMultiProvider();
const { chains } = useScrapedEvmChains(multiProvider); const { chains } = useScrapedChains(multiProvider);
const { chainMetadataOverrides, setChainMetadataOverrides } = useStore((s) => ({ const { chainMetadataOverrides, setChainMetadataOverrides } = useStore((s) => ({
chainMetadataOverrides: s.chainMetadataOverrides, chainMetadataOverrides: s.chainMetadataOverrides,
setChainMetadataOverrides: s.setChainMetadataOverrides, setChainMetadataOverrides: s.setChainMetadataOverrides,

@ -6,51 +6,47 @@ import { objFilter } from '@hyperlane-xyz/utils';
import { unscrapedChainsInDb } from '../../../consts/config'; import { unscrapedChainsInDb } from '../../../consts/config';
import { useStore } from '../../../store'; import { useStore } from '../../../store';
import { isEvmChain, isPiChain } from '../utils'; import { isPiChain } from '../utils';
import { DOMAINS_QUERY, DomainsEntry } from './fragments'; import { DOMAINS_QUERY, DomainsEntry } from './fragments';
export function useScrapedChains() { export function useScrapedDomains() {
const { scrapedChains, setScrapedChains } = useStore((s) => ({ const { scrapedDomains, setScrapedDomains } = useStore((s) => ({
scrapedChains: s.scrapedChains, scrapedDomains: s.scrapedDomains,
setScrapedChains: s.setScrapedChains, setScrapedDomains: s.setScrapedDomains,
})); }));
const [result] = useQuery<{ domain: Array<DomainsEntry> }>({ const [result] = useQuery<{ domain: Array<DomainsEntry> }>({
query: DOMAINS_QUERY, query: DOMAINS_QUERY,
pause: !!scrapedChains?.length, pause: !!scrapedDomains?.length,
}); });
const { data, fetching: isFetching, error } = result; const { data, fetching: isFetching, error } = result;
useEffect(() => { useEffect(() => {
if (!data) return; if (!data) return;
setScrapedChains(data.domain); setScrapedDomains(data.domain);
}, [data, error, setScrapedChains]); }, [data, error, setScrapedDomains]);
return { return {
scrapedChains, scrapedDomains,
isFetching, isFetching,
isError: !!error, isError: !!error,
}; };
} }
export function useScrapedEvmChains(multiProvider: MultiProvider) { export function useScrapedChains(multiProvider: MultiProvider) {
const { scrapedChains, isFetching, isError } = useScrapedChains(); const { scrapedDomains, isFetching, isError } = useScrapedDomains();
const chainMetadata = useStore((s) => s.chainMetadata); const chainMetadata = useStore((s) => s.chainMetadata);
const { chains } = useMemo(() => { const { chains } = useMemo(() => {
// Filtering to EVM is necessary to prevent errors until cosmos support is added const scrapedChains = objFilter(
// https://github.com/hyperlane-xyz/hyperlane-explorer/issues/61
const scrapedEvmChains = objFilter(
chainMetadata, chainMetadata,
(_, chainMetadata): chainMetadata is ChainMetadata => (_, chainMetadata): chainMetadata is ChainMetadata =>
isEvmChain(multiProvider, chainMetadata.chainId) && !isPiChain(multiProvider, scrapedDomains, chainMetadata.chainId) &&
!isPiChain(multiProvider, scrapedChains, chainMetadata.chainId) &&
!isUnscrapedDbChain(multiProvider, chainMetadata.chainId), !isUnscrapedDbChain(multiProvider, chainMetadata.chainId),
); );
// Return only evmChains because of graphql only accept query non-evm chains (with bigint type not string) return { chains: scrapedChains };
return { chains: scrapedEvmChains }; }, [multiProvider, chainMetadata, scrapedDomains]);
}, [multiProvider, chainMetadata, scrapedChains]);
return { chains, isFetching, isError }; return { chains, isFetching, isError };
} }

@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js';
import { PropsWithChildren, ReactNode, useState } from 'react'; import { PropsWithChildren, ReactNode, useState } from 'react';
import { MultiProvider } from '@hyperlane-xyz/sdk'; import { MultiProvider } from '@hyperlane-xyz/sdk';
import { isAddress, isZeroish } from '@hyperlane-xyz/utils'; import { ProtocolType, isAddress, isZeroish, strip0x } from '@hyperlane-xyz/utils';
import { Modal, useModal } from '@hyperlane-xyz/widgets'; import { Modal, useModal } from '@hyperlane-xyz/widgets';
import { Spinner } from '../../../components/animations/Spinner'; import { Spinner } from '../../../components/animations/Spinner';
@ -127,8 +127,7 @@ export function DestinationTransactionCard({
<ChainSearchModal isOpen={isOpen} close={close} showAddChainMenu={true} /> <ChainSearchModal isOpen={isOpen} close={close} showAddChainMenu={true} />
</> </>
); );
} else if (status === MessageStatus.Pending) { } else if (status === MessageStatus.Pending && isDestinationEvmChain) {
if (isDestinationEvmChain) {
content = ( content = (
<DeliveryStatus> <DeliveryStatus>
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
@ -146,19 +145,12 @@ export function DestinationTransactionCard({
} else { } else {
content = ( content = (
<DeliveryStatus> <DeliveryStatus>
<div>Sorry, delivery information is currently available for only EVM-type chains.</div> <div>Delivery to status is currently unknown.</div>
<div className="mt-2 text-sm pb-4">Support for other protocols is coming soon.</div> <div className="mt-2 text-sm pb-4">
</DeliveryStatus> {isPiMsg
);
}
} else {
content = (
<DeliveryStatus>
<div>{`Delivery to status is currently unknown. ${
isPiMsg
? 'Please ensure your chain config is correct and check back later.' ? 'Please ensure your chain config is correct and check back later.'
: 'Please check again later' : 'Please check again later'}
}`}</div> </div>
</DeliveryStatus> </DeliveryStatus>
); );
} }
@ -210,12 +202,14 @@ function TransactionDetails({
blur: boolean; blur: boolean;
}) { }) {
const multiProvider = useMultiProvider(); const multiProvider = useMultiProvider();
const protocol = multiProvider.tryGetProtocol(domainId) || ProtocolType.Ethereum;
const { hash, from, timestamp, blockNumber, mailbox } = transaction; const { hash, from, timestamp, blockNumber, mailbox } = transaction;
const formattedHash = protocol === ProtocolType.Cosmos ? strip0x(hash) : hash;
const txExplorerLink = const txExplorerLink =
hash && !new BigNumber(hash).isZero() hash && !new BigNumber(hash).isZero()
? multiProvider.tryGetExplorerTxUrl(chainId, { hash }) ? multiProvider.tryGetExplorerTxUrl(chainId, { hash: formattedHash })
: null; : null;
return ( return (

@ -7,7 +7,7 @@ import { ensure0x, timeout } from '@hyperlane-xyz/utils';
import { useReadyMultiProvider, useRegistry } from '../../../store'; import { useReadyMultiProvider, useRegistry } from '../../../store';
import { Message } from '../../../types'; import { Message } from '../../../types';
import { logger } from '../../../utils/logger'; import { logger } from '../../../utils/logger';
import { useScrapedChains } from '../../chains/queries/useScrapedChains'; import { useScrapedDomains } from '../../chains/queries/useScrapedChains';
import { isEvmChain, isPiChain } from '../../chains/utils'; import { isEvmChain, isPiChain } from '../../chains/utils';
import { isValidSearchQuery } from '../queries/useMessageQuery'; import { isValidSearchQuery } from '../queries/useMessageQuery';
@ -30,7 +30,7 @@ export function usePiChainMessageSearchQuery({
piQueryType?: PiQueryType; piQueryType?: PiQueryType;
pause: boolean; pause: boolean;
}) { }) {
const { scrapedChains } = useScrapedChains(); const { scrapedDomains: scrapedChains } = useScrapedDomains();
const multiProvider = useReadyMultiProvider(); const multiProvider = useReadyMultiProvider();
const registry = useRegistry(); const registry = useRegistry();

@ -2,7 +2,7 @@ import { isAddress } from '@hyperlane-xyz/utils';
import { adjustToUtcTime } from '../../../utils/time'; import { adjustToUtcTime } from '../../../utils/time';
import { stringToPostgresBytea } from './encoding'; import { searchValueToPostgresBytea } from './encoding';
import { messageDetailsFragment, messageStubFragment } from './fragments'; import { messageDetailsFragment, messageStubFragment } from './fragments';
/** /**
@ -47,7 +47,7 @@ export function buildMessageQuery(
} else { } else {
throw new Error(`Invalid id type: ${idType}`); throw new Error(`Invalid id type: ${idType}`);
} }
const variables = { identifier: stringToPostgresBytea(idValue) }; const variables = { identifier: searchValueToPostgresBytea(idValue) };
const query = ` const query = `
query ($identifier: bytea!) @cached(ttl: 5) { query ($identifier: bytea!) @cached(ttl: 5) {
@ -78,7 +78,7 @@ export function buildMessageSearchQuery(
const startTime = startTimeFilter ? adjustToUtcTime(startTimeFilter) : undefined; const startTime = startTimeFilter ? adjustToUtcTime(startTimeFilter) : undefined;
const endTime = endTimeFilter ? adjustToUtcTime(endTimeFilter) : undefined; const endTime = endTimeFilter ? adjustToUtcTime(endTimeFilter) : undefined;
const variables = { const variables = {
search: hasInput ? stringToPostgresBytea(searchInput) : undefined, search: hasInput ? searchValueToPostgresBytea(searchInput) : undefined,
originChains, originChains,
destinationChains, destinationChains,
startTime, startTime,
@ -93,8 +93,8 @@ export function buildMessageSearchQuery(
`q${i}: message_view( `q${i}: message_view(
where: { where: {
_and: [ _and: [
${originFilter ? '{origin_chain_id: {_in: $originChains}},' : ''} ${originFilter ? '{origin_domain_id: {_in: $originChains}},' : ''}
${destFilter ? '{destination_chain_id: {_in: $destinationChains}},' : ''} ${destFilter ? '{destination_domain_id: {_in: $destinationChains}},' : ''}
${startTimeFilter ? '{send_occurred_at: {_gte: $startTime}},' : ''} ${startTimeFilter ? '{send_occurred_at: {_gte: $startTime}},' : ''}
${endTimeFilter ? '{send_occurred_at: {_lte: $endTime}},' : ''} ${endTimeFilter ? '{send_occurred_at: {_lte: $endTime}},' : ''}
${whereClause} ${whereClause}
@ -107,7 +107,7 @@ export function buildMessageSearchQuery(
}`, }`,
); );
const query = `query ($search: bytea, $originChains: [bigint!], $destinationChains: [bigint!], $startTime: timestamp, $endTime: timestamp) @cached(ttl: 5) { const query = `query ($search: bytea, $originChains: [Int!], $destinationChains: [Int!], $startTime: timestamp, $endTime: timestamp) @cached(ttl: 5) {
${queries.join('\n')} ${queries.join('\n')}
}`; }`;
return { query, variables }; return { query, variables };

@ -1,12 +1,39 @@
import { ensure0x, strip0x } from '@hyperlane-xyz/utils'; import { ChainMetadata } from '@hyperlane-xyz/sdk';
import {
addressToByteHexString,
bytesToProtocolAddress,
ensure0x,
isAddress,
strip0x,
} from '@hyperlane-xyz/utils';
export function stringToPostgresBytea(hexString: string) { export function stringToPostgresBytea(hexString: string): string {
const trimmed = strip0x(hexString).toLowerCase(); const trimmed = strip0x(hexString).toLowerCase();
const prefix = `\\x`; const prefix = `\\x`;
return `${prefix}${trimmed}`; return `${prefix}${trimmed}`;
} }
export function postgresByteaToString(byteString: string) { export function postgresByteaToString(byteString: string): string {
if (!byteString || byteString.length < 4) throw new Error('Invalid byte string'); if (!byteString || byteString.length < 4) throw new Error('Invalid byte string');
return ensure0x(byteString.substring(2)); return ensure0x(byteString.substring(2));
} }
export function addressToPostgresBytea(address: Address): string {
const hexString = addressToByteHexString(address);
return stringToPostgresBytea(hexString);
}
export function postgresByteaToAddress(
byteString: string,
chainMetadata: ChainMetadata | null | undefined,
): Address {
const hexString = postgresByteaToString(byteString);
if (!chainMetadata) return hexString;
const addressBytes = Buffer.from(strip0x(hexString), 'hex');
return bytesToProtocolAddress(addressBytes, chainMetadata.protocol, chainMetadata.bech32Prefix);
}
export function searchValueToPostgresBytea(input: string): string {
if (isAddress(input)) return addressToPostgresBytea(input);
else return stringToPostgresBytea(input);
}

@ -6,7 +6,7 @@ import { tryUtf8DecodeBytes } from '../../../utils/string';
import { DomainsEntry } from '../../chains/queries/fragments'; import { DomainsEntry } from '../../chains/queries/fragments';
import { isPiChain } from '../../chains/utils'; import { isPiChain } from '../../chains/utils';
import { postgresByteaToString } from './encoding'; import { postgresByteaToAddress, postgresByteaToString } from './encoding';
import { import {
MessageEntry, MessageEntry,
MessageStubEntry, MessageStubEntry,
@ -53,12 +53,14 @@ function parseMessageStub(
m: MessageStubEntry, m: MessageStubEntry,
): MessageStub | null { ): MessageStub | null {
try { try {
const destinationDomainId = m.destination_domain_id; const originMetadata = multiProvider.tryGetChainMetadata(m.origin_domain_id);
let destinationChainId = const destinationMetadata = multiProvider.tryGetChainMetadata(m.destination_domain_id);
m.destination_chain_id || multiProvider.tryGetChainId(destinationDomainId); let destinationChainId = m.destination_chain_id || destinationMetadata?.chainId;
if (!destinationChainId) { if (!destinationChainId) {
logger.debug(`No chainId known for domain ${destinationDomainId}. Using domain as chainId`); logger.debug(
destinationChainId = destinationDomainId; `No chainId known for domain ${m.destination_domain_id}. Using domain as chainId`,
);
destinationChainId = m.destination_domain_id;
} }
const isPiMsg = const isPiMsg =
isPiChain(multiProvider, scrapedChains, m.origin_chain_id) || isPiChain(multiProvider, scrapedChains, m.origin_chain_id) ||
@ -69,22 +71,22 @@ function parseMessageStub(
id: m.id.toString(), id: m.id.toString(),
msgId: postgresByteaToString(m.msg_id), msgId: postgresByteaToString(m.msg_id),
nonce: m.nonce, nonce: m.nonce,
sender: postgresByteaToString(m.sender), sender: postgresByteaToAddress(m.sender, originMetadata),
recipient: postgresByteaToString(m.recipient), recipient: postgresByteaToAddress(m.recipient, destinationMetadata),
originChainId: m.origin_chain_id, originChainId: m.origin_chain_id,
originDomainId: m.origin_domain_id, originDomainId: m.origin_domain_id,
destinationChainId, destinationChainId,
destinationDomainId, destinationDomainId: m.destination_domain_id,
origin: { origin: {
timestamp: parseTimestampString(m.send_occurred_at), timestamp: parseTimestampString(m.send_occurred_at),
hash: postgresByteaToString(m.origin_tx_hash), hash: postgresByteaToString(m.origin_tx_hash),
from: postgresByteaToString(m.origin_tx_sender), from: postgresByteaToAddress(m.origin_tx_sender, originMetadata),
}, },
destination: m.is_delivered destination: m.is_delivered
? { ? {
timestamp: parseTimestampString(m.delivery_occurred_at!), timestamp: parseTimestampString(m.delivery_occurred_at!),
hash: postgresByteaToString(m.destination_tx_hash!), hash: postgresByteaToString(m.destination_tx_hash!),
from: postgresByteaToString(m.destination_tx_sender!), from: postgresByteaToAddress(m.destination_tx_sender!, destinationMetadata),
} }
: undefined, : undefined,
isPiMsg, isPiMsg,
@ -104,6 +106,9 @@ function parseMessage(
const stub = parseMessageStub(multiProvider, scrapedChains, m); const stub = parseMessageStub(multiProvider, scrapedChains, m);
if (!stub) throw new Error('Message stub required'); if (!stub) throw new Error('Message stub required');
const originMetadata = multiProvider.tryGetChainMetadata(m.origin_domain_id);
const destinationMetadata = multiProvider.tryGetChainMetadata(m.destination_domain_id);
const body = postgresByteaToString(m.message_body ?? ''); const body = postgresByteaToString(m.message_body ?? '');
const decodedBody = tryUtf8DecodeBytes(body); const decodedBody = tryUtf8DecodeBytes(body);
@ -115,9 +120,9 @@ function parseMessage(
...stub.origin, ...stub.origin,
blockHash: postgresByteaToString(m.origin_block_hash), blockHash: postgresByteaToString(m.origin_block_hash),
blockNumber: m.origin_block_height, blockNumber: m.origin_block_height,
mailbox: postgresByteaToString(m.origin_mailbox), mailbox: postgresByteaToAddress(m.origin_mailbox, originMetadata),
nonce: m.origin_tx_nonce, nonce: m.origin_tx_nonce,
to: postgresByteaToString(m.origin_tx_recipient), to: postgresByteaToAddress(m.origin_tx_recipient, originMetadata),
gasLimit: m.origin_tx_gas_limit, gasLimit: m.origin_tx_gas_limit,
gasPrice: m.origin_tx_gas_price, gasPrice: m.origin_tx_gas_price,
effectiveGasPrice: m.origin_tx_effective_gas_price, effectiveGasPrice: m.origin_tx_effective_gas_price,
@ -131,9 +136,9 @@ function parseMessage(
...stub.destination, ...stub.destination,
blockHash: postgresByteaToString(m.destination_block_hash!), blockHash: postgresByteaToString(m.destination_block_hash!),
blockNumber: m.destination_block_height!, blockNumber: m.destination_block_height!,
mailbox: postgresByteaToString(m.destination_mailbox!), mailbox: postgresByteaToAddress(m.destination_mailbox!, destinationMetadata),
nonce: m.destination_tx_nonce!, nonce: m.destination_tx_nonce!,
to: postgresByteaToString(m.destination_tx_recipient!), to: postgresByteaToAddress(m.destination_tx_recipient!, destinationMetadata),
gasLimit: m.destination_tx_gas_limit!, gasLimit: m.destination_tx_gas_limit!,
gasPrice: m.destination_tx_gas_price!, gasPrice: m.destination_tx_gas_price!,
effectiveGasPrice: m.destination_tx_effective_gas_price!, effectiveGasPrice: m.destination_tx_effective_gas_price!,

@ -1,12 +1,16 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useQuery } from 'urql'; import { useQuery } from 'urql';
import { isAddressEvm, isValidTransactionHashEvm } from '@hyperlane-xyz/utils'; import {
isAddress,
isValidTransactionHashCosmos,
isValidTransactionHashEvm,
} from '@hyperlane-xyz/utils';
import { useMultiProvider } from '../../../store'; import { useMultiProvider } from '../../../store';
import { MessageStatus } from '../../../types'; import { MessageStatus } from '../../../types';
import { useInterval } from '../../../utils/useInterval'; import { useInterval } from '../../../utils/useInterval';
import { useScrapedChains } from '../../chains/queries/useScrapedChains'; import { useScrapedDomains } from '../../chains/queries/useScrapedChains';
import { MessageIdentifierType, buildMessageQuery, buildMessageSearchQuery } from './build'; import { MessageIdentifierType, buildMessageQuery, buildMessageSearchQuery } from './build';
import { MessagesQueryResult, MessagesStubQueryResult } from './fragments'; import { MessagesQueryResult, MessagesStubQueryResult } from './fragments';
@ -19,8 +23,11 @@ const SEARCH_QUERY_LIMIT = 50;
export function isValidSearchQuery(input: string, allowAddress?: boolean) { export function isValidSearchQuery(input: string, allowAddress?: boolean) {
if (!input) return false; if (!input) return false;
if (isValidTransactionHashEvm(input)) return true; return !!(
return !!(allowAddress && isAddressEvm(input)); isValidTransactionHashEvm(input) ||
isValidTransactionHashCosmos(input) ||
(allowAddress && isAddress(input))
);
} }
export function useMessageSearchQuery( export function useMessageSearchQuery(
@ -30,7 +37,7 @@ export function useMessageSearchQuery(
startTimeFilter: number | null, startTimeFilter: number | null,
endTimeFilter: number | null, endTimeFilter: number | null,
) { ) {
const { scrapedChains } = useScrapedChains(); const { scrapedDomains: scrapedChains } = useScrapedDomains();
const hasInput = !!sanitizedInput; const hasInput = !!sanitizedInput;
const isValidInput = hasInput ? isValidSearchQuery(sanitizedInput, true) : true; const isValidInput = hasInput ? isValidSearchQuery(sanitizedInput, true) : true;
@ -80,7 +87,7 @@ export function useMessageSearchQuery(
} }
export function useMessageQuery({ messageId, pause }: { messageId: string; pause: boolean }) { export function useMessageQuery({ messageId, pause }: { messageId: string; pause: boolean }) {
const { scrapedChains } = useScrapedChains(); const { scrapedDomains: scrapedChains } = useScrapedDomains();
// Assemble GraphQL Query // Assemble GraphQL Query
const { query, variables } = buildMessageQuery(MessageIdentifierType.Id, messageId, 1); const { query, variables } = buildMessageQuery(MessageIdentifierType.Id, messageId, 1);

@ -15,8 +15,8 @@ const PERSIST_STATE_VERSION = 2;
// Keeping everything here for now as state is simple // Keeping everything here for now as state is simple
// Will refactor into slices as necessary // Will refactor into slices as necessary
interface AppState { interface AppState {
scrapedChains: Array<DomainsEntry>; scrapedDomains: Array<DomainsEntry>;
setScrapedChains: (chains: Array<DomainsEntry>) => void; setScrapedDomains: (chains: Array<DomainsEntry>) => void;
chainMetadata: ChainMap<ChainMetadata>; chainMetadata: ChainMap<ChainMetadata>;
setChainMetadata: (metadata: ChainMap<ChainMetadata>) => void; setChainMetadata: (metadata: ChainMap<ChainMetadata>) => void;
chainMetadataOverrides: ChainMap<Partial<ChainMetadata>>; chainMetadataOverrides: ChainMap<Partial<ChainMetadata>>;
@ -32,8 +32,8 @@ interface AppState {
export const useStore = create<AppState>()( export const useStore = create<AppState>()(
persist( persist(
(set, get) => ({ (set, get) => ({
scrapedChains: [], scrapedDomains: [],
setScrapedChains: (chains: Array<DomainsEntry>) => set({ scrapedChains: chains }), setScrapedDomains: (domains: Array<DomainsEntry>) => set({ scrapedDomains: domains }),
chainMetadata: {}, chainMetadata: {},
setChainMetadata: (metadata: ChainMap<ChainMetadata>) => set({ chainMetadata: metadata }), setChainMetadata: (metadata: ChainMap<ChainMetadata>) => set({ chainMetadata: metadata }),
chainMetadataOverrides: {}, chainMetadataOverrides: {},

Loading…
Cancel
Save