├── .eslintrc.json ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── next-env.d.ts ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── browserconfig.xml ├── create-dapp-demo.mov ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── site.webmanifest └── vercel.svg ├── src ├── __tests__ │ └── App.test.tsx ├── config │ └── candy-machine.config.js ├── constants │ └── index.ts ├── contexts │ └── ClientWalletProvider.tsx ├── manifest.json ├── pages │ ├── _app.tsx │ ├── _document.js │ └── index.tsx ├── react-app-env.d.ts ├── setupTests.ts ├── styles │ ├── App.css │ └── globals.css ├── types.ts └── views │ ├── Home │ ├── Header.tsx │ ├── SiteDescription.tsx │ ├── index.module.css │ └── index.tsx │ └── JupiterForm │ ├── FeeInfo.tsx │ ├── JupiterForm.module.css │ ├── JupiterForm.tsx │ └── SpinnerProgress.tsx ├── tailwind.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "react-hooks/rules-of-hooks": 0, 5 | "react-hooks/exhaustive-deps": 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '34 13 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.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 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "singleQuote": false 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Thug DAO 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 | # Jupiter Solana dApp with Next.JS example 2 | 3 | ![1*cXNgzu0Pvj4HwOLw4Akwqg](https://user-images.githubusercontent.com/34560707/145749257-e48cb199-521b-476e-9d81-f79bb45ef834.png) 4 | 5 |

6 | Jupiter Aggregator (jup.ag) 7 |
8 | The best swap aggregator on Solana. Built for smart traders who like money. 9 |

