thirdweb 5.80.1-nightly-ce3e850fdbf34911e20919ecc2674e4a63f08fa3-20241226000321 → 5.80.1-nightly-a7b6cb3211e8b5232e77965cb8dd8ff96b369424-20241228000338

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/cjs/gas/fee-data.js +1 -1
  2. package/dist/cjs/gas/fee-data.js.map +1 -1
  3. package/dist/cjs/react/web/ui/ConnectWallet/ConnectButton.js +11 -0
  4. package/dist/cjs/react/web/ui/ConnectWallet/ConnectButton.js.map +1 -1
  5. package/dist/cjs/react/web/ui/ConnectWallet/Details.js +59 -16
  6. package/dist/cjs/react/web/ui/ConnectWallet/Details.js.map +1 -1
  7. package/dist/cjs/react/web/ui/ConnectWallet/NetworkSelector.js +48 -24
  8. package/dist/cjs/react/web/ui/ConnectWallet/NetworkSelector.js.map +1 -1
  9. package/dist/cjs/react/web/ui/components/ChainActiveDot.js +20 -0
  10. package/dist/cjs/react/web/ui/components/ChainActiveDot.js.map +1 -0
  11. package/dist/cjs/react/web/ui/components/ChainIcon.js +24 -29
  12. package/dist/cjs/react/web/ui/components/ChainIcon.js.map +1 -1
  13. package/dist/cjs/react/web/ui/components/fallbackChainIcon.js +9 -0
  14. package/dist/cjs/react/web/ui/components/fallbackChainIcon.js.map +1 -0
  15. package/dist/cjs/react/web/ui/prebuilt/Chain/icon.js +44 -28
  16. package/dist/cjs/react/web/ui/prebuilt/Chain/icon.js.map +1 -1
  17. package/dist/cjs/version.js +1 -1
  18. package/dist/esm/gas/fee-data.js +1 -1
  19. package/dist/esm/gas/fee-data.js.map +1 -1
  20. package/dist/esm/react/web/ui/ConnectWallet/ConnectButton.js +11 -0
  21. package/dist/esm/react/web/ui/ConnectWallet/ConnectButton.js.map +1 -1
  22. package/dist/esm/react/web/ui/ConnectWallet/Details.js +59 -17
  23. package/dist/esm/react/web/ui/ConnectWallet/Details.js.map +1 -1
  24. package/dist/esm/react/web/ui/ConnectWallet/NetworkSelector.js +45 -21
  25. package/dist/esm/react/web/ui/ConnectWallet/NetworkSelector.js.map +1 -1
  26. package/dist/esm/react/web/ui/components/ChainActiveDot.js +17 -0
  27. package/dist/esm/react/web/ui/components/ChainActiveDot.js.map +1 -0
  28. package/dist/esm/react/web/ui/components/ChainIcon.js +22 -28
  29. package/dist/esm/react/web/ui/components/ChainIcon.js.map +1 -1
  30. package/dist/esm/react/web/ui/components/fallbackChainIcon.js +6 -0
  31. package/dist/esm/react/web/ui/components/fallbackChainIcon.js.map +1 -0
  32. package/dist/esm/react/web/ui/prebuilt/Chain/icon.js +42 -28
  33. package/dist/esm/react/web/ui/prebuilt/Chain/icon.js.map +1 -1
  34. package/dist/esm/version.js +1 -1
  35. package/dist/types/react/core/hooks/connection/ConnectButtonProps.d.ts +5 -0
  36. package/dist/types/react/core/hooks/connection/ConnectButtonProps.d.ts.map +1 -1
  37. package/dist/types/react/web/ui/ConnectWallet/ConnectButton.d.ts +11 -0
  38. package/dist/types/react/web/ui/ConnectWallet/ConnectButton.d.ts.map +1 -1
  39. package/dist/types/react/web/ui/ConnectWallet/Details.d.ts +34 -1
  40. package/dist/types/react/web/ui/ConnectWallet/Details.d.ts.map +1 -1
  41. package/dist/types/react/web/ui/ConnectWallet/NetworkSelector.d.ts +46 -1
  42. package/dist/types/react/web/ui/ConnectWallet/NetworkSelector.d.ts.map +1 -1
  43. package/dist/types/react/web/ui/components/ChainActiveDot.d.ts +10 -0
  44. package/dist/types/react/web/ui/components/ChainActiveDot.d.ts.map +1 -0
  45. package/dist/types/react/web/ui/components/ChainIcon.d.ts +8 -0
  46. package/dist/types/react/web/ui/components/ChainIcon.d.ts.map +1 -1
  47. package/dist/types/react/web/ui/components/fallbackChainIcon.d.ts +6 -0
  48. package/dist/types/react/web/ui/components/fallbackChainIcon.d.ts.map +1 -0
  49. package/dist/types/react/web/ui/prebuilt/Chain/icon.d.ts +18 -0
  50. package/dist/types/react/web/ui/prebuilt/Chain/icon.d.ts.map +1 -1
  51. package/dist/types/version.d.ts +1 -1
  52. package/package.json +1 -1
  53. package/src/gas/fee-data.ts +1 -1
  54. package/src/react/core/hooks/connection/ConnectButtonProps.ts +6 -0
  55. package/src/react/web/ui/ConnectWallet/ConnectButton.tsx +11 -0
  56. package/src/react/web/ui/ConnectWallet/Details.test.tsx +55 -0
  57. package/src/react/web/ui/ConnectWallet/Details.tsx +176 -98
  58. package/src/react/web/ui/ConnectWallet/NetworkSelector.test.tsx +189 -0
  59. package/src/react/web/ui/ConnectWallet/NetworkSelector.tsx +117 -76
  60. package/src/react/web/ui/components/ChainActiveDot.tsx +17 -0
  61. package/src/react/web/ui/components/ChainIcon.test.tsx +60 -0
  62. package/src/react/web/ui/components/ChainIcon.tsx +27 -31
  63. package/src/react/web/ui/components/fallbackChainIcon.ts +6 -0
  64. package/src/react/web/ui/prebuilt/Chain/icon.test.tsx +150 -0
  65. package/src/react/web/ui/prebuilt/Chain/icon.tsx +53 -31
  66. package/src/version.ts +1 -1
