├── .env.example
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── components
└── layout
│ ├── Card.tsx
│ ├── CustomButton.tsx
│ ├── HeadingComponent.tsx
│ ├── Navbar.tsx
│ ├── SEO.tsx
│ └── index.ts
├── next.config.js
├── package.json
├── pages
├── _app.tsx
├── _document.tsx
├── api
│ └── siwe
│ │ ├── logout.ts
│ │ ├── me.ts
│ │ ├── nonce.ts
│ │ └── verify.ts
├── examples
│ ├── ens.tsx
│ ├── index.tsx
│ ├── mint-nft.tsx
│ ├── send-erc20.tsx
│ ├── send-ether.tsx
│ ├── sign.tsx
│ └── siwe.tsx
└── index.tsx
├── postcss.config.js
├── providers
└── Web3.tsx
├── public
├── boilr3.svg
├── favicon
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ └── site.webmanifest
├── icons
│ ├── ens.png
│ ├── erc20.png
│ ├── ether.png
│ ├── nft.png
│ ├── sign.png
│ └── siwe.png
└── og.png
├── styles
└── globals.css
├── tailwind.config.js
├── tsconfig.json
├── types
└── iron-session
│ └── index.d.ts
└── utils
└── config.ts
/.env.example:
--------------------------------------------------------------------------------
1 | # Note - WalletConnect v2
2 | # Every dApp that relies on WalletConnect now needs to obtain a projectId from WalletConnect Cloud.
3 | # This is absolutely free and only takes a few minutes.
4 | # https://cloud.walletconnect.com/
5 | NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=PUT_YOUR_PROJECT_ID_HERE
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
37 | # env
38 | .env
39 | .env.local
40 |
41 | # lock files
42 | package-lock.json
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Vedant Chainani
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Welcome to the [boilr3](https://github.com/Envoy-VC/boilr3), a comprehensive starting point for building decentralized applications using [Next.js](https://nextjs.org/), [RainbowKit](https://www.rainbowkit.com/) wallet authentication, [Tailwind CSS](https://tailwindcss.com/) styling, and [WAGMI](https://wagmi.sh/) React Hooks for Ethereum.
2 |
3 | ---
4 |
5 | ## Getting Started
6 |
7 | To use this template, follow these steps:
8 |
9 | 1. Clone the repository:
10 | ```bash
11 | git clone https://github.com/Envoy-VC/boilr3.git
12 | ```
13 | 2. Change to the project directory:
14 | ```bash
15 | cd boilr3
16 | ```
17 | 3. Install the dependencies:
18 |
19 | ```bash
20 | npm install
21 | ```
22 |
23 | 4. Start the development server:
24 | ```bash
25 | npm run dev
26 | # or
27 | yarn dev
28 | # or
29 | pnpm dev
30 | ```
31 |
32 | Now you can open your browser and navigate to [http://localhost:3000](http://localhost:3000) to see the boilerplate in action.
33 |
34 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
35 |
36 | ---
37 |
38 | ## Features
39 |
40 | - **Next.js Integration** - This boilerplate is built on top of Next.js, a popular React-based framework for building server-side rendered applications. With Next.js, you can easily create dynamic, SEO-friendly pages and leverage powerful features like automatic code splitting and serverless functions to optimize your app's performance.
41 |
42 | - **Secure Wallet Authentication** - Authenticate users securely with RainbowKit, a powerful and easy-to-use wallet authentication library for Ethereum-based dApps. With RainbowKit, you can easily integrate wallet authentication into your app, without having to worry about the complexities of managing private keys or user accounts. RainbowKit supports a wide range of wallets, including MetaMask, WalletConnect, and more.
43 |
44 | - **Tailwind CSS Styling** - Effortlessly style your decentralized application with Tailwind CSS, a utility-first CSS framework. Tailwind CSS makes it easy to create beautiful, responsive UIs without writing custom CSS, allowing you to focus on building your app's functionality.
45 |
46 | - **WAGMI React Hooks for Ethereum** - WAGMI is a set of React Hooks designed to simplify Ethereum development. With WAGMI, you can easily interact with Ethereum smart contracts, manage user accounts, and handle transactions, all within your React components.
47 |
48 | ---
49 |
50 | ## Contributing
51 |
52 | We welcome contributions to improve and expand this boilerplate. To contribute, please submit a pull request with your proposed changes, and we'll review it as soon as possible.
53 |
54 | ---
55 |
56 | ## License
57 |
58 | This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for more information.
59 |
60 | ---
61 |
--------------------------------------------------------------------------------
/components/layout/Card.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import Link from 'next/link';
3 |
4 | interface Props {
5 | icon: string;
6 | title: string;
7 | description: string;
8 | href: string;
9 | }
10 |
11 | const Card = (props: Props) => {
12 | return (
13 |
14 |
20 |
21 |
22 | {props.title}
23 |
24 |
25 |
{props.description}
26 |
27 | );
28 | };
29 |
30 | export default Card;
31 |
--------------------------------------------------------------------------------
/components/layout/CustomButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Inter } from 'next/font/google';
3 |
4 | const inter = Inter({ subsets: ['latin'] });
5 |
6 | type btnType = 'blue' | 'red' | 'grey';
7 |
8 | interface Props {
9 | text: string;
10 | type: btnType;
11 | className?: string;
12 | submit?: boolean;
13 | disabled?: boolean;
14 | handleClick: () => any;
15 | }
16 |
17 | const CustomButton = (props: Props) => {
18 | return (
19 | props.handleClick()}
30 | type={`${props.submit ? 'submit' : 'button'}`}
31 | disabled={props.disabled ? props.disabled : false}
32 | >
33 | {props.text}
34 |
35 | );
36 | };
37 |
38 | export default CustomButton;
39 |
--------------------------------------------------------------------------------
/components/layout/HeadingComponent.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Inter } from 'next/font/google';
3 | const inter = Inter({ subsets: ['latin'] });
4 |
5 | interface Props {
6 | title: string;
7 | description: string;
8 | }
9 |
10 | const HeadingComponent = (props: Props) => {
11 | return (
12 |
13 |
14 | {props.title}
15 |
16 |
17 | {props.description}
18 |
19 |
20 | );
21 | };
22 |
23 | export default HeadingComponent;
24 |
--------------------------------------------------------------------------------
/components/layout/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import { ConnectButton } from '@rainbow-me/rainbowkit';
3 | import Link from 'next/link';
4 |
5 | const Navbar = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
20 |
21 |
22 |
23 |
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default Navbar;
39 |
--------------------------------------------------------------------------------
/components/layout/SEO.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | SITE_DESCRIPTION,
4 | SITE_NAME,
5 | SITE_URL,
6 | SOCIAL_TWITTER,
7 | } from '@/utils/config';
8 | import { DefaultSeo } from 'next-seo';
9 |
10 | const SEO = () => {
11 | const origin =
12 | typeof window !== 'undefined' && window.location.origin
13 | ? window.location.origin
14 | : SITE_URL;
15 |
16 | return (
17 |
41 | );
42 | };
43 |
44 | export default SEO;
45 |
--------------------------------------------------------------------------------
/components/layout/index.ts:
--------------------------------------------------------------------------------
1 | import SEO from './SEO';
2 | import Navbar from './Navbar';
3 | import HeadingComponent from './HeadingComponent';
4 | import CustomButton from './CustomButton';
5 |
6 | export { SEO, Navbar, HeadingComponent, CustomButton };
7 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "boilr3",
3 | "version": "0.1.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@rainbow-me/rainbowkit": "^1.3.2",
13 | "@types/node": "20.10.6",
14 | "@types/react": "18.2.46",
15 | "@types/react-dom": "18.2.18",
16 | "autoprefixer": "10.4.16",
17 | "eslint": "8.56.0",
18 | "eslint-config-next": "14.0.4",
19 | "ethers": "^6.9.1",
20 | "iron-session": "^6.3.1",
21 | "next": "14.0.4",
22 | "next-seo": "^6.4.0",
23 | "postcss": "8.4.32",
24 | "react": "18.2.0",
25 | "react-dom": "18.2.0",
26 | "react-hot-toast": "^2.4.1",
27 | "siwe": "^2.1.4",
28 | "tailwindcss": "3.4.0",
29 | "typescript": "5.3.3",
30 | "use-debounce": "^10.0.0",
31 | "wagmi": "^1.4.12"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { AppProps } from 'next/app';
2 | import Web3Provider from '@/providers/Web3';
3 | import { SEO } from '@/components/layout';
4 |
5 | import '@/styles/globals.css';
6 |
7 | export default function App({ Component, pageProps }: AppProps) {
8 | return (
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document';
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
7 | {/* Character Set */}
8 |
9 |
10 | {/* Favicon */}
11 |
16 |
22 |
28 |
29 |
30 | {/* Robots Search Indexing */}
31 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/pages/api/siwe/logout.ts:
--------------------------------------------------------------------------------
1 | import { withIronSessionApiRoute } from 'iron-session/next';
2 | import { NextApiRequest, NextApiResponse } from 'next';
3 | import { ironOptions } from '@/utils/config';
4 |
5 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
6 | const { method } = req;
7 | switch (method) {
8 | case 'GET':
9 | req.session.destroy();
10 | res.send({ ok: true });
11 | break;
12 | default:
13 | res.setHeader('Allow', ['GET']);
14 | res.status(405).end(`Method ${method} Not Allowed`);
15 | }
16 | };
17 |
18 | export default withIronSessionApiRoute(handler, ironOptions);
19 |
--------------------------------------------------------------------------------
/pages/api/siwe/me.ts:
--------------------------------------------------------------------------------
1 | import { withIronSessionApiRoute } from 'iron-session/next';
2 | import { NextApiRequest, NextApiResponse } from 'next';
3 | import { ironOptions } from '@/utils/config';
4 |
5 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
6 | const { method } = req;
7 | switch (method) {
8 | case 'GET':
9 | res.send({ address: req.session.siwe?.address });
10 | break;
11 | default:
12 | res.setHeader('Allow', ['GET']);
13 | res.status(405).end(`Method ${method} Not Allowed`);
14 | }
15 | };
16 |
17 | export default withIronSessionApiRoute(handler, ironOptions);
18 |
--------------------------------------------------------------------------------
/pages/api/siwe/nonce.ts:
--------------------------------------------------------------------------------
1 | import { withIronSessionApiRoute } from 'iron-session/next';
2 | import { NextApiRequest, NextApiResponse } from 'next';
3 | import { generateNonce } from 'siwe';
4 | import { ironOptions } from '@/utils/config';
5 |
6 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
7 | const { method } = req;
8 | switch (method) {
9 | case 'GET':
10 | req.session.nonce = generateNonce();
11 | await req.session.save();
12 | res.setHeader('Content-Type', 'text/plain');
13 | res.send(req.session.nonce);
14 | break;
15 | default:
16 | res.setHeader('Allow', ['GET']);
17 | res.status(405).end(`Method ${method} Not Allowed`);
18 | }
19 | };
20 |
21 | export default withIronSessionApiRoute(handler, ironOptions);
22 |
--------------------------------------------------------------------------------
/pages/api/siwe/verify.ts:
--------------------------------------------------------------------------------
1 | import { withIronSessionApiRoute } from 'iron-session/next';
2 | import { NextApiRequest, NextApiResponse } from 'next';
3 | import { SiweMessage } from 'siwe';
4 | import { ironOptions } from '@/utils/config';
5 |
6 | const handler = async (req: NextApiRequest, res: NextApiResponse) => {
7 | const { method } = req;
8 | switch (method) {
9 | case 'POST':
10 | try {
11 | const { message, signature } = req.body;
12 | const siweMessage = new SiweMessage(message);
13 | const fields = await siweMessage.validate(signature);
14 |
15 | if (fields.nonce !== req.session.nonce)
16 | return res.status(422).json({ message: 'Invalid nonce.' });
17 |
18 | req.session.siwe = fields;
19 | await req.session.save();
20 | res.json({ ok: true });
21 | } catch (_error) {
22 | res.json({ ok: false });
23 | }
24 | break;
25 | default:
26 | res.setHeader('Allow', ['POST']);
27 | res.status(405).end(`Method ${method} Not Allowed`);
28 | }
29 | };
30 |
31 | export default withIronSessionApiRoute(handler, ironOptions);
32 |
--------------------------------------------------------------------------------
/pages/examples/ens.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { NextSeo } from 'next-seo';
3 | import { fetchEnsAddress, fetchEnsName } from '@wagmi/core';
4 | import { Toaster, toast } from 'react-hot-toast';
5 | import { Navbar, HeadingComponent, CustomButton } from '@/components/layout';
6 | import { Inter } from 'next/font/google';
7 |
8 | const inter = Inter({ subsets: ['latin'] });
9 |
10 | const ENS = () => {
11 | const [status, setStatus] = useState<'idle' | 'fetching'>('idle');
12 | const [input, setInput] = useState('');
13 | const [resolved, setResolved] = useState('');
14 |
15 | async function submit() {
16 | try {
17 | setStatus('fetching');
18 | if (input.endsWith('.eth')) {
19 | let resolvedENS = await fetchEnsAddress({
20 | name: input,
21 | });
22 | setResolved(String(resolvedENS));
23 | } else {
24 | let resolvedAddress = await fetchEnsName({
25 | address: input as `0x{string}`,
26 | });
27 | setResolved(String(resolvedAddress));
28 | }
29 | } catch (error) {
30 | console.log(error);
31 | } finally {
32 | setStatus('idle');
33 | }
34 | }
35 |
36 | return (
37 |
38 |
39 |
40 |
41 |
42 |
46 |
47 |
setInput(e.target.value)}
52 | className={`w-full max-w-sm bg-[#202020] mt-12 rounded-xl border-2 border-gray-600 p-2 ps-4 text-white outline-none ${inter.className}`}
53 | />
54 |
60 | {resolved && (
61 |
64 | Resolved ENS/Address : {resolved}
65 |
66 | )}
67 |
68 |
69 |
70 | );
71 | };
72 |
73 | export default ENS;
74 |
--------------------------------------------------------------------------------
/pages/examples/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/no-unescaped-entities */
2 | import React from 'react';
3 | import { NextSeo } from 'next-seo';
4 | import { Navbar } from '@/components/layout';
5 | import { Inter } from 'next/font/google';
6 | const inter = Inter({ subsets: ['latin'] });
7 |
8 | import Card from '@/components/layout/Card';
9 |
10 | const examples = [
11 | {
12 | title: 'Sign and Verify Message',
13 | description:
14 | 'Sign in with your Ethereum account to securely verify your identity and access exclusive blockchain-based features.',
15 | href: '/examples/sign',
16 | icon: '/icons/sign.png',
17 | },
18 | {
19 | title: 'Sign-in with Ethereum',
20 | description:
21 | 'Experience the power of secure communication with Sign-In with Ethereum (SIWE), an EIP-4361 authentication standard that creates user sessions based on wallet connections and more!',
22 | href: '/examples/siwe',
23 | icon: '/icons/siwe.png',
24 | },
25 | {
26 | title: 'Fetch ENS Names',
27 | description:
28 | 'Fetch Ethereum Name Service names to Ethereum addresses using a decentralized domain name system that maps human-readable names to addresses.',
29 | href: '/examples/ens',
30 | icon: '/icons/ens.png',
31 | },
32 | {
33 | title: 'Send Ether',
34 | description:
35 | 'Send ether to another Ethereum address with a basic transaction.',
36 | href: '/examples/send-ether',
37 | icon: '/icons/ether.png',
38 | },
39 | {
40 | title: 'Send ERC20',
41 | description:
42 | ' ERC20 is a standard for fungible tokens on the Ethereum network. The example includes a tutorial on how to interact with a deployed ERC20 token contract.',
43 | href: '/examples/send-erc20',
44 | icon: '/icons/erc20.png',
45 | },
46 | {
47 | title: 'Mint NFT',
48 | description:
49 | 'NFTs are unique digital assets that can represent things like artwork, collectibles, and other unique items.',
50 | href: '/examples/mint-nft',
51 | icon: '/icons/nft.png',
52 | },
53 | ];
54 |
55 | const Examples = () => {
56 | return (
57 |
58 |
59 |
60 |
61 |
62 | Examples
63 |
64 |
65 | Get started by exploring examples to quickstart your dapp development.
66 | All the Examples can be found under the{' '}
67 |
68 | pages/examples
69 | {' '}
70 | directory under the main repo. And don't forget, contributions are
71 | always welcome! Make a pull request to share your own awesome
72 | creations with the community.
73 |
74 |
75 | {examples.map((example, index) => (
76 |
77 | ))}
78 |
79 |
80 |
81 | );
82 | };
83 |
84 | export default Examples;
85 |
--------------------------------------------------------------------------------
/pages/examples/mint-nft.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | useAccount,
3 | useContractWrite,
4 | usePrepareContractWrite,
5 | useWaitForTransaction,
6 | useNetwork,
7 | } from 'wagmi';
8 | import { polygonMumbai } from 'wagmi/chains';
9 | import { parseEther } from 'viem';
10 | import { NFT_CONTRACT_ADDRESS } from '@/utils/config';
11 |
12 | import { NextSeo } from 'next-seo';
13 | import { Toaster, toast } from 'react-hot-toast';
14 |
15 | import { Inter } from 'next/font/google';
16 | import { Navbar, HeadingComponent } from '@/components/layout';
17 |
18 | const inter = Inter({ subsets: ['latin'] });
19 |
20 | function MintNFT() {
21 | const { chain } = useNetwork();
22 | const { address } = useAccount();
23 |
24 | const { config } = usePrepareContractWrite({
25 | // BOILR3 NFT CONTRACT EXAMPLE
26 | // https://testnets.opensea.io/collection/boilr3
27 | address: NFT_CONTRACT_ADDRESS,
28 | chainId: polygonMumbai.id,
29 | abi: [
30 | {
31 | inputs: [{ internalType: 'address', name: 'to', type: 'address' }],
32 | name: 'safeMint',
33 | stateMutability: 'nonpayable',
34 | type: 'function',
35 | outputs: [],
36 | },
37 | ],
38 | functionName: 'safeMint',
39 | args: [address ? address : '0x0'],
40 | });
41 |
42 | const contractWrite = useContractWrite({
43 | ...config,
44 | onError() {
45 | toast.error('User denied transaction');
46 | },
47 | });
48 |
49 | const waitForTransaction = useWaitForTransaction({
50 | hash: contractWrite.data?.hash,
51 | });
52 |
53 | return (
54 |
102 | );
103 | }
104 |
105 | export default function MintNFTExample() {
106 | const { isConnected } = useAccount();
107 | return (
108 |
109 |
110 |
111 |
112 |
113 |
117 | {isConnected ? (
118 |
119 | ) : (
120 |
123 | Connect Wallet to Mint NFT
124 |
125 | )}
126 |
127 |
128 | );
129 | }
130 |
--------------------------------------------------------------------------------
/pages/examples/send-erc20.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { NextSeo } from 'next-seo';
3 | import { Toaster, toast } from 'react-hot-toast';
4 | import { useDebounce } from 'use-debounce';
5 | import {
6 | useNetwork,
7 | useAccount,
8 | useBalance,
9 | useContractWrite,
10 | usePrepareContractWrite,
11 | useWaitForTransaction,
12 | erc20ABI,
13 | } from 'wagmi';
14 |
15 | import { Navbar, HeadingComponent } from '@/components/layout';
16 | import { Inter } from 'next/font/google';
17 | import { parseEther } from 'viem';
18 |
19 | const inter = Inter({ subsets: ['latin'] });
20 |
21 | function SendTransaction() {
22 | const [tokenContract, setTokenContract] = useState('');
23 | const debouncedTokenContract = useDebounce(tokenContract, 500);
24 |
25 | const [to, setTo] = useState('');
26 | const debouncedTo = useDebounce(to, 500);
27 |
28 | const [amount, setAmount] = useState('');
29 | const debouncedAmount = useDebounce(amount, 500);
30 |
31 | const { chain } = useNetwork();
32 | const { address } = useAccount();
33 | const balance = useBalance({
34 | address,
35 | token: debouncedTokenContract[0] as `0x{string}`,
36 | });
37 |
38 | const prepareContractWrite = usePrepareContractWrite({
39 | address: debouncedTokenContract[0] as `0x{string}`,
40 | abi: erc20ABI,
41 | chainId: chain?.id,
42 | functionName: 'transfer',
43 | args: [
44 | (debouncedTo[0] as `0x{string}`) ?? '0x0',
45 | debouncedAmount[0]
46 | ? parseEther(debouncedAmount[0] as `${number}`)
47 | : parseEther('0'),
48 | ],
49 | });
50 | const contractWrite = useContractWrite({
51 | ...prepareContractWrite.config,
52 | onError() {
53 | toast.error('User denied transaction');
54 | },
55 | });
56 | const waitForTransaction = useWaitForTransaction({
57 | hash: contractWrite.data?.hash,
58 | onSettled() {
59 | balance.refetch();
60 | },
61 | });
62 |
63 | return (
64 |
146 | );
147 | }
148 |
149 | const ERC20Example = () => {
150 | const { isConnected } = useAccount();
151 | return (
152 |
153 |
154 |
155 |
156 |
157 |
161 | {isConnected ? (
162 |
163 | ) : (
164 |
167 | Connect Wallet to Transfer Tokens
168 |
169 | )}
170 |
171 |
172 | );
173 | };
174 |
175 | export default ERC20Example;
176 |
--------------------------------------------------------------------------------
/pages/examples/send-ether.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { NextSeo } from 'next-seo';
3 | import { Toaster, toast } from 'react-hot-toast';
4 | import { useDebounce } from 'use-debounce';
5 | import {
6 | useAccount,
7 | useBalance,
8 | useNetwork,
9 | usePrepareSendTransaction,
10 | useSendTransaction,
11 | useWaitForTransaction,
12 | } from 'wagmi';
13 | import { parseEther } from 'viem';
14 |
15 | import { Navbar, HeadingComponent } from '@/components/layout';
16 | import { Inter } from 'next/font/google';
17 |
18 | const inter = Inter({ subsets: ['latin'] });
19 |
20 | function SendTransaction() {
21 | const [to, setTo] = useState('');
22 | const [debouncedTo] = useDebounce(to, 500);
23 |
24 | const [amount, setAmount] = useState('');
25 | const [debouncedAmount] = useDebounce(amount, 500);
26 |
27 | const { chain } = useNetwork();
28 | const { address } = useAccount();
29 |
30 | const balance = useBalance({
31 | address,
32 | });
33 |
34 | const sendTransaction = useSendTransaction({
35 | account: address,
36 | to: debouncedTo,
37 | value: debouncedAmount
38 | ? parseEther(debouncedAmount as `${number}`)
39 | : undefined,
40 | onError() {
41 | toast.error('User Rejected Transaction');
42 | },
43 | });
44 |
45 | const { isLoading, isSuccess, refetch } = useWaitForTransaction({
46 | hash: sendTransaction.data?.hash,
47 | });
48 |
49 | return (
50 |
51 |
94 |
95 | {isSuccess && (
96 |
99 | Successfully sent {amount} ether to {to}
100 |
111 |
112 | )}
113 |
114 | );
115 | }
116 |
117 | const SendEtherExample = () => {
118 | return (
119 |
120 |
121 |
122 |
123 |
130 |
131 | );
132 | };
133 |
134 | export default SendEtherExample;
135 |
--------------------------------------------------------------------------------
/pages/examples/sign.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from 'react';
2 | import { NextSeo } from 'next-seo';
3 | import { useAccount, useSignMessage } from 'wagmi';
4 | import { recoverMessageAddress } from 'viem';
5 | import toast, { Toaster } from 'react-hot-toast';
6 |
7 | import { Navbar, HeadingComponent, CustomButton } from '@/components/layout';
8 | import { Inter } from 'next/font/google';
9 |
10 | const inter = Inter({ subsets: ['latin'] });
11 |
12 | const Sign = () => {
13 | const { address } = useAccount();
14 | const recoveredAddress = React.useRef('');
15 |
16 | const [message, setMessage] = useState(
17 | `Sign in with your Ethereum account and let's HODL our way to web3 glory! 🎉 Remember, WAGMI! 🤝`
18 | );
19 |
20 | const { data, error, isLoading, signMessageAsync, variables } =
21 | useSignMessage({
22 | message,
23 | async onSuccess(data, variables) {
24 | const res = await recoverMessageAddress({
25 | message: variables?.message,
26 | signature: data,
27 | });
28 | recoveredAddress.current = res;
29 | if (recoveredAddress.current === address) {
30 | toast.success('Message verified successfully!');
31 | }
32 | },
33 | onError(error) {
34 | toast.error(error.message);
35 | },
36 | });
37 |
38 | const handleSubmit = async () => {
39 | signMessageAsync();
40 | };
41 |
42 | return (
43 |
76 | );
77 | };
78 |
79 | export default Sign;
80 |
--------------------------------------------------------------------------------
/pages/examples/siwe.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { NextSeo } from 'next-seo';
3 | import toast, { Toaster } from 'react-hot-toast';
4 | import { useAccount, useNetwork, useSignMessage } from 'wagmi';
5 | import { SiweMessage } from 'siwe';
6 |
7 | import { Navbar, HeadingComponent, CustomButton } from '@/components/layout';
8 | import { Inter } from 'next/font/google';
9 |
10 | import { SITE_NAME } from '@/utils/config';
11 |
12 | const inter = Inter({ subsets: ['latin'] });
13 |
14 | const SIWE = () => {
15 | const [loggedInAddress, setLoggedInAddress] = useState('');
16 | const { address } = useAccount();
17 | const { chain } = useNetwork();
18 | const { signMessageAsync } = useSignMessage();
19 |
20 | // Fetch User Account
21 | useEffect(() => {
22 | const handler = async () => {
23 | try {
24 | const res = await fetch('/api/siwe/me');
25 | const json = await res.json();
26 | if (json.address) {
27 | setLoggedInAddress(json.address);
28 | }
29 | } catch (_error) {}
30 | };
31 |
32 | // Firstly when page loads
33 | handler();
34 |
35 | // Again if user logs out of another tab
36 | window.addEventListener('focus', handler);
37 | return () => window.removeEventListener('focus', handler);
38 | }, []);
39 |
40 | const signIn = async () => {
41 | try {
42 | const chainId = chain?.id;
43 | if (!address || !chainId) {
44 | toast.error('Please connect your wallet');
45 | throw new Error('Please connect your wallet');
46 | }
47 |
48 | // Step 1: Get Random nonce
49 | const nonceRes = await fetch('/api/siwe/nonce');
50 | const nonce = await nonceRes.text();
51 |
52 | // Step 2: Create SIWE Message
53 | const message = new SiweMessage({
54 | domain: window.location.host,
55 | address,
56 | statement: `Sign in with Ethereum to ${SITE_NAME}.`,
57 | uri: window.location.origin,
58 | version: '1',
59 | chainId,
60 | nonce: nonce,
61 | });
62 |
63 | // Step 3: Sign Message
64 | const signature = await signMessageAsync({
65 | message: message.prepareMessage(),
66 | });
67 |
68 | // Step 4: Send Signature to Backend for Verification
69 | const verifyRes = await fetch('/api/siwe/verify', {
70 | method: 'POST',
71 | headers: {
72 | 'Content-Type': 'application/json',
73 | },
74 | body: JSON.stringify({ message, signature }),
75 | });
76 |
77 | if (!verifyRes.ok) {
78 | toast.error('Error verifying message');
79 | throw new Error('Error verifying message');
80 | } else {
81 | toast.success('Successfully verified message');
82 | setLoggedInAddress(address);
83 | }
84 | } catch (error) {
85 | console.error(error);
86 | setLoggedInAddress('');
87 | }
88 | };
89 |
90 | const logout = async () => {
91 | await fetch('/api/siwe/logout');
92 | setLoggedInAddress('');
93 | };
94 |
95 | return (
96 |
97 |
98 |
99 |
100 |
101 |
105 |
106 |
107 |
108 | {loggedInAddress && address && (
109 |
110 | )}
111 |
112 | {loggedInAddress && address && (
113 |
116 | Address : {loggedInAddress}
117 |
118 | )}
119 |
120 |
121 | );
122 | };
123 |
124 | export default SIWE;
125 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import Head from 'next/head';
3 | import { ConnectButton } from '@rainbow-me/rainbowkit';
4 | import { SITE_NAME, SITE_DESCRIPTION, SITE_URL } from '@/utils/config';
5 | import { Inter } from 'next/font/google';
6 | import Link from 'next/link';
7 |
8 | const inter = Inter({ subsets: ['latin'] });
9 |
10 | export default function Home() {
11 | const origin =
12 | typeof window !== 'undefined' && window.location.origin
13 | ? window.location.origin
14 | : SITE_URL;
15 |
16 | const links = [
17 | {
18 | title: 'Next.js',
19 | description:
20 | 'Seamlessly integrate your decentralized application with Next.js, a popular React-based framework.',
21 | href: 'https://nextjs.org',
22 | },
23 | {
24 | title: 'RainbowKit',
25 | description: 'A powerful and easy-to-use wallet Ethereum-based dApps.',
26 | href: 'https://www.rainbowkit.com',
27 | },
28 | {
29 | title: 'WAGMI',
30 | description:
31 | 'wagmi is a collection of React Hooks containing everything you need to start working with Ethereum.',
32 | href: 'https://wagmi.sh',
33 | },
34 | {
35 | title: 'Examples',
36 | description:
37 | 'Start by exploring some pre-built examples to inspire your creativity!',
38 | href: `${origin}/examples`,
39 | },
40 | ];
41 | return (
42 |
45 |
46 | {SITE_NAME}
47 |
48 |
49 |
50 |
51 |
52 | Get started by editing
53 | pages/index.tsx
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
69 |
70 |
71 | {links.map((link, index) => (
72 |
78 |
79 | {link.title}
80 |
81 | ->
82 |
83 |
84 |
85 | {link.description}
86 |
87 |
88 | ))}
89 |
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/providers/Web3.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/no-children-prop */
2 | import '@rainbow-me/rainbowkit/styles.css';
3 | import {
4 | connectorsForWallets,
5 | RainbowKitProvider,
6 | lightTheme,
7 | darkTheme,
8 | } from '@rainbow-me/rainbowkit';
9 | import {
10 | injectedWallet,
11 | metaMaskWallet,
12 | trustWallet,
13 | walletConnectWallet,
14 | ledgerWallet,
15 | coinbaseWallet,
16 | } from '@rainbow-me/rainbowkit/wallets';
17 |
18 | import { configureChains, createConfig, WagmiConfig } from 'wagmi';
19 | import { publicProvider } from 'wagmi/providers/public';
20 |
21 | import { ReactNode } from 'react';
22 |
23 | import { ETH_CHAINS, WALLET_CONNECT_PROJECT_ID } from '@/utils/config';
24 |
25 | interface Props {
26 | children: ReactNode;
27 | }
28 |
29 | const projectId = WALLET_CONNECT_PROJECT_ID;
30 |
31 | const { chains, publicClient, webSocketPublicClient } = configureChains(
32 | ETH_CHAINS,
33 | [publicProvider()]
34 | );
35 |
36 | const connectors = connectorsForWallets([
37 | {
38 | groupName: 'Recommended',
39 | wallets: [
40 | injectedWallet({ chains }),
41 | metaMaskWallet({ projectId, chains }),
42 | walletConnectWallet({ projectId, chains }),
43 | ],
44 | },
45 | {
46 | groupName: 'Others',
47 | wallets: [
48 | trustWallet({ projectId, chains }),
49 | ledgerWallet({ projectId, chains }),
50 | coinbaseWallet({ chains, appName: 'DAPP KIT' }),
51 | ],
52 | },
53 | ]);
54 |
55 | const wagmiConfig = createConfig({
56 | autoConnect: true,
57 | connectors,
58 | publicClient,
59 | webSocketPublicClient,
60 | });
61 |
62 | const Web3Provider = (props: Props) => {
63 | return (
64 |
65 |
76 | {props.children}
77 |
78 |
79 | );
80 | };
81 |
82 | export default Web3Provider;
83 |
--------------------------------------------------------------------------------
/public/boilr3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/public/icons/ens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/icons/ens.png
--------------------------------------------------------------------------------
/public/icons/erc20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/icons/erc20.png
--------------------------------------------------------------------------------
/public/icons/ether.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/icons/ether.png
--------------------------------------------------------------------------------
/public/icons/nft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/icons/nft.png
--------------------------------------------------------------------------------
/public/icons/sign.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/icons/sign.png
--------------------------------------------------------------------------------
/public/icons/siwe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/icons/siwe.png
--------------------------------------------------------------------------------
/public/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Envoy-VC/boilr3/d0a51b22c66bd2c76010ec1137d3e633d29bac53/public/og.png
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
29 | .card-neumorphism {
30 | border-radius: 50px;
31 | background: #141414;
32 | box-shadow: -20px 20px 43px #121212, 20px -20px 43px #161616;
33 | }
34 |
35 | [data-rk] [aria-labelledby='rk_connect_title'] {
36 | z-index: 2147483645 !important;
37 | }
38 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
5 | './components/**/*.{js,ts,jsx,tsx,mdx}',
6 | './app/**/*.{js,ts,jsx,tsx,mdx}',
7 | ],
8 | theme: {
9 | extend: {
10 | backgroundImage: {
11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
12 | 'gradient-conic':
13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
14 | },
15 | },
16 | },
17 | plugins: [],
18 | }
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "paths": {
18 | "@/*": ["./*"]
19 | }
20 | },
21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types/**/*.d.ts"],
22 | "exclude": ["node_modules"]
23 | }
24 |
--------------------------------------------------------------------------------
/types/iron-session/index.d.ts:
--------------------------------------------------------------------------------
1 | import 'iron-session';
2 | import { SiweMessage } from 'siwe';
3 |
4 | declare module 'iron-session' {
5 | interface IronSessionData {
6 | nonce?: string;
7 | siwe?: SiweMessage;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/utils/config.ts:
--------------------------------------------------------------------------------
1 | import {
2 | mainnet,
3 | polygon,
4 | sepolia,
5 | polygonMumbai,
6 | polygonZkEvm,
7 | polygonZkEvmTestnet,
8 | bsc,
9 | bscTestnet,
10 | arbitrum,
11 | arbitrumGoerli,
12 | } from 'wagmi/chains';
13 |
14 | export const ETH_CHAINS = [
15 | mainnet,
16 | polygon,
17 | sepolia,
18 | polygonMumbai,
19 | polygonZkEvm,
20 | polygonZkEvmTestnet,
21 | bsc,
22 | bscTestnet,
23 | arbitrum,
24 | arbitrumGoerli,
25 | ];
26 | export const WALLET_CONNECT_PROJECT_ID =
27 | process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID || '';
28 |
29 | export const SITE_NAME = 'boilr3';
30 | export const SITE_DESCRIPTION =
31 | 'The Ultimate Next.js dApp Boilerplate with RainbowKit, Tailwind CSS & WAGMI';
32 | export const SITE_URL = 'https://boilr3.vercel.app';
33 |
34 | export const SOCIAL_TWITTER = 'Envoy_1084';
35 | export const SOCIAL_GITHUB = 'Envoy-VC/boilr3';
36 |
37 | export const NFT_CONTRACT_ADDRESS =
38 | '0x0Fc5f8A784810dEd101BD734cC59F6F7b868A3AF';
39 |
40 | export const ironOptions = {
41 | cookieName: SITE_NAME,
42 | password:
43 | process.env.SESSION_PASSWORD ??
44 | 'set_a_complex_password_at_least_32_characters_long',
45 | cookieOptions: {
46 | secure: process.env.NODE_ENV === 'production',
47 | },
48 | };
49 |
--------------------------------------------------------------------------------