├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── Dockerfile ├── README.md ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── logo.svg └── vercel.svg ├── scaffold-desktop.png ├── scaffold-mobile.png ├── src ├── components │ ├── AppBar.tsx │ ├── ContentContainer.tsx │ ├── Footer.tsx │ ├── Notification.tsx │ ├── RequestAirdrop.tsx │ ├── SendTransaction.tsx │ └── SignMessage.tsx ├── contexts │ ├── AutoConnectProvider.tsx │ └── ContextProvider.tsx ├── hooks │ └── useQueryContext.tsx ├── models │ └── types.ts ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ └── hello.ts │ ├── basics.tsx │ └── index.tsx ├── stores │ ├── useNotificationStore.tsx │ └── useUserSOLBalanceStore.tsx ├── styles │ └── globals.css ├── utils │ ├── explorer.ts │ └── notifications.tsx └── views │ ├── basics │ └── index.tsx │ ├── home │ └── index.tsx │ └── index.tsx ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": ["next/babel"] } 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | __generated__ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/babel"] 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 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | 39 | # logs 40 | *.log -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | WORKDIR /opt/app 4 | 5 | ENV NODE_ENV production 6 | 7 | COPY package*.json ./ 8 | 9 | RUN npm ci 10 | 11 | COPY . /opt/app 12 | 13 | RUN npm install --dev && npm run build 14 | 15 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Solana dApp Scaffold Next 3 | 4 | # Note this version is not currently maintained. 5 | ## Please use the newer version: [Solana Dapp Scaffold](https://github.com/solana-labs/dapp-scaffold/) 6 | 7 | ## Older version notes: 8 | 9 | The Solana dApp Scaffold repos are meant to house good starting scaffolds for ecosystem developers to get up and running quickly with a front end client UI that integrates several common features found in dApps with some basic usage examples. Wallet Integration. State management. Components examples. Notificaitons. Setup recommendations. 10 | 11 | Responsive | Desktop 12 | :-------------------------:|:-------------------------: 13 | ![](scaffold-mobile.png) | ![](scaffold-desktop.png) 14 | 15 | ## Getting Started 16 | 17 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 18 | 19 | The responsive version for wallets and wallet adapter may not function or work as expected for mobile based on plugin and wallet compatibility. 20 | 21 | ## Installation 22 | 23 | ```bash 24 | npm install 25 | # or 26 | yarn install 27 | ``` 28 | 29 | ## Build and Run 30 | 31 | Next, run the development server: 32 | 33 | ```bash 34 | npm run dev 35 | # or 36 | yarn dev 37 | ``` 38 | 39 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 40 | 41 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 42 | 43 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 44 | 45 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 46 | 47 | ## Features 48 | 49 | Each Scaffold will contain at least the following features: 50 | 51 | ``` 52 | Wallet Integration with Auto Connec / Refresh 53 | 54 | State Management 55 | 56 | Components: One or more components demonstrating state management 57 | 58 | Web3 Js: Examples of one or more uses of web3 js including a transaction with a connection provider 59 | 60 | Sample navigation and page changing to demonstate state 61 | 62 | Clean Simple Styling 63 | 64 | Notifications (optional): Example of using a notification system 65 | 66 | ``` 67 | 68 | A Solana Components Repo will be released in the near future to house a common components library. 69 | 70 | 71 | ### Structure 72 | 73 | The scaffold project structure may vary based on the front end framework being utilized. The below is an example structure for the Next js Scaffold. 74 | 75 | ``` 76 | ├── public : publically hosted files 77 | ├── src : primary code folders and files 78 | │ ├── components : should house anything considered a resuable UI component 79 | │ ├── contexts` : any context considered reusable and useuful to many compoennts that can be passed down through a component tree 80 | │ ├── hooks` : any functions that let you 'hook' into react state or lifecycle features from function components 81 | │ ├── models` : any data structure that may be reused throughout the project 82 | │ ├── pages` : the pages that host meta data and the intended `View` for the page 83 | │ ├── stores` : stores used in state management 84 | │ ├── styles` : contain any global and reusable styles 85 | │ ├── utils` : any other functionality considered reusable code that can be referenced 86 | │ ├── views` : contains the actual views of the project that include the main content and components within 87 | style, package, configuration, and other project files 88 | 89 | ``` 90 | 91 | ## Contributing 92 | 93 | Anyone is welcome to create an issue to build, discuss or request a new feature or update to the existing code base. Please keep in mind the following when submitting an issue. We consider merging high value features that may be utilized by the majority of scaffold users. If this is not a common feature or fix, consider adding it to the component library or cookbook. Please refer to the project's architecture and style when contributing. 94 | 95 | If submitting a feature, please reference the project structure shown above and try to follow the overall architecture and style presented in the existing scaffold. 96 | 97 | ### Committing 98 | 99 | To choose a task or make your own, do the following: 100 | 101 | 1. [Add an issue](https://github.com/solana-dev-adv/solana-dapp-next/issues/new) for the task and assign it to yourself or comment on the issue 102 | 2. Make a draft PR referencing the issue. 103 | 104 | The general flow for making a contribution: 105 | 106 | 1. Fork the repo on GitHub 107 | 2. Clone the project to your own machine 108 | 3. Commit changes to your own branch 109 | 4. Push your work back up to your fork 110 | 5. Submit a Pull request so that we can review your changes 111 | 112 | **NOTE**: Be sure to merge the latest from "upstream" before making a 113 | pull request! 114 | 115 | You can find tasks on the [project board](https://github.com/solana-dev-adv/solana-dapp-next/projects/1) 116 | or create an issue and assign it to yourself. 117 | 118 | 119 | ## Learn More Next Js 120 | 121 | To learn more about Next.js, take a look at the following resources: 122 | 123 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 124 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 125 | 126 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 127 | 128 | ## Deploy on Vercel 129 | 130 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 131 | 132 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 133 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /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": "solana-dapp-next", 3 | "version": "0.1.0", 4 | "license": "MIT", 5 | "private": false, 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@heroicons/react": "^1.0.5", 14 | "@solana/wallet-adapter-base": "^0.9.3", 15 | "@solana/wallet-adapter-react": "^0.15.3", 16 | "@solana/wallet-adapter-react-ui": "^0.9.5", 17 | "@solana/wallet-adapter-wallets": "^0.15.3", 18 | "@solana/web3.js": "^1.66.2", 19 | "@tailwindcss/typography": "^0.5.0", 20 | "daisyui": "^1.24.3", 21 | "immer": "^9.0.12", 22 | "next": "^12.3.1", 23 | "next-compose-plugins": "^2.2.1", 24 | "next-transpile-modules": "^9.0.0", 25 | "react": "17.0.2", 26 | "react-dom": "17.0.2", 27 | "tweetnacl": "^1.0.3", 28 | "zustand": "^3.6.9" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "17.0.10", 32 | "@types/react": "17.0.38", 33 | "autoprefixer": "^10.4.2", 34 | "eslint": "8.7.0", 35 | "eslint-config-next": "12.0.8", 36 | "postcss": "^8.4.5", 37 | "tailwindcss": "^3.0.15", 38 | "typescript": "4.5.4" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solana-developers/solana-dapp-next/2f3511296ff743c0e91e5c5e358c444cf14d0a00/public/favicon.ico -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /scaffold-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solana-developers/solana-dapp-next/2f3511296ff743c0e91e5c5e358c444cf14d0a00/scaffold-desktop.png -------------------------------------------------------------------------------- /scaffold-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solana-developers/solana-dapp-next/2f3511296ff743c0e91e5c5e358c444cf14d0a00/scaffold-mobile.png -------------------------------------------------------------------------------- /src/components/AppBar.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import Link from "next/link"; 3 | 4 | import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; 5 | import { useAutoConnect } from '../contexts/AutoConnectProvider'; 6 | 7 | export const AppBar: FC = props => { 8 | const { autoConnect, setAutoConnect } = useAutoConnect(); 9 | 10 | return ( 11 |
12 | 13 | {/* NavBar / Header */} 14 |
15 |
16 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |
50 | 51 | {/* Nav Links */} 52 |
53 |
54 | 55 | Home 56 | 57 | 58 | Basics 59 | 60 |
61 |
62 | 63 | {/* Wallet & Settings */} 64 |
65 |
66 |
67 | 68 | 69 | 70 | 71 |
72 |
    73 |
  • 74 |
    75 | 79 |
    80 |
  • 81 |