@@ -27,32 +27,35 @@ import {
27
27
  radius,
28
28
  spacing,
29
29
  } from "../../../core/design-system/index.js";
30
- import {
31
- useChainIconUrl,
32
- useChainName,
33
- useChainsQuery,
34
- } from "../../../core/hooks/others/useChainQuery.js";
30
+ import { useChainsQuery } from "../../../core/hooks/others/useChainQuery.js";
35
31
  import { useActiveWalletChain } from "../../../core/hooks/wallets/useActiveWalletChain.js";
36
32
  import { useSwitchActiveWalletChain } from "../../../core/hooks/wallets/useSwitchActiveWalletChain.js";
37
33
  import { SetRootElementContext } from "../../../core/providers/RootElementContext.js";
38
- import { ChainIcon } from "../components/ChainIcon.js";
34
+ import { ChainActiveDot } from "../components/ChainActiveDot.js";
39
35
  import { Modal } from "../components/Modal.js";
40
36
  import { Skeleton } from "../components/Skeleton.js";
41
37
  import { Spacer } from "../components/Spacer.js";
42
38
  import { Spinner } from "../components/Spinner.js";
43
39
  import { Container, Line, ModalHeader } from "../components/basic.js";
44
40
  import { Button } from "../components/buttons.js";
41
+ import { fallbackChainIcon } from "../components/fallbackChainIcon.js";
45
42
  import { Input } from "../components/formElements.js";
46
43
  import { ModalTitle } from "../components/modalElements.js";
47
44
  import { Text } from "../components/text.js";
48
45
  import { StyledButton, StyledP, StyledUl } from "../design-system/elements.js";
49
46
  import { useDebouncedValue } from "../hooks/useDebouncedValue.js";
50
47
  import { useShowMore } from "../hooks/useShowMore.js";
48
+ import { ChainIcon } from "../prebuilt/Chain/icon.js";
49
+ import { ChainName } from "../prebuilt/Chain/name.js";
50
+ import { ChainProvider } from "../prebuilt/Chain/provider.js";
51
51
  import type { LocaleId } from "../types.js";
52
52
  import { getConnectLocale } from "./locale/getConnectLocale.js";
53
53
  import type { ConnectLocale } from "./locale/types.js";
54
54
 
