Remove all chain override contexts and move to global store (#4743)
## Problem solved
Short description of the bug fixed or feature added
<!-- start pr-codex -->
---
## PR-Codex overview
The focus of this PR is to refactor chain-related functionality and imports, update TypeScript types, and improve store management.
### Detailed summary
- Refactored chain-related functionality and imports
- Updated TypeScript types for chain mapping
- Improved store management for chains
- Updated API calls for chain data
- Removed unused hooks and contexts
> The following files were skipped due to too many changes: `apps/dashboard/src/components/configure-networks/ConfigureNetworkModal.tsx`, `apps/dashboard/src/contract-ui/tabs/account-permissions/components/account-signer.tsx`, `apps/dashboard/src/contract-ui/tabs/split/page.tsx`, `apps/dashboard/src/stores/SyncStoreToStorage.tsx`, `apps/dashboard/src/components/contract-components/contract-publish-form/NetworkDropdown.tsx`, `apps/dashboard/src/components/configure-networks/Form/NetworkIdInput.tsx`, `apps/dashboard/src/contract-ui/tabs/embed/components/embed-setup.tsx`, `apps/dashboard/src/contract-ui/tabs/code/components/code-overview.tsx`, `apps/dashboard/src/components/engine/contract-subscription/contract-subscriptions-table.tsx`, `apps/dashboard/src/components/buttons/MismatchButton.tsx`, `apps/dashboard/src/components/selects/NetworkSelectDropdown.tsx`, `apps/dashboard/src/components/selects/CustomChainRenderer.tsx`, `apps/dashboard/src/components/app-layouts/app.tsx`, `apps/dashboard/src/components/contract-components/published-contract/addresses-modal.tsx`, `apps/dashboard/src/components/contract-components/tables/deployed-contracts.tsx`, `apps/dashboard/src/lib/v5-adapter.ts`, `apps/dashboard/src/components/engine/overview/transactions-table.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useRegistry.ts`, `apps/dashboard/src/stores/chainStores.tsx`, `apps/dashboard/src/components/engine/relayer/relayers-table.tsx`, `apps/dashboard/src/components/configure-networks/ConfigureNetworkForm.tsx`, `apps/dashboard/src/hooks/chains/allChains.ts`, `apps/dashboard/src/pages/[chain_id]/[...paths].tsx`, `apps/dashboard/src/components/selects/NetworkSelectorButton.tsx`, `apps/dashboard/src/@3rdweb-sdk/react/components/connect-wallet/index.tsx`
> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`
<!-- end pr-codex -->
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
"format": "biome format ./src --write",
|
||||
"lint": "biome check ./src && knip && eslint ./src",
|
||||
"fix": "biome check ./src --fix && eslint ./src --fix",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"gen:theme-typings": "chakra-cli tokens src/theme/index.ts",
|
||||
"postinstall": "pnpm run gen:theme-typings",
|
||||
"postbuild": "next-sitemap",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
type Store<T> = {
|
||||
import { useSyncExternalStore } from "react";
|
||||
|
||||
export type Store<T> = {
|
||||
getValue(): T;
|
||||
setValue(newValue: T): void;
|
||||
subscribe(listener: () => void): () => void;
|
||||
@@ -45,3 +47,7 @@ export function createStore<T>(initialValue: T): Store<T> {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function useStore<T>(store: Store<T>): T {
|
||||
return useSyncExternalStore(store.subscribe, store.getValue, store.getValue);
|
||||
}
|
||||
|
||||
@@ -3,28 +3,31 @@
|
||||
import { Spinner } from "@/components/ui/Spinner/Spinner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useThirdwebClient } from "@/constants/thirdweb.client";
|
||||
import { useStore } from "@/lib/reactive";
|
||||
import { getSDKTheme } from "app/components/sdk-component-theme";
|
||||
import { CustomChainRenderer } from "components/selects/CustomChainRenderer";
|
||||
import { mapV4ChainToV5Chain } from "contexts/map-chains";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import {
|
||||
useSupportedChains,
|
||||
useSupportedChainsRecord,
|
||||
} from "hooks/chains/configureChains";
|
||||
import {
|
||||
useAddRecentlyUsedChainId,
|
||||
useRecentlyUsedChains,
|
||||
} from "hooks/chains/recentlyUsedChains";
|
||||
import { useTheme } from "next-themes";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import type { Chain } from "thirdweb";
|
||||
import { AutoConnect, ConnectButton, useConnectModal } from "thirdweb/react";
|
||||
import {
|
||||
AutoConnect,
|
||||
ConnectButton,
|
||||
type NetworkSelectorProps,
|
||||
useConnectModal,
|
||||
} from "thirdweb/react";
|
||||
import { useFavoriteChainIds } from "../../../../app/(dashboard)/(chain)/components/client/star-button";
|
||||
import { LazyConfigureNetworkModal } from "../../../../components/configure-networks/LazyConfigureNetworkModal";
|
||||
import type { StoredChain } from "../../../../contexts/configured-chains";
|
||||
import { useFavoriteChains } from "../../hooks/useFavoriteChains";
|
||||
import { useAllChainsData } from "../../../../hooks/chains/allChains";
|
||||
import {
|
||||
type StoredChain,
|
||||
addRecentlyUsedChainId,
|
||||
recentlyUsedChainIdsStore,
|
||||
} from "../../../../stores/chainStores";
|
||||
import { useLoggedInUser } from "../../hooks/useLoggedInUser";
|
||||
import { popularChains } from "../popularChains";
|
||||
|
||||
@@ -36,43 +39,81 @@ export const CustomConnectWallet = (props: {
|
||||
const thirdwebClient = useThirdwebClient();
|
||||
const loginRequired =
|
||||
props.loginRequired === undefined ? true : props.loginRequired;
|
||||
|
||||
const { theme } = useTheme();
|
||||
const recentChainsv4 = useRecentlyUsedChains();
|
||||
const addRecentlyUsedChainId = useAddRecentlyUsedChainId();
|
||||
const t = theme === "light" ? "light" : "dark";
|
||||
const allv4Chains = useSupportedChains();
|
||||
const chainsRecord = useSupportedChainsRecord();
|
||||
const favChainsQuery = useFavoriteChains();
|
||||
const allChains = useMemo(() => {
|
||||
return allv4Chains.map(mapV4ChainToV5Chain);
|
||||
}, [allv4Chains]);
|
||||
|
||||
const popularChainsWithMeta = useMemo(() => {
|
||||
return popularChains.map((x) => chainsRecord[x.id] || x);
|
||||
}, [chainsRecord]);
|
||||
// chains
|
||||
const favChainIdsQuery = useFavoriteChainIds();
|
||||
const recentChainIds = useStore(recentlyUsedChainIdsStore);
|
||||
const { idToChain, allChains } = useAllChainsData();
|
||||
|
||||
const allChainsWithMetadata = useMemo(
|
||||
() => allChains.map(mapV4ChainToV5Chain),
|
||||
[allChains],
|
||||
);
|
||||
|
||||
const recentlyUsedChainsWithMetadata = useMemo(
|
||||
() =>
|
||||
recentChainIds
|
||||
.map((id) => {
|
||||
const c = idToChain.get(id);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return c ? mapV4ChainToV5Chain(c) : undefined;
|
||||
})
|
||||
.filter((x) => !!x),
|
||||
[recentChainIds, idToChain],
|
||||
);
|
||||
|
||||
const favoriteChainsWithMetadata = useMemo(() => {
|
||||
return (favChainIdsQuery.data || [])
|
||||
.map((id) => {
|
||||
const c = idToChain.get(Number(id));
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return c ? mapV4ChainToV5Chain(c) : undefined;
|
||||
})
|
||||
.filter((x) => !!x);
|
||||
}, [idToChain, favChainIdsQuery.data]);
|
||||
|
||||
const popularChainsWithMetadata = useMemo(() => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return popularChains.map((x) =>
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
{
|
||||
const v4Chain = idToChain.get(x.id);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return v4Chain ? mapV4ChainToV5Chain(v4Chain) : x;
|
||||
},
|
||||
);
|
||||
}, [idToChain]);
|
||||
|
||||
// Network Config Modal
|
||||
const [isNetworkConfigModalOpen, setIsNetworkConfigModalOpen] =
|
||||
useState(false);
|
||||
const [editChain, setEditChain] = useState<StoredChain | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const chainSections = useMemo(() => {
|
||||
const chainSections: NetworkSelectorProps["sections"] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
label: "Favorites",
|
||||
chains: favChainsQuery.data.map(mapV4ChainToV5Chain),
|
||||
},
|
||||
{
|
||||
label: "Popular",
|
||||
chains: popularChainsWithMeta.map(mapV4ChainToV5Chain),
|
||||
chains: favoriteChainsWithMetadata,
|
||||
},
|
||||
{
|
||||
label: "Recent",
|
||||
chains: recentChainsv4.map(mapV4ChainToV5Chain),
|
||||
chains: recentlyUsedChainsWithMetadata,
|
||||
},
|
||||
{
|
||||
label: "Popular",
|
||||
chains: popularChainsWithMetadata,
|
||||
},
|
||||
];
|
||||
}, [recentChainsv4, favChainsQuery.data, popularChainsWithMeta]);
|
||||
}, [
|
||||
recentlyUsedChainsWithMetadata,
|
||||
favoriteChainsWithMetadata,
|
||||
popularChainsWithMetadata,
|
||||
]);
|
||||
|
||||
// ensures login status on pages that need it
|
||||
const { isPending, isLoggedIn } = useLoggedInUser();
|
||||
@@ -84,7 +125,7 @@ export const CustomConnectWallet = (props: {
|
||||
<div className="w-[144px] h-[48px] bg-muted border border-border rounded-lg flex items-center justify-center">
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
{/* need autoconnect here so that we actually connect */}
|
||||
{/* need auto connect here so that we actually connect */}
|
||||
<AutoConnect client={thirdwebClient} />
|
||||
</>
|
||||
);
|
||||
@@ -100,7 +141,7 @@ export const CustomConnectWallet = (props: {
|
||||
Sign In
|
||||
</Link>
|
||||
</Button>
|
||||
{/* need autoconnect here so that we actually connect */}
|
||||
{/* need auto connect here so that we actually connect */}
|
||||
<AutoConnect client={thirdwebClient} />
|
||||
</>
|
||||
);
|
||||
@@ -138,7 +179,7 @@ export const CustomConnectWallet = (props: {
|
||||
detailsButton={{
|
||||
className: props.detailsButtonClassName,
|
||||
}}
|
||||
chains={allChains}
|
||||
chains={allChainsWithMetadata}
|
||||
detailsModal={{
|
||||
networkSelector: {
|
||||
sections: chainSections,
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { THIRDWEB_ANALYTICS_API_HOST, THIRDWEB_API_HOST } from "constants/urls";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import invariant from "tiny-invariant";
|
||||
import { accountKeys, apiKeys, authorizedWallets } from "../cache-keys";
|
||||
import { useMutationWithInvalidate } from "./query/useQueryWithNetwork";
|
||||
@@ -947,28 +946,3 @@ export function useAuthorizedWallets() {
|
||||
gcTime: 0,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
async function fetchChainsFromApi() {
|
||||
// always fetch from prod for chains for now
|
||||
// TODO: re-visit this
|
||||
const res = await fetch("https://api.thirdweb.com/v1/chains");
|
||||
const json = await res.json();
|
||||
|
||||
if (json.error) {
|
||||
throw new Error(json.error.message);
|
||||
}
|
||||
|
||||
return json.data as ChainMetadata[];
|
||||
}
|
||||
|
||||
export function useApiChains() {
|
||||
return useQuery({
|
||||
queryKey: ["all-chains"],
|
||||
queryFn: () => {
|
||||
return fetchChainsFromApi();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useFavouriteChainIds } from "app/(dashboard)/(chain)/components/client/star-button";
|
||||
import { useSupportedChains } from "hooks/chains/configureChains";
|
||||
import { useMemo } from "react";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
|
||||
export function useFavoriteChains() {
|
||||
const allChains = useSupportedChains();
|
||||
const favChainsQuery = useFavouriteChainIds();
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (favChainsQuery.data) {
|
||||
const _chains: ChainMetadata[] = [];
|
||||
// biome-ignore lint/complexity/noForEach: FIXME
|
||||
favChainsQuery.data.forEach((chainId) => {
|
||||
const chain = allChains.find((c) => String(c.chainId) === chainId);
|
||||
if (chain) {
|
||||
_chains.push(chain);
|
||||
}
|
||||
});
|
||||
|
||||
return _chains;
|
||||
}
|
||||
|
||||
return [] as ChainMetadata[];
|
||||
}, [favChainsQuery.data, allChains]);
|
||||
|
||||
return {
|
||||
isPending: favChainsQuery.isPending,
|
||||
data,
|
||||
};
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import type { BasicContract } from "contract-ui/types/types";
|
||||
import { getAllMultichainRegistry } from "dashboard-extensions/common/read/getAllMultichainRegistry";
|
||||
import { useAllChainsData } from "hooks/chains/allChains";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import { useMemo } from "react";
|
||||
import { sendAndConfirmTransaction } from "thirdweb";
|
||||
import { remove } from "thirdweb/extensions/thirdweb";
|
||||
@@ -42,7 +41,8 @@ export const useAllContractList = (
|
||||
) => {
|
||||
const multiChainQuery = useMultiChainRegContractList(walletAddress);
|
||||
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
// TODO - instead of using ALL chains, fetch only the ones used here
|
||||
const { idToChain } = useAllChainsData();
|
||||
const contractList = useMemo(() => {
|
||||
const data = multiChainQuery.data || [];
|
||||
|
||||
@@ -51,14 +51,12 @@ export const useAllContractList = (
|
||||
|
||||
// biome-ignore lint/complexity/noForEach: FIXME
|
||||
data.forEach((net) => {
|
||||
if (net.chainId in configuredChainsRecord) {
|
||||
const chainRecord = configuredChainsRecord[net.chainId];
|
||||
if (chainRecord.status !== "deprecated") {
|
||||
if (chainRecord.testnet) {
|
||||
testnets.push(net);
|
||||
} else {
|
||||
mainnets.push(net);
|
||||
}
|
||||
const chn = idToChain.get(net.chainId);
|
||||
if (chn && chn.status !== "deprecated") {
|
||||
if (chn.testnet) {
|
||||
testnets.push(net);
|
||||
} else {
|
||||
mainnets.push(net);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -71,7 +69,7 @@ export const useAllContractList = (
|
||||
|
||||
testnets.sort((a, b) => a.chainId - b.chainId);
|
||||
return mainnets.concat(testnets);
|
||||
}, [multiChainQuery.data, onlyMainnet, configuredChainsRecord]);
|
||||
}, [multiChainQuery.data, onlyMainnet, idToChain]);
|
||||
|
||||
return {
|
||||
...multiChainQuery,
|
||||
@@ -85,12 +83,10 @@ type RemoveContractParams = {
|
||||
};
|
||||
|
||||
export function useRemoveContractMutation() {
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const queryClient = useQueryClient();
|
||||
const account = useActiveAccount();
|
||||
return useMutation({
|
||||
mutationFn: async (data: RemoveContractParams) => {
|
||||
invariant(chainIdToChainRecord, "chains not initialzed yet");
|
||||
invariant(data.chainId, "chainId not provided");
|
||||
invariant(account, "No wallet connected");
|
||||
const { contractAddress, chainId } = data;
|
||||
|
||||
@@ -42,7 +42,7 @@ async function removeChainFromFavorites(chainId: number) {
|
||||
return result?.data?.favorite;
|
||||
}
|
||||
|
||||
export function useFavouriteChainIds() {
|
||||
export function useFavoriteChainIds() {
|
||||
const loggedInUser = useLoggedInUser();
|
||||
return useQuery({
|
||||
queryKey: ["favoriteChains", loggedInUser.user?.address],
|
||||
@@ -59,7 +59,7 @@ export function StarButton(props: {
|
||||
}) {
|
||||
const loggedInUser = useLoggedInUser();
|
||||
const queryClient = useQueryClient();
|
||||
const favChainsQuery = useFavouriteChainIds();
|
||||
const favChainsQuery = useFavoriteChainIds();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (preferred: boolean) => {
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { AllChainsProvider } from "contexts/all-chains";
|
||||
import { ChainsProvider } from "contexts/configured-chains";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { ThirdwebProvider } from "thirdweb/react";
|
||||
import { SyncChainStores } from "../stores/chainStores";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
export function AppRouterProviders(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AllChainsProvider>
|
||||
<ChainsProvider>
|
||||
<ThirdwebProvider>
|
||||
<ThemeProvider attribute="class" defaultTheme="dark">
|
||||
{props.children}
|
||||
</ThemeProvider>
|
||||
</ThirdwebProvider>
|
||||
</ChainsProvider>
|
||||
</AllChainsProvider>
|
||||
<SyncChainStores />
|
||||
<ThirdwebProvider>
|
||||
<ThemeProvider attribute="class" defaultTheme="dark">
|
||||
{props.children}
|
||||
</ThemeProvider>
|
||||
</ThirdwebProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,14 +15,13 @@ import { AppShell, type AppShellProps } from "components/layout/app-shell";
|
||||
import { Onboarding as OnboardingModal } from "components/onboarding";
|
||||
import { OpCreditsGrantedModalWrapper } from "components/onboarding/OpCreditsGrantedModalWrapper";
|
||||
import { PosthogIdentifier } from "components/wallets/PosthogIdentifier";
|
||||
import { AllChainsProvider } from "contexts/all-chains";
|
||||
import { ChainsProvider } from "contexts/configured-chains";
|
||||
import { ErrorProvider } from "contexts/error-handler";
|
||||
import { isSanctionedAddress } from "data/eth-sanctioned-addresses";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { Heading } from "tw-components";
|
||||
import type { ComponentWithChildren } from "types/component-with-children";
|
||||
import { SyncChainStores } from "../../stores/chainStores";
|
||||
import { DashboardThirdwebProvider } from "./providers";
|
||||
|
||||
interface AppLayoutProps extends AppShellProps {
|
||||
@@ -50,22 +49,19 @@ export const AppLayout: ComponentWithChildren<AppLayoutProps> = (props) => {
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<HydrationBoundary state={props.dehydratedState}>
|
||||
<ErrorProvider>
|
||||
<AllChainsProvider>
|
||||
<ChainsProvider>
|
||||
<EVMContractInfoProvider value={props.contractInfo}>
|
||||
<DashboardThirdwebProvider>
|
||||
<SanctionedAddressesChecker>
|
||||
<PosthogIdentifier />
|
||||
<SyncChainStores />
|
||||
<EVMContractInfoProvider value={props.contractInfo}>
|
||||
<DashboardThirdwebProvider>
|
||||
<SanctionedAddressesChecker>
|
||||
<PosthogIdentifier />
|
||||
|
||||
<OnboardingModal />
|
||||
<OpCreditsGrantedModalWrapper />
|
||||
<OnboardingModal />
|
||||
<OpCreditsGrantedModalWrapper />
|
||||
|
||||
<AppShell {...props} />
|
||||
</SanctionedAddressesChecker>
|
||||
</DashboardThirdwebProvider>
|
||||
</EVMContractInfoProvider>
|
||||
</ChainsProvider>
|
||||
</AllChainsProvider>
|
||||
<AppShell {...props} />
|
||||
</SanctionedAddressesChecker>
|
||||
</DashboardThirdwebProvider>
|
||||
</EVMContractInfoProvider>
|
||||
</ErrorProvider>
|
||||
</HydrationBoundary>
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -37,7 +37,6 @@ import type {
|
||||
import { getSDKTheme } from "app/components/sdk-component-theme";
|
||||
import { LOCAL_NODE_PKEY } from "constants/misc";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { useSupportedChain } from "hooks/chains/configureChains";
|
||||
import { ExternalLinkIcon } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import Link from "next/link";
|
||||
@@ -58,6 +57,7 @@ import {
|
||||
import { privateKeyToAccount } from "thirdweb/wallets";
|
||||
import { Button, type ButtonProps, Card, Heading, Text } from "tw-components";
|
||||
import { THIRDWEB_API_HOST } from "../../constants/urls";
|
||||
import { useAllChainsData } from "../../hooks/chains/allChains";
|
||||
import { useV5DashboardChain } from "../../lib/v5-adapter";
|
||||
|
||||
const GAS_FREE_CHAINS = [
|
||||
@@ -408,10 +408,12 @@ const MismatchNotice: React.FC<{
|
||||
const activeWallet = useActiveWallet();
|
||||
const actuallyCanAttemptSwitch =
|
||||
activeWallet && activeWallet.id !== "global.safe";
|
||||
const walletConnectedNetworkInfo = useSupportedChain(connectedChainId || -1);
|
||||
const { idToChain } = useAllChainsData();
|
||||
const walletConnectedNetworkInfo = connectedChainId
|
||||
? idToChain.get(connectedChainId)
|
||||
: undefined;
|
||||
const isMobile = useBreakpointValue({ base: true, md: false });
|
||||
|
||||
const chain = useSupportedChain(desiredChainId || -1);
|
||||
const chain = desiredChainId ? idToChain.get(desiredChainId) : undefined;
|
||||
const chainV5 = useV5DashboardChain(desiredChainId);
|
||||
|
||||
const onSwitchWallet = useCallback(async () => {
|
||||
|
||||
@@ -8,13 +8,14 @@ import {
|
||||
RadioGroup,
|
||||
} from "@chakra-ui/react";
|
||||
import { ChainIcon } from "components/icons/ChainIcon";
|
||||
import type { StoredChain } from "contexts/configured-chains";
|
||||
import { useAllChainsData } from "hooks/chains/allChains";
|
||||
import { useSupportedChainsNameRecord } from "hooks/chains/configureChains";
|
||||
import { useRemoveChainModification } from "hooks/chains/useModifyChain";
|
||||
import { getDashboardChainRpc } from "lib/rpc";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { FormErrorMessage, FormLabel } from "tw-components";
|
||||
import { useAllChainsData } from "../../hooks/chains/allChains";
|
||||
import {
|
||||
type StoredChain,
|
||||
removeChainOverrides,
|
||||
} from "../../stores/chainStores";
|
||||
import { ChainIdInput } from "./Form/ChainIdInput";
|
||||
import { ConfirmationPopover } from "./Form/ConfirmationPopover";
|
||||
import { IconUpload } from "./Form/IconUpload";
|
||||
@@ -55,9 +56,7 @@ export const ConfigureNetworkForm: React.FC<NetworkConfigFormProps> = ({
|
||||
prefillSlug,
|
||||
prefillChainId,
|
||||
}) => {
|
||||
const configuredChainNameRecord = useSupportedChainsNameRecord();
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const removeChainModification = useRemoveChainModification();
|
||||
const { idToChain, nameToChain } = useAllChainsData();
|
||||
|
||||
const form = useForm<NetworkConfigFormData>({
|
||||
values: {
|
||||
@@ -86,11 +85,11 @@ export const ConfigureNetworkForm: React.FC<NetworkConfigFormProps> = ({
|
||||
|
||||
const chainId = Number(form.watch("chainId"));
|
||||
const isChainIdDirty = form.formState.dirtyFields.chainId;
|
||||
const overwritingChain = isChainIdDirty && chainIdToChainRecord[chainId];
|
||||
const overwritingChain = isChainIdDirty && idToChain.get(chainId);
|
||||
|
||||
const editedFrom =
|
||||
editingChain?.isModified || editingChain?.isOverwritten
|
||||
? chainIdToChainRecord[editingChain.chainId]
|
||||
? idToChain.get(editingChain.chainId)
|
||||
: undefined;
|
||||
|
||||
const { ref } = form.register("name", {
|
||||
@@ -104,7 +103,7 @@ export const ConfigureNetworkForm: React.FC<NetworkConfigFormProps> = ({
|
||||
return true;
|
||||
}
|
||||
|
||||
const chain = configuredChainNameRecord[value];
|
||||
const chain = nameToChain.get(value);
|
||||
|
||||
// valid if chain is not found
|
||||
if (!chain) {
|
||||
@@ -173,7 +172,7 @@ export const ConfigureNetworkForm: React.FC<NetworkConfigFormProps> = ({
|
||||
}
|
||||
|
||||
if (editingChain && editingChain.chainId !== configuredNetwork.chainId) {
|
||||
removeChainModification(editingChain.chainId);
|
||||
removeChainOverrides(editingChain.chainId);
|
||||
}
|
||||
|
||||
if (overwritingChain) {
|
||||
@@ -394,7 +393,7 @@ export const ConfigureNetworkForm: React.FC<NetworkConfigFormProps> = ({
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
onSubmit(editedFrom);
|
||||
removeChainModification(editedFrom.chainId);
|
||||
removeChainOverrides(editedFrom.chainId);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
import type { StoredChain } from "contexts/configured-chains";
|
||||
import { useAddRecentlyUsedChainId } from "../../hooks/chains/recentlyUsedChains";
|
||||
import {
|
||||
type StoredChain,
|
||||
addRecentlyUsedChainId,
|
||||
} from "../../stores/chainStores";
|
||||
import { ConfigureNetworks } from "./ConfigureNetworks";
|
||||
|
||||
export type ConfigureNetworkModalProps = {
|
||||
@@ -14,8 +16,6 @@ export type ConfigureNetworkModalProps = {
|
||||
export const ConfigureNetworkModal: React.FC<ConfigureNetworkModalProps> = (
|
||||
props,
|
||||
) => {
|
||||
const addRecentlyUsedChains = useAddRecentlyUsedChainId();
|
||||
|
||||
const onModalClose = () => {
|
||||
props.onOpenChange(false);
|
||||
};
|
||||
@@ -30,7 +30,7 @@ export const ConfigureNetworkModal: React.FC<ConfigureNetworkModalProps> = (
|
||||
<ChakraProviderSetup>
|
||||
<ConfigureNetworks
|
||||
onNetworkAdded={(chain) => {
|
||||
addRecentlyUsedChains(chain.chainId);
|
||||
addRecentlyUsedChainId(chain.chainId);
|
||||
props.onNetworkAdded?.(chain);
|
||||
onModalClose();
|
||||
}}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { StoredChain } from "contexts/configured-chains";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { useAddRecentlyUsedChainId } from "hooks/chains/recentlyUsedChains";
|
||||
import { useModifyChain } from "hooks/chains/useModifyChain";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
type StoredChain,
|
||||
addChainOverrides,
|
||||
addRecentlyUsedChainId,
|
||||
} from "../../stores/chainStores";
|
||||
import { ConfigureNetworkForm } from "./ConfigureNetworkForm";
|
||||
|
||||
function useChainConfigTrack() {
|
||||
@@ -26,12 +28,10 @@ interface ConfigureNetworksProps {
|
||||
|
||||
export const ConfigureNetworks: React.FC<ConfigureNetworksProps> = (props) => {
|
||||
const trackChainConfig = useChainConfigTrack();
|
||||
const addRecentlyUsedChainId = useAddRecentlyUsedChainId();
|
||||
const modifyChain = useModifyChain();
|
||||
const { editChain } = props;
|
||||
|
||||
const handleSubmit = (chain: StoredChain) => {
|
||||
modifyChain(chain);
|
||||
addChainOverrides(chain);
|
||||
addRecentlyUsedChainId(chain.chainId);
|
||||
|
||||
if (chain.isCustom) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { FormControl } from "@chakra-ui/react";
|
||||
import { useAllChainsData } from "hooks/chains/allChains";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import { FormErrorMessage, FormLabel } from "tw-components";
|
||||
import { useAllChainsData } from "../../../hooks/chains/allChains";
|
||||
import type { NetworkConfigFormData } from "../ConfigureNetworkForm";
|
||||
import { TooltipBox } from "./TooltipBox";
|
||||
|
||||
@@ -11,7 +11,8 @@ export const NetworkIDInput: React.FC<{
|
||||
disabled?: boolean;
|
||||
}> = ({ form, disabled }) => {
|
||||
const slug = form.watch("slug");
|
||||
const { slugToChainRecord } = useAllChainsData();
|
||||
const { slugToChain } = useAllChainsData();
|
||||
const existingChain = slugToChain.get(slug);
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
@@ -52,18 +53,18 @@ export const NetworkIDInput: React.FC<{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !(_slug in slugToChainRecord);
|
||||
return !slugToChain.has(_slug);
|
||||
},
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>
|
||||
Can not use Network ID {`"${slug}"`}.
|
||||
{slug && slug in slugToChainRecord && (
|
||||
{slug && existingChain && (
|
||||
<>
|
||||
{" "}
|
||||
It is being used by {`"`}
|
||||
{slugToChainRecord[slug].name}
|
||||
{existingChain.name}
|
||||
{`"`}
|
||||
</>
|
||||
)}
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
import { Select } from "chakra-react-select";
|
||||
import type { SizeProp } from "chakra-react-select";
|
||||
import { useSupportedChains } from "hooks/chains/configureChains";
|
||||
import { useMemo } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { useAllChainsData } from "../../../hooks/chains/allChains";
|
||||
|
||||
interface NetworkDropdownProps {
|
||||
useCleanChainName?: boolean;
|
||||
@@ -26,10 +26,10 @@ export const NetworkDropdown: React.FC<NetworkDropdownProps> = ({
|
||||
size = "md",
|
||||
}) => {
|
||||
const form = useFormContext();
|
||||
const supportedChains = useSupportedChains();
|
||||
const { allChains } = useAllChainsData();
|
||||
|
||||
const options = useMemo(() => {
|
||||
return supportedChains.map((chain) => {
|
||||
return allChains.map((chain) => {
|
||||
return {
|
||||
label: useCleanChainName
|
||||
? cleanChainName(chain.name)
|
||||
@@ -37,7 +37,7 @@ export const NetworkDropdown: React.FC<NetworkDropdownProps> = ({
|
||||
value: chain.chainId,
|
||||
};
|
||||
});
|
||||
}, [supportedChains, useCleanChainName]);
|
||||
}, [allChains, useCleanChainName]);
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
const networksEnabled = form?.watch(
|
||||
|
||||
+38
-40
@@ -14,7 +14,7 @@ export const AddressesModal: React.FC<AddressesModalProps> = ({
|
||||
buttonTitle,
|
||||
modalTitle,
|
||||
}) => {
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const { idToChain } = useAllChainsData();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
@@ -35,47 +35,45 @@ export const AddressesModal: React.FC<AddressesModalProps> = ({
|
||||
</Link>
|
||||
</Text>
|
||||
|
||||
{chainIdToChainRecord && (
|
||||
<Drawer
|
||||
size="lg"
|
||||
allowPinchZoom
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
header={{
|
||||
children: (
|
||||
<Heading as="h3" size="title.sm">
|
||||
{modalTitle}
|
||||
</Heading>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Flex flexDir="column" gap={{ base: 2, md: 1 }}>
|
||||
{Object.entries(chainAddressRecord).map(([chainId, address]) =>
|
||||
!address ? null : (
|
||||
<Flex
|
||||
key={chainId}
|
||||
flexDir={{ base: "column", md: "row" }}
|
||||
alignItems={{ base: "auto", md: "center" }}
|
||||
justifyContent="space-between"
|
||||
<Drawer
|
||||
size="lg"
|
||||
allowPinchZoom
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
header={{
|
||||
children: (
|
||||
<Heading as="h3" size="title.sm">
|
||||
{modalTitle}
|
||||
</Heading>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Flex flexDir="column" gap={{ base: 2, md: 1 }}>
|
||||
{Object.entries(chainAddressRecord).map(([chainId, address]) =>
|
||||
!address ? null : (
|
||||
<Flex
|
||||
key={chainId}
|
||||
flexDir={{ base: "column", md: "row" }}
|
||||
alignItems={{ base: "auto", md: "center" }}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Text size="body.md">
|
||||
{idToChain.get(Number.parseInt(chainId))?.name}
|
||||
</Text>
|
||||
<Link
|
||||
href={`${THIRDWEB_DOMAIN}/${chainId}/${address}`}
|
||||
isExternal
|
||||
color="blue.500"
|
||||
>
|
||||
<Text size="body.md">
|
||||
{chainIdToChainRecord[Number.parseInt(chainId)]?.name}
|
||||
<Text color="inherit" fontFamily="mono">
|
||||
{address}
|
||||
</Text>
|
||||
<Link
|
||||
href={`${THIRDWEB_DOMAIN}/${chainId}/${address}`}
|
||||
isExternal
|
||||
color="blue.500"
|
||||
>
|
||||
<Text color="inherit" fontFamily="mono">
|
||||
{address}
|
||||
</Text>
|
||||
</Link>
|
||||
</Flex>
|
||||
),
|
||||
)}
|
||||
</Flex>
|
||||
</Drawer>
|
||||
)}
|
||||
</Link>
|
||||
</Flex>
|
||||
),
|
||||
)}
|
||||
</Flex>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,9 +26,6 @@ import {
|
||||
import { ChainIcon } from "components/icons/ChainIcon";
|
||||
import { NetworkSelectDropdown } from "components/selects/NetworkSelectDropdown";
|
||||
import type { BasicContract } from "contract-ui/types/types";
|
||||
import { useAllChainsData } from "hooks/chains/allChains";
|
||||
import { useChainSlug } from "hooks/chains/chainSlug";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import {
|
||||
DownloadIcon,
|
||||
EllipsisVerticalIcon,
|
||||
@@ -45,6 +42,8 @@ import {
|
||||
usePagination,
|
||||
useTable,
|
||||
} from "react-table";
|
||||
import { useAllChainsData } from "../../../hooks/chains/allChains";
|
||||
import { useChainSlug } from "../../../hooks/chains/chainSlug";
|
||||
import { ImportModal } from "../import-contract/modal";
|
||||
import { AsyncContractNameCell, AsyncContractTypeCell } from "./cells";
|
||||
import { ShowMoreButton } from "./show-more-button";
|
||||
@@ -198,8 +197,7 @@ const ContractTable: React.FC<ContractTableProps> = ({
|
||||
chainIdsWithDeployments,
|
||||
loading,
|
||||
}) => {
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const configuredChains = useSupportedChainsRecord();
|
||||
const { idToChain } = useAllChainsData();
|
||||
|
||||
const columns: Column<(typeof combinedList)[number]>[] = useMemo(
|
||||
() => [
|
||||
@@ -231,9 +229,7 @@ const ContractTable: React.FC<ContractTableProps> = ({
|
||||
filter: "equals",
|
||||
// biome-ignore lint/suspicious/noExplicitAny: FIXME
|
||||
Cell: (cell: any) => {
|
||||
const data =
|
||||
configuredChains[cell.row.original.chainId] ||
|
||||
chainIdToChainRecord[cell.row.original.chainId];
|
||||
const data = idToChain.get(cell.row.original.chainId);
|
||||
const cleanedChainName =
|
||||
data?.name?.replace("Mainnet", "").trim() ||
|
||||
`Unknown Network (#${cell.row.original.chainId})`;
|
||||
@@ -296,7 +292,7 @@ const ContractTable: React.FC<ContractTableProps> = ({
|
||||
},
|
||||
},
|
||||
],
|
||||
[configuredChains, chainIdsWithDeployments, chainIdToChainRecord],
|
||||
[chainIdsWithDeployments, idToChain],
|
||||
);
|
||||
|
||||
const defaultColumn = useMemo(
|
||||
|
||||
+5
-5
@@ -71,13 +71,13 @@ export const ContractSubscriptionTable: React.FC<
|
||||
const removeDisclosure = useDisclosure();
|
||||
const [selectedContractSub, setSelectedContractSub] =
|
||||
useState<EngineContractSubscription>();
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const { idToChain } = useAllChainsData();
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor("chainId", {
|
||||
header: "Chain",
|
||||
cell: (cell) => {
|
||||
const chain = chainIdToChainRecord[cell.getValue()];
|
||||
const chain = idToChain.get(cell.getValue());
|
||||
return (
|
||||
<Flex align="center" gap={2}>
|
||||
<ChainIcon size={12} ipfsSrc={chain?.icon?.url} />
|
||||
@@ -90,7 +90,7 @@ export const ContractSubscriptionTable: React.FC<
|
||||
header: "Contract Address",
|
||||
cell: (cell) => {
|
||||
const { chainId } = cell.row.original;
|
||||
const chain = chainIdToChainRecord[chainId];
|
||||
const chain = idToChain.get(chainId);
|
||||
const explorer = chain?.explorers?.[0];
|
||||
if (!explorer) {
|
||||
return (
|
||||
@@ -338,8 +338,8 @@ const RemoveModal = ({
|
||||
"Successfully removed contract subscription.",
|
||||
"Failed to remove contract subscription.",
|
||||
);
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const chain = chainIdToChainRecord[contractSubscription.chainId];
|
||||
const { idToChain } = useAllChainsData();
|
||||
const chain = idToChain.get(contractSubscription.chainId);
|
||||
|
||||
const onClick = () => {
|
||||
removeContractSubscription(
|
||||
|
||||
@@ -22,7 +22,6 @@ import { useAllChainsData } from "hooks/chains/allChains";
|
||||
import { useState } from "react";
|
||||
import { FiArrowLeft, FiArrowRight, FiInfo } from "react-icons/fi";
|
||||
import { toTokens } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@@ -101,7 +100,7 @@ export const TransactionsTable: React.FC<TransactionsTableProps> = ({
|
||||
isFetched,
|
||||
instanceUrl,
|
||||
}) => {
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const { idToChain } = useAllChainsData();
|
||||
const transactionDisclosure = useDisclosure();
|
||||
const [selectedTransaction, setSelectedTransaction] =
|
||||
useState<Transaction | null>(null);
|
||||
@@ -128,7 +127,7 @@ export const TransactionsTable: React.FC<TransactionsTableProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const chain = chainIdToChainRecord[Number.parseInt(chainId)];
|
||||
const chain = idToChain.get(Number.parseInt(chainId));
|
||||
if (chain) {
|
||||
return (
|
||||
<Flex align="center" gap={2} className="py-2">
|
||||
@@ -200,7 +199,7 @@ export const TransactionsTable: React.FC<TransactionsTableProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const chain = chainIdToChainRecord[Number.parseInt(chainId)];
|
||||
const chain = idToChain.get(Number.parseInt(chainId));
|
||||
if (chain) {
|
||||
const explorer = chain.explorers?.[0];
|
||||
if (!explorer) {
|
||||
@@ -306,7 +305,7 @@ const TransactionDetailsDrawer = ({
|
||||
onClickPrevious?: () => void;
|
||||
onClickNext?: () => void;
|
||||
}) => {
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const { idToChain } = useAllChainsData();
|
||||
const errorMessageDisclosure = useDisclosure();
|
||||
const advancedTxDetailsDisclosure = useDisclosure();
|
||||
|
||||
@@ -314,10 +313,9 @@ const TransactionDetailsDrawer = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const chain: ChainMetadata | undefined =
|
||||
chainIdToChainRecord[Number.parseInt(transaction.chainId)];
|
||||
const decimals = chain.nativeCurrency.decimals || 18;
|
||||
const symbol = chain.nativeCurrency.symbol || "ETH";
|
||||
const chain = idToChain.get(Number.parseInt(transaction.chainId));
|
||||
const decimals = chain?.nativeCurrency.decimals || 18;
|
||||
const symbol = chain?.nativeCurrency.symbol || "ETH";
|
||||
const explorer = chain?.explorers?.[0];
|
||||
|
||||
const status = statusDetails[transaction.status as EngineStatus];
|
||||
|
||||
@@ -75,7 +75,7 @@ const AddModal = ({
|
||||
}) => {
|
||||
const { mutate: createRelayer } = useEngineCreateRelayer(instanceUrl);
|
||||
const { data: backendWallets } = useEngineBackendWallets(instanceUrl);
|
||||
const { slugToChainRecord, chainIdToChainRecord } = useAllChainsData();
|
||||
const { idToChain } = useAllChainsData();
|
||||
const trackEvent = useTrack();
|
||||
const { onSuccess, onError } = useTxNotifications(
|
||||
"Relayer created successfully.",
|
||||
@@ -84,13 +84,13 @@ const AddModal = ({
|
||||
|
||||
const form = useForm<AddModalInput>({
|
||||
defaultValues: {
|
||||
chainId: slugToChainRecord.sepolia.chainId,
|
||||
chainId: 11155111, // sepolia chain id
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (data: AddModalInput) => {
|
||||
const createRelayerData: CreateRelayerInput = {
|
||||
chain: chainIdToChainRecord[data.chainId]?.slug ?? "unknown",
|
||||
chain: idToChain.get(data.chainId)?.slug ?? "unknown",
|
||||
backendWalletAddress: data.backendWalletAddress,
|
||||
name: data.name,
|
||||
allowedContracts: parseAddressListRaw(data.allowedContractsRaw),
|
||||
|
||||
@@ -62,13 +62,13 @@ export const RelayersTable: React.FC<RelayersTableProps> = ({
|
||||
const editDisclosure = useDisclosure();
|
||||
const removeDisclosure = useDisclosure();
|
||||
const [selectedRelayer, setSelectedRelayer] = useState<EngineRelayer>();
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const { idToChain } = useAllChainsData();
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor("chainId", {
|
||||
header: "Chain",
|
||||
cell: (cell) => {
|
||||
const chain = chainIdToChainRecord[Number.parseInt(cell.getValue())];
|
||||
const chain = idToChain.get(Number.parseInt(cell.getValue()));
|
||||
return (
|
||||
<Flex align="center" gap={2}>
|
||||
<ChainIcon size={12} ipfsSrc={chain?.icon?.url} />
|
||||
@@ -81,7 +81,7 @@ export const RelayersTable: React.FC<RelayersTableProps> = ({
|
||||
header: "Backend Wallet",
|
||||
cell: (cell) => {
|
||||
const { chainId, backendWalletAddress } = cell.row.original;
|
||||
const chain = chainIdToChainRecord[Number.parseInt(chainId)];
|
||||
const chain = idToChain.get(Number.parseInt(chainId));
|
||||
|
||||
const explorer = chain?.explorers?.[0];
|
||||
if (!explorer) {
|
||||
@@ -208,7 +208,7 @@ const EditModal = ({
|
||||
}) => {
|
||||
const { mutate: updateRelayer } = useEngineUpdateRelayer(instanceUrl);
|
||||
const { data: backendWallets } = useEngineBackendWallets(instanceUrl);
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const { idToChain } = useAllChainsData();
|
||||
const trackEvent = useTrack();
|
||||
const { onSuccess, onError } = useTxNotifications(
|
||||
"Successfully updated relayer",
|
||||
@@ -227,7 +227,7 @@ const EditModal = ({
|
||||
const onSubmit = (data: AddModalInput) => {
|
||||
const updateRelayerData: UpdateRelayerInput = {
|
||||
id: relayer.id,
|
||||
chain: chainIdToChainRecord[data.chainId]?.slug ?? "unknown",
|
||||
chain: idToChain.get(data.chainId)?.slug ?? "unknown",
|
||||
backendWalletAddress: data.backendWalletAddress,
|
||||
name: data.name,
|
||||
allowedContracts: parseAddressListRaw(data.allowedContractsRaw),
|
||||
@@ -347,7 +347,7 @@ const RemoveModal = ({
|
||||
"Successfully removed relayer",
|
||||
"Failed to remove relayer",
|
||||
);
|
||||
const { chainIdToChainRecord } = useAllChainsData();
|
||||
const { idToChain } = useAllChainsData();
|
||||
|
||||
const onClick = () => {
|
||||
revokeRelayer(
|
||||
@@ -392,12 +392,11 @@ const RemoveModal = ({
|
||||
<ChainIcon
|
||||
size={12}
|
||||
ipfsSrc={
|
||||
chainIdToChainRecord[Number.parseInt(relayer.chainId)]?.icon
|
||||
?.url
|
||||
idToChain.get(Number.parseInt(relayer.chainId))?.icon?.url
|
||||
}
|
||||
/>
|
||||
<Text>
|
||||
{chainIdToChainRecord[Number.parseInt(relayer.chainId)]?.name}
|
||||
{idToChain.get(Number.parseInt(relayer.chainId))?.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
</FormControl>
|
||||
|
||||
@@ -3,12 +3,13 @@ import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ChainIcon } from "components/icons/ChainIcon";
|
||||
import { OPSponsoredChains } from "constants/chains";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import { useAddRecentlyUsedChainId } from "hooks/chains/recentlyUsedChains";
|
||||
import { SettingsIcon } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import type { UseNetworkSwitcherModalOptions } from "thirdweb/react";
|
||||
import type { StoredChain } from "../../contexts/configured-chains";
|
||||
import { useAllChainsData } from "../../hooks/chains/allChains";
|
||||
import {
|
||||
type StoredChain,
|
||||
addRecentlyUsedChainId,
|
||||
} from "../../stores/chainStores";
|
||||
|
||||
type ChainRenderProps = React.ComponentProps<
|
||||
NonNullable<UseNetworkSwitcherModalOptions["renderChain"]>
|
||||
@@ -28,15 +29,8 @@ export const CustomChainRenderer = ({
|
||||
disableChainConfig,
|
||||
openEditChainModal,
|
||||
}: CustomChainRendererProps) => {
|
||||
const addRecentlyUsedChain = useAddRecentlyUsedChainId();
|
||||
const supportedChainsRecord = useSupportedChainsRecord();
|
||||
|
||||
const storedChain = useMemo(() => {
|
||||
return chain.id in supportedChainsRecord
|
||||
? supportedChainsRecord[chain.id]
|
||||
: undefined;
|
||||
}, [chain, supportedChainsRecord]);
|
||||
|
||||
const { idToChain } = useAllChainsData();
|
||||
const storedChain = idToChain.get(chain.id);
|
||||
const isDeprecated = storedChain?.status === "deprecated";
|
||||
const isSponsored = OPSponsoredChains.includes(chain.id);
|
||||
|
||||
@@ -104,7 +98,7 @@ export const CustomChainRenderer = ({
|
||||
aria-label="Configure Network"
|
||||
onClick={() => {
|
||||
openEditChainModal(storedChain);
|
||||
addRecentlyUsedChain(chain.id);
|
||||
addRecentlyUsedChainId(chain.id);
|
||||
if (close) {
|
||||
close();
|
||||
} else {
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { ChainIcon } from "components/icons/ChainIcon";
|
||||
import { useSupportedChains } from "hooks/chains/configureChains";
|
||||
import { useMemo } from "react";
|
||||
import { useAllChainsData } from "../../hooks/chains/allChains";
|
||||
|
||||
interface NetworkSelectDropdownProps {
|
||||
enabledChainIds?: number[];
|
||||
@@ -26,26 +26,24 @@ export const NetworkSelectDropdown: React.FC<NetworkSelectDropdownProps> = ({
|
||||
selectedChain,
|
||||
onSelect,
|
||||
}) => {
|
||||
const supportedChains = useSupportedChains();
|
||||
const { allChains } = useAllChainsData();
|
||||
|
||||
const chains = useMemo(() => {
|
||||
// return only enabled chains if enabled chains are specified
|
||||
if (enabledChainIds && enabledChainIds.length > 0) {
|
||||
const enabledChainIdsSet = new Set(enabledChainIds);
|
||||
return supportedChains.filter((chain) =>
|
||||
enabledChainIdsSet.has(chain.chainId),
|
||||
);
|
||||
return allChains.filter((chain) => enabledChainIdsSet.has(chain.chainId));
|
||||
}
|
||||
// return supported chains that are not disabled if disabled chains are specified
|
||||
if (disabledChainIds && disabledChainIds.length > 0) {
|
||||
const disabledChainIdsSet = new Set(disabledChainIds);
|
||||
return supportedChains.filter(
|
||||
return allChains.filter(
|
||||
(chain) => !disabledChainIdsSet.has(chain.chainId),
|
||||
);
|
||||
}
|
||||
// if no enabled or disabled chains are specified, return all supported chains
|
||||
return supportedChains;
|
||||
}, [supportedChains, enabledChainIds, disabledChainIds]);
|
||||
return allChains;
|
||||
}, [allChains, enabledChainIds, disabledChainIds]);
|
||||
|
||||
const cleanChainName = (chainName: string) => {
|
||||
return chainName.replace("Mainnet", "");
|
||||
|
||||
@@ -2,26 +2,24 @@
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useThirdwebClient } from "@/constants/thirdweb.client";
|
||||
import { useStore } from "@/lib/reactive";
|
||||
import { popularChains } from "@3rdweb-sdk/react/components/popularChains";
|
||||
import { useFavoriteChains } from "@3rdweb-sdk/react/hooks/useFavoriteChains";
|
||||
import { ChainIcon } from "components/icons/ChainIcon";
|
||||
import type { StoredChain } from "contexts/configured-chains";
|
||||
import {
|
||||
useSupportedChains,
|
||||
useSupportedChainsRecord,
|
||||
} from "hooks/chains/configureChains";
|
||||
import {
|
||||
useAddRecentlyUsedChainId,
|
||||
useRecentlyUsedChains,
|
||||
} from "hooks/chains/recentlyUsedChains";
|
||||
import { useActiveChainAsDashboardChain } from "lib/v5-adapter";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { BiChevronDown } from "react-icons/bi";
|
||||
import { useActiveWallet } from "thirdweb/react";
|
||||
import { useNetworkSwitcherModal } from "thirdweb/react";
|
||||
import { useFavoriteChainIds } from "../../app/(dashboard)/(chain)/components/client/star-button";
|
||||
import { getSDKTheme } from "../../app/components/sdk-component-theme";
|
||||
import { mapV4ChainToV5Chain } from "../../contexts/map-chains";
|
||||
import { useAllChainsData } from "../../hooks/chains/allChains";
|
||||
import {
|
||||
type StoredChain,
|
||||
addRecentlyUsedChainId,
|
||||
recentlyUsedChainIdsStore,
|
||||
} from "../../stores/chainStores";
|
||||
import { LazyConfigureNetworkModal } from "../configure-networks/LazyConfigureNetworkModal";
|
||||
import { CustomChainRenderer } from "./CustomChainRenderer";
|
||||
|
||||
@@ -39,37 +37,66 @@ export const NetworkSelectorButton: React.FC<NetworkSelectorButtonProps> = ({
|
||||
onSwitchChain,
|
||||
}) => {
|
||||
const client = useThirdwebClient();
|
||||
const recentlyUsedChains = useRecentlyUsedChains();
|
||||
const addRecentlyUsedChains = useAddRecentlyUsedChainId();
|
||||
const { idToChain, allChains } = useAllChainsData();
|
||||
|
||||
// recently used chains
|
||||
const recentlyUsedChainIds = useStore(recentlyUsedChainIdsStore);
|
||||
const recentlyUsedChains = useMemo(() => {
|
||||
return recentlyUsedChainIds
|
||||
.map((id) => idToChain.get(id))
|
||||
.filter((v) => !!v);
|
||||
}, [recentlyUsedChainIds, idToChain]);
|
||||
|
||||
// configure network modal
|
||||
const [isNetworkConfigModalOpen, setIsNetworkConfigModalOpen] =
|
||||
useState(false);
|
||||
const [editChain, setEditChain] = useState<StoredChain | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const { theme } = useTheme();
|
||||
const supportedChains = useSupportedChains();
|
||||
const supportedChainsRecord = useSupportedChainsRecord();
|
||||
const favoriteChainsQuery = useFavoriteChains();
|
||||
const favoriteChainIdsQuery = useFavoriteChainIds();
|
||||
|
||||
const popularChainsWithMetadata = useMemo(() => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return popularChains.map((x) =>
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
{
|
||||
const v4Chain = idToChain.get(x.id);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return v4Chain ? mapV4ChainToV5Chain(v4Chain) : x;
|
||||
},
|
||||
);
|
||||
}, [idToChain]);
|
||||
|
||||
const favoriteChainsWithMetadata = useMemo(() => {
|
||||
return (favoriteChainIdsQuery.data || [])
|
||||
.map((id) => {
|
||||
const c = idToChain.get(Number(id));
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return c ? mapV4ChainToV5Chain(c) : undefined;
|
||||
})
|
||||
.filter((x) => !!x);
|
||||
}, [idToChain, favoriteChainIdsQuery.data]);
|
||||
|
||||
const networkSwitcherModal = useNetworkSwitcherModal();
|
||||
|
||||
const chains = useMemo(() => {
|
||||
if (disabledChainIds && disabledChainIds.length > 0) {
|
||||
const disabledChainIdsSet = new Set(disabledChainIds);
|
||||
return supportedChains.filter(
|
||||
return allChains.filter(
|
||||
(chain) => !disabledChainIdsSet.has(chain.chainId),
|
||||
);
|
||||
}
|
||||
|
||||
if (networksEnabled && networksEnabled.length > 0) {
|
||||
const networksEnabledSet = new Set(networksEnabled);
|
||||
return supportedChains.filter((chain) =>
|
||||
networksEnabledSet.has(chain.chainId),
|
||||
);
|
||||
return allChains.filter((chain) => networksEnabledSet.has(chain.chainId));
|
||||
}
|
||||
|
||||
// if no restrictions, show all supported chains
|
||||
return supportedChains;
|
||||
}, [disabledChainIds, networksEnabled, supportedChains]);
|
||||
return allChains;
|
||||
}, [disabledChainIds, networksEnabled, allChains]);
|
||||
|
||||
const filteredRecentlyUsedChains = useMemo(() => {
|
||||
if (recentlyUsedChains && recentlyUsedChains.length > 0) {
|
||||
@@ -103,10 +130,10 @@ export const NetworkSelectorButton: React.FC<NetworkSelectorButtonProps> = ({
|
||||
if (onSwitchChain) {
|
||||
onSwitchChain(chain);
|
||||
}
|
||||
addRecentlyUsedChains(chain.chainId);
|
||||
addRecentlyUsedChainId(chain.chainId);
|
||||
prevChain.current = chain;
|
||||
}
|
||||
}, [chain, onSwitchChain, addRecentlyUsedChains]);
|
||||
}, [chain, onSwitchChain]);
|
||||
|
||||
const wallet = useActiveWallet();
|
||||
|
||||
@@ -128,13 +155,11 @@ export const NetworkSelectorButton: React.FC<NetworkSelectorButtonProps> = ({
|
||||
},
|
||||
{
|
||||
label: "Favorites",
|
||||
chains: (favoriteChainsQuery.data ?? []).map(
|
||||
mapV4ChainToV5Chain,
|
||||
),
|
||||
chains: favoriteChainsWithMetadata,
|
||||
},
|
||||
{
|
||||
label: "Popular",
|
||||
chains: networksEnabled ? [] : popularChains,
|
||||
chains: networksEnabled ? [] : popularChainsWithMetadata,
|
||||
},
|
||||
{
|
||||
label: "All Networks",
|
||||
@@ -159,10 +184,11 @@ export const NetworkSelectorButton: React.FC<NetworkSelectorButtonProps> = ({
|
||||
setIsNetworkConfigModalOpen(true);
|
||||
},
|
||||
async onSwitch(chain) {
|
||||
addRecentlyUsedChains(chain.id);
|
||||
addRecentlyUsedChainId(chain.id);
|
||||
if (onSwitchChain) {
|
||||
if (supportedChainsRecord[chain.id]) {
|
||||
onSwitchChain(supportedChainsRecord[chain.id]);
|
||||
const storedChain = idToChain.get(chain.id);
|
||||
if (storedChain) {
|
||||
onSwitchChain(storedChain);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useDashboardEVMChainId } from "@3rdweb-sdk/react";
|
||||
import { Flex, Input, Select, type SelectProps } from "@chakra-ui/react";
|
||||
import { CURRENCIES, type CurrencyMetadata } from "constants/currencies";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import { useMemo, useState } from "react";
|
||||
import { NATIVE_TOKEN_ADDRESS, ZERO_ADDRESS, isAddress } from "thirdweb";
|
||||
import { Button } from "tw-components";
|
||||
import { useAllChainsData } from "../../hooks/chains/allChains";
|
||||
|
||||
interface CurrencySelectorProps extends SelectProps {
|
||||
value: string;
|
||||
@@ -26,8 +26,8 @@ export const CurrencySelector: React.FC<CurrencySelectorProps> = ({
|
||||
...props
|
||||
}) => {
|
||||
const chainId = useDashboardEVMChainId();
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
const chain = chainId ? configuredChainsRecord[chainId] : undefined;
|
||||
const { idToChain } = useAllChainsData();
|
||||
const chain = chainId ? idToChain.get(chainId) : undefined;
|
||||
|
||||
const helperCurrencies =
|
||||
defaultCurrencies.length > 0
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { useApiChains } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { createContext, useMemo } from "react";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
|
||||
type AllChainsData = {
|
||||
allChains: ChainMetadata[];
|
||||
chainIdToChainRecord: Record<number, ChainMetadata>;
|
||||
chainIdToIndexRecord: Record<number, number>;
|
||||
slugToChainRecord: Record<string, ChainMetadata>;
|
||||
};
|
||||
|
||||
// Legacy: we need to find a way to remove this
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const AllChainsContext = createContext<AllChainsData | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
* if no networks are configured by the user, return the defaultChains
|
||||
*/
|
||||
export function AllChainsProvider(props: { children: React.ReactNode }) {
|
||||
const { data } = useApiChains();
|
||||
|
||||
const allChainsData: AllChainsData = useMemo(() => {
|
||||
const slugToChainRecord: Record<string, ChainMetadata> = {};
|
||||
const chainIdToChainRecord: Record<number, ChainMetadata> = {};
|
||||
const chainIdToIndexRecord: Record<number, number> = {};
|
||||
if (data?.length) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const chain = data[i];
|
||||
slugToChainRecord[chain.slug] = chain;
|
||||
chainIdToChainRecord[chain.chainId] = chain;
|
||||
chainIdToIndexRecord[chain.chainId] = i;
|
||||
}
|
||||
}
|
||||
return {
|
||||
allChains: data || [],
|
||||
chainIdToChainRecord,
|
||||
slugToChainRecord,
|
||||
chainIdToIndexRecord,
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<AllChainsContext.Provider value={allChainsData}>
|
||||
{props.children}
|
||||
</AllChainsContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -1,336 +0,0 @@
|
||||
import { isProd } from "@/constants/env";
|
||||
import { useAllChainsData } from "hooks/chains/allChains";
|
||||
import { createContext, useCallback, useEffect, useState } from "react";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
|
||||
// extra information on top of Chain interface
|
||||
// all keys added here must be optional
|
||||
export interface StoredChain extends ChainMetadata {
|
||||
isModified?: boolean;
|
||||
isCustom?: boolean;
|
||||
isOverwritten?: boolean;
|
||||
}
|
||||
|
||||
const MODIFIED_CHAINS_KEY = "tw-modified-chains";
|
||||
const RECENTLY_USED_CHAIN_IDS_KEY = "tw-recently-used-chains";
|
||||
|
||||
/**
|
||||
* LEGACY
|
||||
*
|
||||
* holds the "supported chains" array
|
||||
* initially it is set to the defaultChains, then it is updated to the "allChains" with "modified chains" overrides
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const SupportedChainsContext = createContext<StoredChain[] | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
* LEGACY
|
||||
*
|
||||
* holds the "modified chains" array
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const ModifiedChainsContext = createContext<StoredChain[] | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
* LEGACY
|
||||
*
|
||||
* holds the "modified chains" array
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const RemoveChainModification = createContext<
|
||||
((chainId: number) => void) | undefined
|
||||
>(undefined);
|
||||
|
||||
/**
|
||||
* LEGACY
|
||||
*
|
||||
* holds the "recently used chain ids" array
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const RecentlyUsedChainIdsContext = createContext<number[] | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
* LEGACY
|
||||
*
|
||||
* holds the function that takes a chainId and adds it to the "recently used chains" and handles its storage
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const AddRecentlyUsedChainIdsContext = createContext<
|
||||
((chainId: number) => void) | undefined
|
||||
>(undefined);
|
||||
|
||||
/**
|
||||
* LEGACY
|
||||
*
|
||||
* holds the function that takes the "modified chain" object
|
||||
* and handles the logic of updating the "supported chains" and "modified chains" and "recently used chains"
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const ModifyChainContext = createContext<
|
||||
((chain: ChainMetadata, remove?: boolean) => void) | undefined
|
||||
>(undefined);
|
||||
|
||||
/**
|
||||
* LEGACY
|
||||
*
|
||||
* flag indicating if the supported chains is having the final value or not
|
||||
* app starts with the defaultChains as supported chains
|
||||
* then allChains is dynamically imported or fetched from indexedDB, user modified chains are fetched from localStorage
|
||||
* and then the final supported chains is calculated by overriding the modifiedChains on allChains
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const SupportedChainsReadyContext = createContext(false);
|
||||
|
||||
const replaceRpcsWithDevUrl = (chains: ChainMetadata[]) => {
|
||||
if (isProd) {
|
||||
return chains;
|
||||
}
|
||||
|
||||
return chains.map((chn) => {
|
||||
return {
|
||||
...chn,
|
||||
rpc: chn.rpc.map((rpc) =>
|
||||
rpc.replace("rpc.thirdweb.com", "rpc.thirdweb-dev.com"),
|
||||
),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* if no networks are configured by the user, return the defaultChains
|
||||
*/
|
||||
export function ChainsProvider(props: { children: React.ReactNode }) {
|
||||
const [supportedChains, setSupportedChains] = useState<StoredChain[]>([]);
|
||||
const [modifiedChains, setModifiedChains] = useState<StoredChain[]>([]);
|
||||
const [isSupportedChainsReady, setIsSupportedChainsReady] = useState(false);
|
||||
const [recentlyUsedChainIds, setRecentlyUsedChainIds] = useState<number[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const addRecentlyUsedChainId = useCallback((chainId: number) => {
|
||||
setRecentlyUsedChainIds((_recentlyUsedChainIds) => {
|
||||
const newRecentlyUsedChains = _recentlyUsedChainIds.filter(
|
||||
(c) => c !== chainId,
|
||||
);
|
||||
|
||||
// add to the front of the array
|
||||
newRecentlyUsedChains.unshift(chainId);
|
||||
|
||||
// only keep the first 5
|
||||
newRecentlyUsedChains.splice(5);
|
||||
|
||||
return newRecentlyUsedChains;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// save recently used chains to storage
|
||||
// FIXME: probably want to move this to backend (similar to favorites)
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
RECENTLY_USED_CHAIN_IDS_KEY,
|
||||
JSON.stringify(recentlyUsedChainIds),
|
||||
);
|
||||
} catch {
|
||||
localStorage.clear();
|
||||
localStorage.setItem(
|
||||
RECENTLY_USED_CHAIN_IDS_KEY,
|
||||
JSON.stringify(recentlyUsedChainIds),
|
||||
);
|
||||
}
|
||||
}, [recentlyUsedChainIds]);
|
||||
|
||||
const { allChains, chainIdToIndexRecord } = useAllChainsData();
|
||||
|
||||
// get recently used chains from stroage
|
||||
// FIXME: probably want to move this to backend (similar to favorites)
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
if (!isSupportedChainsReady) {
|
||||
return;
|
||||
}
|
||||
const _recentlyUsedChainsStr = localStorage.getItem(
|
||||
RECENTLY_USED_CHAIN_IDS_KEY,
|
||||
);
|
||||
if (!_recentlyUsedChainsStr) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const _recentlyUsedChainIds = JSON.parse(
|
||||
_recentlyUsedChainsStr,
|
||||
) as number[];
|
||||
|
||||
setRecentlyUsedChainIds(_recentlyUsedChainIds);
|
||||
} catch {
|
||||
localStorage.removeItem(RECENTLY_USED_CHAIN_IDS_KEY);
|
||||
}
|
||||
}, [isSupportedChainsReady]);
|
||||
|
||||
const applyOverrides = useCallback(
|
||||
(target: StoredChain[], overrides: StoredChain[]) => {
|
||||
const result = [...target];
|
||||
|
||||
// biome-ignore lint/complexity/noForEach: FIXME
|
||||
overrides.forEach((modifiedChain) => {
|
||||
// if this chain is already in the supported chains, update it
|
||||
if (modifiedChain.chainId in chainIdToIndexRecord) {
|
||||
const i = chainIdToIndexRecord[modifiedChain.chainId];
|
||||
if (!result[i]) {
|
||||
throw new Error("invalid attempt to overide");
|
||||
}
|
||||
|
||||
result[i] = modifiedChain;
|
||||
} else {
|
||||
result.push(modifiedChain);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
[chainIdToIndexRecord],
|
||||
);
|
||||
|
||||
const applyModificationsToSupportedChains = useCallback(
|
||||
(newModifiedChains: ChainMetadata[]) => {
|
||||
setSupportedChains((_supportedChains) => {
|
||||
return applyOverrides(_supportedChains, newModifiedChains);
|
||||
});
|
||||
},
|
||||
[applyOverrides],
|
||||
);
|
||||
|
||||
// create supported chains and modified chains on mount
|
||||
// FIXME: this should be computed not via setState
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
if (allChains.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSupportedChainsReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allChainsReplaced = replaceRpcsWithDevUrl(allChains);
|
||||
|
||||
const _modifiedChains = chainStorage.get(MODIFIED_CHAINS_KEY);
|
||||
|
||||
if (_modifiedChains.length === 0) {
|
||||
setSupportedChains(allChainsReplaced);
|
||||
setIsSupportedChainsReady(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const newSupportedChains = applyOverrides(
|
||||
allChainsReplaced,
|
||||
_modifiedChains,
|
||||
);
|
||||
setSupportedChains(newSupportedChains);
|
||||
setModifiedChains(_modifiedChains);
|
||||
setIsSupportedChainsReady(true);
|
||||
}, [allChains, isSupportedChainsReady, applyOverrides]);
|
||||
|
||||
const modifyChain = useCallback(
|
||||
(chain: StoredChain) => {
|
||||
setModifiedChains((_modifiedChains) => {
|
||||
const i = _modifiedChains.findIndex((c) => c.chainId === chain.chainId);
|
||||
let newModifiedChains: StoredChain[];
|
||||
if (i !== -1) {
|
||||
// if this chain is already in the modified chains, update it
|
||||
newModifiedChains = [..._modifiedChains];
|
||||
newModifiedChains[i] = chain as StoredChain;
|
||||
} else {
|
||||
// else add it to the modified chains
|
||||
newModifiedChains = [..._modifiedChains, chain as StoredChain];
|
||||
}
|
||||
|
||||
applyModificationsToSupportedChains(newModifiedChains);
|
||||
chainStorage.set(MODIFIED_CHAINS_KEY, newModifiedChains);
|
||||
|
||||
return newModifiedChains;
|
||||
});
|
||||
},
|
||||
[applyModificationsToSupportedChains],
|
||||
);
|
||||
|
||||
const removeChainModification = useCallback(
|
||||
(chainId: number) => {
|
||||
setModifiedChains((_modifiedChains) => {
|
||||
const newModifiedChains = _modifiedChains.filter(
|
||||
(c) => c.chainId !== chainId,
|
||||
);
|
||||
|
||||
applyModificationsToSupportedChains(newModifiedChains);
|
||||
chainStorage.set(MODIFIED_CHAINS_KEY, newModifiedChains);
|
||||
|
||||
// also remove from recently used chains
|
||||
setRecentlyUsedChainIds((prevIds) => {
|
||||
return prevIds.filter((id) => id !== chainId);
|
||||
});
|
||||
return newModifiedChains;
|
||||
});
|
||||
},
|
||||
[applyModificationsToSupportedChains],
|
||||
);
|
||||
|
||||
return (
|
||||
<SupportedChainsContext.Provider
|
||||
// always run this again
|
||||
value={replaceRpcsWithDevUrl(supportedChains)}
|
||||
>
|
||||
<SupportedChainsReadyContext.Provider value={isSupportedChainsReady}>
|
||||
<ModifiedChainsContext.Provider value={modifiedChains}>
|
||||
<ModifyChainContext.Provider value={modifyChain}>
|
||||
<RemoveChainModification.Provider value={removeChainModification}>
|
||||
<RecentlyUsedChainIdsContext.Provider
|
||||
value={recentlyUsedChainIds}
|
||||
>
|
||||
<AddRecentlyUsedChainIdsContext.Provider
|
||||
value={addRecentlyUsedChainId}
|
||||
>
|
||||
{props.children}
|
||||
</AddRecentlyUsedChainIdsContext.Provider>
|
||||
</RecentlyUsedChainIdsContext.Provider>
|
||||
</RemoveChainModification.Provider>
|
||||
</ModifyChainContext.Provider>
|
||||
</ModifiedChainsContext.Provider>
|
||||
</SupportedChainsReadyContext.Provider>
|
||||
</SupportedChainsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// storage --------------------------------------------
|
||||
|
||||
const chainStorage = {
|
||||
get(key: string): ChainMetadata[] {
|
||||
try {
|
||||
const networkListStr = localStorage.getItem(key);
|
||||
return networkListStr ? JSON.parse(networkListStr) : [];
|
||||
} catch {
|
||||
// if parsing error, clear dirty storage
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
|
||||
set(key: string, networkList: ChainMetadata[]) {
|
||||
const value = JSON.stringify(networkList);
|
||||
try {
|
||||
localStorage.setItem(key, value);
|
||||
} catch {
|
||||
// if storage limit exceed
|
||||
// clear entire local storage and then try again
|
||||
localStorage.clear();
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Chain } from "thirdweb";
|
||||
import type { StoredChain } from "./configured-chains";
|
||||
import type { StoredChain } from "../stores/chainStores";
|
||||
|
||||
export function mapV4ChainToV5Chain(v4Chain: StoredChain) {
|
||||
const chain: Chain = {
|
||||
|
||||
+3
-3
@@ -2,9 +2,9 @@ import { WalletAddress } from "@/components/blocks/wallet-address";
|
||||
import { useDashboardEVMChainId } from "@3rdweb-sdk/react";
|
||||
import { Flex, SimpleGrid, useBreakpointValue } from "@chakra-ui/react";
|
||||
import { formatDistance } from "date-fns/formatDistance";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { Badge, Card, Heading, Text } from "tw-components";
|
||||
import { useAllChainsData } from "../../../../hooks/chains/allChains";
|
||||
|
||||
export type AccountSignerType = {
|
||||
signer: string;
|
||||
@@ -20,8 +20,8 @@ interface AccountSignerProps {
|
||||
export const AccountSigner: React.FC<AccountSignerProps> = ({ item }) => {
|
||||
const address = useActiveAccount()?.address;
|
||||
const chainId = useDashboardEVMChainId();
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
const chain = chainId ? configuredChainsRecord[chainId] : undefined;
|
||||
const { idToChain } = useAllChainsData();
|
||||
const chain = chainId ? idToChain.get(chainId) : undefined;
|
||||
const isMobile = useBreakpointValue({ base: true, md: false });
|
||||
const {
|
||||
isAdmin,
|
||||
|
||||
@@ -7,8 +7,8 @@ import { type ChangeEvent, useState } from "react";
|
||||
import { prepareTransaction, toWei } from "thirdweb";
|
||||
import { useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { Card } from "tw-components";
|
||||
import type { StoredChain } from "../../../../contexts/configured-chains";
|
||||
import { useV5DashboardChain } from "../../../../lib/v5-adapter";
|
||||
import type { StoredChain } from "../../../../stores/chainStores";
|
||||
|
||||
interface DepositNativeProps {
|
||||
address: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useDashboardEVMChainId } from "@3rdweb-sdk/react";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { Heading } from "tw-components";
|
||||
import { useAllChainsData } from "../../../hooks/chains/allChains";
|
||||
import { AccountBalance } from "./components/account-balance";
|
||||
import { DepositNative } from "./components/deposit-native";
|
||||
import { NftsOwned } from "./components/nfts-owned";
|
||||
@@ -11,9 +11,9 @@ interface AccountPageProps {
|
||||
}
|
||||
|
||||
export const AccountPage: React.FC<AccountPageProps> = ({ contract }) => {
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
const { idToChain } = useAllChainsData();
|
||||
const chainId = useDashboardEVMChainId();
|
||||
const chain = chainId ? configuredChainsRecord[chainId] : undefined;
|
||||
const chain = chainId ? idToChain.get(chainId) : undefined;
|
||||
const symbol = chain?.nativeCurrency.symbol || "Native Token";
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useDashboardEVMChainId } from "@3rdweb-sdk/react";
|
||||
import { CURRENCIES } from "constants/currencies";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import { Text } from "tw-components";
|
||||
import { shortenIfAddress } from "utils/usedapp-external";
|
||||
import { isAddressZero } from "utils/zeroAddress";
|
||||
import { useAllChainsData } from "../../../../hooks/chains/allChains";
|
||||
|
||||
interface PricePreviewProps {
|
||||
price: string | number | undefined;
|
||||
@@ -15,8 +15,8 @@ export const PricePreview: React.FC<PricePreviewProps> = ({
|
||||
currencyAddress,
|
||||
}) => {
|
||||
const chainId = useDashboardEVMChainId();
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
const chain = chainId ? configuredChainsRecord[chainId] : undefined;
|
||||
const { idToChain } = useAllChainsData();
|
||||
const chain = chainId ? idToChain.get(chainId) : undefined;
|
||||
|
||||
const helperCurrencies = chainId ? CURRENCIES[chainId] || [] : [];
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
} from "components/contract-components/hooks";
|
||||
import { CodeSegment } from "components/contract-tabs/code/CodeSegment";
|
||||
import type { CodeEnvironment } from "components/contract-tabs/code/types";
|
||||
import { useSupportedChain } from "hooks/chains/configureChains";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useMemo, useState } from "react";
|
||||
import * as ERC20Ext from "thirdweb/extensions/erc20";
|
||||
@@ -38,6 +37,7 @@ import * as ERC4337Ext from "thirdweb/extensions/erc4337";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { toFunctionSelector } from "thirdweb/utils";
|
||||
import { Button, Card, Heading, Link, Text, TrackedLink } from "tw-components";
|
||||
import { useAllChainsData } from "../../../../hooks/chains/allChains";
|
||||
|
||||
interface CodeOverviewProps {
|
||||
abi?: Abi;
|
||||
@@ -592,7 +592,8 @@ export const CodeOverview: React.FC<CodeOverviewProps> = ({
|
||||
}, [isERC20, isERC721, isERC1155]);
|
||||
|
||||
const chainId = useDashboardEVMChainId() || chainIdProp || 1;
|
||||
const chainInfo = useSupportedChain(chainId || -1);
|
||||
const { idToChain } = useAllChainsData();
|
||||
const chainInfo = chainId ? idToChain.get(chainId) : undefined;
|
||||
|
||||
const functions = useContractFunctions(abi || []);
|
||||
const events = useContractEvents(abi as Abi);
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import { IoMdCheckmark } from "@react-icons/all-files/io/IoMdCheckmark";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import { useClipboard } from "hooks/useClipboard";
|
||||
import { useTxNotifications } from "hooks/useTxNotifications";
|
||||
import { useMemo } from "react";
|
||||
@@ -28,7 +27,8 @@ import {
|
||||
Heading,
|
||||
Text,
|
||||
} from "tw-components";
|
||||
import type { StoredChain } from "../../../../contexts/configured-chains";
|
||||
import { useAllChainsData } from "../../../../hooks/chains/allChains";
|
||||
import type { StoredChain } from "../../../../stores/chainStores";
|
||||
|
||||
interface EmbedSetupProps {
|
||||
contract: ThirdwebContract;
|
||||
@@ -236,11 +236,9 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
|
||||
);
|
||||
|
||||
const chainId = useDashboardEVMChainId();
|
||||
const configuredChains = useSupportedChainsRecord();
|
||||
const { idToChain } = useAllChainsData();
|
||||
|
||||
const chain = (configuredChains[chainId as number] as
|
||||
| StoredChain
|
||||
| undefined) || {
|
||||
const chain: StoredChain = (chainId ? idToChain.get(chainId) : undefined) || {
|
||||
name: "Unknown Chain",
|
||||
chainId: chainId || -1,
|
||||
rpc: [],
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
useBreakpointValue,
|
||||
} from "@chakra-ui/react";
|
||||
import { useNFTDrawerTabs } from "core-ui/nft-drawer/useNftDrawerTabs";
|
||||
import { useChainSlug } from "hooks/chains/chainSlug";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { IoChevronBack } from "react-icons/io5";
|
||||
@@ -24,6 +23,7 @@ import { useReadContract } from "thirdweb/react";
|
||||
import { Badge, Button, Card, CodeBlock, Heading, Text } from "tw-components";
|
||||
import { AddressCopyButton } from "tw-components/AddressCopyButton";
|
||||
import { NFTMediaWithEmptyState } from "tw-components/nft-media";
|
||||
import { useChainSlug } from "../../../../hooks/chains/chainSlug";
|
||||
import { NftProperty } from "./nft-property";
|
||||
|
||||
function isValidUrl(possibleUrl?: string | null) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
StatLabel,
|
||||
StatNumber,
|
||||
} from "@chakra-ui/react";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
type ThirdwebContract,
|
||||
@@ -25,6 +24,7 @@ import {
|
||||
} from "thirdweb/react";
|
||||
import { Card, Heading, Text } from "tw-components";
|
||||
import { shortenIfAddress } from "utils/usedapp-external";
|
||||
import { useAllChainsData } from "../../../hooks/chains/allChains";
|
||||
import { DistributeButton } from "./components/distribute-button";
|
||||
|
||||
export type Balance = {
|
||||
@@ -41,9 +41,9 @@ interface SplitPageProps {
|
||||
|
||||
export const ContractSplitPage: React.FC<SplitPageProps> = ({ contract }) => {
|
||||
const address = useActiveAccount()?.address;
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
const { idToChain } = useAllChainsData();
|
||||
const chainId = contract.chain.id;
|
||||
const v4Chain = configuredChainsRecord[chainId];
|
||||
const v4Chain = idToChain.get(chainId);
|
||||
const contractAddress = contract.address;
|
||||
const nativeBalanceQuery = useWalletBalance({
|
||||
address: contractAddress,
|
||||
|
||||
@@ -1,9 +1,134 @@
|
||||
import { AllChainsContext } from "contexts/all-chains";
|
||||
import { useContext } from "react";
|
||||
import invariant from "tiny-invariant";
|
||||
import { isProd } from "@/constants/env";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { createStore, useStore } from "../../@/lib/reactive";
|
||||
import {
|
||||
type StoredChain,
|
||||
chainOverridesStore,
|
||||
} from "../../stores/chainStores";
|
||||
|
||||
type AllChainsStore = {
|
||||
allChains: StoredChain[];
|
||||
idToChain: Map<number, StoredChain | undefined>;
|
||||
nameToChain: Map<string, StoredChain | undefined>;
|
||||
slugToChain: Map<string, StoredChain | undefined>;
|
||||
};
|
||||
|
||||
function createAllChainsStore() {
|
||||
const store = createStore<AllChainsStore>({
|
||||
allChains: [],
|
||||
idToChain: new Map(),
|
||||
nameToChain: new Map(),
|
||||
slugToChain: new Map(),
|
||||
});
|
||||
|
||||
const dependencies = [chainOverridesStore, originalAllChainsStore];
|
||||
for (const dep of dependencies) {
|
||||
dep.subscribe(() => {
|
||||
updateAllChainsStore(
|
||||
originalAllChainsStore.getValue(),
|
||||
chainOverridesStore.getValue(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function updateAllChainsStore(
|
||||
originalAllChains: ChainMetadata[],
|
||||
chainOverrides: StoredChain[],
|
||||
) {
|
||||
// if original chains are not loaded yet - ignore
|
||||
if (originalAllChains.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// but don't ignore if chainOverrides is empty!
|
||||
|
||||
const allChains: StoredChain[] = [];
|
||||
const idToChain: Map<number, StoredChain | undefined> = new Map();
|
||||
const nameToChain: Map<string, StoredChain | undefined> = new Map();
|
||||
const slugToChain: Map<string, StoredChain | undefined> = new Map();
|
||||
|
||||
for (let i = 0; i < originalAllChains.length; i++) {
|
||||
let _chain = originalAllChains[i];
|
||||
|
||||
// for dev env, replace the rpc urls to dev domain
|
||||
if (!isProd) {
|
||||
_chain = {
|
||||
..._chain,
|
||||
rpc: _chain.rpc.map((rpc) =>
|
||||
rpc.replace("rpc.thirdweb.com", "rpc.thirdweb-dev.com"),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const chain: StoredChain =
|
||||
chainOverrides.find((x) => x.chainId === _chain.chainId) || _chain;
|
||||
|
||||
allChains.push(chain);
|
||||
idToChain.set(chain.chainId, chain);
|
||||
nameToChain.set(chain.name, chain);
|
||||
slugToChain.set(chain.slug, chain);
|
||||
}
|
||||
|
||||
// add custom chains
|
||||
for (const c of chainOverrides) {
|
||||
if (c.isCustom) {
|
||||
allChains.push(c);
|
||||
idToChain.set(c.chainId, c);
|
||||
nameToChain.set(c.name, c);
|
||||
slugToChain.set(c.slug, c);
|
||||
}
|
||||
}
|
||||
|
||||
store.setValue({
|
||||
allChains,
|
||||
idToChain: idToChain,
|
||||
nameToChain: nameToChain,
|
||||
slugToChain: slugToChain,
|
||||
});
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
const originalAllChainsStore = /* @__PURE__ */ createStore<ChainMetadata[]>([]);
|
||||
const allChainsStore = /* @__PURE__ */ createAllChainsStore();
|
||||
|
||||
async function fetchChainsFromApi() {
|
||||
// always fetch from prod for chains for now
|
||||
// TODO: re-visit this
|
||||
const res = await fetch("https://api.thirdweb.com/v1/chains");
|
||||
const json = await res.json();
|
||||
|
||||
if (json.error) {
|
||||
throw new Error(json.error.message);
|
||||
}
|
||||
|
||||
return json.data as ChainMetadata[];
|
||||
}
|
||||
|
||||
export function useAllChainsData() {
|
||||
const data = useContext(AllChainsContext);
|
||||
invariant(data, "useAllChains must be used within AllChainsContext");
|
||||
return data;
|
||||
// trigger fetching all chains if this hook is used instead of putting this on root
|
||||
// so we can avoid fetching all chains if it's not required
|
||||
const allChainsQuery = useQuery({
|
||||
queryKey: ["all-chains"],
|
||||
queryFn: () => fetchChainsFromApi(),
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
// already set
|
||||
if (originalAllChainsStore.getValue().length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allChainsQuery.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
originalAllChainsStore.setValue(allChainsQuery.data);
|
||||
}, [allChainsQuery.data]);
|
||||
|
||||
return useStore(allChainsStore);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import { useSupportedChainsRecord } from "./configureChains";
|
||||
import { useAllChainsData } from "./allChains";
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns the slug for the given evm chainId
|
||||
*/
|
||||
export function useChainSlug(chainId: string | number) {
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
export function useChainSlug(chainId: string | number | undefined) {
|
||||
const { idToChain } = useAllChainsData();
|
||||
|
||||
// EVM
|
||||
if (chainId in configuredChainsRecord) {
|
||||
return configuredChainsRecord[chainId as number].slug;
|
||||
if (!chainId) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const _chain = idToChain.get(
|
||||
typeof chainId === "string" ? Number.parseInt(chainId) : chainId,
|
||||
);
|
||||
|
||||
if (_chain) {
|
||||
return _chain.slug;
|
||||
}
|
||||
|
||||
// if can not find a network slug - use chainId as slug
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import {
|
||||
type StoredChain,
|
||||
SupportedChainsContext,
|
||||
} from "contexts/configured-chains";
|
||||
import { useContext, useMemo } from "react";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
* @returns a list of all the chains that are configured
|
||||
*/
|
||||
export function useSupportedChains() {
|
||||
const chains = useContext(SupportedChainsContext);
|
||||
|
||||
invariant(
|
||||
chains,
|
||||
"useSupportedChains must be used within a SupportedChainsContext",
|
||||
);
|
||||
return chains;
|
||||
}
|
||||
|
||||
// maps chainId to Chain
|
||||
type ConfiguredChainRecord = Record<number, StoredChain>;
|
||||
|
||||
/**
|
||||
* @returns a list of record that maps configured chainId to `Chain` object
|
||||
*/
|
||||
export function useSupportedChainsRecord() {
|
||||
const chains = useSupportedChains();
|
||||
return useMemo(() => {
|
||||
const record: ConfiguredChainRecord = {};
|
||||
// biome-ignore lint/complexity/noForEach: FIXME
|
||||
chains.forEach((network) => {
|
||||
record[network.chainId] = network;
|
||||
});
|
||||
|
||||
return record;
|
||||
}, [chains]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns a list of record that maps configured chainId to `Chain` object
|
||||
*/
|
||||
export function useSupportedChainsNameRecord() {
|
||||
const chains = useSupportedChains();
|
||||
return useMemo(() => {
|
||||
const record: Record<string, StoredChain | undefined> = {};
|
||||
// biome-ignore lint/complexity/noForEach: FIXME
|
||||
chains.forEach((network) => {
|
||||
record[network.name] = network;
|
||||
});
|
||||
|
||||
return record;
|
||||
}, [chains]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns a list of record that maps configured chainSlug to `Chain` object
|
||||
*/
|
||||
export function useSupportedChainsSlugRecord() {
|
||||
const chains = useSupportedChains();
|
||||
return useMemo(() => {
|
||||
const record: Record<string, ChainMetadata> = {};
|
||||
// biome-ignore lint/complexity/noForEach: FIXME
|
||||
chains.forEach((network) => {
|
||||
record[network.slug] = network;
|
||||
});
|
||||
|
||||
return record;
|
||||
}, [chains]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `Chain` object for the given chainId if it is configured, otherwise `undefined`
|
||||
*/
|
||||
export function useSupportedChain(chainId: number) {
|
||||
const record = useSupportedChainsRecord();
|
||||
if (chainId in record) {
|
||||
return record[chainId];
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import {
|
||||
AddRecentlyUsedChainIdsContext,
|
||||
RecentlyUsedChainIdsContext,
|
||||
SupportedChainsReadyContext,
|
||||
} from "contexts/configured-chains";
|
||||
import { useContext, useMemo } from "react";
|
||||
import invariant from "tiny-invariant";
|
||||
import { useSupportedChainsRecord } from "./configureChains";
|
||||
|
||||
export function useRecentlyUsedChains() {
|
||||
const recentlyUsedChainIds = useContext(RecentlyUsedChainIdsContext);
|
||||
const supportedChainsRecord = useSupportedChainsRecord();
|
||||
|
||||
const isSupportedChainsReady = useContext(SupportedChainsReadyContext);
|
||||
|
||||
const recentlyUsedChains = useMemo(() => {
|
||||
if (!recentlyUsedChainIds || !isSupportedChainsReady) {
|
||||
return [];
|
||||
}
|
||||
return recentlyUsedChainIds.map(
|
||||
(chainId) => supportedChainsRecord[chainId],
|
||||
);
|
||||
}, [supportedChainsRecord, recentlyUsedChainIds, isSupportedChainsReady]);
|
||||
|
||||
invariant(
|
||||
recentlyUsedChainIds,
|
||||
"useRecentlyUsedChains must be used within RecentlyUsedChainIdsContext",
|
||||
);
|
||||
|
||||
return recentlyUsedChains;
|
||||
}
|
||||
|
||||
export function useAddRecentlyUsedChainId() {
|
||||
const context = useContext(AddRecentlyUsedChainIdsContext);
|
||||
invariant(
|
||||
context,
|
||||
"useAddRecentlyUsedChainId must be used within AddRecentlyUsedChainIdsContext",
|
||||
);
|
||||
return context;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import {
|
||||
ModifyChainContext,
|
||||
RemoveChainModification,
|
||||
} from "contexts/configured-chains";
|
||||
import { useContext } from "react";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export function useModifyChain() {
|
||||
const context = useContext(ModifyChainContext);
|
||||
invariant(context, "useModifyChain must be used within ModifyChainContext");
|
||||
return context;
|
||||
}
|
||||
|
||||
export function useRemoveChainModification() {
|
||||
const context = useContext(RemoveChainModification);
|
||||
invariant(
|
||||
context,
|
||||
"useRemoveChainModification must be used within ModifyChainContext",
|
||||
);
|
||||
return context;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { defineDashboardChain } from "lib/defineDashboardChain";
|
||||
import { useMemo } from "react";
|
||||
import type { Chain, ChainMetadata } from "thirdweb/chains";
|
||||
import { useActiveWalletChain } from "thirdweb/react";
|
||||
import { useSupportedChainsRecord } from "../hooks/chains/configureChains";
|
||||
import { useAllChainsData } from "../hooks/chains/allChains";
|
||||
|
||||
export function useV5DashboardChain(chainId: undefined): undefined;
|
||||
export function useV5DashboardChain(chainId: number): Chain;
|
||||
@@ -12,23 +12,17 @@ export function useV5DashboardChain(
|
||||
export function useV5DashboardChain(
|
||||
chainId: number | undefined,
|
||||
): Chain | undefined {
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
const { idToChain } = useAllChainsData();
|
||||
|
||||
// memo is very very important!
|
||||
return useMemo(() => {
|
||||
let configuedChain = undefined;
|
||||
|
||||
if (chainId === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (chainId in configuredChainsRecord) {
|
||||
configuedChain = configuredChainsRecord[chainId as number];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return defineDashboardChain(chainId, configuedChain);
|
||||
}, [chainId, configuredChainsRecord]);
|
||||
return defineDashboardChain(chainId, idToChain.get(chainId));
|
||||
}, [chainId, idToChain]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,14 +30,11 @@ export function useV5DashboardChain(
|
||||
*/
|
||||
export function useActiveChainAsDashboardChain(): ChainMetadata | undefined {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const activeChain = useActiveWalletChain()?.id;
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
const activeChainId = useActiveWalletChain()?.id;
|
||||
const { idToChain } = useAllChainsData();
|
||||
|
||||
// memo is very very important!
|
||||
return useMemo(() => {
|
||||
if (activeChain && activeChain in configuredChainsRecord) {
|
||||
return configuredChainsRecord[activeChain as number];
|
||||
}
|
||||
return undefined;
|
||||
}, [activeChain, configuredChainsRecord]);
|
||||
return activeChainId ? idToChain.get(activeChainId) : undefined;
|
||||
}, [activeChainId, idToChain]);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Spinner } from "@/components/ui/Spinner/Spinner";
|
||||
import { Alert } from "@/components/ui/alert";
|
||||
import { useThirdwebClient } from "@/constants/thirdweb.client";
|
||||
import { getThirdwebClient } from "@/constants/thirdweb.server";
|
||||
import { useStore } from "@/lib/reactive";
|
||||
import {
|
||||
type EVMContractInfo,
|
||||
useEVMContractInfo,
|
||||
@@ -20,15 +21,10 @@ import { ensQuery } from "components/contract-components/hooks";
|
||||
import { ContractMetadata } from "components/custom-contract/contract-header/contract-metadata";
|
||||
import { DeprecatedAlert } from "components/shared/DeprecatedAlert";
|
||||
import { THIRDWEB_DOMAIN } from "constants/urls";
|
||||
import { SupportedChainsReadyContext } from "contexts/configured-chains";
|
||||
import { mapV4ChainToV5Chain } from "contexts/map-chains";
|
||||
import { PrimaryDashboardButton } from "contract-ui/components/primary-dashboard-button";
|
||||
import { useContractRouteConfig } from "contract-ui/hooks/useRouteConfig";
|
||||
import { ContractSidebar } from "core-ui/sidebar/detail-page";
|
||||
import {
|
||||
useSupportedChainsRecord,
|
||||
useSupportedChainsSlugRecord,
|
||||
} from "hooks/chains/configureChains";
|
||||
import { getDashboardChainRpc } from "lib/rpc";
|
||||
import { resolveFunctionSelectors } from "lib/selectors";
|
||||
import { useV5DashboardChain } from "lib/v5-adapter";
|
||||
@@ -37,7 +33,7 @@ import { NextSeo } from "next-seo";
|
||||
import { useRouter } from "next/router";
|
||||
import { ContractOG } from "og-lib/url-utils";
|
||||
import { PageId } from "page-id";
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
type ThirdwebContract,
|
||||
getAddress,
|
||||
@@ -52,6 +48,8 @@ import { stringify } from "thirdweb/utils";
|
||||
import { fetchChain } from "utils/fetchChain";
|
||||
import type { ThirdwebNextPage } from "utils/types";
|
||||
import { shortenIfAddress } from "utils/usedapp-external";
|
||||
import { useAllChainsData } from "../../hooks/chains/allChains";
|
||||
import { isChainOverridesLoadedStore } from "../../stores/chainStores";
|
||||
|
||||
type EVMContractProps = {
|
||||
contractInfo?: EVMContractInfo;
|
||||
@@ -68,7 +66,7 @@ type EVMContractProps = {
|
||||
const ContractPage: ThirdwebNextPage = () => {
|
||||
// show optimistic UI first - assume chain is configured until proven otherwise
|
||||
const [chainNotFound, setChainNotFound] = useState(false);
|
||||
const isSupportedChainsReady = useContext(SupportedChainsReadyContext);
|
||||
const isChainOverridesLoaded = useStore(isChainOverridesLoadedStore);
|
||||
|
||||
const contractInfo = useEVMContractInfo();
|
||||
|
||||
@@ -77,13 +75,12 @@ const ContractPage: ThirdwebNextPage = () => {
|
||||
const contractAddress = contractInfo?.contractAddress || "";
|
||||
|
||||
const setContractInfo = useSetEVMContractInfo();
|
||||
const supportedChainsSlugRecord = useSupportedChainsSlugRecord();
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
const { idToChain, slugToChain } = useAllChainsData();
|
||||
|
||||
// this will go away as part of the RSC rewrite!
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
if (!isSupportedChainsReady || !chainSlug) {
|
||||
if (!isChainOverridesLoaded || !chainSlug) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -95,7 +92,7 @@ const ContractPage: ThirdwebNextPage = () => {
|
||||
// currently user can only update RPC - so check if it is updated or not
|
||||
// if updated, update the contractInfo.chain
|
||||
|
||||
const configuredChain = supportedChainsSlugRecord[chainSlug];
|
||||
const configuredChain = slugToChain.get(chainSlug);
|
||||
if (
|
||||
configuredChain &&
|
||||
getDashboardChainRpc(configuredChain.chainId, configuredChain) !==
|
||||
@@ -116,32 +113,32 @@ const ContractPage: ThirdwebNextPage = () => {
|
||||
|
||||
// if server could not resolve the chain using allChains
|
||||
else {
|
||||
const chainFoundBySlug = slugToChain.get(chainSlug);
|
||||
const chainFoundById = idToChain.get(Number(chainSlug));
|
||||
|
||||
// if it is configured on client storage, use that
|
||||
if (chainSlug in supportedChainsSlugRecord) {
|
||||
if (chainFoundBySlug) {
|
||||
setContractInfo({
|
||||
chainSlug,
|
||||
contractAddress,
|
||||
chain: supportedChainsSlugRecord[chainSlug],
|
||||
chain: chainFoundBySlug,
|
||||
});
|
||||
} else if (chainSlug in configuredChainsRecord) {
|
||||
} else if (chainFoundById) {
|
||||
// this is for thirdweb internal tools
|
||||
// it allows us to use chainId as slug for a custom network as well
|
||||
|
||||
const chainId = Number(chainSlug);
|
||||
const _chain = configuredChainsRecord[chainId];
|
||||
|
||||
// replace the chainId with slug in URL without reloading the page
|
||||
// If we don't do this, tanstack router creates issues
|
||||
window.history.replaceState(
|
||||
null,
|
||||
document.title,
|
||||
`/${_chain.slug}/${contractAddress}`,
|
||||
`/${chainSlug}/${contractAddress}`,
|
||||
);
|
||||
|
||||
setContractInfo({
|
||||
chainSlug: _chain.slug,
|
||||
chainSlug,
|
||||
contractAddress,
|
||||
chain: _chain,
|
||||
chain: chainFoundById,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -154,11 +151,11 @@ const ContractPage: ThirdwebNextPage = () => {
|
||||
}, [
|
||||
chain,
|
||||
chainSlug,
|
||||
supportedChainsSlugRecord,
|
||||
configuredChainsRecord,
|
||||
idToChain,
|
||||
slugToChain,
|
||||
contractAddress,
|
||||
setContractInfo,
|
||||
isSupportedChainsReady,
|
||||
isChainOverridesLoaded,
|
||||
]);
|
||||
|
||||
const isSlugNumber = !Number.isNaN(Number(chainSlug));
|
||||
@@ -216,7 +213,7 @@ const ContractPage: ThirdwebNextPage = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (!isSupportedChainsReady) {
|
||||
if (!isChainOverridesLoaded) {
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<Spinner className="size-10" />
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { type Store, useStore } from "@/lib/reactive";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const loadedStoreKeys = new Set<string>();
|
||||
|
||||
export function SyncStoreToStorage<T>(props: {
|
||||
store: Store<T>;
|
||||
storageKey: string;
|
||||
onLoaded?: () => void;
|
||||
}) {
|
||||
const storeValue = useStore(props.store);
|
||||
|
||||
// load from storage
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
if (loadedStoreKeys.has(props.storageKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const storedValueStr = localStorage.getItem(props.storageKey);
|
||||
if (storedValueStr) {
|
||||
const parsedValue = JSON.parse(storedValueStr);
|
||||
props.store.setValue(parsedValue);
|
||||
}
|
||||
} catch {
|
||||
// ignore errors
|
||||
}
|
||||
|
||||
loadedStoreKeys.add(props.storageKey);
|
||||
props.onLoaded?.();
|
||||
}, [props.store, props.storageKey, props.onLoaded]);
|
||||
|
||||
// save changes to storage
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(props.storageKey, JSON.stringify(storeValue));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, [storeValue, props.storageKey]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { createStore } from "../@/lib/reactive";
|
||||
import { SyncStoreToStorage } from "./SyncStoreToStorage";
|
||||
|
||||
export interface StoredChain extends ChainMetadata {
|
||||
// any of the name, logo, rpc etc is modified, testnet or not
|
||||
// user can not change chainId, symbol, slug
|
||||
isModified?: boolean;
|
||||
|
||||
// if this chain (id) is not in our db, user added it
|
||||
isCustom?: boolean;
|
||||
|
||||
// TODO - remove this, we can not allow this anymore
|
||||
isOverwritten?: boolean;
|
||||
}
|
||||
|
||||
export const isChainOverridesLoadedStore = createStore(false);
|
||||
export const chainOverridesStore = createStore<StoredChain[]>([]);
|
||||
export const recentlyUsedChainIdsStore = createStore<number[]>([]);
|
||||
|
||||
export function SyncChainStores() {
|
||||
return (
|
||||
<>
|
||||
<SyncStoreToStorage
|
||||
storageKey="TW_LOCAL_CHAIN_STORE"
|
||||
store={chainOverridesStore}
|
||||
onLoaded={() => {
|
||||
console.log("chain overrides loaded");
|
||||
isChainOverridesLoadedStore.setValue(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
<SyncStoreToStorage
|
||||
storageKey="TW_RECENTLY_USED_CHAIN_IDS"
|
||||
store={recentlyUsedChainIdsStore}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function addRecentlyUsedChainId(chainId: number) {
|
||||
const newRecentlyUsedChains = recentlyUsedChainIdsStore
|
||||
.getValue()
|
||||
.filter((c) => c !== chainId);
|
||||
|
||||
// add to the front of the array
|
||||
newRecentlyUsedChains.unshift(chainId);
|
||||
// only keep the first 5
|
||||
newRecentlyUsedChains.splice(5);
|
||||
|
||||
recentlyUsedChainIdsStore.setValue(newRecentlyUsedChains);
|
||||
}
|
||||
|
||||
export function addChainOverrides(newChain: StoredChain) {
|
||||
const currentChains = chainOverridesStore.getValue();
|
||||
const index = currentChains.findIndex((c) => c.chainId === newChain.chainId);
|
||||
|
||||
let newChains: StoredChain[];
|
||||
|
||||
// if this chain is already in the chains, update it in array
|
||||
if (index !== -1) {
|
||||
newChains = [...currentChains];
|
||||
newChains[index] = newChain;
|
||||
}
|
||||
|
||||
// else append it
|
||||
else {
|
||||
newChains = [...currentChains, newChain];
|
||||
}
|
||||
|
||||
chainOverridesStore.setValue(newChains);
|
||||
}
|
||||
|
||||
export function removeChainOverrides(id: number) {
|
||||
chainOverridesStore.setValue(
|
||||
chainOverridesStore.getValue().filter((c) => c.chainId !== id),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user