82 |
83 | 84 |
85 |
86 | {props.children} 87 |
88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /src/components/ContentContainer.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import Link from "next/link"; 3 | export const ContentContainer: FC = props => { 4 | 5 | return ( 6 |
7 | {/*
*/} 8 | 9 |
10 | {props.children} 11 |
12 | 13 | {/* SideBar / Drawer */} 14 |
15 | 16 |
    17 |
  • 18 |

    Menu

    19 |
  • 20 |
  • 21 | 22 | Home 23 | 24 |
  • 25 |
  • 26 | 27 | Basics 28 | 29 |
  • 30 |
31 |
32 |
33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | 3 | export const Footer: FC = () => { 4 | return ( 5 |
6 | 65 |
66 | ); 67 | }; 68 | -------------------------------------------------------------------------------- /src/components/Notification.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { 3 | CheckCircleIcon, 4 | InformationCircleIcon, 5 | XCircleIcon, 6 | } from '@heroicons/react/outline' 7 | import { XIcon } from '@heroicons/react/solid' 8 | import useNotificationStore from '../stores/useNotificationStore' 9 | import { useConnection } from '@solana/wallet-adapter-react'; 10 | import { getExplorerUrl } from '../utils/explorer' 11 | 12 | const NotificationList = () => { 13 | const { notifications, set: setNotificationStore } = useNotificationStore( 14 | (s) => s 15 | ) 16 | 17 | const reversedNotifications = [...notifications].reverse() 18 | 19 | return ( 20 |
23 |
24 | {reversedNotifications.map((n, idx) => ( 25 | { 32 | setNotificationStore((state: any) => { 33 | const reversedIndex = reversedNotifications.length - 1 - idx; 34 | state.notifications = [ 35 | ...notifications.slice(0, reversedIndex), 36 | ...notifications.slice(reversedIndex + 1), 37 | ]; 38 | }); 39 | }} 40 | /> 41 | ))} 42 |
43 |
44 | ); 45 | } 46 | 47 | const Notification = ({ type, message, description, txid, onHide }) => { 48 | const { connection } = useConnection(); 49 | 50 | // TODO: we dont have access to the network or endpoint here.. 51 | // getExplorerUrl(connection., txid, 'tx') 52 | // Either a provider, context, and or wallet adapter related pro/contx need updated 53 | 54 | 55 | useEffect(() => { 56 | const id = setTimeout(() => { 57 | onHide() 58 | }, 8000); 59 | 60 | return () => { 61 | clearInterval(id); 62 | }; 63 | }, [onHide]); 64 | 65 | return ( 66 |
69 |
70 |
71 |
72 | {type === 'success' ? ( 73 | 74 | ) : null} 75 | {type === 'info' && } 76 | {type === 'error' && ( 77 | 78 | )} 79 |
80 |
81 |
{message}
82 | {description ? ( 83 |

{description}

84 | ) : null} 85 | {txid ? ( 86 | 100 | ) : null} 101 |
102 |
103 | 110 |
111 |
112 |
113 |
114 | ) 115 | } 116 | 117 | export default NotificationList 118 | -------------------------------------------------------------------------------- /src/components/RequestAirdrop.tsx: -------------------------------------------------------------------------------- 1 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'; 2 | import { LAMPORTS_PER_SOL, TransactionSignature } from '@solana/web3.js'; 3 | import { FC, useCallback } from 'react'; 4 | import { notify } from "../utils/notifications"; 5 | import useUserSOLBalanceStore from '../stores/useUserSOLBalanceStore'; 6 | 7 | export const RequestAirdrop: FC = () => { 8 | const { connection } = useConnection(); 9 | const { publicKey } = useWallet(); 10 | const { getUserSOLBalance } = useUserSOLBalanceStore(); 11 | 12 | const onClick = useCallback(async () => { 13 | if (!publicKey) { 14 | console.log('error', 'Wallet not connected!'); 15 | notify({ type: 'error', message: 'error', description: 'Wallet not connected!' }); 16 | return; 17 | } 18 | 19 | let signature: TransactionSignature = ''; 20 | 21 | try { 22 | signature = await connection.requestAirdrop(publicKey, LAMPORTS_PER_SOL); 23 | await connection.confirmTransaction(signature, 'confirmed'); 24 | notify({ type: 'success', message: 'Airdrop successful!', txid: signature }); 25 | 26 | getUserSOLBalance(publicKey, connection); 27 | } catch (error: any) { 28 | notify({ type: 'error', message: `Airdrop failed!`, description: error?.message, txid: signature }); 29 | console.log('error', `Airdrop failed! ${error?.message}`, signature); 30 | } 31 | }, [publicKey, connection, getUserSOLBalance]); 32 | 33 | return ( 34 |
35 | 41 |
42 | ); 43 | }; 44 | 45 | -------------------------------------------------------------------------------- /src/components/SendTransaction.tsx: -------------------------------------------------------------------------------- 1 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'; 2 | import { Keypair, SystemProgram, Transaction, TransactionSignature } from '@solana/web3.js'; 3 | import { FC, useCallback } from 'react'; 4 | import { notify } from "../utils/notifications"; 5 | 6 | export const SendTransaction: FC = () => { 7 | const { connection } = useConnection(); 8 | const { publicKey, sendTransaction } = useWallet(); 9 | 10 | const onClick = useCallback(async () => { 11 | if (!publicKey) { 12 | notify({ type: 'error', message: `Wallet not connected!` }); 13 | console.log('error', `Send Transaction: Wallet not connected!`); 14 | return; 15 | } 16 | 17 | let signature: TransactionSignature = ''; 18 | try { 19 | const transaction = new Transaction().add( 20 | SystemProgram.transfer({ 21 | fromPubkey: publicKey, 22 | toPubkey: Keypair.generate().publicKey, 23 | lamports: 1, 24 | }) 25 | ); 26 | 27 | signature = await sendTransaction(transaction, connection); 28 | 29 | await connection.confirmTransaction(signature, 'confirmed'); 30 | notify({ type: 'success', message: 'Transaction successful!', txid: signature }); 31 | } catch (error: any) { 32 | notify({ type: 'error', message: `Transaction failed!`, description: error?.message, txid: signature }); 33 | console.log('error', `Transaction failed! ${error?.message}`, signature); 34 | return; 35 | } 36 | }, [publicKey, notify, connection, sendTransaction]); 37 | 38 | return ( 39 |
40 | 51 |
52 | ); 53 | }; 54 | -------------------------------------------------------------------------------- /src/components/SignMessage.tsx: -------------------------------------------------------------------------------- 1 | // TODO: SignMessage 2 | import { useWallet } from '@solana/wallet-adapter-react'; 3 | import bs58 from 'bs58'; 4 | import { FC, useCallback } from 'react'; 5 | import { sign } from 'tweetnacl'; 6 | import { notify } from "../utils/notifications"; 7 | 8 | export const SignMessage: FC = () => { 9 | const { publicKey, signMessage } = useWallet(); 10 | 11 | const onClick = useCallback(async () => { 12 | try { 13 | // `publicKey` will be null if the wallet isn't connected 14 | if (!publicKey) throw new Error('Wallet not connected!'); 15 | // `signMessage` will be undefined if the wallet doesn't support it 16 | if (!signMessage) throw new Error('Wallet does not support message signing!'); 17 | // Encode anything as bytes 18 | const message = new TextEncoder().encode('Hello, world!'); 19 | // Sign the bytes using the wallet 20 | const signature = await signMessage(message); 21 | // Verify that the bytes were signed using the private key that matches the known public key 22 | if (!sign.detached.verify(message, signature, publicKey.toBytes())) throw new Error('Invalid signature!'); 23 | notify({ type: 'success', message: 'Sign message successful!', txid: bs58.encode(signature) }); 24 | } catch (error: any) { 25 | notify({ type: 'error', message: `Sign Message failed!`, description: error?.message }); 26 | console.log('error', `Sign Message failed! ${error?.message}`); 27 | } 28 | }, [publicKey, notify, signMessage]); 29 | 30 | return ( 31 |
32 | 43 |
44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/contexts/AutoConnectProvider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, Dispatch, FC, ReactNode, SetStateAction, useCallback, useContext, useEffect, useState } from 'react'; 2 | 3 | export interface AutoConnectContextState { 4 | autoConnect: boolean; 5 | setAutoConnect: Dispatch>; 6 | } 7 | 8 | export const AutoConnectContext = createContext({} as AutoConnectContextState); 9 | 10 | export function useAutoConnect(): AutoConnectContextState { 11 | return useContext(AutoConnectContext); 12 | } 13 | 14 | export const AutoConnectProvider: FC<{ children: ReactNode }> = ({ children }) => { 15 | const [autoConnect, setAutoConnect] = useState(() => { 16 | if (typeof window === 'undefined') return false 17 | const value = localStorage.getItem('auto-connect') 18 | 19 | return value ? JSON.parse(value) : false 20 | }); 21 | 22 | useEffect(() => { 23 | localStorage.setItem('auto-connect', JSON.stringify(autoConnect)) 24 | },[autoConnect]) 25 | 26 | return ( 27 | {children} 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/contexts/ContextProvider.tsx: -------------------------------------------------------------------------------- 1 | import { WalletAdapterNetwork, WalletError } from '@solana/wallet-adapter-base'; 2 | import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react'; 3 | import { 4 | PhantomWalletAdapter, 5 | SolflareWalletAdapter, 6 | SolletExtensionWalletAdapter, 7 | SolletWalletAdapter, 8 | TorusWalletAdapter, 9 | // LedgerWalletAdapter, 10 | // SlopeWalletAdapter, 11 | } from '@solana/wallet-adapter-wallets'; 12 | import { clusterApiUrl } from '@solana/web3.js'; 13 | import { FC, ReactNode, useCallback, useMemo } from 'react'; 14 | import { AutoConnectProvider, useAutoConnect } from './AutoConnectProvider'; 15 | import { notify } from "../utils/notifications"; 16 | import dynamic from "next/dynamic"; 17 | 18 | const ReactUIWalletModalProviderDynamic = dynamic( 19 | async () => 20 | (await import("@solana/wallet-adapter-react-ui")).WalletModalProvider, 21 | { ssr: false } 22 | ); 23 | 24 | const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => { 25 | const { autoConnect } = useAutoConnect(); 26 | const network = WalletAdapterNetwork.Devnet; 27 | const endpoint = useMemo(() => clusterApiUrl(network), [network]); 28 | 29 | const wallets = useMemo( 30 | () => [ 31 | new PhantomWalletAdapter(), 32 | new SolflareWalletAdapter(), 33 | new SolletWalletAdapter({ network }), 34 | new SolletExtensionWalletAdapter({ network }), 35 | new TorusWalletAdapter(), 36 | // new LedgerWalletAdapter(), 37 | // new SlopeWalletAdapter(), 38 | ], 39 | [network] 40 | ); 41 | 42 | const onError = useCallback( 43 | (error: WalletError) => { 44 | notify({ type: 'error', message: error.message ? `${error.name}: ${error.message}` : error.name }); 45 | console.error(error); 46 | }, 47 | [] 48 | ); 49 | 50 | return ( 51 | // TODO: updates needed for updating and referencing endpoint: wallet adapter rework 52 | 53 | 54 | 55 | {children} 56 | 57 | 58 | 59 | ); 60 | }; 61 | 62 | export const ContextProvider: FC<{ children: ReactNode }> = ({ children }) => { 63 | return ( 64 | 65 | {children} 66 | 67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /src/hooks/useQueryContext.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import { EndpointTypes } from '../models/types' 3 | 4 | export default function useQueryContext() { 5 | const router = useRouter() 6 | const { cluster } = router.query 7 | 8 | const endpoint = cluster ? (cluster as EndpointTypes) : 'mainnet' 9 | const hasClusterOption = endpoint !== 'mainnet' 10 | const fmtUrlWithCluster = (url) => { 11 | if (hasClusterOption) { 12 | const mark = url.includes('?') ? '&' : '?' 13 | return decodeURIComponent(`${url}${mark}cluster=${endpoint}`) 14 | } 15 | return url 16 | } 17 | 18 | return { 19 | fmtUrlWithCluster, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/models/types.ts: -------------------------------------------------------------------------------- 1 | export type EndpointTypes = 'mainnet' | 'devnet' | 'localnet' 2 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from 'next/app'; 2 | import Head from 'next/head'; 3 | import { FC } from 'react'; 4 | import { ContextProvider } from '../contexts/ContextProvider'; 5 | import { AppBar } from '../components/AppBar'; 6 | import { ContentContainer } from '../components/ContentContainer'; 7 | import { Footer } from '../components/Footer'; 8 | import Notifications from '../components/Notification' 9 | 10 | require('@solana/wallet-adapter-react-ui/styles.css'); 11 | require('../styles/globals.css'); 12 | 13 | const App: FC = ({ Component, pageProps }) => { 14 | return ( 15 | <> 16 | 17 | Solana Scaffold Lite 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 | ); 32 | }; 33 | 34 | export default App; 35 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document' 2 | 3 | class MyDocument extends Document { 4 | static async getInitialProps(ctx: DocumentContext) { 5 | const initialProps = await Document.getInitialProps(ctx) 6 | 7 | return initialProps 8 | } 9 | 10 | render() { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | ); 22 | } 23 | } 24 | 25 | export default MyDocument; 26 | -------------------------------------------------------------------------------- /src/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/basics.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import Head from "next/head"; 3 | import { BasicsView } from "../views"; 4 | 5 | const Basics: NextPage = (props) => { 6 | return ( 7 |
8 | 9 | Solana Scaffold 10 | 14 | 15 | 16 |
17 | ); 18 | }; 19 | 20 | export default Basics; 21 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import Head from "next/head"; 3 | import { HomeView } from "../views"; 4 | 5 | const Home: NextPage = (props) => { 6 | return ( 7 |
8 | 9 | Solana Scaffold 10 | 14 | 15 | 16 |
17 | ); 18 | }; 19 | 20 | export default Home; 21 | -------------------------------------------------------------------------------- /src/stores/useNotificationStore.tsx: -------------------------------------------------------------------------------- 1 | import create, { State } from "zustand"; 2 | import produce from "immer"; 3 | 4 | interface NotificationStore extends State { 5 | notifications: Array<{ 6 | type: string 7 | message: string 8 | description?: string 9 | txid?: string 10 | }> 11 | set: (x: any) => void 12 | } 13 | 14 | const useNotificationStore = create((set, _get) => ({ 15 | notifications: [], 16 | set: (fn) => set(produce(fn)), 17 | })) 18 | 19 | export default useNotificationStore 20 | -------------------------------------------------------------------------------- /src/stores/useUserSOLBalanceStore.tsx: -------------------------------------------------------------------------------- 1 | import create, { State } from 'zustand' 2 | import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js' 3 | 4 | interface UserSOLBalanceStore extends State { 5 | balance: number; 6 | getUserSOLBalance: (publicKey: PublicKey, connection: Connection) => void 7 | } 8 | 9 | const useUserSOLBalanceStore = create((set, _get) => ({ 10 | balance: 0, 11 | getUserSOLBalance: async (publicKey, connection) => { 12 | let balance = 0; 13 | try { 14 | balance = await connection.getBalance( 15 | publicKey, 16 | 'confirmed' 17 | ); 18 | balance = balance / LAMPORTS_PER_SOL; 19 | } catch (e) { 20 | console.log(`error getting balance: `, e); 21 | } 22 | set((s) => { 23 | s.balance = balance; 24 | console.log(`balance updated, `, balance); 25 | }) 26 | }, 27 | })); 28 | 29 | export default useUserSOLBalanceStore; -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | padding: 0; 8 | margin: 0; 9 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 10 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 11 | } 12 | 13 | a { 14 | color: inherit; 15 | text-decoration: none; 16 | } 17 | 18 | * { 19 | box-sizing: border-box; 20 | } 21 | 22 | /* example: override wallet button style */ 23 | .wallet-adapter-button:not([disabled]):hover { 24 | background-color: #707070; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/utils/explorer.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, Transaction } from '@solana/web3.js' 2 | import base58 from 'bs58' 3 | 4 | export function getExplorerUrl( 5 | endpoint: string, 6 | viewTypeOrItemAddress: 'inspector' | PublicKey | string, 7 | itemType = 'address' // | 'tx' | 'block' 8 | ) { 9 | const getClusterUrlParam = () => { 10 | let cluster = '' 11 | if (endpoint === 'localnet') { 12 | cluster = `custom&customUrl=${encodeURIComponent( 13 | 'http://127.0.0.1:8899' 14 | )}` 15 | } else if (endpoint === 'https://api.devnet.solana.com') { 16 | cluster = 'devnet' 17 | } 18 | 19 | return cluster ? `?cluster=${cluster}` : '' 20 | } 21 | 22 | return `https://explorer.solana.com/${itemType}/${viewTypeOrItemAddress}${getClusterUrlParam()}` 23 | } -------------------------------------------------------------------------------- /src/utils/notifications.tsx: -------------------------------------------------------------------------------- 1 | import useNotificationStore from "../stores/useNotificationStore"; 2 | 3 | export function notify(newNotification: { 4 | type?: string 5 | message: string 6 | description?: string 7 | txid?: string 8 | }) { 9 | const { 10 | notifications, 11 | set: setNotificationStore, 12 | } = useNotificationStore.getState() 13 | 14 | setNotificationStore((state: { notifications: any[] }) => { 15 | state.notifications = [ 16 | ...notifications, 17 | { type: 'success', ...newNotification }, 18 | ] 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/views/basics/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { FC } from "react"; 3 | import { SignMessage } from '../../components/SignMessage'; 4 | import { SendTransaction } from '../../components/SendTransaction'; 5 | 6 | export const BasicsView: FC = ({ }) => { 7 | 8 | return ( 9 |
10 |
11 |

12 | Basics 13 |

14 | {/* CONTENT GOES HERE */} 15 |
16 | 17 | 18 |
19 |
20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/views/home/index.tsx: -------------------------------------------------------------------------------- 1 | // Next, React 2 | import { FC, useEffect, useState } from 'react'; 3 | import Link from 'next/link'; 4 | 5 | // Wallet 6 | import { useWallet, useConnection } from '@solana/wallet-adapter-react'; 7 | 8 | // Components 9 | import { RequestAirdrop } from '../../components/RequestAirdrop'; 10 | import pkg from '../../../package.json'; 11 | 12 | // Store 13 | import useUserSOLBalanceStore from '../../stores/useUserSOLBalanceStore'; 14 | 15 | export const HomeView: FC = ({ }) => { 16 | const wallet = useWallet(); 17 | const { connection } = useConnection(); 18 | 19 | const balance = useUserSOLBalanceStore((s) => s.balance) 20 | const { getUserSOLBalance } = useUserSOLBalanceStore() 21 | 22 | useEffect(() => { 23 | if (wallet.publicKey) { 24 | console.log(wallet.publicKey.toBase58()) 25 | getUserSOLBalance(wallet.publicKey, connection) 26 | } 27 | }, [wallet.publicKey, connection, getUserSOLBalance]) 28 | 29 | return ( 30 | 31 |
32 |
33 |

34 | Scaffold Lite v{pkg.version} 35 |

36 |

37 |

Simply the fastest way to get started.

38 | Next.js, tailwind, wallet, web3.js, and more. 39 |

40 |
41 |
42 |             Start building on Solana  
43 |           
44 |
45 |
46 | 47 | {/* {wallet.publicKey &&

Public Key: {wallet.publicKey.toBase58()}

} */} 48 | {wallet &&

SOL Balance: {(balance || 0).toLocaleString()}

} 49 |
50 |
51 |
52 | ); 53 | }; 54 | -------------------------------------------------------------------------------- /src/views/index.tsx: -------------------------------------------------------------------------------- 1 | export { HomeView } from "./home"; 2 | export { BasicsView } from "./basics"; 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: "jit", 3 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 4 | darkMode: "media", 5 | theme: { 6 | extend: {}, 7 | }, 8 | plugins: [ 9 | require("@tailwindcss/typography"), 10 | require('daisyui') 11 | ], 12 | daisyui: { 13 | styled: true, 14 | // TODO: Theme needs works 15 | themes: [ 16 | { 17 | 'solana': { /* your theme name */ 18 | fontFamily: { 19 | display: ['PT Mono, monospace'], 20 | body: ['Inter, sans-serif'], 21 | }, 22 | 'primary': '#2a2a2a', /* Primary color */ 23 | 'primary-focus': '#9945FF', /* Primary color - focused */ 24 | 'primary-content': '#ffffff', /* Foreground content color to use on primary color */ 25 | 26 | 'secondary': '#f6d860', /* Secondary color */ 27 | 'secondary-focus': '#f3cc30', /* Secondary color - focused */ 28 | 'secondary-content': '#ffffff', /* Foreground content color to use on secondary color */ 29 | 30 | 'accent': '#33a382', /* Accent color */ 31 | 'accent-focus': '#2aa79b', /* Accent color - focused */ 32 | 'accent-content': '#ffffff', /* Foreground content color to use on accent color */ 33 | 34 | 'neutral': '#2b2b2b', /* Neutral color */ 35 | 'neutral-focus': '#2a2e37', /* Neutral color - focused */ 36 | 'neutral-content': '#ffffff', /* Foreground content color to use on neutral color */ 37 | 38 | 'base-100': '#181818', /* Base color of page, used for blank backgrounds */ 39 | 'base-200': '#35363a', /* Base color, a little darker */ 40 | 'base-300': '#222222', /* Base color, even more darker */ 41 | 'base-content': '#f9fafb', /* Foreground content color to use on base color */ 42 | 43 | 'info': '#2094f3', /* Info */ 44 | 'success': '#009485', /* Success */ 45 | 'warning': '#ff9900', /* Warning */ 46 | 'error': '#ff5724', /* Error */ 47 | }, 48 | }, 49 | // backup themes: 50 | // 'dark', 51 | // 'synthwave' 52 | ], 53 | base: true, 54 | utils: true, 55 | logs: true, 56 | rtl: false, 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "target": "es6", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": false, 9 | "strictNullChecks": false, 10 | "forceConsistentCasingInFileNames": true, 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "incremental": true 19 | }, 20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 21 | "exclude": ["node_modules", ".next",], 22 | "ts-node": { 23 | "require": ["tsconfig-paths/register"], 24 | "compilerOptions": { 25 | "module": "commonjs" 26 | } 27 | } 28 | } 29 | --------------------------------------------------------------------------------