55
- type NetworkSelectorChainProps = {
55
+ /**
56
+ * @internal
57
+ */
58
+ export type NetworkSelectorChainProps = {
56
59
  /**
57
60
  * `Chain` object to be displayed
58
61
  */
@@ -548,7 +551,10 @@ type NetworkListProps = {
548
551
  connectLocale: ConnectLocale;
549
552
  };
550
553
 
551
- const NetworkList = /* @__PURE__ */ memo(function NetworkList(
554
+ /**
555
+ * @internal Exported for tests
556
+ */
557
+ export const NetworkList = /* @__PURE__ */ memo(function NetworkList(
552
558
  props: NetworkListProps,
553
559
  ) {
554
560
  // show 10 items first, when reaching the last item, show 10 more
@@ -626,6 +632,9 @@ const NetworkList = /* @__PURE__ */ memo(function NetworkList(
626
632
  );
627
633
  } as React.FC<NetworkListProps>);
628
634
 
635
+ /**
636
+ * @internal
637
+ */
629
638
  export const ChainButton = /* @__PURE__ */ memo(function ChainButton(props: {
630
639
  chain: Chain;
631
640
  onClick: () => void;
@@ -636,72 +645,95 @@ export const ChainButton = /* @__PURE__ */ memo(function ChainButton(props: {
636
645
  }) {
637
646
  const locale = props.connectLocale;
638
647
  const { chain, confirming, switchingFailed } = props;
639
-
640
648
  const activeChain = useActiveWalletChain();
641
649
 
642
- const chainNameQuery = useChainName(chain);
643
- const chainIconQuery = useChainIconUrl(chain);
644
-
645
- let chainName: React.ReactNode;
646
- if (chainNameQuery.name) {
647
- chainName = <span>{chainNameQuery.name} </span>;
648
- } else {
649
- chainName = <Skeleton width="150px" height="20px" />;
650
- }
651
-
652
650
  return (
653
- <NetworkButton
654
- data-active={activeChain?.id === chain.id}
655
- onClick={props.onClick}
656
- >
657
- {!chainIconQuery.isLoading ? (
658
- <ChainIcon
659
- chainIconUrl={chainIconQuery.url}
660
- size={iconSize.lg}
661
- active={activeChain?.id === chain.id}
662
- loading="lazy"
663
- client={props.client}
664
- />
665
- ) : (
666
- <Skeleton width={`${iconSize.lg}px`} height={`${iconSize.lg}px`} />
667
- )}
668
-
669
- {confirming || switchingFailed ? (
670
- <div
651
+ <ChainProvider chain={chain}>
652
+ <NetworkButton
653
+ data-active={activeChain?.id === chain.id}
654
+ onClick={props.onClick}
655
+ >
656
+ <Container
671
657
  style={{
658
+ position: "relative",
672
659
  display: "flex",
673
- flexDirection: "column",
674
- gap: spacing.xs,
660
+ flexShrink: 0,
661
+ alignItems: "center",
675
662
  }}
676
663
  >
677
- {chainName}
678
- <Container animate="fadein" flex="row" gap="xxs" center="y">
679
- {confirming && (
680
- <>
681
- <Text size="xs" color="accentText">
682
- {locale.confirmInWallet}
683
- </Text>
684
- <Spinner size="xs" color="accentText" />
685
- </>
686
- )}
687
-
688
- {switchingFailed && (
689
- <Container animate="fadein">
690
- <Text size="xs" color="danger">
691
- {locale.networkSelector.failedToSwitch}
692
- </Text>
693
- </Container>
694
- )}
695
- </Container>
696
- </div>
697
- ) : (
698
- chainName
699
- )}
700
- </NetworkButton>
664
+ <ChainIcon
665
+ client={props.client}
666
+ loadingComponent={
667
+ <Skeleton
668
+ width={`${iconSize.lg}px`}
669
+ height={`${iconSize.lg}px`}
670
+ />
671
+ }
672
+ fallbackComponent={
673
+ <img
674
+ src={fallbackChainIcon}
675
+ alt=""
676
+ style={{
677
+ width: `${iconSize.lg}px`,
678
+ height: `${iconSize.lg}px`,
679
+ }}
680
+ />
681
+ }
682
+ style={{
683
+ width: `${iconSize.lg}px`,
684
+ height: `${iconSize.lg}px`,
685
+ }}
686
+ loading="lazy"
687
+ />
688
+ {activeChain?.id === chain.id && (
689
+ <ChainActiveDot className="tw-chain-active-dot-button-network-selector" />
690
+ )}
691
+ </Container>
692
+ {confirming || switchingFailed ? (
693
+ <div
694
+ style={{
695
+ display: "flex",
696
+ flexDirection: "column",
697
+ gap: spacing.xs,
698
+ }}
699
+ >
700
+ <ChainName
701
+ loadingComponent={<Skeleton width="150px" height="20px" />}
702
+ />
703
+ <Container animate="fadein" flex="row" gap="xxs" center="y">
704
+ {confirming && (
705
+ <>
706
+ <Text size="xs" color="accentText">
707
+ {locale.confirmInWallet}
708
+ </Text>
709
+ <Spinner size="xs" color="accentText" />
710
+ </>
711
+ )}
712
+
713
+ {switchingFailed && (
714
+ <Container animate="fadein">
715
+ <Text size="xs" color="danger">
716
+ {locale.networkSelector.failedToSwitch}
717
+ </Text>
718
+ </Container>
719
+ )}
720
+ </Container>
721
+ </div>
722
+ ) : (
723
+ <ChainName
724
+ loadingComponent={<Skeleton width="150px" height="20px" />}
725
+ className="tw-chain-icon-none-confirming"
726
+ />
727
+ )}
728
+ </NetworkButton>
729
+ </ChainProvider>
701
730
  );
702
731
  });
703
732
 
704
- const TabButton = /* @__PURE__ */ (() =>
733
+ /**
734
+ * @internal Exported for tests
735
+ */
736
+ export const TabButton = /* @__PURE__ */ (() =>
705
737
  styled.button((_) => {
706
738
  const theme = useCustomTheme();
707
739
  return {
@@ -722,7 +754,10 @@ const TabButton = /* @__PURE__ */ (() =>
722
754
  };
723
755
  }))();
724
756
 
725
- const SectionLabel = /* @__PURE__ */ StyledP(() => {
757
+ /**
758
+ * @internal Exported for tests
759
+ */
760
+ export const SectionLabel = /* @__PURE__ */ StyledP(() => {
726
761
  const theme = useCustomTheme();
727
762
  return {
728
763
  fontSize: fontSize.sm,
@@ -743,7 +778,10 @@ const NetworkListUl = /* @__PURE__ */ StyledUl({
743
778
  boxSizing: "border-box",
744
779
  });
745
780
 
746
- const NetworkButton = /* @__PURE__ */ StyledButton((_) => {
781
+ /**
782
+ * @internal Exported for tests
783
+ */
784
+ export const NetworkButton = /* @__PURE__ */ StyledButton((_) => {
747
785
  const theme = useCustomTheme();
748
786
  return {
749
787
  all: "unset",
@@ -769,16 +807,19 @@ const NetworkButton = /* @__PURE__ */ StyledButton((_) => {
769
807
  };
770
808
  });
771
809
 
772
- const StyledMagnifyingGlassIcon = /* @__PURE__ */ styled(MagnifyingGlassIcon)(
773
- (_) => {
774
- const theme = useCustomTheme();
775
- return {
776
- color: theme.colors.secondaryText,
777
- position: "absolute",
778
- left: spacing.sm,
779
- };
780
- },
781
- );
810
+ /**
811
+ * @internal Exported for tests
812
+ */
813
+ export const StyledMagnifyingGlassIcon = /* @__PURE__ */ styled(
814
+ MagnifyingGlassIcon,
815
+ )((_) => {
816
+ const theme = useCustomTheme();
817
+ return {
818
+ color: theme.colors.secondaryText,
819
+ position: "absolute",
820
+ left: spacing.sm,
821
+ };
822
+ });
782
823
 
783
824
  /**
784
825
  * Options for the `useNetworkSwitcherModal` hook's returned `open` function
@@ -0,0 +1,17 @@
1
+ import { StyledDiv } from "../design-system/elements.js";
2
+
3
+ /**
4
+ * The greet dot that is placed at the corner of the chain icon -
5
+ * indicating that the chain is currently active (connected to)
6
+ * @internal
7
+ */
8
+ export const ChainActiveDot = /* @__PURE__ */ StyledDiv({
9
+ width: "28%",
10
+ height: "28%",
11
+ borderRadius: "50%",
12
+ position: "absolute",
13
+ bottom: 0,
14
+ right: 0,
15
+ backgroundColor: "#00d395",
16
+ boxShadow: "0 0 0 2px var(--bg)",
17
+ });
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { TEST_CONTRACT_URI } from "~test/ipfs-uris.js";
3
+ import { render } from "~test/react-render.js";
4
+ import { TEST_CLIENT } from "~test/test-clients.js";
5
+ import { ChainIcon, getSrcChainIcon } from "./ChainIcon.js";
6
+ import { fallbackChainIcon } from "./fallbackChainIcon.js";
7
+
8
+ const client = TEST_CLIENT;
9
+
10
+ describe.runIf(process.env.TW_SECRET_KEY)("ChainIcon-legacy", () => {
11
+ it("getSrcChainIcon should return fallbackChainIcon if chainIconUrl does not exist", () => {
12
+ expect(getSrcChainIcon({ client })).toBe(fallbackChainIcon);
13
+ });
14
+
15
+ it("getSrcChainIcon should return the resolved url", () => {
16
+ const resolvedUrl = getSrcChainIcon({
17
+ client,
18
+ chainIconUrl: TEST_CONTRACT_URI,
19
+ });
20
+ expect(resolvedUrl.startsWith("https://")).toBe(true);
21
+ });
22
+
23
+ it("getSrcChainIcon should return the fallbackChainIcon if fails to resolve", () => {
24
+ expect(getSrcChainIcon({ client, chainIconUrl: "test" })).toBe(
25
+ fallbackChainIcon,
26
+ );
27
+ });
28
+
29
+ it("should render an image", () => {
30
+ const { container } = render(
31
+ <ChainIcon
32
+ chainIconUrl="ipfs://QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9/ethereum/512.png"
33
+ client={client}
34
+ size="lg"
35
+ />,
36
+ );
37
+
38
+ const image = container.querySelector("img");
39
+ expect(image).toBeTruthy();
40
+
41
+ expect(
42
+ image?.src.includes("QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9"),
43
+ ).toBe(true);
44
+ });
45
+
46
+ it("should render ChainActiveDot if the `active` prop is set to true", () => {
47
+ const { container } = render(
48
+ <ChainIcon
49
+ chainIconUrl="ipfs://QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9/ethereum/512.png"
50
+ client={client}
51
+ size="lg"
52
+ active
53
+ />,
54
+ );
55
+ const activeDot = container.querySelector(
56
+ ".tw-chain-active-dot-legacy-chain-icon",
57
+ );
58
+ expect(activeDot).toBeTruthy();
59
+ });
60
+ });
@@ -1,13 +1,12 @@
1
1
  import type { ThirdwebClient } from "../../../../client/client.js";
2
2
  import { resolveScheme } from "../../../../utils/ipfs.js";
3
- import { StyledDiv } from "../design-system/elements.js";
3
+ import { ChainActiveDot } from "./ChainActiveDot.js";
4
4
  import { Img } from "./Img.js";
5
5
  import { Container } from "./basic.js";
6
-
7
- const fallbackChainIcon =
8
- "data:image/svg+xml;charset=UTF-8,%3csvg width='15' height='14' viewBox='0 0 15 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M7 8.04238e-07C5.1435 8.04238e-07 3.36301 0.737501 2.05025 2.05025C0.7375 3.36301 0 5.1435 0 7C0 7.225 -1.52737e-07 7.445 0.0349998 7.665C0.16385 9.0151 0.68213 10.2988 1.52686 11.3598C2.37158 12.4209 3.50637 13.2137 4.79326 13.642C6.0801 14.0702 7.4637 14.1153 8.7758 13.7719C10.0879 13.4285 11.2719 12.7113 12.184 11.7075C13.0961 10.7038 13.6969 9.4567 13.9135 8.1178C14.1301 6.7789 13.9531 5.406 13.4039 4.16587C12.8548 2.92574 11.9573 1.87184 10.8204 1.13228C9.6835 0.392721 8.3563 -0.000649196 7 8.04238e-07ZM7 1C8.581 1.00137 10.0975 1.62668 11.22 2.74V3.24C9.2438 2.55991 7.0956 2.56872 5.125 3.265C4.96758 3.1116 4.76997 3.00586 4.555 2.96H4.43C4.37 2.75 4.315 2.54 4.27 2.325C4.225 2.11 4.2 1.92 4.175 1.715C5.043 1.24658 6.0137 1.00091 7 1ZM5.5 3.935C7.3158 3.32693 9.2838 3.34984 11.085 4C10.8414 5.2703 10.3094 6.4677 9.53 7.5C9.312 7.4077 9.0707 7.3855 8.8395 7.4366C8.6083 7.4877 8.3988 7.6094 8.24 7.785C8.065 7.685 7.89 7.585 7.74 7.47C6.7307 6.7966 5.8877 5.9023 5.275 4.855C5.374 4.73221 5.4461 4.58996 5.4866 4.43749C5.5271 4.28502 5.5351 4.12575 5.51 3.97L5.5 3.935ZM3.5 2.135C3.5 2.24 3.53 2.35 3.55 2.455C3.595 2.675 3.655 2.89 3.715 3.105C3.52353 3.21838 3.36943 3.38531 3.2717 3.58522C3.17397 3.78513 3.13688 4.00927 3.165 4.23C2.37575 4.7454 1.67078 5.3795 1.075 6.11C1.19455 5.3189 1.47112 4.55966 1.88843 3.87701C2.30575 3.19437 2.85539 2.60208 3.505 2.135H3.5ZM3.5 9.99C3.30481 10.0555 3.13037 10.1714 2.9943 10.3259C2.85822 10.4804 2.76533 10.6681 2.725 10.87H2.405C1.59754 9.9069 1.1146 8.7136 1.025 7.46L1.08 7.365C1.70611 6.3942 2.52463 5.562 3.485 4.92C3.62899 5.0704 3.81094 5.179 4.01162 5.2345C4.2123 5.2899 4.42423 5.2901 4.625 5.235C5.2938 6.3652 6.208 7.3306 7.3 8.06C7.505 8.195 7.715 8.32 7.925 8.44C7.9082 8.6312 7.9391 8.8237 8.015 9C7.1 9.7266 6.0445 10.256 4.915 10.555C4.78401 10.3103 4.57028 10.1201 4.31199 10.0184C4.05369 9.9167 3.76766 9.9102 3.505 10L3.5 9.99ZM7 12.99C5.9831 12.9903 4.98307 12.7304 4.095 12.235L4.235 12.205C4.43397 12.1397 4.61176 12.0222 4.74984 11.8648C4.88792 11.7074 4.98122 11.5158 5.02 11.31C6.2985 10.984 7.4921 10.3872 8.52 9.56C8.7642 9.7027 9.0525 9.75 9.3295 9.6927C9.6064 9.6355 9.8524 9.4778 10.02 9.25C10.7254 9.4334 11.4511 9.5275 12.18 9.53H12.445C11.9626 10.5673 11.1938 11.4451 10.2291 12.0599C9.2643 12.6747 8.144 13.0009 7 13V12.99ZM10.255 8.54C10.2545 8.3304 10.1975 8.1249 10.09 7.945C10.9221 6.8581 11.5012 5.5991 11.785 4.26C12.035 4.37667 12.2817 4.50667 12.525 4.65C13.0749 5.9495 13.1493 7.4012 12.735 8.75C11.9049 8.8142 11.0698 8.7484 10.26 8.555L10.255 8.54Z' fill='%23646D7A'/%3e%3c/svg%3e";
6
+ import { fallbackChainIcon } from "./fallbackChainIcon.js";
9
7
 
10
8
  /**
9
+ * This component (file) will eventually be replaced with the ChainIcon prebuilt version.
11
10
  * @internal
12
11
  */
13
12
  export const ChainIcon: React.FC<{
@@ -18,21 +17,6 @@ export const ChainIcon: React.FC<{
18
17
  loading?: "lazy" | "eager";
19
18
  client: ThirdwebClient;
20
19
  }> = (props) => {
21
- const getSrc = () => {
22
- const url = props.chainIconUrl;
23
- if (!url) {
24
- return fallbackChainIcon;
25
- }
26
- try {
27
- return resolveScheme({
28
- uri: url,
29
- client: props.client,
30
- });
31
- } catch {
32
- return fallbackChainIcon;
33
- }
34
- };
35
-
36
20
  return (
37
21
  <Container
38
22
  style={{
@@ -43,24 +27,36 @@ export const ChainIcon: React.FC<{
43
27
  }}
44
28
  >
45
29
  <Img
46
- src={getSrc()}
30
+ src={getSrcChainIcon(props)}
47
31
  width={props.size}
48
32
  height={props.size}
49
33
  fallbackImage={fallbackChainIcon}
50
34
  client={props.client}
51
35
  />
52
- {props.active && <ActiveDot />}
36
+ {props.active && (
37
+ <ChainActiveDot className="tw-chain-active-dot-legacy-chain-icon" />
38
+ )}
53
39
  </Container>
54
40
  );
55
41
  };
56
42
 
57
- const ActiveDot = /* @__PURE__ */ StyledDiv({
58
- width: "28%",
59
- height: "28%",
60
- borderRadius: "50%",
61
- position: "absolute",
62
- bottom: 0,
63
- right: 0,
64
- backgroundColor: "#00d395",
65
- boxShadow: "0 0 0 2px var(--bg)",
66
- });
43
+ /**
44
+ * @internal
45
+ */
46
+ export const getSrcChainIcon = (props: {
47
+ client: ThirdwebClient;
48
+ chainIconUrl?: string;
49
+ }) => {
50
+ const url = props.chainIconUrl;
51
+ if (!url) {
52
+ return fallbackChainIcon;
53
+ }
54
+ try {
55
+ return resolveScheme({
56
+ uri: url,
57
+ client: props.client,
58
+ });
59
+ } catch {
60
+ return fallbackChainIcon;
61
+ }
62
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The dummy image to be used in case the chain icon does not exist
3
+ * @internal
4
+ */
5
+ export const fallbackChainIcon =
6
+ "data:image/svg+xml;charset=UTF-8,%3csvg width='15' height='14' viewBox='0 0 15 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M7 8.04238e-07C5.1435 8.04238e-07 3.36301 0.737501 2.05025 2.05025C0.7375 3.36301 0 5.1435 0 7C0 7.225 -1.52737e-07 7.445 0.0349998 7.665C0.16385 9.0151 0.68213 10.2988 1.52686 11.3598C2.37158 12.4209 3.50637 13.2137 4.79326 13.642C6.0801 14.0702 7.4637 14.1153 8.7758 13.7719C10.0879 13.4285 11.2719 12.7113 12.184 11.7075C13.0961 10.7038 13.6969 9.4567 13.9135 8.1178C14.1301 6.7789 13.9531 5.406 13.4039 4.16587C12.8548 2.92574 11.9573 1.87184 10.8204 1.13228C9.6835 0.392721 8.3563 -0.000649196 7 8.04238e-07ZM7 1C8.581 1.00137 10.0975 1.62668 11.22 2.74V3.24C9.2438 2.55991 7.0956 2.56872 5.125 3.265C4.96758 3.1116 4.76997 3.00586 4.555 2.96H4.43C4.37 2.75 4.315 2.54 4.27 2.325C4.225 2.11 4.2 1.92 4.175 1.715C5.043 1.24658 6.0137 1.00091 7 1ZM5.5 3.935C7.3158 3.32693 9.2838 3.34984 11.085 4C10.8414 5.2703 10.3094 6.4677 9.53 7.5C9.312 7.4077 9.0707 7.3855 8.8395 7.4366C8.6083 7.4877 8.3988 7.6094 8.24 7.785C8.065 7.685 7.89 7.585 7.74 7.47C6.7307 6.7966 5.8877 5.9023 5.275 4.855C5.374 4.73221 5.4461 4.58996 5.4866 4.43749C5.5271 4.28502 5.5351 4.12575 5.51 3.97L5.5 3.935ZM3.5 2.135C3.5 2.24 3.53 2.35 3.55 2.455C3.595 2.675 3.655 2.89 3.715 3.105C3.52353 3.21838 3.36943 3.38531 3.2717 3.58522C3.17397 3.78513 3.13688 4.00927 3.165 4.23C2.37575 4.7454 1.67078 5.3795 1.075 6.11C1.19455 5.3189 1.47112 4.55966 1.88843 3.87701C2.30575 3.19437 2.85539 2.60208 3.505 2.135H3.5ZM3.5 9.99C3.30481 10.0555 3.13037 10.1714 2.9943 10.3259C2.85822 10.4804 2.76533 10.6681 2.725 10.87H2.405C1.59754 9.9069 1.1146 8.7136 1.025 7.46L1.08 7.365C1.70611 6.3942 2.52463 5.562 3.485 4.92C3.62899 5.0704 3.81094 5.179 4.01162 5.2345C4.2123 5.2899 4.42423 5.2901 4.625 5.235C5.2938 6.3652 6.208 7.3306 7.3 8.06C7.505 8.195 7.715 8.32 7.925 8.44C7.9082 8.6312 7.9391 8.8237 8.015 9C7.1 9.7266 6.0445 10.256 4.915 10.555C4.78401 10.3103 4.57028 10.1201 4.31199 10.0184C4.05369 9.9167 3.76766 9.9102 3.505 10L3.5 9.99ZM7 12.99C5.9831 12.9903 4.98307 12.7304 4.095 12.235L4.235 12.205C4.43397 12.1397 4.61176 12.0222 4.74984 11.8648C4.88792 11.7074 4.98122 11.5158 5.02 11.31C6.2985 10.984 7.4921 10.3872 8.52 9.56C8.7642 9.7027 9.0525 9.75 9.3295 9.6927C9.6064 9.6355 9.8524 9.4778 10.02 9.25C10.7254 9.4334 11.4511 9.5275 12.18 9.53H12.445C11.9626 10.5673 11.1938 11.4451 10.2291 12.0599C9.2643 12.6747 8.144 13.0009 7 13V12.99ZM10.255 8.54C10.2545 8.3304 10.1975 8.1249 10.09 7.945C10.9221 6.8581 11.5012 5.5991 11.785 4.26C12.035 4.37667 12.2817 4.50667 12.525 4.65C13.0749 5.9495 13.1493 7.4012 12.735 8.75C11.9049 8.8142 11.0698 8.7484 10.26 8.555L10.255 8.54Z' fill='%23646D7A'/%3e%3c/svg%3e";
@@ -0,0 +1,150 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render, waitFor } from "~test/react-render.js";
3
+ import { TEST_CLIENT } from "~test/test-clients.js";
4
+ import { ethereum } from "../../../../../chains/chain-definitions/ethereum.js";
5
+ import { defineChain } from "../../../../../chains/utils.js";
6
+ import { getFunctionId } from "../../../../../utils/function-id.js";
7
+ import { ChainIcon, fetchChainIcon, getQueryKeys } from "./icon.js";
8
+ import { ChainProvider } from "./provider.js";
9
+
10
+ const client = TEST_CLIENT;
11
+
12
+ describe.runIf(process.env.TW_SECRET_KEY)("ChainIcon", () => {
13
+ it("fetchChainIcon should respect iconResolver as a string", async () => {
14
+ expect(
15
+ await fetchChainIcon({ chain: ethereum, client, iconResolver: "test" }),
16
+ ).toBe("test");
17
+ });
18
+
19
+ it("fetchChainIcon should respect iconResolver as a non-async function", async () => {
20
+ expect(
21
+ await fetchChainIcon({
22
+ chain: ethereum,
23
+ client,
24
+ iconResolver: () => "test",
25
+ }),
26
+ ).toBe("test");
27
+ });
28
+
29
+ it("fetchChainIcon should respect iconResolver as an async function", async () => {
30
+ expect(
31
+ await fetchChainIcon({
32
+ chain: ethereum,
33
+ client,
34
+ iconResolver: async () => "test",
35
+ }),
36
+ ).toBe("test");
37
+ });
38
+
39
+ it("fetchChainIcon should return a resolved url from the backend server, NOT ipfs uri", async () => {
40
+ const resolvedUrl = await fetchChainIcon({ chain: ethereum, client });
41
+ expect(
42
+ resolvedUrl.includes("QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9"),
43
+ ).toBe(true);
44
+ expect(resolvedUrl.startsWith("https://")).toBe(true);
45
+ });
46
+
47
+ it("fetchChainIcon should return a resolved url from the chain object, NOT the ipfs uri", async () => {
48
+ const mockEthereum = defineChain({
49
+ id: 1,
50
+ name: "Ethereum",
51
+ nativeCurrency: {
52
+ name: "Ether",
53
+ symbol: "ETH",
54
+ decimals: 18,
55
+ },
56
+ blockExplorers: [
57
+ {
58
+ name: "Etherscan",
59
+ url: "https://etherscan.io",
60
+ },
61
+ ],
62
+ icon: {
63
+ url: "ipfs://QmdwQDr6vmBtXmK2TmknkEuZNoaDqTasFdZdu3DRw8b2wt",
64
+ format: "png",
65
+ width: 100,
66
+ height: 100,
67
+ },
68
+ });
69
+ const resolvedUrl = await fetchChainIcon({ chain: mockEthereum, client });
70
+ expect(
71
+ resolvedUrl.endsWith(
72
+ ".ipfscdn.io/ipfs/QmdwQDr6vmBtXmK2TmknkEuZNoaDqTasFdZdu3DRw8b2wt",
73
+ ),
74
+ ).toBe(true);
75
+ expect(resolvedUrl.startsWith("https://")).toBe(true);
76
+ });
77
+
78
+ it("fetchChainIcon should throw error if failed to resolve chain icon", async () => {
79
+ await expect(() =>
80
+ fetchChainIcon({ chain: defineChain(-1), client }),
81
+ ).rejects.toThrowError("Failed to resolve icon for chain");
82
+ });
83
+
84
+ it("getQueryKeys should work without resolver", () => {
85
+ expect(getQueryKeys({ chainId: 1 })).toStrictEqual([
86
+ "_internal_chain_icon_",
87
+ 1,
88
+ {
89
+ resolver: undefined,
90
+ },
91
+ ]);
92
+ });
93
+
94
+ it("getQueryKeys should work with resolver being a string", () => {
95
+ expect(getQueryKeys({ chainId: 1, iconResolver: "tw" })).toStrictEqual([
96
+ "_internal_chain_icon_",
97
+ 1,
98
+ {
99
+ resolver: "tw",
100
+ },
101
+ ]);
102
+ });
103
+
104
+ it("getQueryKeys should work with resolver being a non-async fn that returns a string", () => {
105
+ const fn = () => "tw";
106
+ const fnId = getFunctionId(fn);
107
+ expect(getQueryKeys({ chainId: 1, iconResolver: fn })).toStrictEqual([
108
+ "_internal_chain_icon_",
109
+ 1,
110
+ {
111
+ resolver: fnId,
112
+ },
113
+ ]);
114
+ });
115
+
116
+ it("getQueryKeys should work with resolver being an async fn that returns a string", () => {
117
+ const fn = async () => {
118
+ await new Promise((resolve) => setTimeout(resolve, 2000));
119
+ return "tw";
120
+ };
121
+ const fnId = getFunctionId(fn);
122
+ expect(
123
+ getQueryKeys({
124
+ chainId: 1,
125
+ iconResolver: fn,
126
+ }),
127
+ ).toStrictEqual([
128
+ "_internal_chain_icon_",
129
+ 1,
130
+ {
131
+ resolver: fnId,
132
+ },
133
+ ]);
134
+ });
135
+
136
+ it("should render an image", async () => {
137
+ const { container } = render(
138
+ <ChainProvider chain={ethereum}>
139
+ <ChainIcon client={client} />
140
+ </ChainProvider>,
141
+ );
142
+ await waitFor(() => {
143
+ const image = container.querySelector("img");
144
+ expect(image).toBeTruthy();
145
+ expect(
146
+ image?.src.includes("QmcxZHpyJa8T4i63xqjPYrZ6tKrt55tZJpbXcjSDKuKaf9"),
147
+ ).toBe(true);
148
+ });
149
+ });
150
+ });