{"id":13810165,"url":"https://github.com/vaunblu/SimpleKit","last_synced_at":"2025-05-14T10:32:20.656Z","repository":{"id":248511138,"uuid":"828634005","full_name":"vaunblu/SimpleKit","owner":"vaunblu","description":"Responsive connect wallet and account component built on top of Wagmi and shadcn/ui.","archived":false,"fork":false,"pushed_at":"2024-07-17T15:16:18.000Z","size":805,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-07-25T19:23:49.938Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://simplekit.vaunb.lu","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vaunblu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-14T18:21:57.000Z","updated_at":"2024-07-25T13:36:49.000Z","dependencies_parsed_at":"2024-07-15T12:53:35.598Z","dependency_job_id":"77c35014-b14b-4bf9-840a-e8e756564431","html_url":"https://github.com/vaunblu/SimpleKit","commit_stats":null,"previous_names":["vaunblu/simplekit"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaunblu%2FSimpleKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaunblu%2FSimpleKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaunblu%2FSimpleKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaunblu%2FSimpleKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vaunblu","download_url":"https://codeload.github.com/vaunblu/SimpleKit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":213870383,"owners_count":15650179,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T02:00:47.147Z","updated_at":"2024-08-04T02:01:34.530Z","avatar_url":"https://github.com/vaunblu.png","language":"TypeScript","readme":"# SimpleKit\n\nResponsive connect wallet and account component built on top of Wagmi and shadcn/ui.\n\nSimpleKit is the simplest way to integrate a connect wallet experience into your React.js web application. It is built on top of primitives where it's your components, your code. No more editing style/theme props.\n\n![Demo](https://utfs.io/f/77740eed-6f9e-4379-a9c9-e7122ceea01f-bfjzn0.gif)\n\n## Installation\n\n### 1. Install Wagmi\n\nInstall Wagmi and its peer dependencies:\n\n```bash\npnpm add wagmi viem@2.x @tanstack/react-query\n```\n\n- [Wagmi](https://wagmi.sh/) is a React Hooks library for Ethereum, this is the library you will use to interact with the connected wallet.\n- [Viem](https://viem.sh/) is a TypeScript interface for Ethereum that performs blockchain operations.\n- [TanStack](https://tanstack.com/query/v5) Query is an async state manager that handles requests, caching, and more.\n- [TypeScript](https://wagmi.sh/react/typescript) is optional, but highly recommended.\n\n### 2. API Keys\n\nSimpleKit utilises [WalletConnect's](https://walletconnect.com/) SDK to help with connecting wallets. WalletConnect 2.0 requires a `projectId` which you can create quickly and easily for free over at [WalletConnect Cloud](https://cloud.walletconnect.com/).\n\n### 3. Set up the `Web3Provider`: [web3-provider.tsx](src/components/web3-provider.tsx)\n\nMake sure to replace the `projectId` with your own WalletConnect Project ID, if you wish to use WalletConnect (highly recommended)!\n\n```tsx\n\"use client\";\n\n// 1. Import modules\nimport * as React from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { WagmiProvider, http, createConfig } from \"wagmi\";\nimport { mainnet } from \"wagmi/chains\";\nimport { injected, coinbaseWallet, walletConnect } from \"wagmi/connectors\";\nimport { SimpleKitProvider } from \"@/components/simplekit\";\n\n// Make sure to replace the projectId with your own WalletConnect Project ID,\n// if you wish to use WalletConnect (recommended)!\nconst projectId = \"123...abc\";\n\n// 2. Define your Wagmi config\nconst config = createConfig({\n  chains: [mainnet],\n  connectors: [\n    injected({ target: \"metaMask\" }),\n    coinbaseWallet(),\n    walletConnect({ projectId }),\n  ],\n  transports: {\n    [mainnet.id]: http(),\n  },\n});\n\n// 3. Initialize your new QueryClient\nconst queryClient = new QueryClient();\n\n// 4. Create your Wagmi provider\nexport function Web3Provider(props: { children: React.ReactNode }) {\n  return (\n    \u003cWagmiProvider config={config}\u003e\n      \u003cQueryClientProvider client={queryClient}\u003e\n        \u003cSimpleKitProvider\u003e{props.children}\u003c/SimpleKitProvider\u003e\n      \u003c/QueryClientProvider\u003e\n    \u003c/WagmiProvider\u003e\n  );\n}\n```\n\n\u003e :warning: When using a framework that doesn't support [React Server Components](https://react.dev/learn/start-a-new-react-project#bleeding-edge-react-frameworks), you will need to remove the `\"use client\"` directive at the beginning of this file.\n\nNow that you have your `Web3Provider` component, you can wrap your app with it\n\n```tsx\nimport { Web3Provider } from \"@/components/web3-provider\";\n\nconst App = () =\u003e {\n  return (\n    \u003cWeb3Provider\u003e\n      ...\n      {children}\n      ...\n    \u003c/Web3Provider\u003e\n  );\n};\n```\n\n### 4. Install the `dialog`, `drawer`, `scroll-area`, and `button` components from shadcn/ui.\n\n```bash\npnpm dlx shadcn-ui@latest add dialog drawer scroll-area button\n```\n\n\u003e :warning: You will have to run the command below to initialize shadcn/ui if it is not already configured. Please see the official [shadcn/ui install](https://ui.shadcn.com/docs/installation) documentation if you are not using Next.js.\n\n```bash\npnpm dlx shadcn-ui@latest init\n```\n\nAlternatively, if you are not using shadcn/ui cli, you can manually copy the components from [shadcn/ui](https://ui.shadcn.com/docs) or directly copy from [dialog.tsx](src/components/ui/dialog.tsx), [drawer.tsx](src/components/ui/drawer.tsx), [scroll-area.tsx](src/components/ui/scroll-area.tsx), and [button.tsx](src/components/ui/button.tsx).\n\nIf you copied the drawer component manually, make sure to install vaul.\n\n```bash\npnpm add vaul\n```\n\n### 5. Copy the `simplekit-modal` component: [simplekit-modal.tsx](src/components/simplekit-modal.tsx)\n\nThis component is a modified version of the [Credenza](https://github.com/redpangilinan/credenza) component that combines the shadcn/ui `dialog` and `drawer`.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to show code\u003c/summary\u003e\n\n```tsx\n\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport {\n  Drawer,\n  DrawerClose,\n  DrawerContent,\n  DrawerDescription,\n  DrawerFooter,\n  DrawerHeader,\n  DrawerTitle,\n  DrawerTrigger,\n} from \"@/components/ui/drawer\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\n\ninterface BaseProps {\n  children: React.ReactNode;\n}\n\ninterface RootSimpleKitModalProps extends BaseProps {\n  open?: boolean;\n  onOpenChange?: (open: boolean) =\u003e void;\n}\n\ninterface SimpleKitModalProps extends BaseProps {\n  className?: string;\n  asChild?: true;\n}\n\nconst desktop = \"(min-width: 768px)\";\n\nconst SimpleKitModal = ({ children, ...props }: RootSimpleKitModalProps) =\u003e {\n  const isDesktop = useMediaQuery(desktop);\n  const SimpleKitModal = isDesktop ? Dialog : Drawer;\n\n  return \u003cSimpleKitModal {...props}\u003e{children}\u003c/SimpleKitModal\u003e;\n};\n\nconst SimpleKitModalTrigger = ({\n  className,\n  children,\n  ...props\n}: SimpleKitModalProps) =\u003e {\n  const isDesktop = useMediaQuery(desktop);\n  const SimpleKitModalTrigger = isDesktop ? DialogTrigger : DrawerTrigger;\n\n  return (\n    \u003cSimpleKitModalTrigger className={className} {...props}\u003e\n      {children}\n    \u003c/SimpleKitModalTrigger\u003e\n  );\n};\n\nconst SimpleKitModalClose = ({\n  className,\n  children,\n  ...props\n}: SimpleKitModalProps) =\u003e {\n  const isDesktop = useMediaQuery(desktop);\n  const SimpleKitModalClose = isDesktop ? DialogClose : DrawerClose;\n\n  return (\n    \u003cSimpleKitModalClose className={className} {...props}\u003e\n      {children}\n    \u003c/SimpleKitModalClose\u003e\n  );\n};\n\nconst SimpleKitModalContent = ({\n  className,\n  children,\n  ...props\n}: SimpleKitModalProps) =\u003e {\n  const isDesktop = useMediaQuery(desktop);\n  const SimpleKitModalContent = isDesktop ? DialogContent : DrawerContent;\n\n  return (\n    \u003cSimpleKitModalContent\n      className={cn(\n        \"rounded-t-3xl sm:rounded-3xl md:max-w-[360px] [\u0026\u003ebutton]:right-[26px] [\u0026\u003ebutton]:top-[26px]\",\n        className,\n      )}\n      onOpenAutoFocus={(e) =\u003e e.preventDefault()}\n      {...props}\n    \u003e\n      {children}\n    \u003c/SimpleKitModalContent\u003e\n  );\n};\n\nconst SimpleKitModalDescription = ({\n  className,\n  children,\n  ...props\n}: SimpleKitModalProps) =\u003e {\n  const isDesktop = useMediaQuery(desktop);\n  const SimpleKitModalDescription = isDesktop\n    ? DialogDescription\n    : DrawerDescription;\n\n  return (\n    \u003cSimpleKitModalDescription className={className} {...props}\u003e\n      {children}\n    \u003c/SimpleKitModalDescription\u003e\n  );\n};\n\nconst SimpleKitModalHeader = ({\n  className,\n  children,\n  ...props\n}: SimpleKitModalProps) =\u003e {\n  const isDesktop = useMediaQuery(desktop);\n  const SimpleKitModalHeader = isDesktop ? DialogHeader : DrawerHeader;\n\n  return (\n    \u003cSimpleKitModalHeader\n      className={cn(\"space-y-0 pb-6 md:pb-3\", className)}\n      {...props}\n    \u003e\n      {children}\n    \u003c/SimpleKitModalHeader\u003e\n  );\n};\n\nconst SimpleKitModalTitle = ({\n  className,\n  children,\n  ...props\n}: SimpleKitModalProps) =\u003e {\n  const isDesktop = useMediaQuery(desktop);\n  const SimpleKitModalTitle = isDesktop ? DialogTitle : DrawerTitle;\n\n  return (\n    \u003cSimpleKitModalTitle className={cn(\"text-center\", className)} {...props}\u003e\n      {children}\n    \u003c/SimpleKitModalTitle\u003e\n  );\n};\n\nconst SimpleKitModalBody = ({\n  className,\n  children,\n  ...props\n}: SimpleKitModalProps) =\u003e {\n  return (\n    \u003cScrollArea\n      className={cn(\n        \"h-[234px] max-h-[300px] px-6 md:-mr-4 md:h-full md:min-h-[260px] md:px-0 md:pr-4\",\n        className,\n      )}\n      {...props}\n    \u003e\n      {children}\n    \u003c/ScrollArea\u003e\n  );\n};\n\nconst SimpleKitModalFooter = ({\n  className,\n  children,\n  ...props\n}: SimpleKitModalProps) =\u003e {\n  const isDesktop = useMediaQuery(desktop);\n  const SimpleKitModalFooter = isDesktop ? DialogFooter : DrawerFooter;\n\n  return (\n    \u003cSimpleKitModalFooter\n      className={cn(\"py-3.5 md:py-0\", className)}\n      {...props}\n    \u003e\n      {children}\n    \u003c/SimpleKitModalFooter\u003e\n  );\n};\n\nexport {\n  SimpleKitModal,\n  SimpleKitModalTrigger,\n  SimpleKitModalClose,\n  SimpleKitModalContent,\n  SimpleKitModalDescription,\n  SimpleKitModalHeader,\n  SimpleKitModalTitle,\n  SimpleKitModalBody,\n  SimpleKitModalFooter,\n};\n\n/*\n * Hook used to calculate the width of the screen using the\n * MediaQueryListEvent. This can be moved to a separate file\n * if desired (src/hooks/use-media-query.tsx).\n */\nexport function useMediaQuery(query: string) {\n  const [value, setValue] = React.useState(false);\n\n  React.useEffect(() =\u003e {\n    function onChange(event: MediaQueryListEvent) {\n      setValue(event.matches);\n    }\n\n    const result = matchMedia(query);\n    result.addEventListener(\"change\", onChange);\n    setValue(result.matches);\n\n    return () =\u003e result.removeEventListener(\"change\", onChange);\n  }, [query]);\n\n  return value;\n}\n```\n\n\u003c/details\u003e\n\n\u003e :warning: When using a framework that doesn't support [React Server Components](https://react.dev/learn/start-a-new-react-project#bleeding-edge-react-frameworks), you will need to remove the `\"use client\"` directive at the beginning of this file.\n\n### 6. Copy the `simplekit` component: [simplekit.tsx](src/components/simplekit.tsx)\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to show code\u003c/summary\u003e\n\n```tsx\n\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  SimpleKitModal,\n  SimpleKitModalBody,\n  SimpleKitModalContent,\n  SimpleKitModalDescription,\n  SimpleKitModalFooter,\n  SimpleKitModalHeader,\n  SimpleKitModalTitle,\n} from \"@/components/simplekit-modal\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  type Connector,\n  useAccount,\n  useConnect,\n  useDisconnect,\n  useEnsAvatar,\n  useEnsName,\n  useBalance,\n} from \"wagmi\";\nimport { formatEther } from \"viem\";\nimport { Check, ChevronLeft, Copy, RotateCcw } from \"lucide-react\";\n\nconst MODAL_CLOSE_DURATION = 320;\n\nconst SimpleKitContext = React.createContext\u003c{\n  pendingConnector: Connector | null;\n  setPendingConnector: React.Dispatch\u003cReact.SetStateAction\u003cConnector | null\u003e\u003e;\n  isConnectorError: boolean;\n  setIsConnectorError: React.Dispatch\u003cReact.SetStateAction\u003cboolean\u003e\u003e;\n  open: boolean;\n  setOpen: React.Dispatch\u003cReact.SetStateAction\u003cboolean\u003e\u003e;\n}\u003e({\n  pendingConnector: null,\n  setPendingConnector: () =\u003e null,\n  isConnectorError: false,\n  setIsConnectorError: () =\u003e false,\n  open: false,\n  setOpen: () =\u003e false,\n});\n\nfunction SimpleKitProvider(props: { children: React.ReactNode }) {\n  const { status, address } = useAccount();\n  const [pendingConnector, setPendingConnector] =\n    React.useState\u003cConnector | null\u003e(null);\n  const [isConnectorError, setIsConnectorError] = React.useState(false);\n  const [open, setOpen] = React.useState(false);\n  const isConnected = address \u0026\u0026 !pendingConnector;\n\n  React.useEffect(() =\u003e {\n    if (status === \"connected\" \u0026\u0026 pendingConnector) {\n      setOpen(false);\n\n      const timeout = setTimeout(() =\u003e {\n        setPendingConnector(null);\n        setIsConnectorError(false);\n      }, MODAL_CLOSE_DURATION);\n\n      return () =\u003e clearTimeout(timeout);\n    }\n  }, [status, setOpen, pendingConnector, setPendingConnector]);\n\n  return (\n    \u003cSimpleKitContext.Provider\n      value={{\n        pendingConnector,\n        setPendingConnector,\n        isConnectorError,\n        setIsConnectorError,\n        open,\n        setOpen,\n      }}\n    \u003e\n      {props.children}\n      \u003cSimpleKitModal open={open} onOpenChange={setOpen}\u003e\n        \u003cSimpleKitModalContent\u003e\n          {isConnected ? \u003cAccount /\u003e : \u003cConnectors /\u003e}\n        \u003c/SimpleKitModalContent\u003e\n      \u003c/SimpleKitModal\u003e\n    \u003c/SimpleKitContext.Provider\u003e\n  );\n}\n\nfunction ConnectWalletButton() {\n  const simplekit = useSimpleKit();\n  const { address } = useAccount();\n  const { data: ensName } = useEnsName({ address });\n  const { data: ensAvatar } = useEnsAvatar({ name: ensName! });\n\n  return (\n    \u003cButton onClick={simplekit.toggleModal} className=\"rounded-xl\"\u003e\n      {simplekit.isConnected ? (\n        \u003c\u003e\n          {ensAvatar \u0026\u0026 \u003cimg src={ensAvatar} alt=\"ENS Avatar\" /\u003e}\n          {address \u0026\u0026 (\n            \u003cspan\u003e{ensName ? `${ensName}` : simplekit.formattedAddress}\u003c/span\u003e\n          )}\n        \u003c/\u003e\n      ) : (\n        \"Connect Wallet\"\n      )}\n    \u003c/Button\u003e\n  );\n}\n\nfunction Account() {\n  const { address } = useAccount();\n  const { disconnect } = useDisconnect();\n  const { data: ensName } = useEnsName({ address });\n  const { data: userBalance } = useBalance({ address });\n  const context = React.useContext(SimpleKitContext);\n\n  const formattedAddress = address?.slice(0, 6) + \"•••\" + address?.slice(-4);\n  const formattedUserBalace = userBalance?.value\n    ? parseFloat(formatEther(userBalance.value)).toFixed(4)\n    : undefined;\n\n  function handleDisconnect() {\n    context.setOpen(false);\n    setTimeout(() =\u003e {\n      disconnect();\n    }, MODAL_CLOSE_DURATION);\n  }\n\n  return (\n    \u003c\u003e\n      \u003cSimpleKitModalHeader\u003e\n        \u003cSimpleKitModalTitle\u003eConnected\u003c/SimpleKitModalTitle\u003e\n        \u003cSimpleKitModalDescription className=\"sr-only\"\u003e\n          Account modal for your connected Web3 wallet.\n        \u003c/SimpleKitModalDescription\u003e\n      \u003c/SimpleKitModalHeader\u003e\n      \u003cSimpleKitModalBody className=\"h-[280px]\"\u003e\n        \u003cdiv className=\"flex w-full flex-col items-center justify-center gap-8 md:pt-5\"\u003e\n          \u003cdiv className=\"size-24 flex items-center justify-center\"\u003e\n            \u003cimg\n              className=\"rounded-full\"\n              src={`https://avatar.vercel.sh/${address}?size=150`}\n              alt=\"User gradient avatar\"\n            /\u003e\n          \u003c/div\u003e\n\n          \u003cdiv className=\"space-y-1 px-3.5 text-center sm:px-0\"\u003e\n            \u003cdiv className=\"flex items-center gap-1.5\"\u003e\n              \u003ch1 className=\"text-xl font-semibold\"\u003e\n                \u003cdiv\u003e{ensName ? `${ensName}` : formattedAddress}\u003c/div\u003e\n              \u003c/h1\u003e\n              \u003cCopyAddressButton /\u003e\n            \u003c/div\u003e\n            \u003cp className=\"text-balance text-sm text-muted-foreground\"\u003e\n              {`${formattedUserBalace ?? \"0.00\"} ETH`}\n            \u003c/p\u003e\n          \u003c/div\u003e\n\n          \u003cButton className=\"w-full rounded-xl\" onClick={handleDisconnect}\u003e\n            Disconnect\n          \u003c/Button\u003e\n        \u003c/div\u003e\n      \u003c/SimpleKitModalBody\u003e\n    \u003c/\u003e\n  );\n}\n\nfunction Connectors() {\n  const context = React.useContext(SimpleKitContext);\n\n  return (\n    \u003c\u003e\n      \u003cSimpleKitModalHeader\u003e\n        \u003cBackChevron /\u003e\n        \u003cSimpleKitModalTitle\u003e\n          {context.pendingConnector?.name ?? \"Connect Wallet\"}\n        \u003c/SimpleKitModalTitle\u003e\n        \u003cSimpleKitModalDescription className=\"sr-only\"\u003e\n          Connect your Web3 wallet or create a new one.\n        \u003c/SimpleKitModalDescription\u003e\n      \u003c/SimpleKitModalHeader\u003e\n      \u003cSimpleKitModalBody\u003e\n        {context.pendingConnector ? \u003cWalletConnecting /\u003e : \u003cWalletOptions /\u003e}\n      \u003c/SimpleKitModalBody\u003e\n      \u003cSimpleKitModalFooter\u003e\n        \u003cdiv className=\"h-0\" /\u003e\n      \u003c/SimpleKitModalFooter\u003e\n    \u003c/\u003e\n  );\n}\n\nfunction WalletConnecting() {\n  const context = React.useContext(SimpleKitContext);\n\n  return (\n    \u003cdiv className=\"flex w-full flex-col items-center justify-center gap-9 md:pt-5\"\u003e\n      {context.pendingConnector?.icon \u0026\u0026 (\n        \u003cdiv className=\"size-[116px] relative flex items-center justify-center rounded-2xl border p-3\"\u003e\n          \u003cimg\n            src={context.pendingConnector?.icon}\n            alt={context.pendingConnector?.name}\n            className=\"size-full overflow-hidden rounded-2xl\"\n          /\u003e\n          \u003cimg /\u003e\n          {context.isConnectorError ? \u003cRetryConnectorButton /\u003e : null}\n        \u003c/div\u003e\n      )}\n\n      \u003cdiv className=\"space-y-3.5 px-3.5 text-center sm:px-0\"\u003e\n        \u003ch1 className=\"text-xl font-semibold\"\u003e\n          {context.isConnectorError ? \"Request Error\" : \"Requesting Connection\"}\n        \u003c/h1\u003e\n        \u003cp className=\"text-balance text-sm text-muted-foreground\"\u003e\n          {context.isConnectorError\n            ? \"There was an error with the request. Click above to try again.\"\n            : `Open the ${context.pendingConnector?.name} browser extension to connect your wallet.`}\n        \u003c/p\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n}\n\nfunction WalletOptions() {\n  const context = React.useContext(SimpleKitContext);\n  const { connectors, connect } = useConnectors();\n\n  return (\n    \u003cdiv className=\"flex flex-col gap-3.5\"\u003e\n      {connectors.map((connector) =\u003e (\n        \u003cWalletOption\n          key={connector.uid}\n          connector={connector}\n          onClick={() =\u003e {\n            context.setIsConnectorError(false);\n            context.setPendingConnector(connector);\n            connect({ connector });\n          }}\n        /\u003e\n      ))}\n    \u003c/div\u003e\n  );\n}\n\nfunction WalletOption(props: { connector: Connector; onClick: () =\u003e void }) {\n  const [ready, setReady] = React.useState(false);\n\n  React.useEffect(() =\u003e {\n    async function checkReady() {\n      const provider = await props.connector.getProvider();\n      setReady(!!provider);\n    }\n    checkReady()\n      .then(() =\u003e null)\n      .catch(() =\u003e null);\n  }, [props.connector]);\n\n  return (\n    \u003cButton\n      disabled={!ready}\n      onClick={props.onClick}\n      size=\"lg\"\n      variant=\"secondary\"\n      className=\"justify-between rounded-xl px-4 py-7 text-base font-semibold\"\n    \u003e\n      \u003cp\u003e{props.connector.name}\u003c/p\u003e\n      {props.connector.icon \u0026\u0026 (\n        \u003cimg\n          src={props.connector.icon}\n          alt={props.connector.name}\n          className=\"size-8 overflow-hidden rounded-[6px]\"\n        /\u003e\n      )}\n    \u003c/Button\u003e\n  );\n}\n\nfunction CopyAddressButton() {\n  const { address } = useAccount();\n  const [copied, setCopied] = React.useState(false);\n\n  React.useEffect(() =\u003e {\n    const timeout = setTimeout(() =\u003e {\n      if (copied) setCopied(false);\n    }, 1000);\n    return () =\u003e clearTimeout(timeout);\n  }, [copied, setCopied]);\n\n  async function handleCopy() {\n    setCopied(true);\n    await navigator.clipboard.writeText(address!);\n  }\n\n  return (\n    \u003cbutton className=\"text-muted-foreground\" onClick={handleCopy}\u003e\n      {copied ? (\n        \u003cCheck className=\"size-4\" strokeWidth={4} /\u003e\n      ) : (\n        \u003cCopy className=\"size-4\" strokeWidth={4} /\u003e\n      )}\n    \u003c/button\u003e\n  );\n}\n\nfunction BackChevron() {\n  const context = React.useContext(SimpleKitContext);\n\n  if (!context.pendingConnector) {\n    return null;\n  }\n\n  function handleClick() {\n    context.setIsConnectorError(false);\n    context.setPendingConnector(null);\n  }\n\n  return (\n    \u003cbutton\n      className=\"absolute left-[26px] top-[42px] z-50 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground md:top-[26px]\"\n      onClick={handleClick}\n    \u003e\n      \u003cChevronLeft className=\"h-4 w-4\" /\u003e\n      \u003cspan className=\"sr-only\"\u003eCancel connection\u003c/span\u003e\n    \u003c/button\u003e\n  );\n}\n\nfunction RetryConnectorButton() {\n  const context = React.useContext(SimpleKitContext);\n  const { connect } = useConnect({\n    mutation: {\n      onError: () =\u003e context.setIsConnectorError(true),\n    },\n  });\n\n  function handleClick() {\n    if (context.pendingConnector) {\n      context.setIsConnectorError(false);\n      connect({ connector: context.pendingConnector });\n    }\n  }\n\n  return (\n    \u003cButton\n      size=\"icon\"\n      variant=\"secondary\"\n      className=\"group absolute -bottom-2 -right-2 rounded-full bg-muted p-1.5 shadow\"\n      onClick={handleClick}\n    \u003e\n      \u003cRotateCcw className=\"size-4 transition-transform group-hover:-rotate-45\" /\u003e\n    \u003c/Button\u003e\n  );\n}\n\nfunction useConnectors() {\n  const context = React.useContext(SimpleKitContext);\n  const { connect, connectors } = useConnect({\n    mutation: {\n      onError: () =\u003e context.setIsConnectorError(true),\n    },\n  });\n\n  const sortedConnectors = React.useMemo(() =\u003e {\n    let metaMaskConnector: Connector | undefined;\n    let injectedConnector: Connector | undefined;\n\n    const formattedConnectors = connectors.reduce(\n      (acc: Array\u003cConnector\u003e, curr) =\u003e {\n        console.log(curr.id);\n        switch (curr.id) {\n          case \"metaMaskSDK\":\n            metaMaskConnector = {\n              ...curr,\n              icon: \"https://utfs.io/f/be0bd88f-ce87-4cbc-b2e5-c578fa866173-sq4a0b.png\",\n            };\n            return acc;\n          case \"metaMask\":\n            injectedConnector = {\n              ...curr,\n              icon: \"https://utfs.io/f/be0bd88f-ce87-4cbc-b2e5-c578fa866173-sq4a0b.png\",\n            };\n            return acc;\n          case \"safe\":\n            acc.push({\n              ...curr,\n              icon: \"https://utfs.io/f/164ea200-3e15-4a9b-9ce5-a397894c442a-awpd29.png\",\n            });\n            return acc;\n          case \"coinbaseWalletSDK\":\n            acc.push({\n              ...curr,\n              icon: \"https://utfs.io/f/53e47f86-5f12-404f-a98b-19dc7b760333-chngxw.png\",\n            });\n            return acc;\n          case \"walletConnect\":\n            acc.push({\n              ...curr,\n              icon: \"https://utfs.io/f/5bfaa4d1-b872-48a7-9d37-c2517d4fc07a-utlf4g.png\",\n            });\n            return acc;\n          default:\n            acc.unshift(curr);\n            return acc;\n        }\n      },\n      [],\n    );\n\n    if (\n      metaMaskConnector \u0026\u0026\n      !formattedConnectors.find(\n        ({ id }) =\u003e\n          id === \"io.metamask\" ||\n          id === \"io.metamask.mobile\" ||\n          id === \"injected\",\n      )\n    ) {\n      return [metaMaskConnector, ...formattedConnectors];\n    }\n\n    if (injectedConnector) {\n      const nonMetaMaskConnectors = formattedConnectors.filter(\n        ({ id }) =\u003e id !== \"io.metamask\" \u0026\u0026 id !== \"io.metamask.mobile\",\n      );\n      return [injectedConnector, ...nonMetaMaskConnectors];\n    }\n    return formattedConnectors;\n  }, [connectors]);\n\n  return { connectors: sortedConnectors, connect };\n}\n\n/*\n * This hook can be moved to a separate file\n * if desired (src/hooks/use-simple-kit.tsx).\n */\nfunction useSimpleKit() {\n  const { address } = useAccount();\n  const context = React.useContext(SimpleKitContext);\n\n  const isModalOpen = context.open;\n  const isConnected = address \u0026\u0026 !context.pendingConnector;\n  const formattedAddress = address?.slice(0, 6) + \"•••\" + address?.slice(-4);\n\n  function open() {\n    context.setOpen(true);\n  }\n\n  function close() {\n    context.setOpen(false);\n  }\n\n  function toggleModal() {\n    context.setOpen((prevState) =\u003e !prevState);\n  }\n\n  return {\n    isModalOpen,\n    isConnected,\n    formattedAddress,\n    open,\n    close,\n    toggleModal,\n  };\n}\n\nexport {\n  SimpleKitProvider,\n  ConnectWalletButton,\n  useSimpleKit,\n  SimpleKitContext,\n};\n```\n\n\u003c/details\u003e\n\n\u003e :warning: When using a framework that doesn't support [React Server Components](https://react.dev/learn/start-a-new-react-project#bleeding-edge-react-frameworks), you will need to remove the `\"use client\"` directive at the beginning of this file.\n\n### 7. Update the import paths based on your project structure.\n\n## Usage\n\nAn example [connect wallet button](https://github.com/vaunblu/SimpleKit/blob/main/src/components/simplekit.tsx#L87) component is exported from `simplekit` and can be used as follows\n\n```tsx\nimport { ConnectWalletButton } from \"@/components/simplekit\";\n```\n\n```tsx\n\u003cConnectWalletButton /\u003e\n```\n\nA [useSimpleKit](https://github.com/vaunblu/SimpleKit/blob/main/src/components/simplekit.tsx#L430) hook is also exported from `simplekit` and can be used to trigger the SimpleKit modal from any component. Below is an example of a custom component that opens the SimpleKit modal.\n\n```tsx\n\"use client\";\n\nimport { useSimpleKit } from \"@/components/simplekit\";\nimport { Button } from \"@/components/ui/button\";\n\nexport function OpenModalButton() {\n  const simplekit = useSimpleKit();\n\n  return \u003cButton onClick={simplekit.open}\u003eOpen SimpleKit Modal\u003c/Button\u003e;\n}\n```\n\n## Additional Build Tooling Setup\n\n### Next.js support and using SSR with the app router\n\nSimpleKit uses [WalletConnect's](https://walletconnect.com/) SDK to help with connecting wallets. WalletConnect 2.0 pulls in Node.js dependencies that Next.js does not support by default. You can mitigate this by adding the following to your `next.config.js` file:\n\n```js\nconst nextConfig = {\n  webpack: (config) =\u003e {\n    config.resolve.fallback = { fs: false, net: false, tls: false };\n    config.externals.push(\"pino-pretty\", \"encoding\");\n    return config;\n  },\n};\n```\n\nIf you are looking to use SimpleKit with the Next.js app router, you can follow the official [Wagmi SSR](https://wagmi.sh/react/guides/ssr) documentation or change the following:\n\n1. Copy the `wagmi-config` file: [wagmi-config.ts](src/lib/wagmi-config.ts)\n\nThe config needs to be separate from the WagmiProvider given that is a client component with the `\"use client\"` directive.\n\n```ts\nimport { http, createConfig, cookieStorage, createStorage } from \"wagmi\";\nimport { mainnet } from \"wagmi/chains\";\nimport { injected, coinbaseWallet, walletConnect } from \"wagmi/connectors\";\n\n// Make sure to replace the projectId with your own WalletConnect Project ID,\n// if you wish to use WalletConnect (recommended)!\nconst projectId = \"123...abc\";\n\nexport function getConfig() {\n  return createConfig({\n    chains: [mainnet],\n    connectors: [\n      injected({ target: \"metaMask\" }),\n      coinbaseWallet(),\n      walletConnect({ projectId }),\n    ],\n    ssr: true,\n    storage: createStorage({\n      storage: cookieStorage,\n    }),\n    transports: {\n      [mainnet.id]: http(),\n    },\n  });\n}\n\nexport const config = getConfig();\n```\n\nThis version of the config sets the `ssr` config property to `true` and uses Wagmi's `createStorage` to initialize a `cookieStorage` on the `storage` config property.\n\n2. Hydrate your cookie in Layout\n\nIn our [layout.tsx](src/app/layout.tsx) file (a Server Component), we will need to extract the cookie from the `headers` function and pass it to `cookieToInitialState`.\n\nWe use the `getConfig()` helper from [wagmi-config.ts](src/lib/wagmi-config) to pass in `cookieToInitialState`.\n\n```tsx\nimport { Web3Provider } from \"@/components/web3-provider\";\nimport { headers } from \"next/headers\";\nimport { cookieToInitialState } from \"wagmi\";\nimport { getConfig } from \"@/lib/wagmi-config\";\n...\n\nexport default function Layout() {\n  ...\n  const initialState = cookieToInitialState(\n    getConfig(),\n    headers().get(\"cookie\"),\n  );\n\n  return (\n    \u003cWeb3Provider initialState={initialState}\u003e\n      ...\n      {children}\n      ...\n    \u003c/Web3Provider\u003e\n  );\n}\n```\n\n3. Replace the contents of `web3-provider` with the content from the `web3-provider-ssr` component: [web3-provider-ssr.tsx](src/components/web3-provider-ssr.tsx)\n\n```tsx\n\"use client\";\n\n// 1. Import modules\nimport * as React from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { WagmiProvider, State } from \"wagmi\";\nimport { getConfig } from \"@/lib/wagmi-config\";\nimport { SimpleKitProvider } from \"@/components/simplekit\";\n\n// 2. Define your Wagmi config\nconst config = getConfig();\n\n// 3. Initialize your new QueryClient\nconst queryClient = new QueryClient();\n\n// 4. Create your Wagmi provider\nexport function Web3Provider(props: {\n  initialState: State | undefined;\n  children: React.ReactNode;\n}) {\n  return (\n    \u003cWagmiProvider config={config} initialState={props.initialState}\u003e\n      \u003cQueryClientProvider client={queryClient}\u003e\n        \u003cSimpleKitProvider\u003e{props.children}\u003c/SimpleKitProvider\u003e\n      \u003c/QueryClientProvider\u003e\n    \u003c/WagmiProvider\u003e\n  );\n}\n```\n\nThe two changes here are we use `getConfig()` to initialize our Wagmi config and our `WagmiProvider` consumes the `initialState` we passed from our Layout.\n\n---\n\n### Vaul background scaling\n\nIf you want to enable background scaling, wrap your app with the `vaul-drawer-wrapper`.\n\n```html\n\u003cdiv vaul-drawer-wrapper=\"\" className=\"bg-background\"\u003e{children}\u003c/div\u003e\n```\n\nSee my implementation at [layout.tsx](src/app/layout.tsx). Make sure to update the background color to match your project's theme.\n\n---\n\n### Local connector icons\n\nImported Wagmi connectors do not have their own icons. I provided URLs to hosted files so you don't need to worry about them. However, if you want to self host your icons you can copy the files in the [icons](public/icons) directory into your `public` folder.\n\nThen change the following code in your `simplekit` component:\n\n```tsx\nconst formattedConnectors = connectors.reduce((acc: Array\u003cConnector\u003e, curr) =\u003e {\n  switch (curr.id) {\n    case \"metaMaskSDK\":\n      metaMaskConnector = {\n        ...curr,\n        icon: \"/icons/metamask-icon.png\",\n      };\n      return acc;\n    case \"injected\":\n      injectedConnector = {\n        ...curr,\n        icon: \"/icons/metamask-icon.png\",\n      };\n      return acc;\n    case \"safe\":\n      acc.push({\n        ...curr,\n        icon: \"/icons/gnosis-safe-icon.png\",\n      });\n      return acc;\n    case \"coinbaseWalletSDK\":\n      acc.push({\n        ...curr,\n        icon: \"coinbase-icon.png\",\n      });\n      return acc;\n    case \"walletConnect\":\n      acc.push({\n        ...curr,\n        icon: \"wallet-connect-icon.png\",\n      });\n      return acc;\n    default:\n      acc.unshift(curr);\n      return acc;\n  }\n}, []);\n```\n\n## Credits\n\n- [shadcn/ui](https://github.com/shadcn-ui/ui) by [shadcn](https://github.com/shadcn)\n- [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski](https://github.com/emilkowalski)\n- [Credenza](https://github.com/redpangilinan/credenza) by [redpangilinan](https://github.com/redpangilinan)\n- [ConnectKit](https://docs.family.co/connectkit) by [Family](https://family.co/)\n","funding_links":[],"categories":["Libs and Components","Components","Components \u0026 Libraries"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvaunblu%2FSimpleKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvaunblu%2FSimpleKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvaunblu%2FSimpleKit/lists"}