10 |
11 | 12 | ## Getting Started 13 | 1. Add a `.env` to root 14 | 2. Populate these value 15 | ``` 16 | NEXT_PUBLIC_CLUSTER=devnet 17 | ``` 18 | 3. `npm i` 19 | 4. `npm run dev` 20 | 21 | 22 | ## Demo 23 | https://user-images.githubusercontent.com/188568/137521416-7274837b-6969-4cfc-ba25-84a560f124df.mov 24 | ## 🛵 ◍ Demo: https://jupiter-nextjs-example.vercel.app/ 25 | 26 | ## loom demo: https://www.loom.com/share/b1137aaaceed433a98b7edd52873186a 27 | -------------------------------------------------------------------------------- /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 withPlugins = require("next-compose-plugins"); 3 | 4 | // add this if you need LESS 5 | // also install less and less-loader 6 | // const withLess = require("next-with-less"); 7 | 8 | const plugins = [ 9 | // add this if you need LESS 10 | // [withLess, { 11 | // lessLoaderOptions: { 12 | // /* ... */ 13 | // }, 14 | // }], 15 | ]; 16 | 17 | const nextConfig = { 18 | swcMinify: true, 19 | reactStrictMode: true, 20 | webpack5: true, 21 | webpack: (config, { isServer }) => { 22 | if (!isServer) { 23 | config.resolve.fallback.fs = false; 24 | } 25 | return config; 26 | }, 27 | }; 28 | 29 | module.exports = withPlugins(plugins, nextConfig); 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-dapp-solana-nextjs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@jup-ag/react-hook": "4.0.0-beta.5", 13 | "@project-serum/serum": "^0.13.65", 14 | "@solana/spl-token-registry": "^0.2.298", 15 | "@solana/wallet-adapter-base": "^0.9.8", 16 | "@solana/wallet-adapter-react": "^0.15.4", 17 | "@solana/wallet-adapter-react-ui": "^0.9.6", 18 | "@solana/wallet-adapter-wallets": "^0.16.10", 19 | "@solana/web3.js": "~1.72.0", 20 | "@tailwindcss/typography": "^0.4.1", 21 | "bn.js": "5.2.1", 22 | "cross-fetch": "^3.1.5", 23 | "daisyui": "^1.16.2", 24 | "decimal.js": "^10.3.1", 25 | "jsbi": "^4.3.0", 26 | "next": "^12.0.7", 27 | "next-compose-plugins": "^2.2.1", 28 | "next-transpile-modules": "^9.0.0", 29 | "react": "18.1.0", 30 | "react-dom": "18.1.0" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "16.11.7", 34 | "@types/react": "17.0.34", 35 | "autoprefixer": "^10.4.0", 36 | "eslint": "7", 37 | "eslint-config-next": "12.0.3", 38 | "postcss": "^8.3.11", 39 | "tailwindcss": "^2.2.19", 40 | "typescript": "4.7.4" 41 | }, 42 | "resolutions": { 43 | "@solana/buffer-layout": "4.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jup-ag/jupiter-nextjs-example/587178250fd61c2e5abc8a0916a6d616d135f543/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jup-ag/jupiter-nextjs-example/587178250fd61c2e5abc8a0916a6d616d135f543/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jup-ag/jupiter-nextjs-example/587178250fd61c2e5abc8a0916a6d616d135f543/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/create-dapp-demo.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jup-ag/jupiter-nextjs-example/587178250fd61c2e5abc8a0916a6d616d135f543/public/create-dapp-demo.mov -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jup-ag/jupiter-nextjs-example/587178250fd61c2e5abc8a0916a6d616d135f543/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jup-ag/jupiter-nextjs-example/587178250fd61c2e5abc8a0916a6d616d135f543/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jup-ag/jupiter-nextjs-example/587178250fd61c2e5abc8a0916a6d616d135f543/public/favicon.ico -------------------------------------------------------------------------------- /public/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/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "@testing-library/react"; 3 | import App from "../__App"; 4 | 5 | jest.mock("./components/Identicon", () => { 6 | return { 7 | __esModule: true, 8 | Identicon: () => null, 9 | }; 10 | }); 11 | 12 | test("renders balances text", () => { 13 | const { getByText } = render(); 14 | const linkElement = getByText(/Your balances/i); 15 | expect(linkElement).toBeInTheDocument(); 16 | }); 17 | -------------------------------------------------------------------------------- /src/config/candy-machine.config.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | import { Cluster } from "@solana/web3.js"; 2 | import { ENV as ENVChainId } from "@solana/spl-token-registry"; 3 | 4 | // Endpoints, connection 5 | export const ENV: Cluster = 6 | (process.env.NEXT_PUBLIC_CLUSTER as Cluster) || "mainnet-beta"; 7 | export const CHAIN_ID = 8 | ENV === "mainnet-beta" 9 | ? ENVChainId.MainnetBeta 10 | : ENV === "devnet" 11 | ? ENVChainId.Devnet 12 | : ENV === "testnet" 13 | ? ENVChainId.Testnet 14 | : ENVChainId.MainnetBeta; 15 | export const SOLANA_RPC_ENDPOINT = 16 | ENV === "devnet" 17 | ? "https://api.devnet.solana.com" 18 | : "https://solana-api.projectserum.com"; 19 | 20 | // Token Mints 21 | export const INPUT_MINT_ADDRESS = 22 | ENV === "devnet" 23 | ? "So11111111111111111111111111111111111111112" // SOL 24 | : "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; // USDC 25 | export const OUTPUT_MINT_ADDRESS = 26 | ENV === "devnet" 27 | ? "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt" // SRM 28 | : "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"; // USDT 29 | 30 | // Interface 31 | export interface Token { 32 | chainId: number; // 101, 33 | address: string; // '8f9s1sUmzUbVZMoMh6bufMueYH1u4BJSM57RCEvuVmFp', 34 | symbol: string; // 'TRUE', 35 | name: string; // 'TrueSight', 36 | decimals: number; // 9, 37 | logoURI: string; // 'https://i.ibb.co/pKTWrwP/true.jpg', 38 | tags: string[]; // [ 'utility-token', 'capital-token' ] 39 | } 40 | -------------------------------------------------------------------------------- /src/contexts/ClientWalletProvider.tsx: -------------------------------------------------------------------------------- 1 | import type { WalletProviderProps } from "@solana/wallet-adapter-react"; 2 | import { WalletProvider } from "@solana/wallet-adapter-react"; 3 | 4 | import { 5 | BitKeepWalletAdapter, 6 | BitpieWalletAdapter, 7 | BloctoWalletAdapter, 8 | CloverWalletAdapter, 9 | Coin98WalletAdapter, 10 | CoinhubWalletAdapter, 11 | GlowWalletAdapter, 12 | MathWalletAdapter, 13 | SlopeWalletAdapter, 14 | TorusWalletAdapter, 15 | LedgerWalletAdapter, 16 | SolletWalletAdapter, 17 | SolongWalletAdapter, 18 | PhantomWalletAdapter, 19 | SafePalWalletAdapter, 20 | SolflareWalletAdapter, 21 | TokenPocketWalletAdapter, 22 | SolletExtensionWalletAdapter, 23 | } from "@solana/wallet-adapter-wallets"; 24 | import { useMemo } from "react"; 25 | import { WalletModalProvider } from "@solana/wallet-adapter-react-ui"; 26 | 27 | import("@solana/wallet-adapter-react-ui/styles.css" as any); 28 | 29 | export function ClientWalletProvider( 30 | props: Omit 31 | ): JSX.Element { 32 | const wallets = useMemo( 33 | () => [ 34 | new BitKeepWalletAdapter(), 35 | new BitpieWalletAdapter(), 36 | new BloctoWalletAdapter(), 37 | new CloverWalletAdapter(), 38 | new Coin98WalletAdapter(), 39 | new CoinhubWalletAdapter(), 40 | new GlowWalletAdapter(), 41 | new MathWalletAdapter(), 42 | new SlopeWalletAdapter(), 43 | new TorusWalletAdapter(), 44 | new LedgerWalletAdapter(), 45 | new SolletWalletAdapter(), 46 | new SolongWalletAdapter(), 47 | new PhantomWalletAdapter(), 48 | new SafePalWalletAdapter(), 49 | new SolflareWalletAdapter(), 50 | new TokenPocketWalletAdapter(), 51 | new SolletExtensionWalletAdapter(), 52 | ], 53 | [] 54 | ); 55 | 56 | return ( 57 | 58 | 59 | 60 | ); 61 | } 62 | 63 | export default ClientWalletProvider; 64 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Caw Caw", 3 | "short_name": "Caw Caw", 4 | "display": "standalone", 5 | "start_url": "./", 6 | "theme_color": "#002140", 7 | "background_color": "#001529", 8 | "icons": [ 9 | { 10 | "src": "icons/icon-192x192.png", 11 | "sizes": "192x192" 12 | }, 13 | { 14 | "src": "icons/icon-128x128.png", 15 | "sizes": "128x128" 16 | }, 17 | { 18 | "src": "icons/icon-512x512.png", 19 | "sizes": "512x512" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo } from "react"; 2 | import type { AppProps } from "next/app"; 3 | import { 4 | ConnectionProvider, 5 | useConnection, 6 | useWallet, 7 | } from "@solana/wallet-adapter-react"; 8 | import WalletProvider from "../contexts/ClientWalletProvider"; 9 | import "tailwindcss/tailwind.css"; 10 | import "../styles/globals.css"; 11 | import "../styles/App.css"; 12 | import { JupiterProvider } from "@jup-ag/react-hook"; 13 | 14 | export const SECOND_TO_REFRESH = 30 * 1000; 15 | 16 | function MyApp({ Component, pageProps }: AppProps) { 17 | const endpoint = useMemo(() => "https://ssc-dao.genesysgo.net/", []); 18 | 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | } 29 | 30 | const JupiterWrapper: React.FC = ({ children }) => { 31 | const { connection } = useConnection(); 32 | const wallet = useWallet(); 33 | 34 | return ( 35 | 40 | {children} 41 | 42 | ); 43 | }; 44 | 45 | export default MyApp; 46 | -------------------------------------------------------------------------------- /src/pages/_document.js: -------------------------------------------------------------------------------- 1 | import Document, { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | class MyDocument extends Document { 4 | render() { 5 | return ( 6 | 7 | 8 | 12 | 13 | 14 | 19 | 25 | 31 | 32 | {/* */} 33 | 34 | {/* 35 | 40 | */} 44 | 45 | 46 |
47 | 48 | 49 | 50 | ); 51 | } 52 | } 53 | 54 | export default MyDocument; 55 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import Head from "next/head"; 3 | import Home from "../views/Home"; 4 | 5 | const Index: NextPage = (props) => { 6 | return ( 7 |
8 | 9 | Jupiter dApp Scaffold 10 | 14 | 15 | 16 |
17 | ); 18 | }; 19 | 20 | export default Index; 21 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom/extend-expect"; 6 | 7 | Object.defineProperty(window, 'matchMedia', { 8 | value: () => { 9 | return { 10 | matches: false, 11 | addListener: () => {}, 12 | removeListener: () => {} 13 | }; 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /src/styles/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | font-size: 18px; 9 | height: 100vh; 10 | width: 100vw; 11 | } 12 | 13 | code { 14 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 15 | monospace; 16 | } 17 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /src/views/Home/Header.tsx: -------------------------------------------------------------------------------- 1 | import { WalletMultiButton } from '@solana/wallet-adapter-react-ui' 2 | import React from 'react' 3 | 4 | const Header = () => { 5 | return ( 6 |
7 |
8 | 11 |
12 |
13 | Caw Caw 14 |
15 |
16 | 17 |
18 |
19 | ) 20 | } 21 | 22 | export default Header 23 | -------------------------------------------------------------------------------- /src/views/Home/SiteDescription.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useWallet } from '@solana/wallet-adapter-react'; 3 | import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'; 4 | import { ENV } from '../../constants'; 5 | 6 | const SolanaLogo = () => ( 7 | 13 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 45 | 50 | 55 | 56 | 57 | ); 58 | 59 | const SiteDescription = () => { 60 | const { publicKey } = useWallet(); 61 | 62 | return ( 63 |
64 |
65 |
66 |
67 |

