diff --git a/.changeset/two-turtles-march.md b/.changeset/two-turtles-march.md
new file mode 100644
index 00000000..5b576807
--- /dev/null
+++ b/.changeset/two-turtles-march.md
@@ -0,0 +1,5 @@
+---
+'@web3-ui/components': minor
+---
+
+Added a TokenGate component that restricts access to child components unless erc-20/erc-721 token quantity requirements are met.
diff --git a/packages/components/src/components/TokenGate/TokenGate.stories.tsx b/packages/components/src/components/TokenGate/TokenGate.stories.tsx
new file mode 100644
index 00000000..e4f01aa3
--- /dev/null
+++ b/packages/components/src/components/TokenGate/TokenGate.stories.tsx
@@ -0,0 +1,110 @@
+import React from 'react';
+import { TokenGate } from '.';
+import { NETWORKS, Provider, useTokenBalance, useWallet } from '@web3-ui/hooks';
+import { Text } from '@chakra-ui/layout';
+import { Button } from '@chakra-ui/react';
+export default {
+ title: 'Components/TokenGate',
+ component: TokenGate,
+ parameters: {
+ // TODO: Fix window.ethereum is undefined breaking chromatic
+ chromatic: { disableSnapshot: true },
+ },
+};
+
+const Component = ({ ...props }) => {
+ /**
+ * requiredQuantity was done this way because when requiredQuantity is not passed to the component, the required quantity
+ * should default to 1 not 0
+ */
+ const requiredQuantity = props.requiredQuantity === undefined ? 1 : props.requiredQuantity;
+ return (
+ <>
+
+
+ {`This is the child component of TokenGate. You were able to access this component because you hold at least ${requiredQuantity} token. Your token balance: ${props.walletBalance} `}
+
+
+ >
+ );
+};
+
+const WithUseWallet = ({ ...props }) => {
+ const { connected, connectWallet, connection } = useWallet();
+ const { formattedBalance, error } = useTokenBalance({
+ // GTC token contract address
+ tokenAddress: '0xde30da39c46104798bb5aa3fe8b9e0e1f348163f',
+ accountAddress: connection.userAddress!,
+ });
+ // TokenGate only returned if there is a connection and a balance. Done this way to accomplish rendering the loading state.
+ // Using the loading state from useTokenBalance would not work because loading status changes simultaneously with connected status
+ if (connected && formattedBalance) {
+ return (
+ <>
+
+
+ {`This is the child component of TokenGate. You were able to access this component because you hold at least ${
+ props.requiredQuantity === undefined ? 1 : props.requiredQuantity
+ } of the required token: GTC`}
+
+
+ >
+ );
+ }
+
+ if (error) {
+ return Error occured while trying to fetch balance.;
+ }
+ // Using the loading state from useTokenBalance hook does not work here because connected status and loading status change simultaneously.
+ return !connected ? (
+
+ ) : (
+ Loading...
+ );
+};
+
+export const Default = () => ;
+
+export const UsingWeb3Hooks = () => {
+ return (
+
+
+
+ );
+};
+
+export const AccessGrantedDefault = () => ;
+
+export const AccessDeniedDefault = () => (
+
+);
+
+/**
+ * Example of custom access denied node for the deniedMessage prop
+ */
+const DeniedAccess = props => (
+
+
This is a custom component for when access is denied
+
+
Make sure your wallet is connected
+
Verify you are connected to the correct address
+
+ {`Make sure you hold the number of tokens required to access this component:
+ ${props.requiredQuantity === undefined ? 1 : props.requiredQuantity}`}
+
+
Not providing a "deniedMessage" will return null when access is denied
+
+
+);
+
+export const AccessDeniedWithCustomMessage = () => (
+ }
+ label='Denied With Message'
+ />
+);
diff --git a/packages/components/src/components/TokenGate/TokenGate.tsx b/packages/components/src/components/TokenGate/TokenGate.tsx
new file mode 100644
index 00000000..ecb78b8a
--- /dev/null
+++ b/packages/components/src/components/TokenGate/TokenGate.tsx
@@ -0,0 +1,35 @@
+import React, { ReactNode } from 'react';
+export interface TokenGateProps {
+ /**
+ * The balance of the required token held in wallet
+ */
+ walletBalance: number;
+ /**
+ * The token quantity required to access child component. Default=1
+ */
+ requiredQuantity?: number;
+ /**
+ * Child nodes
+ */
+ children: ReactNode;
+ /**
+ * Optional message if access denied
+ */
+ deniedMessage?: ReactNode;
+}
+export const TokenGate: React.FC = ({
+ walletBalance,
+ requiredQuantity = 1,
+ children,
+ deniedMessage,
+}) => {
+ // return children within simple container
+ return (
+ // verify token quantity in wallet is greater than required amount(optional, defaults to 1)
+ walletBalance >= requiredQuantity ? (
+ <>{children}>
+ ) : deniedMessage ? (
+ <>{deniedMessage}>
+ ) : null
+ );
+};
diff --git a/packages/components/src/components/TokenGate/index.ts b/packages/components/src/components/TokenGate/index.ts
new file mode 100644
index 00000000..81ad74eb
--- /dev/null
+++ b/packages/components/src/components/TokenGate/index.ts
@@ -0,0 +1 @@
+export * from './TokenGate';