diff --git a/packages/nextjs/app/blockexplorer/_components/AddressCodeTab.tsx b/packages/nextjs/app/blockexplorer/_components/AddressCodeTab.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx b/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/_components/AddressLogsTab.tsx b/packages/nextjs/app/blockexplorer/_components/AddressLogsTab.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/_components/AddressStorageTab.tsx b/packages/nextjs/app/blockexplorer/_components/AddressStorageTab.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/_components/BackButton.tsx b/packages/nextjs/app/blockexplorer/_components/BackButton.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx b/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/_components/PaginationButton.tsx b/packages/nextjs/app/blockexplorer/_components/PaginationButton.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/_components/SearchBar.tsx b/packages/nextjs/app/blockexplorer/_components/SearchBar.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/_components/TransactionHash.tsx b/packages/nextjs/app/blockexplorer/_components/TransactionHash.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx b/packages/nextjs/app/blockexplorer/_components/TransactionsTable.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/_components/index.tsx b/packages/nextjs/app/blockexplorer/_components/index.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/address/[address]/page.tsx b/packages/nextjs/app/blockexplorer/address/[address]/page.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/layout.tsx b/packages/nextjs/app/blockexplorer/layout.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/page.tsx b/packages/nextjs/app/blockexplorer/page.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/transaction/[txHash]/page.tsx b/packages/nextjs/app/blockexplorer/transaction/[txHash]/page.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/blockexplorer/transaction/_components/TransactionComp.tsx b/packages/nextjs/app/blockexplorer/transaction/_components/TransactionComp.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/config/index.ts b/packages/nextjs/app/config/index.ts
new file mode 100755
index 0000000..6f44024
--- /dev/null
+++ b/packages/nextjs/app/config/index.ts
@@ -0,0 +1 @@
+export { defaultSnapOrigin } from './snap';
diff --git a/packages/nextjs/app/config/snap.ts b/packages/nextjs/app/config/snap.ts
new file mode 100755
index 0000000..d17b20b
--- /dev/null
+++ b/packages/nextjs/app/config/snap.ts
@@ -0,0 +1,11 @@
+/**
+ * The snap origin to use.
+ * Will default to the local hosted snap if no value is provided in environment.
+ *
+ * You may be tempted to change this to the URL where your production snap is hosted, but please
+ * don't. Instead, rename `.env.production.dist` to `.env.production` and set the production URL
+ * there. Running `yarn build` will automatically use the production environment variables.
+ */
+export const defaultSnapOrigin =
+ // eslint-disable-next-line no-restricted-globals
+ process.env.SNAP_ORIGIN ?? `local:http://localhost:8080`;
diff --git a/packages/nextjs/app/config/theme.ts b/packages/nextjs/app/config/theme.ts
new file mode 100755
index 0000000..cd0cc2a
--- /dev/null
+++ b/packages/nextjs/app/config/theme.ts
@@ -0,0 +1,187 @@
+import type { DefaultTheme } from 'styled-components';
+import { createGlobalStyle } from 'styled-components';
+
+const breakpoints = ['600px', '768px', '992px'];
+
+/**
+ * Common theme properties.
+ */
+const theme = {
+ fonts: {
+ default:
+ '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
+ code: 'ui-monospace,Menlo,Monaco,"Cascadia Mono","Segoe UI Mono","Roboto Mono","Oxygen Mono","Ubuntu Monospace","Source Code Pro","Fira Mono","Droid Sans Mono","Courier New", monospace',
+ },
+ fontSizes: {
+ heading: '5.2rem',
+ mobileHeading: '3.6rem',
+ title: '2.4rem',
+ large: '2rem',
+ text: '1.6rem',
+ small: '1.4rem',
+ },
+ radii: {
+ default: '24px',
+ button: '8px',
+ },
+ breakpoints,
+ mediaQueries: {
+ small: `@media screen and (max-width: ${breakpoints[0] as string})`,
+ medium: `@media screen and (min-width: ${breakpoints[1] as string})`,
+ large: `@media screen and (min-width: ${breakpoints[2] as string})`,
+ },
+ shadows: {
+ default: '0px 7px 42px rgba(0, 0, 0, 0.1)',
+ button: '0px 0px 16.1786px rgba(0, 0, 0, 0.15);',
+ },
+};
+
+/**
+ * Light theme color properties.
+ */
+export const light: DefaultTheme = {
+ colors: {
+ background: {
+ default: '#FFFFFF',
+ alternative: '#F2F4F6',
+ inverse: '#141618',
+ },
+ icon: {
+ default: '#141618',
+ alternative: '#BBC0C5',
+ },
+ text: {
+ default: '#24272A',
+ muted: '#6A737D',
+ alternative: '#535A61',
+ inverse: '#FFFFFF',
+ },
+ border: {
+ default: '#BBC0C5',
+ },
+ primary: {
+ default: '#6F4CFF',
+ inverse: '#FFFFFF',
+ },
+ card: {
+ default: '#FFFFFF',
+ },
+ error: {
+ default: '#d73a49',
+ alternative: '#b92534',
+ muted: '#d73a4919',
+ },
+ },
+ ...theme,
+};
+
+/**
+ * Dark theme color properties
+ */
+export const dark: DefaultTheme = {
+ colors: {
+ background: {
+ default: '#24272A',
+ alternative: '#141618',
+ inverse: '#FFFFFF',
+ },
+ icon: {
+ default: '#FFFFFF',
+ alternative: '#BBC0C5',
+ },
+ text: {
+ default: '#FFFFFF',
+ muted: '#FFFFFF',
+ alternative: '#D6D9DC',
+ inverse: '#24272A',
+ },
+ border: {
+ default: '#848C96',
+ },
+ primary: {
+ default: '#6F4CFF',
+ inverse: '#FFFFFF',
+ },
+ card: {
+ default: '#141618',
+ },
+ error: {
+ default: '#d73a49',
+ alternative: '#b92534',
+ muted: '#d73a4919',
+ },
+ },
+ ...theme,
+};
+
+/**
+ * Default style applied to the app.
+ *
+ * @param props - Styled Components props.
+ * @returns Global style React component.
+ */
+export const GlobalStyle = createGlobalStyle`
+ html {
+ /* 62.5% of the base size of 16px = 10px.*/
+ font-size: 62.5%;
+ }
+
+ body {
+ background-color: ${(props) => props.theme.colors.background?.default};
+ color: ${(props) => props.theme.colors.text?.default};
+ font-family: ${(props) => props.theme.fonts.default};
+ font-size: ${(props) => props.theme.fontSizes.text};
+ margin: 0;
+ }
+
+ * {
+ transition: background-color .1s linear;
+ }
+
+ h1, h2, h3, h4, h5, h6 {
+ font-size: ${(props) => props.theme.fontSizes.heading};
+ ${(props) => props.theme.mediaQueries.small} {
+ font-size: ${(props) => props.theme.fontSizes.mobileHeading};
+ }
+ }
+
+ code {
+ background-color: ${(props) => props.theme.colors.background?.alternative};
+ font-family: ${(props) => props.theme.fonts.code};
+ padding: 1.2rem;
+ font-weight: normal;
+ font-size: ${(props) => props.theme.fontSizes.text};
+ }
+
+ button {
+ font-size: ${(props) => props.theme.fontSizes.small};
+ border-radius: ${(props) => props.theme.radii.button};
+ background-color: ${(props) => props.theme.colors.background?.inverse};
+ color: ${(props) => props.theme.colors.text?.inverse};
+ border: 1px solid ${(props) => props.theme.colors.background?.inverse};
+ font-weight: bold;
+ padding: 1rem;
+ min-height: 4.2rem;
+ cursor: pointer;
+ transition: all .2s ease-in-out;
+
+ &:hover {
+ background-color: transparent;
+ border: 1px solid ${(props) => props.theme.colors.background?.inverse};
+ color: ${(props) => props.theme.colors.text?.default};
+ }
+
+ &:disabled,
+ &[disabled] {
+ border: 1px solid ${(props) => props.theme.colors.background?.inverse};
+ cursor: not-allowed;
+ }
+
+ &:disabled:hover,
+ &[disabled]:hover {
+ background-color: ${(props) => props.theme.colors.background?.inverse};
+ color: ${(props) => props.theme.colors.text?.inverse};
+ border: 1px solid ${(props) => props.theme.colors.background?.inverse};
+ }
+ }
+`;
diff --git a/packages/nextjs/app/create/page.tsx b/packages/nextjs/app/create/page.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/DebugContracts.tsx b/packages/nextjs/app/debug/_components/DebugContracts.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/ContractInput.tsx b/packages/nextjs/app/debug/_components/contract/ContractInput.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/ContractReadMethods.tsx b/packages/nextjs/app/debug/_components/contract/ContractReadMethods.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/ContractUI.tsx b/packages/nextjs/app/debug/_components/contract/ContractUI.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/ContractVariables.tsx b/packages/nextjs/app/debug/_components/contract/ContractVariables.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/ContractWriteMethods.tsx b/packages/nextjs/app/debug/_components/contract/ContractWriteMethods.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx b/packages/nextjs/app/debug/_components/contract/DisplayVariable.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/InheritanceTooltip.tsx b/packages/nextjs/app/debug/_components/contract/InheritanceTooltip.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/Tuple.tsx b/packages/nextjs/app/debug/_components/contract/Tuple.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/TupleArray.tsx b/packages/nextjs/app/debug/_components/contract/TupleArray.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/TxReceipt.tsx b/packages/nextjs/app/debug/_components/contract/TxReceipt.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/index.tsx b/packages/nextjs/app/debug/_components/contract/index.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/utilsContract.tsx b/packages/nextjs/app/debug/_components/contract/utilsContract.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx b/packages/nextjs/app/debug/_components/contract/utilsDisplay.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/debug/page.tsx b/packages/nextjs/app/debug/page.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/hooks/MetamaskContext.tsx b/packages/nextjs/app/hooks/MetamaskContext.tsx
new file mode 100755
index 0000000..3ec49ff
--- /dev/null
+++ b/packages/nextjs/app/hooks/MetamaskContext.tsx
@@ -0,0 +1,74 @@
+import type { MetaMaskInpageProvider } from '@metamask/providers';
+import type { ReactNode } from 'react';
+import { createContext, useContext, useEffect, useState } from 'react';
+
+import type { Snap } from '../types';
+import { getSnapsProvider } from '../utils';
+
+type MetaMaskContextType = {
+ provider: MetaMaskInpageProvider | null;
+ installedSnap: Snap | null;
+ error: Error | null;
+ setInstalledSnap: (snap: Snap | null) => void;
+ setError: (error: Error) => void;
+};
+
+export const MetaMaskContext = createContext({
+ provider: null,
+ installedSnap: null,
+ error: null,
+ setInstalledSnap: () => {
+ /* no-op */
+ },
+ setError: () => {
+ /* no-op */
+ },
+});
+
+/**
+ * MetaMask context provider to handle MetaMask and snap status.
+ *
+ * @param props - React Props.
+ * @param props.children - React component to be wrapped by the Provider.
+ * @returns JSX.
+ */
+export const MetaMaskProvider = ({ children }: { children: ReactNode }) => {
+ const [provider, setProvider] = useState(null);
+ const [installedSnap, setInstalledSnap] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ getSnapsProvider().then(setProvider).catch(console.error);
+ }, []);
+
+ useEffect(() => {
+ if (error) {
+ const timeout = setTimeout(() => {
+ setError(null);
+ }, 10000);
+
+ return () => {
+ clearTimeout(timeout);
+ };
+ }
+
+ return undefined;
+ }, [error]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Utility hook to consume the MetaMask context.
+ *
+ * @returns The MetaMask context.
+ */
+export function useMetaMaskContext() {
+ return useContext(MetaMaskContext);
+}
diff --git a/packages/nextjs/app/hooks/index.ts b/packages/nextjs/app/hooks/index.ts
new file mode 100755
index 0000000..b9f151e
--- /dev/null
+++ b/packages/nextjs/app/hooks/index.ts
@@ -0,0 +1,5 @@
+export * from './MetamaskContext';
+export * from './useInvokeSnap';
+export * from './useMetaMask';
+export * from './useRequest';
+export * from './useRequestSnap';
diff --git a/packages/nextjs/app/hooks/useInvokeSnap.ts b/packages/nextjs/app/hooks/useInvokeSnap.ts
new file mode 100755
index 0000000..c02bf6a
--- /dev/null
+++ b/packages/nextjs/app/hooks/useInvokeSnap.ts
@@ -0,0 +1,37 @@
+import { defaultSnapOrigin } from '../config';
+import { useRequest } from './useRequest';
+
+export type InvokeSnapParams = {
+ method: string;
+ params?: Record;
+};
+
+/**
+ * Utility hook to wrap the `wallet_invokeSnap` method.
+ *
+ * @param snapId - The Snap ID to invoke. Defaults to the snap ID specified in the
+ * config.
+ * @returns The invokeSnap wrapper method.
+ */
+export const useInvokeSnap = (snapId = defaultSnapOrigin) => {
+ const request = useRequest();
+
+ /**
+ * Invoke the requested Snap method.
+ *
+ * @param params - The invoke params.
+ * @param params.method - The method name.
+ * @param params.params - The method params.
+ * @returns The Snap response.
+ */
+ const invokeSnap = async ({ method, params }: InvokeSnapParams) =>
+ request({
+ method: 'wallet_invokeSnap',
+ params: {
+ snapId,
+ request: params ? { method, params } : { method },
+ },
+ });
+
+ return invokeSnap;
+};
diff --git a/packages/nextjs/app/hooks/useMetaMask.ts b/packages/nextjs/app/hooks/useMetaMask.ts
new file mode 100755
index 0000000..cbb796e
--- /dev/null
+++ b/packages/nextjs/app/hooks/useMetaMask.ts
@@ -0,0 +1,56 @@
+import { useEffect, useState } from 'react';
+
+import { defaultSnapOrigin } from '../config';
+import type { GetSnapsResponse } from '../types';
+import { useMetaMaskContext } from './MetamaskContext';
+import { useRequest } from './useRequest';
+
+/**
+ * A Hook to retrieve useful data from MetaMask.
+ * @returns The informations.
+ */
+export const useMetaMask = () => {
+ const { provider, setInstalledSnap, installedSnap } = useMetaMaskContext();
+ const request = useRequest();
+
+ const [isFlask, setIsFlask] = useState(false);
+
+ const snapsDetected = provider !== null;
+
+ /**
+ * Detect if the version of MetaMask is Flask.
+ */
+ const detectFlask = async () => {
+ const clientVersion = await request({
+ method: 'web3_clientVersion',
+ });
+
+ const isFlaskDetected = (clientVersion as string[])?.includes('flask');
+
+ setIsFlask(isFlaskDetected);
+ };
+
+ /**
+ * Get the Snap informations from MetaMask.
+ */
+ const getSnap = async () => {
+ const snaps = (await request({
+ method: 'wallet_getSnaps',
+ })) as GetSnapsResponse;
+
+ setInstalledSnap(snaps[defaultSnapOrigin] ?? null);
+ };
+
+ useEffect(() => {
+ const detect = async () => {
+ if (provider) {
+ await detectFlask();
+ await getSnap();
+ }
+ };
+
+ detect().catch(console.error);
+ }, [provider]);
+
+ return { isFlask, snapsDetected, installedSnap, getSnap };
+};
diff --git a/packages/nextjs/app/hooks/useRequest.ts b/packages/nextjs/app/hooks/useRequest.ts
new file mode 100755
index 0000000..68ee255
--- /dev/null
+++ b/packages/nextjs/app/hooks/useRequest.ts
@@ -0,0 +1,40 @@
+import type { RequestArguments } from '@metamask/providers';
+
+import { useMetaMaskContext } from './MetamaskContext';
+
+export type Request = (params: RequestArguments) => Promise;
+
+/**
+ * Utility hook to consume the provider `request` method with the available provider.
+ *
+ * @returns The `request` function.
+ */
+export const useRequest = () => {
+ const { provider, setError } = useMetaMaskContext();
+
+ /**
+ * `provider.request` wrapper.
+ *
+ * @param params - The request params.
+ * @param params.method - The method to call.
+ * @param params.params - The method params.
+ * @returns The result of the request.
+ */
+ const request: Request = async ({ method, params }) => {
+ try {
+ const data =
+ (await provider?.request({
+ method,
+ params,
+ } as RequestArguments)) ?? null;
+
+ return data;
+ } catch (requestError: any) {
+ setError(requestError);
+
+ return null;
+ }
+ };
+
+ return request;
+};
diff --git a/packages/nextjs/app/hooks/useRequestSnap.ts b/packages/nextjs/app/hooks/useRequestSnap.ts
new file mode 100755
index 0000000..b35c9a3
--- /dev/null
+++ b/packages/nextjs/app/hooks/useRequestSnap.ts
@@ -0,0 +1,37 @@
+import { defaultSnapOrigin } from '../config';
+import type { Snap } from '../types';
+import { useMetaMaskContext } from './MetamaskContext';
+import { useRequest } from './useRequest';
+
+/**
+ * Utility hook to wrap the `wallet_requestSnaps` method.
+ *
+ * @param snapId - The requested Snap ID. Defaults to the snap ID specified in the
+ * config.
+ * @param version - The requested version.
+ * @returns The `wallet_requestSnaps` wrapper.
+ */
+export const useRequestSnap = (
+ snapId = defaultSnapOrigin,
+ version?: string,
+) => {
+ const request = useRequest();
+ const { setInstalledSnap } = useMetaMaskContext();
+
+ /**
+ * Request the Snap.
+ */
+ const requestSnap = async () => {
+ const snaps = (await request({
+ method: 'wallet_requestSnaps',
+ params: {
+ [snapId]: version ? { version } : {},
+ },
+ })) as Record;
+
+ // Updates the `installedSnap` context variable since we just installed the Snap.
+ setInstalledSnap(snaps?.[snapId] ?? null);
+ };
+
+ return requestSnap;
+};
diff --git a/packages/nextjs/app/layout.tsx b/packages/nextjs/app/layout.tsx
old mode 100644
new mode 100755
index ac9cbde..9ae3de7
--- a/packages/nextjs/app/layout.tsx
+++ b/packages/nextjs/app/layout.tsx
@@ -1,20 +1,21 @@
+"use client"
+
import "@rainbow-me/rainbowkit/styles.css";
import { ScaffoldEthAppWithProviders } from "~~/components/ScaffoldEthAppWithProviders";
import { ThemeProvider } from "~~/components/ThemeProvider";
import "~~/styles/globals.css";
import { getMetadata } from "~~/utils/scaffold-eth/getMetadata";
+import { MetaMaskProvider } from './hooks';
-export const metadata = getMetadata({
- title: "Oh Snap!",
- description: "On-chain Community Notes using Metamask Snap",
-});
const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => {
return (
- {children}
+
+ {children}
+
diff --git a/packages/nextjs/app/magic/page.tsx b/packages/nextjs/app/magic/page.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/page.tsx b/packages/nextjs/app/page.tsx
old mode 100644
new mode 100755
index 364c6a7..c6ea4a0
--- a/packages/nextjs/app/page.tsx
+++ b/packages/nextjs/app/page.tsx
@@ -1,13 +1,24 @@
"use client";
import Link from "next/link";
+// import ReactDOM from 'react-dom';
+import { useMetaMask, useRequestSnap } from "./hooks";
import type { NextPage } from "next";
+import Confetti from "react-confetti";
import { useAccount } from "wagmi";
import { MagnifyingGlassIcon, PlusIcon } from "@heroicons/react/24/outline";
import { Address } from "~~/components/scaffold-eth";
+// import type { ComponentProps, useState } from 'react';
+// import { ReactComponent as FlaskFox } from '../assets/flask_fox.svg';
+
+
+
const Home: NextPage = () => {
const { address: connectedAddress } = useAccount();
+ const requestSnap = useRequestSnap();
+
+ // const [confettiVisible, setConfettiVisible] = useState(false);
return (
<>
@@ -28,6 +39,16 @@ const Home: NextPage = () => {
// alt={fighters[0].name}
// style={{ height: "200px" }}
/>
+
+ {/* here */}
+
+
+
diff --git a/packages/nextjs/app/types/custom.d.ts b/packages/nextjs/app/types/custom.d.ts
new file mode 100755
index 0000000..e909a80
--- /dev/null
+++ b/packages/nextjs/app/types/custom.d.ts
@@ -0,0 +1,25 @@
+import type {
+ EIP6963AnnounceProviderEvent,
+ EIP6963RequestProviderEvent,
+ MetaMaskInpageProvider,
+} from '@metamask/providers';
+
+/*
+ * Window type extension to support ethereum
+ */
+declare global {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+ interface Window {
+ ethereum: MetaMaskInpageProvider & {
+ setProvider?: (provider: MetaMaskInpageProvider) => void;
+ detected?: MetaMaskInpageProvider[];
+ providers?: MetaMaskInpageProvider[];
+ };
+ }
+
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+ interface WindowEventMap {
+ 'eip6963:requestProvider': EIP6963RequestProviderEvent;
+ 'eip6963:announceProvider': EIP6963AnnounceProviderEvent;
+ }
+}
diff --git a/packages/nextjs/app/types/index.ts b/packages/nextjs/app/types/index.ts
new file mode 100755
index 0000000..fb265f0
--- /dev/null
+++ b/packages/nextjs/app/types/index.ts
@@ -0,0 +1 @@
+export { type GetSnapsResponse, type Snap } from './snap';
diff --git a/packages/nextjs/app/types/snap.ts b/packages/nextjs/app/types/snap.ts
new file mode 100755
index 0000000..8f96603
--- /dev/null
+++ b/packages/nextjs/app/types/snap.ts
@@ -0,0 +1,8 @@
+export type GetSnapsResponse = Record;
+
+export type Snap = {
+ permissionName: string;
+ id: string;
+ version: string;
+ initialPermissions: Record;
+};
diff --git a/packages/nextjs/app/types/styled.d.ts b/packages/nextjs/app/types/styled.d.ts
new file mode 100755
index 0000000..f8fc6ab
--- /dev/null
+++ b/packages/nextjs/app/types/styled.d.ts
@@ -0,0 +1,19 @@
+/* eslint-disable import/no-unassigned-import */
+
+import 'styled-components';
+
+/**
+ * styled-component default theme extension
+ */
+declare module 'styled-components' {
+ /* eslint-disable @typescript-eslint/consistent-type-definitions */
+ export interface DefaultTheme {
+ fonts: Record;
+ fontSizes: Record;
+ breakpoints: string[];
+ mediaQueries: Record;
+ radii: Record;
+ shadows: Record;
+ colors: Record>;
+ }
+}
diff --git a/packages/nextjs/app/types/svg.d.ts b/packages/nextjs/app/types/svg.d.ts
new file mode 100755
index 0000000..032d9ce
--- /dev/null
+++ b/packages/nextjs/app/types/svg.d.ts
@@ -0,0 +1,7 @@
+/* eslint-disable import/unambiguous */
+
+declare module '*.svg' {
+ import type { FunctionComponent, SVGProps } from 'react';
+
+ export const ReactComponent: FunctionComponent>;
+}
diff --git a/packages/nextjs/app/utils/button.ts b/packages/nextjs/app/utils/button.ts
new file mode 100755
index 0000000..b951687
--- /dev/null
+++ b/packages/nextjs/app/utils/button.ts
@@ -0,0 +1,5 @@
+import type { Snap } from '../types';
+import { isLocalSnap } from './snap';
+
+export const shouldDisplayReconnectButton = (installedSnap: Snap | null) =>
+ installedSnap && isLocalSnap(installedSnap?.id);
diff --git a/packages/nextjs/app/utils/index.ts b/packages/nextjs/app/utils/index.ts
new file mode 100755
index 0000000..edce332
--- /dev/null
+++ b/packages/nextjs/app/utils/index.ts
@@ -0,0 +1,5 @@
+export * from './metamask';
+export * from './snap';
+export * from './theme';
+export * from './localStorage';
+export * from './button';
diff --git a/packages/nextjs/app/utils/localStorage.ts b/packages/nextjs/app/utils/localStorage.ts
new file mode 100755
index 0000000..f5b6482
--- /dev/null
+++ b/packages/nextjs/app/utils/localStorage.ts
@@ -0,0 +1,33 @@
+/**
+ * Get a local storage key.
+ *
+ * @param key - The local storage key to access.
+ * @returns The value stored at the key provided if the key exists.
+ */
+export const getLocalStorage = (key: string) => {
+ const { localStorage: ls } = window;
+
+ if (ls !== null) {
+ const data = ls.getItem(key);
+ return data;
+ }
+
+ throw new Error('Local storage is not available.');
+};
+
+/**
+ * Set a value to local storage at a certain key.
+ *
+ * @param key - The local storage key to set.
+ * @param value - The value to set.
+ */
+export const setLocalStorage = (key: string, value: string) => {
+ const { localStorage: ls } = window;
+
+ if (ls !== null) {
+ ls.setItem(key, value);
+ return;
+ }
+
+ throw new Error('Local storage is not available.');
+};
diff --git a/packages/nextjs/app/utils/metamask.ts b/packages/nextjs/app/utils/metamask.ts
new file mode 100755
index 0000000..30b3971
--- /dev/null
+++ b/packages/nextjs/app/utils/metamask.ts
@@ -0,0 +1,115 @@
+import type {
+ EIP6963AnnounceProviderEvent,
+ MetaMaskInpageProvider,
+} from '@metamask/providers';
+
+/**
+ * Check if the current provider supports snaps by calling `wallet_getSnaps`.
+ *
+ * @param provider - The provider to use to check for snaps support. Defaults to
+ * `window.ethereum`.
+ * @returns True if the provider supports snaps, false otherwise.
+ */
+export async function hasSnapsSupport(
+ provider: MetaMaskInpageProvider = window.ethereum,
+) {
+ try {
+ await provider.request({
+ method: 'wallet_getSnaps',
+ });
+
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Get a MetaMask provider using EIP6963. This will return the first provider
+ * reporting as MetaMask. If no provider is found after 500ms, this will
+ * return null instead.
+ *
+ * @returns A MetaMask provider if found, otherwise null.
+ */
+export async function getMetaMaskEIP6963Provider() {
+ return new Promise((rawResolve) => {
+ // Timeout looking for providers after 500ms
+ const timeout = setTimeout(() => {
+ resolve(null);
+ }, 500);
+
+ /**
+ * Resolve the promise with a MetaMask provider and clean up.
+ *
+ * @param provider - A MetaMask provider if found, otherwise null.
+ */
+ function resolve(provider: MetaMaskInpageProvider | null) {
+ window.removeEventListener(
+ 'eip6963:announceProvider',
+ onAnnounceProvider,
+ );
+ clearTimeout(timeout);
+ rawResolve(provider);
+ }
+
+ /**
+ * Listener for the EIP6963 announceProvider event.
+ *
+ * Resolves the promise if a MetaMask provider is found.
+ *
+ * @param event - The EIP6963 announceProvider event.
+ * @param event.detail - The details of the EIP6963 announceProvider event.
+ */
+ function onAnnounceProvider({ detail }: EIP6963AnnounceProviderEvent) {
+ const { info, provider } = detail;
+
+ if (info.rdns.includes('io.metamask')) {
+ resolve(provider);
+ }
+ }
+
+ window.addEventListener('eip6963:announceProvider', onAnnounceProvider);
+
+ window.dispatchEvent(new Event('eip6963:requestProvider'));
+ });
+}
+
+/**
+ * Get a provider that supports snaps. This will loop through all the detected
+ * providers and return the first one that supports snaps.
+ *
+ * @returns The provider, or `null` if no provider supports snaps.
+ */
+export async function getSnapsProvider() {
+ if (typeof window === 'undefined') {
+ return null;
+ }
+
+ if (await hasSnapsSupport()) {
+ return window.ethereum;
+ }
+
+ if (window.ethereum?.detected) {
+ for (const provider of window.ethereum.detected) {
+ if (await hasSnapsSupport(provider)) {
+ return provider;
+ }
+ }
+ }
+
+ if (window.ethereum?.providers) {
+ for (const provider of window.ethereum.providers) {
+ if (await hasSnapsSupport(provider)) {
+ return provider;
+ }
+ }
+ }
+
+ const eip6963Provider = await getMetaMaskEIP6963Provider();
+
+ if (eip6963Provider && (await hasSnapsSupport(eip6963Provider))) {
+ return eip6963Provider;
+ }
+
+ return null;
+}
diff --git a/packages/nextjs/app/utils/snap.ts b/packages/nextjs/app/utils/snap.ts
new file mode 100755
index 0000000..82fc11d
--- /dev/null
+++ b/packages/nextjs/app/utils/snap.ts
@@ -0,0 +1,7 @@
+/**
+ * Check if a snap ID is a local snap ID.
+ *
+ * @param snapId - The snap ID.
+ * @returns True if it's a local Snap, or false otherwise.
+ */
+export const isLocalSnap = (snapId: string) => snapId.startsWith('local:');
diff --git a/packages/nextjs/app/utils/theme.ts b/packages/nextjs/app/utils/theme.ts
new file mode 100755
index 0000000..a5b6a85
--- /dev/null
+++ b/packages/nextjs/app/utils/theme.ts
@@ -0,0 +1,27 @@
+import { getLocalStorage, setLocalStorage } from './localStorage';
+
+/**
+ * Get the user's preferred theme in local storage.
+ * Will default to the browser's preferred theme if there is no value in local storage.
+ *
+ * @returns True if the theme is "dark" otherwise, false.
+ */
+export const getThemePreference = () => {
+ if (typeof window === 'undefined') {
+ return false;
+ }
+
+ const darkModeSystem = window?.matchMedia(
+ '(prefers-color-scheme: dark)',
+ ).matches;
+
+ const localStoragePreference = getLocalStorage('theme');
+ const systemPreference = darkModeSystem ? 'dark' : 'light';
+ const preference = localStoragePreference ?? systemPreference;
+
+ if (!localStoragePreference) {
+ setLocalStorage('theme', systemPreference);
+ }
+
+ return preference === 'dark';
+};
diff --git a/packages/nextjs/app/view/[address]/page.tsx b/packages/nextjs/app/view/[address]/page.tsx
old mode 100644
new mode 100755
diff --git a/packages/nextjs/app/view/page.tsx b/packages/nextjs/app/view/page.tsx
old mode 100644
new mode 100755