68 | Hello Solana World! 69 |

70 | 71 |
72 |

73 | This scaffold includes awesome tools for rapid development and 74 | deploy dapps to Solana: Next.JS, TypeScript, TailwindCSS, 75 | Daisy UI. 76 |

77 |

Solana wallet adapter is connected and ready to use.

78 |

Environment: {ENV}

79 | {publicKey 80 | ? ( 81 | <>Your address: {publicKey.toBase58()} 82 | ) 83 | : ( 84 |
85 |

Wallet not connected

86 |
87 | 88 |
89 |
90 | ) 91 | } 92 |
93 |
94 |
95 |
96 |
97 | ) 98 | } 99 | 100 | export default SiteDescription 101 | -------------------------------------------------------------------------------- /src/views/Home/index.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | @apply flex flex-col items-center 3 | } 4 | -------------------------------------------------------------------------------- /src/views/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | 3 | import Header from "./Header"; 4 | import SiteDescription from "./SiteDescription"; 5 | import JupiterForm from "../JupiterForm/JupiterForm"; 6 | 7 | import styles from "./index.module.css"; 8 | 9 | const Home: FC = ({ }) => { 10 | return ( 11 |
12 |
13 |
14 | 15 | 16 |
17 |
18 | ); 19 | }; 20 | 21 | export default Home; -------------------------------------------------------------------------------- /src/views/JupiterForm/FeeInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, useEffect, useMemo, useState } from "react"; 2 | 3 | import { RouteInfo, TransactionFeeInfo } from "@jup-ag/react-hook"; 4 | 5 | const FeeInfo: FunctionComponent<{ route: RouteInfo }> = ({ 6 | route, 7 | }: { 8 | route: RouteInfo; 9 | }) => { 10 | const fees = useMemo(() => { 11 | return route.fees; 12 | }, [route]); 13 | 14 | return ( 15 |
16 | {fees && ( 17 |
18 |
19 | Deposit For serum: {/* In lamports */} 20 | {fees.openOrdersDeposits.reduce((total, i) => total + i, 0) / 21 | 10 ** 9}{" "} 22 | SOL 23 |
24 | Deposit For ATA: {/* In lamports */} 25 | {fees.ataDeposits.reduce((total, deposit) => total + deposit, 0) / 26 | 10 ** 9}{" "} 27 | SOL 28 |
29 | Fee: {/* In lamports */} 30 | {fees.signatureFee / 10 ** 9} SOL 31 |
32 |
33 | )} 34 |
35 | ); 36 | }; 37 | 38 | export default FeeInfo; 39 | -------------------------------------------------------------------------------- /src/views/JupiterForm/JupiterForm.module.css: -------------------------------------------------------------------------------- 1 | .loader { 2 | border-top-color: #3498db; 3 | -webkit-animation: spinner 1.5s linear infinite; 4 | animation: spinner 1.5s linear infinite; 5 | width: 24px; 6 | height: 24px; 7 | } 8 | 9 | @-webkit-keyframes spinner { 10 | 0% { 11 | -webkit-transform: rotate(0deg); 12 | } 13 | 100% { 14 | -webkit-transform: rotate(360deg); 15 | } 16 | } 17 | 18 | @keyframes spinner { 19 | 0% { 20 | transform: rotate(0deg); 21 | } 22 | 100% { 23 | transform: rotate(360deg); 24 | } 25 | } -------------------------------------------------------------------------------- /src/views/JupiterForm/JupiterForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, useEffect, useMemo, useState } from "react"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import { TokenInfo } from "@solana/spl-token-registry"; 4 | import { useConnection, useWallet } from "@solana/wallet-adapter-react"; 5 | 6 | import { TOKEN_LIST_URL, useJupiter } from "@jup-ag/react-hook"; 7 | import { INPUT_MINT_ADDRESS, OUTPUT_MINT_ADDRESS } from "../../constants"; 8 | 9 | import FeeInfo from "./FeeInfo"; 10 | import SpinnerProgress from "./SpinnerProgress"; 11 | import fetch from "cross-fetch"; 12 | import JSBI from "jsbi"; 13 | import Decimal from "decimal.js"; 14 | import { SECOND_TO_REFRESH } from "../../pages/_app"; 15 | 16 | interface IJupiterFormProps {} 17 | type UseJupiterProps = Parameters[0]; 18 | 19 | const JupiterForm: FunctionComponent = (props) => { 20 | const wallet = useWallet(); 21 | const { connection } = useConnection(); 22 | const [tokenMap, setTokenMap] = useState>(new Map()); 23 | 24 | const [formValue, setFormValue] = useState< 25 | Omit & { amount: Decimal } 26 | >({ 27 | amount: new Decimal(1), // unit in lamports (Decimals) 28 | inputMint: new PublicKey(INPUT_MINT_ADDRESS), 29 | outputMint: new PublicKey(OUTPUT_MINT_ADDRESS), 30 | slippageBps: 1, // 0.1% 31 | }); 32 | 33 | const [inputTokenInfo, outputTokenInfo] = useMemo(() => { 34 | return [ 35 | tokenMap.get(formValue.inputMint?.toBase58() || ""), 36 | tokenMap.get(formValue.outputMint?.toBase58() || ""), 37 | ]; 38 | }, [ 39 | tokenMap, 40 | formValue.inputMint?.toBase58(), 41 | formValue.outputMint?.toBase58(), 42 | ]); 43 | 44 | useEffect(() => { 45 | fetch(TOKEN_LIST_URL["mainnet-beta"]) 46 | .then((res) => res.json()) 47 | .then((tokens: TokenInfo[]) => { 48 | setTokenMap( 49 | tokens.reduce((map, item) => { 50 | map.set(item.address, item); 51 | return map; 52 | }, new Map()) 53 | ); 54 | }); 55 | }, [setTokenMap]); 56 | 57 | const amountInInteger = useMemo(() => { 58 | return JSBI.BigInt( 59 | formValue.amount.mul(10 ** (inputTokenInfo?.decimals || 1)) 60 | ); 61 | }, [inputTokenInfo, formValue.amount]); 62 | 63 | const { 64 | routeMap, 65 | allTokenMints, 66 | routes, 67 | loading, 68 | exchange, 69 | error, 70 | refresh, 71 | lastRefreshTimestamp, 72 | } = useJupiter({ ...formValue, amount: amountInInteger }); 73 | 74 | const validOutputMints = useMemo( 75 | () => routeMap.get(formValue.inputMint?.toBase58() || "") || allTokenMints, 76 | [routeMap, formValue.inputMint?.toBase58()] 77 | ); 78 | 79 | // ensure outputMint can be swapable to inputMint 80 | useEffect(() => { 81 | if (formValue.inputMint) { 82 | const possibleOutputs = routeMap.get(formValue.inputMint.toBase58()); 83 | 84 | if ( 85 | possibleOutputs && 86 | !possibleOutputs?.includes(formValue.outputMint?.toBase58() || "") 87 | ) { 88 | setFormValue((val) => ({ 89 | ...val, 90 | outputMint: new PublicKey(possibleOutputs[0]), 91 | })); 92 | } 93 | } 94 | }, [formValue.inputMint?.toBase58(), formValue.outputMint?.toBase58()]); 95 | 96 | const [timeDiff, setTimeDiff] = useState(lastRefreshTimestamp); 97 | useEffect(() => { 98 | const intervalId = setInterval(() => { 99 | if (loading) return; 100 | 101 | const diff = new Date().getTime() - lastRefreshTimestamp; 102 | setTimeDiff((diff / SECOND_TO_REFRESH) * 100); 103 | 104 | if (diff >= SECOND_TO_REFRESH) { 105 | refresh(); 106 | } 107 | }, 1000); 108 | return () => clearInterval(intervalId); 109 | }, [loading]); 110 | 111 | return ( 112 |
113 |
114 | 117 | 144 |
145 | 146 |
147 | 150 | 175 |
176 | 177 |
178 | 181 |
182 | { 190 | let newValue = e.target?.value || "0"; 191 | 192 | let newAmount = new Decimal(newValue); 193 | 194 | if (newAmount.lessThan(0)) { 195 | newAmount = new Decimal(0); 196 | } 197 | setFormValue((val) => ({ 198 | ...val, 199 | amount: newAmount, 200 | })); 201 | }} 202 | /> 203 |
204 |
205 | 206 |
207 | 218 |
219 | 220 |
Total routes: {routes?.length}
221 | 222 | {routes?.[0] && 223 | (() => { 224 | const route = routes[0]; 225 | return ( 226 |
227 |
228 | Best route info :{" "} 229 | {route.marketInfos.map((info) => info.label).join(" -> ")} 230 |
231 |
232 | Output:{" "} 233 | {new Decimal(route.outAmount.toString()) 234 | .div(10 ** (outputTokenInfo?.decimals || 1)) 235 | .toString()}{" "} 236 | {outputTokenInfo?.symbol} 237 |
238 | 239 |
240 | ); 241 | })()} 242 | 243 | {error &&
Error in Jupiter, try changing your intpu
} 244 | 245 |
246 | 286 |
287 |
288 | ); 289 | }; 290 | 291 | export default JupiterForm; 292 | -------------------------------------------------------------------------------- /src/views/JupiterForm/SpinnerProgress.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const SpinnerProgress = ({ 4 | percentage, 5 | strokeWidth = 2, 6 | sqSize = 14, 7 | }: { 8 | percentage: number; 9 | strokeWidth?: number; 10 | sqSize?: number; 11 | }) => { 12 | const radius = (sqSize - strokeWidth) / 2; 13 | const viewBox = `0 0 ${sqSize} ${sqSize}`; 14 | const dashArray = radius * Math.PI * 2; 15 | const dashOffset = dashArray - (dashArray * percentage) / 100; 16 | 17 | return ( 18 | 19 | 28 | 45 | 46 | ); 47 | }; 48 | 49 | export default SpinnerProgress; 50 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | // default settings can be found here 2 | // https://unpkg.com/browse/tailwindcss@2.2.17/stubs/defaultConfig.stub.js 3 | 4 | module.exports = { 5 | mode: "jit", 6 | purge: ["./src/**/*.{js,jsx,ts,tsx}"], 7 | darkMode: "media", // or 'false' or 'class' 8 | theme: { 9 | fontFamily: { 10 | // sans: ['Graphik', 'sans-serif'], 11 | // serif: ['Merriweather', 'serif'], 12 | }, 13 | extend: { 14 | // spacing: { 15 | // '128': '32rem', 16 | // '144': '36rem', 17 | // }, 18 | // borderRadius: { 19 | // '4xl': '2rem', 20 | // } 21 | }, 22 | }, 23 | variants: { 24 | extend: {}, 25 | }, 26 | plugins: [require("@tailwindcss/typography"), require("daisyui")], 27 | daisyui: { 28 | styled: true, 29 | themes: [ 30 | // first one will be the default theme 31 | "dark", 32 | // uncomment to enable 33 | // "light (default)", 34 | // "dark", 35 | // "cupcake", 36 | // "bumblebee", 37 | // "emerald", 38 | // "corporate", 39 | // "synthwave", 40 | // "retro", 41 | // "cyberpunk", 42 | // "valentine", 43 | // "halloween", 44 | // "garden", 45 | // "forest", 46 | // "aqua", 47 | // "lofi", 48 | // "pastel", 49 | // "fantasy", 50 | // "wireframe", 51 | // "black", 52 | // "luxury", 53 | // "dracula", 54 | ], 55 | base: true, 56 | utils: true, 57 | logs: true, 58 | rtl: false, 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 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 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | --------------------------------------------------------------------------------