diff --git a/.changeset/rich-moments-act.md b/.changeset/rich-moments-act.md new file mode 100644 index 00000000..a13ca5b2 --- /dev/null +++ b/.changeset/rich-moments-act.md @@ -0,0 +1,5 @@ +--- +"@stakekit/widget": patch +--- + +feat: chain + token icon mapping diff --git a/packages/widget/src/components/atoms/token-icon/token-icon-container/hooks/use-variant-network-urls.ts b/packages/widget/src/components/atoms/token-icon/token-icon-container/hooks/use-variant-network-urls.ts new file mode 100644 index 00000000..e611c70d --- /dev/null +++ b/packages/widget/src/components/atoms/token-icon/token-icon-container/hooks/use-variant-network-urls.ts @@ -0,0 +1,39 @@ +import type { SupportedSKChains } from "@sk-widget/domain/types/chains"; +import { type SettingsProps, useSettings } from "@sk-widget/providers/settings"; +import { getNetworkLogo } from "@sk-widget/utils"; +import type { Networks } from "@stakekit/common"; +import { Maybe } from "purify-ts"; +import { useMemo } from "react"; + +export const getVariantNetworkUrl = ({ + chainIconMapping, + network, +}: { + network: Networks; + chainIconMapping: SettingsProps["chainIconMapping"]; +}) => { + const chainMappingResult = Maybe.fromNullable(chainIconMapping) + .chainNullable((mapping) => { + if (typeof mapping === "function") { + return mapping(network as SupportedSKChains); + } + + return mapping[network as SupportedSKChains]; + }) + .extractNullable(); + + if (chainMappingResult) { + return chainMappingResult; + } + + return getNetworkLogo(network); +}; + +export const useVariantNetworkUrls = (network: Networks) => { + const { chainIconMapping } = useSettings(); + + return useMemo( + () => getVariantNetworkUrl({ chainIconMapping, network }), + [chainIconMapping, network] + ); +}; diff --git a/packages/widget/src/hooks/use-variant-token-urls.ts b/packages/widget/src/components/atoms/token-icon/token-icon-container/hooks/use-variant-token-urls.ts similarity index 74% rename from packages/widget/src/hooks/use-variant-token-urls.ts rename to packages/widget/src/components/atoms/token-icon/token-icon-container/hooks/use-variant-token-urls.ts index 72b98149..300ed40b 100644 --- a/packages/widget/src/hooks/use-variant-token-urls.ts +++ b/packages/widget/src/components/atoms/token-icon/token-icon-container/hooks/use-variant-token-urls.ts @@ -13,7 +13,7 @@ export const useVariantTokenUrls = ( name: string; providerIcon: string | undefined; } => { - const { variant } = useSettings(); + const { variant, tokenIconMapping } = useSettings(); return useMemo(() => { if (metadata) { @@ -32,6 +32,26 @@ export const useVariantTokenUrls = ( }; } + const tokenMappingResult = Maybe.fromNullable(tokenIconMapping) + .chainNullable((mapping) => { + if (typeof mapping === "function") { + return mapping(token); + } + + return mapping[token.symbol]; + }) + .map((url) => ({ + mainUrl: url, + fallbackUrl: url, + name: token.name, + providerIcon: undefined, + })) + .extractNullable(); + + if (tokenMappingResult) { + return tokenMappingResult; + } + const mainUrl = Maybe.fromFalsy(variant === "zerion") .map(() => { /** @@ -54,7 +74,7 @@ export const useVariantTokenUrls = ( name: token.name, providerIcon: undefined, }; - }, [token, metadata, variant]); + }, [token, metadata, variant, tokenIconMapping]); }; const skETHIconUrlsSuffix = ["/tokens/eth.svg", "/tokens/steth2.svg"]; diff --git a/packages/widget/src/components/atoms/token-icon/token-icon-container/index.tsx b/packages/widget/src/components/atoms/token-icon/token-icon-container/index.tsx index b2b1b669..e044276a 100644 --- a/packages/widget/src/components/atoms/token-icon/token-icon-container/index.tsx +++ b/packages/widget/src/components/atoms/token-icon/token-icon-container/index.tsx @@ -1,6 +1,6 @@ import { Box } from "@sk-widget/components"; -import { useVariantTokenUrls } from "@sk-widget/hooks/use-variant-token-urls"; -import { getNetworkLogo } from "@sk-widget/utils"; +import { useVariantNetworkUrls } from "@sk-widget/components/atoms/token-icon/token-icon-container/hooks/use-variant-network-urls"; +import { useVariantTokenUrls } from "@sk-widget/components/atoms/token-icon/token-icon-container/hooks/use-variant-token-urls"; import type { TokenDto, YieldMetadataDto } from "@stakekit/api-hooks"; import type { Networks } from "@stakekit/common"; import type { ReactElement } from "react"; @@ -26,7 +26,8 @@ export const TokenIconContainer = ({ token, metadata ); - const networkLogoUri = getNetworkLogo(token.network as Networks); + + const networkLogoUri = useVariantNetworkUrls(token.network as Networks); return ( + | ((token: TokenDto) => string); + chainIconMapping?: + | Record + | ((chain: SupportedSKChains) => string); }; export type SettingsContextType = SettingsProps & VariantProps; diff --git a/packages/widget/src/providers/wagmi/index.ts b/packages/widget/src/providers/wagmi/index.ts index a7d43cc1..119bf1d3 100644 --- a/packages/widget/src/providers/wagmi/index.ts +++ b/packages/widget/src/providers/wagmi/index.ts @@ -1,10 +1,13 @@ +import { getVariantNetworkUrl } from "@sk-widget/components/atoms/token-icon/token-icon-container/hooks/use-variant-network-urls"; import type { CosmosChainsMap } from "@sk-widget/domain/types/chains/cosmos"; import type { EvmChainsMap } from "@sk-widget/domain/types/chains/evm"; import type { MiscChainsMap } from "@sk-widget/domain/types/chains/misc"; import type { SubstrateChainsMap } from "@sk-widget/domain/types/chains/substrate"; import { useWhitelistedValidators } from "@sk-widget/hooks/use-whitelisted-validators"; +import type { Networks } from "@stakekit/common"; import type { Wallet, WalletList } from "@stakekit/rainbowkit"; import { connectorsForWallets } from "@stakekit/rainbowkit"; +import type { Chain as RainbowkitChain } from "@stakekit/rainbowkit"; import type { QueryClient } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query"; import { EitherAsync, Left, Maybe, Right } from "purify-ts"; @@ -27,7 +30,7 @@ import { getConfig as getLedgerLiveConfig } from "../ledger/config"; import { getConfig as getMiscConfig } from "../misc/config"; import { useSKQueryClient } from "../query-client"; import { getConfig as getSafeConnector } from "../safe/config"; -import { useSettings } from "../settings"; +import { type SettingsProps, useSettings } from "../settings"; import { getConfig as getSubstrateConfig } from "../substrate/config"; export type BuildWagmiConfig = typeof buildWagmiConfig; @@ -51,6 +54,7 @@ const buildWagmiConfig = async (opts: { isLedgerLive: boolean; isSafe: boolean; whitelistedValidatorAddresses: Set | null; + chainIconMapping: SettingsProps["chainIconMapping"]; }): Promise<{ evmConfig: GetEitherAsyncRight>; cosmosConfig: GetEitherAsyncRight>; @@ -132,12 +136,53 @@ const buildWagmiConfig = async (opts: { substrateConfig, ledgerLiveConnector, } = val; - const chains = [ - ...evmConfig.evmChains, - ...cosmosConfig.cosmosWagmiChains, - ...miscConfig.miscChains, - ...substrateConfig.substrateChains, - ] as [Chain, ...Chain[]]; + + const chains = Maybe.fromNullable(opts.chainIconMapping) + .map((chainIconMapping) => { + const mapWagmiChain = (val: { + wagmiChain: RainbowkitChain; + skChainName: Networks; + }) => { + const res = getVariantNetworkUrl({ + network: val.skChainName, + chainIconMapping, + }); + + if (res === val.wagmiChain.iconUrl) { + return val.wagmiChain; + } + + return { + ...val.wagmiChain, + iconBackground: undefined, + iconUrl: res, + } as RainbowkitChain; + }; + + return [ + ...Object.values(evmConfig.evmChainsMap).map((val) => + mapWagmiChain(val) + ), + ...Object.values(cosmosConfig.cosmosChainsMap).map((val) => + mapWagmiChain(val) + ), + ...Object.values(miscConfig.miscChainsMap).map((val) => + mapWagmiChain(val) + ), + ...Object.values(substrateConfig.substrateChainsMap).map((val) => + mapWagmiChain(val) + ), + ] as [RainbowkitChain, ...RainbowkitChain[]]; + }) + .orDefaultLazy( + () => + [ + ...evmConfig.evmChains, + ...cosmosConfig.cosmosWagmiChains, + ...miscConfig.miscChains, + ...substrateConfig.substrateChains, + ] as [RainbowkitChain, ...RainbowkitChain[]] + ); const multiInjectedProviderDiscovery = !opts.disableInjectedProviderDiscovery && @@ -235,6 +280,7 @@ export const useWagmiConfig = () => { isSafe, disableInjectedProviderDiscovery, mapWalletFn, + chainIconMapping, } = useSettings(); const queryClient = useSKQueryClient(); @@ -261,6 +307,7 @@ export const useWagmiConfig = () => { externalProviders: externalProvidersRef, }), whitelistedValidatorAddresses, + chainIconMapping, }), }); };