├── .editorconfig ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app ├── docs │ └── page.tsx ├── favicon.ico ├── globals.css ├── layout.tsx └── page.tsx ├── components.json ├── components ├── demo │ ├── connect-wallet-info.tsx │ ├── get-logging-in-state-demo.tsx │ ├── get-login-info-demo.tsx │ ├── get-user-data-demo.tsx │ ├── sign-message-demo.tsx │ ├── simple-demo.tsx │ ├── simple-deploy-sc-demo.tsx │ ├── simple-egld-tx-demo.tsx │ ├── simple-esdt-tx-demo.tsx │ ├── simple-nft-mint-demo.tsx │ └── simple-sc-query-demo.tsx ├── elven-ui │ ├── authenticated.tsx │ ├── elven-init.tsx │ ├── ledger-accounts-list.tsx │ ├── login-component.tsx │ ├── login-modal-button.tsx │ ├── protected-page-wrapper.tsx │ ├── walletconnect-pairings.tsx │ └── walletconnect-qr-code.tsx ├── mode-toggle.tsx ├── theme-provider.tsx └── ui │ ├── button.tsx │ ├── card.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── spinner.tsx │ └── tooltip.tsx ├── hooks └── use-effect-only-on-update.tsx ├── lib ├── error-parse.ts ├── get-signing-device-name.ts ├── is-mobile.ts ├── shorten-hash.ts └── utils.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── github.svg ├── logo.png ├── og-image.png └── piggybank.wasm └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | charset = utf-8 11 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # ============================================= 2 | # Public variables (exposed on the frontend) 3 | # ============================================= 4 | 5 | # MultiversX chain (can be devnet, testnet, mainnet) 6 | NEXT_PUBLIC_MULTIVERSX_CHAIN = devnet 7 | 8 | # Wallet Connect 2 Project Id. This one will work only with this project 9 | # Get yours at: https://cloud.walletconnect.com/sign-in 10 | NEXT_PUBLIC_WC_PROJECT_ID = be161e9c2764269adc6a5cf4304c3a22 11 | 12 | # This is basically the main domain of your dapp 13 | NEXT_PUBLIC_DAPP_HOST = http://localhost:3000 14 | 15 | # ============================================= 16 | # Public variables for the demo only 17 | # ============================================= 18 | 19 | # The wallet address used for the demo EGLD transaction on the devnet 20 | NEXT_PUBLIC_TRANSFER_ADDRESS = erd17a4wydhhd6t3hhssvcp9g23ppn7lgkk4g2tww3eqzx4mlq95dukss0g50f 21 | 22 | # The smart contract address used for minting the NFT token (as example deployed Elven Tools Smart Contract) 23 | NEXT_PUBLIC_MINT_SMART_CONTRACT_ADDRESS = erd1qqqqqqqqqqqqqpgqufmyqvy3kvda2uywqgx809lglxftq9t667es3956pv 24 | 25 | # The function/endpoint name for minting on the smart contract 26 | NEXT_PUBLIC_MINT_FUNCTION_NAME = mint 27 | 28 | # The function/view name for getting the total tokens left to be mint on smart contract 29 | NEXT_PUBLIC_QUERY_FUNCTION_NAME = getTotalTokensLeft 30 | 31 | # The payment per one NFT token, defined on smart contract (0.01 EGLD) 32 | NEXT_PUBLIC_MINT_PAYMENT_PER_TOKEN = 0.01 33 | 34 | # The amount of EGLD to send in the demo transfer (0.001 EGLD) 35 | NEXT_PUBLIC_EGLD_TRANSFER_AMOUNT = 0.001 36 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "prettier", "plugin:@typescript-eslint/recommended"], 3 | "plugins": ["@typescript-eslint"], 4 | "parser": "@typescript-eslint/parser" 5 | } 6 | -------------------------------------------------------------------------------- /.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.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | certificates -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "singleQuote": true, 6 | "arrowParens": "always", 7 | "trailingComma": "es5", 8 | "endOfLine": "auto" 9 | } 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### [10.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v10.0.0) (2025-02-08) 2 | - update useElven 3 | - update Next 4 | - update React 5 | - update Tailwind 6 | 7 | ### [9.10.2](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.10.2) (2024-11-11) 8 | - add `--experimental-https` 9 | 10 | ### [9.10.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.10.1) (2024-11-10) 11 | - add transaction ids to prevent multiple API calls when using Web Wallet signing provider 12 | 13 | ### [9.10.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.10.0) (2024-11-09) 14 | - update Next 15 | - update useElven 16 | - update other dependencies 17 | 18 | ### [9.9.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.9.0) (2024-07-13) 19 | - support for multiple tabs and transactions signing 20 | 21 | ### [9.8.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.8.1) (2024-07-06) 22 | - update dependencies 23 | - adjust UI 24 | 25 | ### [9.8.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.8.0) (2024-06-20) 26 | - update useElven - added MultiversX 'apps hub' support (experimental, report problems) 27 | 28 | ### [9.7.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.7.0) (2024-04-13) 29 | - update useElven, which now supports sdk-core v13, and now, it is required to install it alongside with useElven 30 | - adjust current codebase 31 | - update other dependencies 32 | 33 | ### [9.6.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.6.1) (2024-02-28) 34 | - update useElven with fixes for useTokenTransfer 35 | - update dependencies 36 | 37 | ### [9.6.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.6.0) (2024-02-25) 38 | - update useElven, changes in callbacks naming and added transaction watcher configuration 39 | - update dependencies 40 | 41 | ### [9.5.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.5.1) (2024-02-17) 42 | - improve UI 43 | - update dependencies 44 | 45 | ### [9.5.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.5.0) (2024-01-11) 46 | - add ESDT transfer demo 47 | - update useElven and dependencies 48 | - cleanup 49 | 50 | ### [9.4.4](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.4.4) (2023-12-29) 51 | - fix missing transaction id logic for guardians 2fa hook 52 | 53 | ### [9.4.3](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.4.3) (2023-12-28) 54 | - update useElven and cleanup 55 | 56 | ### [9.4.2](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.4.2) (2023-12-26) 57 | - reveal demo section for not logged in 58 | 59 | ### [9.4.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.4.1) (2023-12-24) 60 | - update useElven with bugfixes 61 | - update other dependencies 62 | 63 | ### [9.4.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.4.0) (2023-12-03) 64 | - update useElven and add deploy a smart contract demo 65 | 66 | ### [9.3.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.3.0) (2023-11-30) 67 | - update useElven and add Sign message demo 68 | 69 | ### [9.2.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.2.0) (2023-11-05) 70 | - update useElven - new useMultiTokenTransfer hook 71 | 72 | ### [9.1.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.1.1) (2023-10-28) 73 | - update useElven - fix NaiveAuth configuration 74 | 75 | ### [9.1.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.1.0) (2023-10-28) 76 | - useelven update - add support for xAlias 77 | - update Next.js to v14 (required min Node version: 18.7.0) 78 | - update dependencies 79 | 80 | ### [9.0.3](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.0.3) (2023-10-02) 81 | - update useElven (bug related to guardian address and localstorage entry) 82 | - update dependencies 83 | 84 | ### [9.0.2](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.0.2) (2023-09-28) 85 | - update useElven (native token configuration improvements) 86 | - fix styles for xPortal deeplink on login modal + other minor style fixes 87 | - update dependencies 88 | 89 | ### [9.0.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.0.1) (2023-09-23) 90 | - update useElven (update with a bugfix) 91 | - update dependencies 92 | - minor style fixes 93 | 94 | ### [9.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v9.0.0) (2023-09-02) 95 | - switch to the Next.js App Router architecture 96 | - remove API proxy and rewrites, you can still have them, but it isn't implemented by default. It was too confusing. 97 | - remove the Chakra UI and switch to [Radix UI](https://www.radix-ui.com/) + [Tailwind](https://tailwindcss.com/), wrapped with [Shadcn UI](https://ui.shadcn.com/). I think these are more future proof solutions 98 | 99 | ### [8.4.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v8.4.0) (2023-07-28) 100 | - update useElven (guardians support) 101 | - update other dependencies 102 | 103 | ### [8.3.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v8.3.0) (2023-06-29) 104 | - update useElven (switch to tsup) 105 | - update other dependancies 106 | 107 | ### [8.2.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v8.2.0) (2023-06-19) 108 | - update useElven (new useTokenTransfer hook available) 109 | - update other dependancies 110 | 111 | ### [8.1.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v8.1.1) (2023-06-07) 112 | - update useElven with fix for native auth login token handling 113 | - update other dependenecies 114 | 115 | ### [8.1.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v8.1.0) (2023-06-04) 116 | - update useElven and other dependencies 117 | 118 | ### [8.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v8.0.0) (2023-05-28) 119 | - **Breaking:** The dapp now uses the useElven version with built-in native token support. There is no fallback, so it is a breaking change. Standard string-based tokens will be deprecated across the MultiversX soon 120 | - update dependencies 121 | 122 | ### [7.2.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v7.2.1) (2023-05-14) 123 | - update [useElven](https://www.useelven.com/) 124 | - fix problems with initialization of the HW provider 125 | 126 | ### [7.2.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v7.2.0) (2023-05-06) 127 | - update [useElven](https://www.useelven.com/) 128 | - fix problems with handling states when strict mode is enabled 129 | 130 | ### [7.1.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v7.1.1) (2023-04-25) 131 | - update dependecies, including [useElven](https://www.useelven.com/) with fixed bug related to WalletConnect pairings removal (thanks to @nikos-koukis for reporting) 132 | 133 | ### [7.1.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v7.1.0) (2023-04-23) 134 | - update useElven where sdk-core and sdk-hw-provider where updated 135 | - update sdk-core also in app 136 | - code adjustments 137 | 138 | ### [7.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v7.0.0) (2023-03-05) 139 | - switch to v0.1.0 of [useElven](https://www.useelven.com/) with support for xPortal when signing 140 | - changes for Wallet Connect pairings list 141 | - other minor improvements 142 | 143 | ### [6.1.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v6.1.0) (2023-03-04) 144 | - fix passing custom configuration, one should use .env variables for that, `useNetworkSync` will read from them 145 | 146 | ### [6.0.4](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v6.0.4) (2023-02-26) 147 | - change how the value is provided in useTransaction 148 | - bump dependencies 149 | 150 | ### [6.0.3](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v6.0.3) (2023-02-21) 151 | - remove unused dependencies 152 | 153 | ### [6.0.2](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v6.0.2) (2023-02-19) 154 | - update @useelven/core and other dependencies 155 | 156 | ### [6.0.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v6.0.1) (2023-02-15) 157 | - update @useelven/core and other dependencies 158 | 159 | ### [6.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v6.0.0) (2023-02-14) 160 | - switch to [@useelven/core](https://www.useElven.com) when it comes to auth and core functionality 161 | 162 | ### [5.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v5.0.0) (2023-02-04) 163 | - **Breaking:** There is no more `useScTransaction` hook. You can use `useTransaction` for all cases. You would need to prepare a proper data payload for custom smart contracts. Check the example in the Readme and code 164 | - switch to `useProxy` from `valtio` 165 | - enable React strict mode 166 | 167 | ### [4.3.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v4.3.0) (2023-01-28) 168 | - `txResults` is now returned in `useTransaction` and `useScTransaction` hooks (it is ITransactionOnNetwork in sdk-core) 169 | - dependencies updates 170 | 171 | ### [4.2.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v4.2.0) (2023-01-14) 172 | - rebrand to multiversx (continuation) 173 | - npm packages are replaced 174 | - public api/explorer endpoints are replaced 175 | - update dependencies 176 | 177 | ### [4.1.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v4.1.0) (2022-12-17) 178 | - added `ProtectedPageWrapper` component - client side only 'gate keeper', check README.md for more info 179 | - fix for the `useApiCall` hook 180 | - npm dependencies updates 181 | 182 | ### [4.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v4.0.0) (2022-12-04) 183 | - changes in how the API proxy work 184 | - renamed env variables 185 | - check the [README.md](https://github.com/xdevguild/nextjs-dapp-template#working-with-the-env-and-config-files) and `.env.example` file for more info 186 | - thanks to @janniksam 187 | 188 | ### [3.1.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v3.1.0) (2022-12-03) 189 | - rewritten useScQuery, but it keeps backward compatibility, you can still use simple data types like number, string and boolan as the results without ABI, if you need to catch more complex data types, you need to provide the ABI file, check for more info in the README.md file 190 | - dependencies updates 191 | 192 | ### [3.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v3.0.0) (2022-11-16) 193 | - dependencies updates 194 | - first phase of 'rebranding' into MultiversX ;) 195 | - **Breaking**: `useElrondNetworkSync` is now `useNetworkSync` 196 | 197 | ### [2.2.2](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v2.2.2) (2022-10-30) 198 | - dependencies updates, also Next 13, changes in the routing will be introduced later. Waits for new Next 13 docs to be completed. 199 | 200 | ### [2.2.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v2.2.1) (2022-10-10) 201 | - bugfix for the wrong usage of the Chakra Factory on CardWrapper and FlexCardWrapper components. Thanks to @janniksam for reporting that 202 | 203 | ### [2.2.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v2.2.0) (2022-10-09) 204 | - dependencies updates (Next, erdjs, etc.) 205 | 206 | ### [2.1.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v2.1.0) (2022-09-04) 207 | - new `useApiCall` hook, check the readme for more info 208 | 209 | ### [2.0.1](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v2.0.1) (2022-08-13) 210 | - fix problems with rerendering (wrong memo usage) 211 | - update dependencies 212 | 213 | ### [2.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v2.0.0) (2022-08-01) 214 | - Nextjs update 215 | - React update 216 | - erdjs libraries update 217 | - other dependencies updates 218 | - improvements for `useScQuery` hook 219 | - config moved to env variables - see .env.example 220 | - minor fixes 221 | - ts types improvements 222 | - switched to MIT license (erdjs libs are now MIT too) 223 | 224 | ### [1.1.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v1.1.0) (2022-06-19) 225 | - added HW provider 226 | 227 | ### [1.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v1.0.0) (2022-05-15) 228 | - initial code 229 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Julian Ćwirko 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 | ### NextJS Dapp Template 2 | 3 | - [multiversx-nextjs-dapp.netlify.com](https://multiversx-nextjs-dapp.netlify.com) 4 | 5 | Nextjs alternative to the [sdk-dapp](https://github.com/multiversx/mx-sdk-dapp). 6 | 7 | The Dapp is built using Nextjs and a couple of helpful tools ([Shadcn UI](https://ui.shadcn.com/) ([Radix UI](https://www.radix-ui.com/)) + [Tailwind](https://tailwindcss.com/)) 8 | It has straightforward and complete functionality. 9 | 10 | **For older Chakra UI version and Next.js Page Router architecture check the [v8.4.0](https://github.com/xdevguild/nextjs-dapp-template/tree/v8.4.0)** 11 | 12 | ### Main assumption for the dapp: 13 | 14 | - it works with Next.js and new App Router architecture 15 | - it uses [sdk-core](https://github.com/multiversx/mx-sdk-js-core) **without** the [dapp-core](https://github.com/multiversx/mx-sdk-dapp) library. All through [useElven](https://www.useElven.com) library. 16 | - it uses the .env file - there is an example in the repo (for all configurations, also for the demo config) 17 | - it uses [Radix UI](https://www.radix-ui.com/) + [Tailwind](https://tailwindcss.com/), wrapped with [Shadcn UI](https://ui.shadcn.com/) 18 | 19 | ### How to start it locally: 20 | 21 | `npx buildo-begins@latest init` - from the list, choose Next.js dapp template. 22 | 23 | Or you can also do this manually: 24 | 1. clone or download the repo 25 | 2. `cd nextjs-dapp-template` 26 | 3. `npm install` 27 | 4. configure .env.local (you can copy the contents of the .env.example) `cp .env.example .env.local` 28 | 5. `npm run dev` -> for development 29 | 6. `npm run build` -> `npm start` for production 30 | 31 | Check how to deploy a very similar dapp using the Netlify services: https://www.elven.tools/docs/dapp-deployment.html 32 | 33 | ### Howto 34 | 35 | Below you will find the list of most essential utilities, hooks, and components with examples that are actual code from the template. You can search them in the code to better understand how they work. 36 | 37 | There are much more hooks and tools, but most of them are already used in the ones listed below. 38 | 39 | The code samples are not ready to copy and paste. Please search for them in the code. 40 | 41 | ### @useelven/hooks 42 | 43 | The template is based on `@useelven/core` npm library. 44 | 45 | - [@useelven/hooks docs](https://www.useElven.com) - React hooks for MultiversX blockchain 46 | 47 | Besides that, there are custom React components that will help you with development. 48 | 49 | #### LoginModalButton 50 | 51 | The component provides the `Connect` button with the modal, which will contain another three buttons for four different authentication possibilities (xPortal Mobile App, MultiversX Defi Wallet - browser extension, MultiversX Web Wallet). You should be able to use it in any place on the website. 52 | 53 | ```jsx 54 | import { LoginModalButton } from '../tools/LoginModalButton'; 55 | 56 | ; 57 | ``` 58 | 59 | #### Authenticated 60 | 61 | The component is used as a small wrapper where we need to be in the authenticated context, for example, for all transactions. 62 | 63 | It can display the spinner and also the fallback React element. 64 | 65 | **Important** Do not wrap it in big sections of the code. Its purpose is to be used multiple times on as small blocks as possible. 66 | 67 | ```jsx 68 | 72 | Connect your wallet! 73 | 74 | } 75 | > 76 | ``` 77 | 78 | ### ProtectedPageWrapper 79 | 80 | The component wraps your page contents and will display them only for logged-in users. Otherwise, it will redirect to a defined path. Remember that this is only a client-side check. So don't rely on it with the data that should be private and secured. 81 | 82 | ```jsx 83 | import { ProtectedPageWrapper } from './components/tools/ProtectedPageWrapper'; 84 | 85 | const Profile = () => { 86 | return ( 87 | 88 |
The content for logged-in only!
89 |
For example the profile page or any other that should be accessible only for logged-in users
90 |
91 | ); 92 | }; 93 | 94 | export default Profile; 95 | ``` 96 | 97 | ### Working with the API 98 | 99 | By default, the Dapp provides the `.env.example`, configured to use the official public MultiversX API endpoint. 100 | 101 | You can use the public API, but it is always recommended to maintain your own instance of the API, or you can also use some third party services. 102 | 103 | ### Working with the .env and config files 104 | 105 | There is an `env.example` file that you can copy and rename into `.env.local` to run the app locally. You would need to configure these variables for your production-ready dapp. 106 | 107 | Here are all variables: 108 | 109 | ```bash 110 | # ============================================= 111 | # Public variables (exposed on the frontend) 112 | # ============================================= 113 | 114 | # MultiversX chain (can be devnet, testnet, mainnet) 115 | NEXT_PUBLIC_MULTIVERSX_CHAIN = devnet 116 | 117 | # Wallet Connect 2 Project Id. This one will work only with this project 118 | # Get yours at: https://cloud.walletconnect.com/sign-in 119 | NEXT_PUBLIC_WC_PROJECT_ID = be161e9c2764269adc6a5cf4304c3a22 120 | 121 | # This is basically the main domain of your dapp 122 | NEXT_PUBLIC_DAPP_HOST = http://localhost:3000 123 | 124 | # ============================================= 125 | # Public variables for the demo only 126 | # ============================================= 127 | 128 | # The wallet address used for the demo EGLD transaction on the devnet 129 | NEXT_PUBLIC_TRANSFER_ADDRESS = erd17a4wydhhd6t3hhssvcp9g23ppn7lgkk4g2tww3eqzx4mlq95dukss0g50f 130 | 131 | # The smart contract address used for minting the NFT token (as example deployed Elven Tools Smart Contract) 132 | NEXT_PUBLIC_MINT_SMART_CONTRACT_ADDRESS = erd1qqqqqqqqqqqqqpgqztp5vpqrxe2tha224jwsa3sv2800a88zgtksar2kc8 133 | 134 | # The function/endpoint name for minting on the smart contract 135 | NEXT_PUBLIC_MINT_FUNCTION_NAME = mint 136 | 137 | # The function/view name for getting the total tokens left to be mint on smart contract 138 | NEXT_PUBLIC_QUERY_FUNCTION_NAME = getTotalTokensLeft 139 | 140 | # The payment per one NFT token, defined on smart contract (0.01 EGLD) 141 | NEXT_PUBLIC_MINT_PAYMENT_PER_TOKEN = 0.01 142 | 143 | # The amount of EGLD to send in the demo transfer (0.001 EGLD) 144 | NEXT_PUBLIC_EGLD_TRANSFER_AMOUNT = 0.001 145 | 146 | ``` 147 | 148 | All variables which start with `NEXT_PUBLIC_` will be readable on the frontend side of the dapp. So please don't use them for any secret keys and data. If you need something to be available only on the backend side, don't use the `NEXT_PUBLIC_` prefix. 149 | 150 | You can set up the chain type. Use `NEXT_PUBLIC_MULTIVERSX_CHAIN` to set `devnet`, `testnet` or `mainnet`. 151 | 152 | Each hosting provider will have a different way of setting the env variables. We will take a look at how Netlify is doing that below. 153 | 154 | ### Deployment 155 | 156 | For deployment, we recommend the [Netlify](https://www.netlify.com/). Why Netlify? Because it is the simplest way to deploy the Nextjs app for free. Of course, the most recommended is the [Vercel](https://vercel.com/) which you could also try. 157 | 158 | As for Netlify, the only what you need to do there is to go to the settings and configure from which repository the app should be deployed. Check out how: [Netlify getting started](https://docs.netlify.com/get-started/). 159 | 160 | Then fill up the env variables. See how here: [Netlify env vars setup](https://docs.netlify.com/configure-builds/environment-variables). 161 | 162 | On each repository code push, the Netlify services will redeploy the app. 163 | 164 | Read more about it here: https://www.elven.tools/docs/dapp-deployment.html 165 | 166 | Here are other deployment solutions: [NextJS Deployment](https://nextjs.org/docs/deployment). 167 | 168 | ### Other tools 169 | 170 | If you would like to test other templates check: 171 | 172 | - [erd-next-starter](https://github.com/Elrond-Giants/erd-next-starter) 173 | - [dapp-template](https://github.com/multiversx/mx-template-dapp) 174 | 175 | Compact MultiversX SDK for browsers (no build steps required) 176 | 177 | - [Elven.js](https://www.elvenjs.com) 178 | 179 | Tools that can help you with interactions: 180 | 181 | - [Buildo.dev](https://www.buildo.dev) 182 | - [Buildo Begins](https://github.com/xdevguild/buildo-begins) 183 | - [Elven Tools](https://www.elven.tools) 184 | 185 | ### Contact 186 | 187 | - [julian.io](https://www.julian.io) 188 | -------------------------------------------------------------------------------- /app/docs/page.tsx: -------------------------------------------------------------------------------- 1 | import { NextPage } from 'next'; 2 | import { Card, CardContent } from '@/components/ui/card'; 3 | 4 | const Docs: NextPage = () => { 5 | return ( 6 |
7 | 8 | 9 |

Documentation

10 |

11 | Comprehensive documentation for the useElven SDK integrated within 12 | our Next.js Dapp Template. 13 |

14 |
15 |
16 | 17 | 18 |

Project Overview

19 |

20 | The Next.js Dapp Template is a complete solution for building 21 | decentralized applications on the MultiversX blockchain. Built with 22 | Next.js, Shadcn UI, and Tailwind CSS, it leverages the useElven SDK 23 | for seamless blockchain interactions. 24 |

25 |

26 | It supports the new Next.js App Router architecture and includes 27 | built-in tools for user authentication, wallet connections, 28 | transaction processing, and more. 29 |

30 |
31 |
32 | 33 | 34 |

useElven SDK Reference

35 |

36 | The useElven SDK provides a set of React hooks and utilities 37 | designed to facilitate interactions with the MultiversX blockchain. 38 | It includes support for: 39 |

40 |
    41 |
  • 42 | Authentication and wallet connection (e.g.,{' '} 43 | LoginModalButton, Authenticated,{' '} 44 | ProtectedPageWrapper) 45 |
  • 46 |
  • Fetching user data and blockchain state
  • 47 |
  • Performing transactions like EGLD transfers and NFT minting
  • 48 |
49 |

50 | For more detailed information, check out the official{' '} 51 | 56 | useElven SDK documentation 57 | 58 | . 59 |

60 |
61 |
62 | 63 | 64 |

65 | Next.js Dapp Template Integration 66 |

67 |

68 | This template integrates the useElven SDK seamlessly with Next.js, 69 | taking advantage of modern React patterns and the App Router for 70 | improved performance and developer experience. 71 |

72 |

73 | The starter kit includes examples of various components and hooks to 74 | initiate wallet connections, handle user authentication, and 75 | interact with smart contracts, making it easy to build and scale 76 | your dapp. 77 |

78 |
79 |
80 | 81 | 82 |

Running Locally

83 |

84 | You can run the project locally using one of the following methods: 85 |

86 |

87 | Option 1: Using buildo-begins CLI 88 |

89 |
 90 |             {`npx buildo-begins@latest init`}
 91 |           
92 |

Choose Next.js dapp template from the list.

93 |

Option 2: Manual Setup

94 |
    95 |
  1. Clone or download the repo.
  2. 96 |
  3. 97 | Navigate to the project directory:{' '} 98 | cd nextjs-dapp-template 99 |
  4. 100 |
  5. 101 | Install dependencies with npm install. 102 |
  6. 103 |
  7. 104 | Copy the environment example file:{' '} 105 | cp .env.example .env.local. 106 |
  8. 107 |
  9. 108 | Run the development server with npm run dev. 109 |
  10. 110 |
  11. 111 | For production, build with npm run build and start 112 | with npm start. 113 |
  12. 114 |
115 |
116 |
117 | 118 | 119 |

120 | Environment Variables and Configuration 121 |

122 |

123 | The project uses a set of environment variables defined in an{' '} 124 | .env file to manage configuration for different 125 | environments. Variables prefixed with NEXT_PUBLIC_ are 126 | exposed to the frontend. 127 |

128 |

129 | Copy the .env.example file to .env.local{' '} 130 | and modify the values as needed. Key variables include: 131 |

132 |
    133 |
  • 134 | NEXT_PUBLIC_MULTIVERSX_CHAIN: Set this to{' '} 135 | devnet, testnet, or mainnet 136 |
  • 137 |
  • 138 | NEXT_PUBLIC_WC_PROJECT_ID: Your WalletConnect 2 139 | Project ID 140 |
  • 141 |
  • 142 | NEXT_PUBLIC_DAPP_HOST: The main URL of your dapp 143 |
  • 144 |
  • 145 | NEXT_PUBLIC_TRANSFER_ADDRESS: The address used for 146 | demo EGLD transfers 147 |
  • 148 |
  • 149 | NEXT_PUBLIC_MINT_SMART_CONTRACT_ADDRESS: Smart 150 | contract address for NFT minting 151 |
  • 152 |
  • 153 | NEXT_PUBLIC_MINT_FUNCTION_NAME and{' '} 154 | NEXT_PUBLIC_QUERY_FUNCTION_NAME: Function names for 155 | minting operations and token queries 156 |
  • 157 |
  • 158 | NEXT_PUBLIC_MINT_PAYMENT_PER_TOKEN and{' '} 159 | NEXT_PUBLIC_EGLD_TRANSFER_AMOUNT: Define token prices 160 | and transfer amounts 161 |
  • 162 |
163 |
164 |             {`# .env.local example
165 | NEXT_PUBLIC_MULTIVERSX_CHAIN=devnet
166 | NEXT_PUBLIC_WC_PROJECT_ID=your_walletconnect_project_id
167 | NEXT_PUBLIC_DAPP_HOST=http://localhost:3000
168 | NEXT_PUBLIC_TRANSFER_ADDRESS=your_demo_transfer_address
169 | NEXT_PUBLIC_MINT_SMART_CONTRACT_ADDRESS=your_mint_contract_address
170 | NEXT_PUBLIC_MINT_FUNCTION_NAME=mint
171 | NEXT_PUBLIC_QUERY_FUNCTION_NAME=getTotalTokensLeft
172 | NEXT_PUBLIC_MINT_PAYMENT_PER_TOKEN=0.01
173 | NEXT_PUBLIC_EGLD_TRANSFER_AMOUNT=0.001`}
174 |           
175 |

176 | Ensure these values are properly configured for your deployed 177 | environment to securely manage both public and private settings. 178 |

179 |
180 |
181 |
182 | ); 183 | }; 184 | 185 | export default Docs; 186 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdevguild/nextjs-dapp-template/3262045c5851254243ca286395e72e6ed5dbfddb/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | 3 | @plugin 'tailwindcss-animate'; 4 | 5 | @custom-variant dark (&:is(.dark *)); 6 | 7 | @theme { 8 | --color-border: hsl(var(--border)); 9 | --color-input: hsl(var(--input)); 10 | --color-ring: hsl(var(--ring)); 11 | --color-background: hsl(var(--background)); 12 | --color-foreground: hsl(var(--foreground)); 13 | 14 | --color-primary: hsl(var(--primary)); 15 | --color-primary-foreground: hsl(var(--primary-foreground)); 16 | 17 | --color-secondary: hsl(var(--secondary)); 18 | --color-secondary-foreground: hsl(var(--secondary-foreground)); 19 | 20 | --color-destructive: hsl(var(--destructive)); 21 | --color-destructive-foreground: hsl(var(--destructive-foreground)); 22 | 23 | --color-muted: hsl(var(--muted)); 24 | --color-muted-foreground: hsl(var(--muted-foreground)); 25 | 26 | --color-accent: hsl(var(--accent)); 27 | --color-accent-foreground: hsl(var(--accent-foreground)); 28 | 29 | --color-popover: hsl(var(--popover)); 30 | --color-popover-foreground: hsl(var(--popover-foreground)); 31 | 32 | --color-card: hsl(var(--card)); 33 | --color-card-foreground: hsl(var(--card-foreground)); 34 | 35 | --radius-lg: var(--radius); 36 | --radius-md: calc(var(--radius) - 2px); 37 | --radius-sm: calc(var(--radius) - 4px); 38 | 39 | --animate-accordion-down: accordion-down 0.2s ease-out; 40 | --animate-accordion-up: accordion-up 0.2s ease-out; 41 | 42 | @keyframes accordion-down { 43 | from { 44 | height: 0; 45 | } 46 | to { 47 | height: var(--radix-accordion-content-height); 48 | } 49 | } 50 | @keyframes accordion-up { 51 | from { 52 | height: var(--radix-accordion-content-height); 53 | } 54 | to { 55 | height: 0; 56 | } 57 | } 58 | } 59 | 60 | @utility container { 61 | margin-inline: auto; 62 | padding-inline: 2rem; 63 | @media (width >= --theme(--breakpoint-sm)) { 64 | max-width: none; 65 | } 66 | @media (width >= 1400px) { 67 | max-width: 1400px; 68 | } 69 | } 70 | 71 | /* 72 | The default border color has changed to `currentColor` in Tailwind CSS v4, 73 | so we've added these compatibility styles to make sure everything still 74 | looks the same as it did with Tailwind CSS v3. 75 | 76 | If we ever want to remove these styles, we need to add an explicit border 77 | color utility to any element that depends on these defaults. 78 | */ 79 | @layer base { 80 | *, 81 | ::after, 82 | ::before, 83 | ::backdrop, 84 | ::file-selector-button { 85 | border-color: var(--color-gray-200, currentColor); 86 | } 87 | } 88 | 89 | @layer base { 90 | :root { 91 | --background: 0 0% 100%; 92 | --foreground: 240 10% 3.9%; 93 | --card: 0 0% 100%; 94 | --card-foreground: 240 10% 3.9%; 95 | --popover: 0 0% 100%; 96 | --popover-foreground: 240 10% 3.9%; 97 | --primary: 240 5.9% 10%; 98 | --primary-foreground: 0 0% 98%; 99 | --secondary: 240 4.8% 95.9%; 100 | --secondary-foreground: 240 5.9% 10%; 101 | --muted: 240 4.8% 95.9%; 102 | --muted-foreground: 240 3.8% 46.1%; 103 | --accent: 240 4.8% 95.9%; 104 | --accent-foreground: 240 5.9% 10%; 105 | --destructive: 0 84.2% 60.2%; 106 | --destructive-foreground: 0 0% 98%; 107 | --border: 240 5.9% 90%; 108 | --input: 240 5.9% 90%; 109 | --ring: 240 5.9% 10%; 110 | --radius: 0.5rem; 111 | } 112 | 113 | .dark { 114 | --background: 240 10% 3.9%; 115 | --foreground: 0 0% 98%; 116 | --card: 240 10% 3.9%; 117 | --card-foreground: 0 0% 98%; 118 | --popover: 240 10% 3.9%; 119 | --popover-foreground: 0 0% 98%; 120 | --primary: 0 0% 98%; 121 | --primary-foreground: 240 5.9% 10%; 122 | --secondary: 240 3.7% 15.9%; 123 | --secondary-foreground: 0 0% 98%; 124 | --muted: 240 3.7% 15.9%; 125 | --muted-foreground: 240 5% 64.9%; 126 | --accent: 240 3.7% 15.9%; 127 | --accent-foreground: 0 0% 98%; 128 | --destructive: 0 62.8% 30.6%; 129 | --destructive-foreground: 0 0% 98%; 130 | --border: 240 3.7% 15.9%; 131 | --input: 240 3.7% 15.9%; 132 | --ring: 240 4.9% 83.9%; 133 | } 134 | } 135 | 136 | @layer base { 137 | * { 138 | @apply border-border; 139 | scrollbar-width: thin; 140 | } 141 | body { 142 | @apply bg-background text-foreground; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from '@/components/theme-provider'; 2 | import './globals.css'; 3 | import packageJson from '@/package.json'; 4 | import type { Metadata } from 'next'; 5 | import { Inter } from 'next/font/google'; 6 | import { ModeToggle } from '@/components/mode-toggle'; 7 | import { ElvenInit } from '@/components/elven-ui/elven-init'; 8 | import { LoginModalButton } from '@/components/elven-ui/login-modal-button'; 9 | import Link from 'next/link'; 10 | import GitHubIcon from '/public/github.svg'; 11 | import Image from 'next/image'; 12 | import { Button } from '@/components/ui/button'; 13 | 14 | const inter = Inter({ subsets: ['latin'] }); 15 | 16 | const dappHostname = process.env.NEXT_PUBLIC_DAPP_HOST; 17 | const globalTitle = 'MultiversX Next.js dapp template'; 18 | const globalDescription = 19 | 'Open source Dapp template for the MultiversX blockchain.'; 20 | const globalImage = `${dappHostname}/og-image.png`; 21 | 22 | export const metadata: Metadata = { 23 | metadataBase: new URL(dappHostname!), 24 | title: globalTitle, 25 | description: globalDescription, 26 | authors: { name: 'xDevGuild', url: 'https://www.xdevguild.com' }, 27 | openGraph: { 28 | title: globalTitle, 29 | images: [globalImage], 30 | description: globalDescription, 31 | type: 'website', 32 | url: dappHostname, 33 | }, 34 | twitter: { 35 | title: globalTitle, 36 | description: globalDescription, 37 | images: [globalImage], 38 | card: 'summary_large_image', 39 | }, 40 | }; 41 | 42 | export default function RootLayout({ 43 | children, 44 | }: { 45 | children: React.ReactNode; 46 | }) { 47 | return ( 48 | 49 | 50 | 51 | 52 |
53 |
54 | 55 |
56 | 57 | MultiversX Dapp Template 58 | 59 |
60 | 61 |
62 |
63 | 67 | xDevGuild GitHub 73 | 74 |
75 | 78 | 79 | 80 |
81 |
82 |
83 |
84 | {children} 85 |
86 |
87 |
88 |
89 | MultiversX NextJS Dapp Template (v{`${packageJson.version}`}) 90 |
91 |
92 | Support Our Project: While attribution isn't mandatory, we 93 | deeply appreciate any form of recognition. 94 |
95 | 112 |
113 |
114 |
115 | 116 | 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next'; 2 | import { SimpleDemo } from '@/components/demo/simple-demo'; 3 | import { GetUserDataDemo } from '@/components/demo/get-user-data-demo'; 4 | import { GetLoggingInStateDemo } from '@/components/demo/get-logging-in-state-demo'; 5 | import { GetLoginInfoDemo } from '@/components/demo/get-login-info-demo'; 6 | import { CardContent, Card } from '@/components/ui/card'; 7 | 8 | const Home: NextPage = () => { 9 | return ( 10 | <> 11 | 12 | 13 |
14 | Here is the demo of an MultiversX dapp for interactions with the 15 | blockchain and smart contracts. It provides four different ways of 16 | authentication and also a couple of React-based helpers/hooks. It is 17 | based on NextJS and uses JS SDK (sdk-core). It also uses Shadcn UI 18 | and Tailwind CSS. 19 |
20 |
21 | We have hardcoded a setup for five different operations to simplify 22 | things. These are: 23 |
24 |
    25 |
  • - Simple EGLD transfer to hardcoded address.
  • 26 |
  • 27 | - Simple Mint operation on{' '} 28 | 29 | Elven Tools 30 | {' '} 31 | demo minter smart contract. 32 |
  • 33 |
  • 34 | - Random query operation on the Elven Tools demo minter smart 35 | contract. 36 |
  • 37 |
  • - Simple smart contract deployment
  • 38 |
  • - Signing a mesage
  • 39 |
40 | 41 | It is to demonstrate how such things can be achieved without much 42 | development. Maybe later, we will come up with a much better demo 43 | dapp. 44 | 45 |
46 | For more examples please check the{' '} 47 | 52 | Buildo.dev 53 | {' '} 54 | app. Check the source at{' '} 55 | 59 | xDevGuild 60 | 61 | . 62 |
63 |
64 |
65 | 66 | 67 | 68 |
69 | Now let us see what other valuable tools are included. 70 |
71 |
72 | You can get the data of currently logged-in users and network state. 73 | These are: 74 |
75 |
    76 |
  • - User data such as: address, nonce, balance.
  • 77 |
  • - User logging in state: pending, error, loggedIn.
  • 78 |
  • 79 | - Login info state: loginMethod, expires, loginToken, signature. 80 |
  • 81 |
82 |
83 |
84 |
85 | 86 | 87 | 88 |
89 | 90 | 91 | You will also get a couple of other tools, like: 92 |
    93 |
  • - Authenticated component - wrapper to check the auth state
  • 94 |
  • - LoginComponent - component with 3 auth options
  • 95 |
  • 96 | - LoginModalButton component - ready to use modal with 97 | LoginComponent 98 |
  • 99 |
  • 100 | - You will get all tools from{' '} 101 | 102 | useElven 103 | 104 |
  • 105 |
  • - Preserved app state after hard refresh of the page
  • 106 |
  • 107 | - And of course Shadcn UI, Tailwind CSS and NextJS framework 108 |
  • 109 |
110 |
111 |
112 | 113 | 114 |
Better docs, and more improvements soon!
115 |
116 | Check the{' '} 117 | 122 | xDevGuild 123 | 124 |
125 |
126 |
127 | 128 | ); 129 | }; 130 | 131 | export default Home; 132 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /components/demo/connect-wallet-info.tsx: -------------------------------------------------------------------------------- 1 | export const ConnectWalletInfo = ({ loggedIn }: { loggedIn: boolean }) => { 2 | if (loggedIn) return null; 3 | 4 | return ( 5 |
6 | Connect your wallet! 7 |
8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /components/demo/get-logging-in-state-demo.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useLoggingIn } from '@useelven/core'; 4 | import { Card, CardContent } from '@/components/ui/card'; 5 | 6 | export const GetLoggingInStateDemo = () => { 7 | const { pending, error, loggedIn } = useLoggingIn(); 8 | 9 | return ( 10 | 11 | 12 |
Logging in current state:
13 |
14 | isLoggingIn:{' '} 15 | {pending ? 'true' : 'false'} 16 |
17 |
18 | error: {error || '-'} 19 |
20 |
21 | isLoggedIn:{' '} 22 | {loggedIn ? 'true' : 'false'} 23 |
24 |
25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /components/demo/get-login-info-demo.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useLoginInfo } from '@useelven/core'; 4 | import { shortenHash } from '@/lib/shorten-hash'; 5 | import { 6 | Tooltip, 7 | TooltipContent, 8 | TooltipProvider, 9 | TooltipTrigger, 10 | } from '@/components/ui/tooltip'; 11 | import { Card, CardContent } from '@/components/ui/card'; 12 | 13 | export const GetLoginInfoDemo = () => { 14 | const { loginMethod, expires, loginToken, signature } = useLoginInfo(); 15 | 16 | return ( 17 | 18 | 19 |
Login info state:
20 |
21 | loginMethod:{' '} 22 | {loginMethod} 23 |
24 |
25 | expires: {expires} 26 |
27 |
28 | loginToken: 29 | {loginToken ? ( 30 |
{loginToken}
31 | ) : ( 32 | '-' 33 | )} 34 |
35 | 36 | 37 | 38 |
39 | signature:{' '} 40 | {signature ? shortenHash(signature, 8) : '-'} 41 |
42 |
43 | 44 |

{signature}

45 |
46 |
47 |
48 |
49 |
50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /components/demo/get-user-data-demo.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { TokenTransfer } from '@multiversx/sdk-core'; 4 | import Link from 'next/link'; 5 | import { shortenHash } from '@/lib/shorten-hash'; 6 | import { useAccount, useConfig } from '@useelven/core'; 7 | import { Card, CardContent } from '@/components/ui/card'; 8 | 9 | export const GetUserDataDemo = () => { 10 | const { address, nonce, balance, activeGuardianAddress } = useAccount(); 11 | const { explorerAddress } = useConfig(); 12 | 13 | return ( 14 | 15 | 16 |
User data:
17 |
18 | address:{' '} 19 | {address ? ( 20 | 24 | {shortenHash(address, 8)} 25 | 26 | ) : ( 27 | '-' 28 | )} 29 |
30 |
31 | guardian:{' '} 32 | {activeGuardianAddress ? ( 33 | 37 | {shortenHash(activeGuardianAddress, 8)} 38 | 39 | ) : ( 40 | - 41 | )} 42 |
43 |
44 | nonce: {nonce} 45 |
46 |
47 | balance:{' '} 48 | {balance 49 | ? parseFloat( 50 | TokenTransfer.egldFromBigInteger(balance).toPrettyString() 51 | ) 52 | : '-'} 53 |
54 |
55 |
56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /components/demo/sign-message-demo.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useLoggingIn, useLoginInfo, useSignMessage } from '@useelven/core'; 4 | import { Card, CardContent, CardFooter } from '@/components/ui/card'; 5 | import { Button } from '@/components/ui/button'; 6 | import { ConnectWalletInfo } from '@/components/demo/connect-wallet-info'; 7 | 8 | export const SimpleSignMessageDemo = () => { 9 | const { loginMethod } = useLoginInfo(); 10 | const { loggedIn } = useLoggingIn(); 11 | const { signMessage, pending, signature } = useSignMessage(); 12 | 13 | const handleSignMessage = () => { 14 | signMessage({ message: 'ElvenFamily' }); 15 | }; 16 | 17 | return ( 18 | 19 | 20 |
21 | You will be signing a hardcoded message:{' '} 22 | ElvenFamily{' '} 23 |
24 | {signature && ( 25 |
26 | Your signature for that message: 27 |
28 | {signature} 29 |
30 | 31 | You can verify it using{' '} 32 | 37 | devnet.buildo.dev 38 | 39 | 40 |
41 | )} 42 |
43 | 44 | 53 | 54 | 55 |
56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /components/demo/simple-demo.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useCallback, useState } from 'react'; 4 | import { Button } from '@/components/ui/button'; 5 | import { Spinner } from '@/components/ui/spinner'; 6 | import { 7 | TransactionCallbackParams, 8 | LoginMethodsEnum, 9 | useConfig, 10 | useLoginInfo, 11 | } from '@useelven/core'; 12 | import { SimpleEGLDTxDemo } from './simple-egld-tx-demo'; 13 | import { SimpleNftMintDemo } from './simple-nft-mint-demo'; 14 | import { SimpleScQeryDemo } from './simple-sc-query-demo'; 15 | import { shortenHash } from '@/lib/shorten-hash'; 16 | import { SimpleSignMessageDemo } from './sign-message-demo'; 17 | import { SimpleDeployScDemo } from './simple-deploy-sc-demo'; 18 | import { SimpleESDTTxDemo } from './simple-esdt-tx-demo'; 19 | 20 | export const SimpleDemo = () => { 21 | const [result, setResult] = useState<{ type: string; content: string }>(); 22 | const [pending, setPending] = useState(false); 23 | const [error, setError] = useState(); 24 | const { loginMethod } = useLoginInfo(); 25 | const { explorerAddress } = useConfig(); 26 | 27 | // TODO: these callbacks will be deprecated in useElven 28 | const handleTxCb = useCallback( 29 | ({ transaction, pending, error }: TransactionCallbackParams) => { 30 | if (transaction) { 31 | setResult({ type: 'tx', content: transaction.getHash().hex() }); 32 | setPending(false); 33 | setError(undefined); 34 | } 35 | if (pending) { 36 | setPending(true); 37 | setError(undefined); 38 | setResult(undefined); 39 | } 40 | if (error) { 41 | setError(error); 42 | setPending(false); 43 | setResult(undefined); 44 | } 45 | }, 46 | [] 47 | ); 48 | 49 | const handleQueryCb = useCallback( 50 | (queryResult: string, pending: boolean, error: string) => { 51 | if (queryResult) { 52 | setResult({ type: 'query', content: queryResult }); 53 | setPending(false); 54 | setError(undefined); 55 | } 56 | if (pending) { 57 | setPending(true); 58 | setError(undefined); 59 | setResult(undefined); 60 | } 61 | if (error) { 62 | setError(error); 63 | setPending(false); 64 | setResult(undefined); 65 | } 66 | }, 67 | [] 68 | ); 69 | 70 | const handleDeployCb = useCallback( 71 | ({ txResult, pending, error }: TransactionCallbackParams) => { 72 | if (txResult) { 73 | setResult({ type: 'tx', content: txResult.hash }); 74 | setPending(false); 75 | setError(undefined); 76 | } 77 | if (pending) { 78 | setPending(true); 79 | setError(undefined); 80 | setResult(undefined); 81 | } 82 | if (error) { 83 | setError(error); 84 | setPending(false); 85 | setResult(undefined); 86 | } 87 | }, 88 | [] 89 | ); 90 | 91 | const handleClose = useCallback(() => { 92 | setResult(undefined); 93 | setPending(false); 94 | setError(undefined); 95 | }, []); 96 | 97 | return ( 98 |
99 |
100 | 101 | 102 | 103 |
104 |
105 | 106 | 107 | 108 |
109 | {error && ( 110 |
111 |
Transaction status:
112 |
{error}
113 | 116 |
117 | )} 118 | {pending && ( 119 |
120 |
121 | Transaction is pending. Please wait. 122 |
123 | {loginMethod === LoginMethodsEnum.walletconnect && ( 124 |
125 | Confirm it on the xPortal mobile app and wait till it finishes. 126 |
127 | )} 128 | {loginMethod === LoginMethodsEnum.ledger && ( 129 |
Confirm it on the Ledger app and wait till it finishes.
130 | )} 131 | {loginMethod === LoginMethodsEnum.extension && ( 132 |
Confirm using the MultiversX browser extension.
133 | )} 134 |
You will get the transaction hash and link at the end.
135 |
136 | 137 |
138 |
139 | )} 140 | {result?.type && ( 141 |
142 | {result.type === 'tx' ? ( 143 | <> 144 |
Transaction hash:
145 | 150 | {shortenHash(result.content, 10)} 151 | 152 | 153 | ) : ( 154 | <> 155 |
Query result
156 |
157 | There is{' '} 158 | 159 | {result.content} 160 | {' '} 161 | NFTs left to mint! 162 |
163 | 164 | )} 165 | 166 | 169 |
170 | )} 171 |
172 | ); 173 | }; 174 | -------------------------------------------------------------------------------- /components/demo/simple-deploy-sc-demo.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { 4 | TransactionCallbackParams, 5 | useConfig, 6 | useLoggingIn, 7 | useScDeploy, 8 | } from '@useelven/core'; 9 | import { Card, CardContent, CardFooter } from '@/components/ui/card'; 10 | import { Button } from '@/components/ui/button'; 11 | import { ConnectWalletInfo } from '@/components/demo/connect-wallet-info'; 12 | import { shortenHash } from '@/lib/shorten-hash'; 13 | 14 | export const SimpleDeployScDemo = ({ 15 | cb, 16 | }: { 17 | cb: (params: TransactionCallbackParams) => void; 18 | }) => { 19 | const { explorerAddress } = useConfig(); 20 | const { loggedIn } = useLoggingIn(); 21 | const { deploy, scAddress, txResult, pending } = useScDeploy({ 22 | cb, 23 | id: 'SimpleDeployScDemo', 24 | }); 25 | 26 | const handleDeploy = () => { 27 | deploy({ source: '/piggybank.wasm' }); 28 | }; 29 | 30 | return ( 31 | 32 | 33 |
34 | You will be deploying a{' '} 35 | 40 | Piggy Bank 41 | {' '} 42 | simple smart contract. 43 |
44 | {!pending && txResult && scAddress && ( 45 |
46 | Your deployed smart contract address: 47 |
48 | 53 | {shortenHash(scAddress, 12)} 54 | 55 |
56 | 57 | To interact with it, check the{' '} 58 | 63 | Piggy Bank Dapp 64 | 65 | 66 |
67 | )} 68 |
69 | 70 | 77 | 78 | 79 |
80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /components/demo/simple-egld-tx-demo.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionPayload, TokenTransfer } from '@multiversx/sdk-core'; 2 | import { 3 | useTransaction, 4 | TransactionCallbackParams, 5 | useConfig, 6 | useLoggingIn, 7 | } from '@useelven/core'; 8 | import { shortenHash } from '@/lib/shorten-hash'; 9 | import { Button } from '@/components/ui/button'; 10 | import { Card, CardContent, CardFooter } from '@/components/ui/card'; 11 | import { ConnectWalletInfo } from '@/components/demo/connect-wallet-info'; 12 | 13 | const transferAddress = process.env.NEXT_PUBLIC_TRANSFER_ADDRESS || ''; 14 | const egldTransferAmount = process.env.NEXT_PUBLIC_EGLD_TRANSFER_AMOUNT || ''; 15 | 16 | export const SimpleEGLDTxDemo = ({ 17 | cb, 18 | }: { 19 | cb: (params: TransactionCallbackParams) => void; 20 | }) => { 21 | const { pending, triggerTx } = useTransaction({ cb, id: 'SimpleEGLDTxDemo' }); 22 | const { loggedIn } = useLoggingIn(); 23 | const { explorerAddress, chainType } = useConfig(); 24 | 25 | const handleSendTx = () => { 26 | const demoMessage = 27 | 'Transaction demo from xDevGuild Next.js dapp template!'; 28 | 29 | triggerTx({ 30 | address: transferAddress, 31 | gasLimit: 50000 + 1500 * demoMessage.length, 32 | data: new TransactionPayload(demoMessage), 33 | value: TokenTransfer.egldFromAmount(egldTransferAmount), 34 | }); 35 | }; 36 | 37 | return ( 38 | 39 | 40 |
41 | 1. You will be sending 0.001 EGLD to the address:
42 | 47 | {shortenHash(transferAddress, 8)} 48 | {' '} 49 |
({chainType}) 50 |
51 |
52 | 53 | 60 | 61 | 62 |
63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /components/demo/simple-esdt-tx-demo.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ESDTType, 3 | TransactionCallbackParams, 4 | useConfig, 5 | useLoggingIn, 6 | useTokenTransfer, 7 | } from '@useelven/core'; 8 | import { shortenHash } from '@/lib/shorten-hash'; 9 | import { Button } from '@/components/ui/button'; 10 | import { Card, CardContent, CardFooter } from '@/components/ui/card'; 11 | import { ConnectWalletInfo } from '@/components/demo/connect-wallet-info'; 12 | 13 | const transferAddress = process.env.NEXT_PUBLIC_TRANSFER_ADDRESS || ''; 14 | 15 | export const SimpleESDTTxDemo = ({ 16 | cb, 17 | }: { 18 | cb: (params: TransactionCallbackParams) => void; 19 | }) => { 20 | const { transfer, pending } = useTokenTransfer({ 21 | cb, 22 | id: 'SimpleESDTTxDemo', 23 | }); 24 | const { loggedIn } = useLoggingIn(); 25 | const { explorerAddress, chainType } = useConfig(); 26 | 27 | const handleSendTx = () => { 28 | transfer({ 29 | receiver: transferAddress, 30 | tokens: [ 31 | { 32 | type: ESDTType.FungibleESDT, 33 | amount: '1', 34 | tokenId: 'BUILDO-22c0a5', 35 | }, 36 | ], 37 | }); 38 | }; 39 | 40 | return ( 41 | 42 | 43 |
44 | 1. You will be sending 1 BUILDO-22c0a5 to the address:
45 | 50 | {shortenHash(transferAddress, 8)} 51 | {' '} 52 | ({chainType})
53 | You can get some Buildo tokens here:{' '} 54 | 59 | r3d4.fr/faucet 60 | 61 |
62 |
63 | 64 | 71 | 72 | 73 |
74 | ); 75 | }; 76 | -------------------------------------------------------------------------------- /components/demo/simple-nft-mint-demo.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | U32Value, 3 | ContractFunction, 4 | ContractCallPayloadBuilder, 5 | TokenTransfer, 6 | } from '@multiversx/sdk-core'; 7 | import { 8 | useTransaction, 9 | TransactionCallbackParams, 10 | useConfig, 11 | useAccount, 12 | useLoggingIn, 13 | } from '@useelven/core'; 14 | import { useCallback } from 'react'; 15 | import { shortenHash } from '@/lib/shorten-hash'; 16 | import { Button } from '@/components/ui/button'; 17 | import { Card, CardContent, CardFooter } from '@/components/ui/card'; 18 | import { ConnectWalletInfo } from '@/components/demo/connect-wallet-info'; 19 | 20 | const mintSmartContractAddress = 21 | process.env.NEXT_PUBLIC_MINT_SMART_CONTRACT_ADDRESS || ''; 22 | const mintFunctionName = process.env.NEXT_PUBLIC_MINT_FUNCTION_NAME || ''; 23 | const mintPaymentPerToken = 24 | process.env.NEXT_PUBLIC_MINT_PAYMENT_PER_TOKEN || ''; 25 | 26 | export const SimpleNftMintDemo = ({ 27 | cb, 28 | }: { 29 | cb: (params: TransactionCallbackParams) => void; 30 | }) => { 31 | const { loggedIn } = useLoggingIn(); 32 | const { pending, triggerTx } = useTransaction({ 33 | cb, 34 | id: 'SimpleNftMintDemo', 35 | }); 36 | const { activeGuardianAddress } = useAccount(); 37 | const { explorerAddress, chainType } = useConfig(); 38 | 39 | const handleSendTx = useCallback(() => { 40 | // Prepare data payload for smart contract using MultiversX JS SDK core tools 41 | const data = new ContractCallPayloadBuilder() 42 | .setFunction(new ContractFunction(mintFunctionName)) 43 | .setArgs([new U32Value(1)]) 44 | .build(); 45 | 46 | let gasLimit = 14000000; 47 | 48 | if (activeGuardianAddress) { 49 | gasLimit = gasLimit + 50000; 50 | } 51 | 52 | triggerTx({ 53 | address: mintSmartContractAddress, 54 | gasLimit, 55 | value: TokenTransfer.egldFromAmount(mintPaymentPerToken), 56 | data, 57 | }); 58 | }, [activeGuardianAddress, triggerTx]); 59 | 60 | return ( 61 | 62 | 63 |
64 | 2. You will be minting one NFT using{' '} 65 | Elven Tools smart contract:{' '} 66 |
67 | 72 | {shortenHash(mintSmartContractAddress, 8)} 73 | {' '} 74 |
({chainType}, max 10 NFTs per address) 75 |
76 |
77 | 78 | 85 | 86 | 87 |
88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /components/demo/simple-sc-query-demo.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useScQuery, SCQueryType, useConfig } from '@useelven/core'; 3 | import { shortenHash } from '@/lib/shorten-hash'; 4 | import { Button } from '@/components/ui/button'; 5 | import { Card, CardContent, CardFooter } from '@/components/ui/card'; 6 | 7 | const mintSmartContractAddress = 8 | process.env.NEXT_PUBLIC_MINT_SMART_CONTRACT_ADDRESS || ''; 9 | const queryFunctionName = process.env.NEXT_PUBLIC_QUERY_FUNCTION_NAME || ''; 10 | 11 | export const SimpleScQeryDemo = ({ 12 | cb, 13 | }: { 14 | cb: (queryResult: string, pending: boolean, error: string) => void; 15 | }) => { 16 | const { explorerAddress, chainType } = useConfig(); 17 | const { 18 | data: queryResult, 19 | fetch, // you can always trigger the query manually if 'autoInit' is set to false 20 | isLoading, // pending state for initial load 21 | isValidating, // pending state for each revalidation of the data, for example using the mutate 22 | error, 23 | } = useScQuery({ 24 | type: SCQueryType.NUMBER, // can be int or string 25 | payload: { 26 | scAddress: mintSmartContractAddress, 27 | funcName: queryFunctionName, 28 | args: [], 29 | }, 30 | autoInit: false, // you can enable or disable trigger of the query on component mount 31 | }); 32 | 33 | useEffect(() => { 34 | if (queryResult !== undefined && queryResult !== null) { 35 | cb?.(queryResult.toString(), isLoading || isValidating, error); 36 | } 37 | }, [cb, error, isLoading, isValidating, queryResult]); 38 | 39 | const handleFetch = () => { 40 | fetch(); 41 | }; 42 | 43 | return ( 44 | 45 | 46 |
47 | 3. You will be querying the smart contract for NFT tokens left to 48 | mint:
49 | 54 | {shortenHash(mintSmartContractAddress, 8)} 55 | {' '} 56 | ({chainType}) 57 |
58 |
59 | 60 | 67 | 68 |
69 | ); 70 | }; 71 | -------------------------------------------------------------------------------- /components/elven-ui/authenticated.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { FC, ReactElement, PropsWithChildren } from 'react'; 4 | import { useLoggingIn } from '@useelven/core'; 5 | import { Spinner } from '@/components/ui/spinner'; 6 | import { cn } from '@/lib/utils'; 7 | 8 | interface AuthenticatedProps { 9 | fallback?: ReactElement; 10 | noSpinner?: boolean; 11 | spinnerCentered?: boolean; 12 | } 13 | 14 | export const Authenticated: FC> = ({ 15 | children, 16 | fallback = null, 17 | noSpinner = false, 18 | spinnerCentered = false, 19 | ...props 20 | }) => { 21 | const { pending, loggedIn } = useLoggingIn(); 22 | 23 | if (pending) 24 | return noSpinner ? null : ( 25 |
29 | 30 |
31 | ); 32 | 33 | if (!loggedIn) return fallback; 34 | 35 | return <>{children}; 36 | }; 37 | -------------------------------------------------------------------------------- /components/elven-ui/elven-init.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useNetworkSync } from '@useelven/core'; 4 | 5 | /** 6 | * Initialize the useElven library. Should be run on the client side. 7 | * You can configure all parameters for useNetworkSync. The best if they will come from .env.local file 8 | * */ 9 | export const ElvenInit = () => { 10 | useNetworkSync({ 11 | apiTimeout: '10000', 12 | chainType: process.env.NEXT_PUBLIC_MULTIVERSX_CHAIN, 13 | ...(process.env.NEXT_PUBLIC_MULTIVERSX_API 14 | ? { apiAddress: process.env.NEXT_PUBLIC_MULTIVERSX_API } 15 | : {}), 16 | ...(process.env.NEXT_PUBLIC_WC_PROJECT_ID 17 | ? { walletConnectV2ProjectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID } 18 | : {}), 19 | }); 20 | return null; 21 | }; 22 | -------------------------------------------------------------------------------- /components/elven-ui/ledger-accounts-list.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useCallback, useState, useEffect, useRef } from 'react'; 2 | import { useRouter } from 'next/navigation'; 3 | import { LoginMethodsEnum } from '@useelven/core'; 4 | import { Button } from '@/components/ui/button'; 5 | import { shortenHash } from '@/lib/shorten-hash'; 6 | import { errorParse } from '@/lib/error-parse'; 7 | import { Spinner } from '@/components/ui/spinner'; 8 | 9 | interface LedgerAccountsListProps { 10 | getHWAccounts: (page?: number, pageSize?: number) => Promise; 11 | resetLoginMethod: () => void; 12 | handleLogin: ( 13 | type: LoginMethodsEnum, 14 | ledgerAccountsIndex?: number 15 | ) => () => void; 16 | } 17 | 18 | const ADDRESSES_PER_PAGE = 10; 19 | const LEDGER_NOT_CONNECTED_CODE = 0x6e01; 20 | const LEDGER_DISCONNECTED = 'DisconnectedDeviceDuringOperation'; 21 | 22 | export const LedgerAccountsList: FC = ({ 23 | getHWAccounts, 24 | resetLoginMethod, 25 | handleLogin, 26 | }) => { 27 | const [accounts, setAccounts] = useState(); 28 | const [listPending, setListPending] = useState(true); 29 | const [error, setError] = useState(); 30 | const [chosenAddress, setAddress] = useState(); 31 | 32 | const router = useRouter(); 33 | 34 | const getAccounts = async (page: number) => 35 | await getHWAccounts(page, ADDRESSES_PER_PAGE); 36 | 37 | const handleAccounts = async (page: number) => { 38 | const accountsResult = await getAccounts(page); 39 | if (accountsResult?.length > 0) setAccounts(accountsResult); 40 | }; 41 | 42 | const handleErrors = (e: unknown) => { 43 | const err = e as { statusCode: number; name: string }; 44 | if ( 45 | err.statusCode === LEDGER_NOT_CONNECTED_CODE || 46 | err.name === LEDGER_DISCONNECTED 47 | ) { 48 | setError( 49 | 'Not connected, please check the connection and make sure that you have the MultiversX app opened on your Ledger device.' 50 | ); 51 | } else { 52 | setError(`Error: ${errorParse(e)}`); 53 | } 54 | }; 55 | 56 | const fetchedOnce = useRef(false); 57 | useEffect(() => { 58 | const fetch = async () => { 59 | setListPending(true); 60 | try { 61 | await handleAccounts(0); 62 | } catch (e) { 63 | handleErrors(e); 64 | } finally { 65 | setListPending(false); 66 | } 67 | }; 68 | if (!fetchedOnce.current) fetch(); 69 | return () => { 70 | fetchedOnce.current = true; 71 | }; 72 | // eslint-disable-next-line react-hooks/exhaustive-deps 73 | }, []); 74 | 75 | const currentPage = useRef(0); 76 | 77 | const handlePrev = useCallback(async () => { 78 | setListPending(true); 79 | try { 80 | const prevPage = 81 | currentPage.current > 0 ? currentPage.current - 1 : currentPage.current; 82 | currentPage.current = prevPage; 83 | await handleAccounts(prevPage); 84 | } catch (e) { 85 | handleErrors(e); 86 | } finally { 87 | setListPending(false); 88 | } 89 | // eslint-disable-next-line react-hooks/exhaustive-deps 90 | }, []); 91 | 92 | const handleNext = useCallback(async () => { 93 | setListPending(true); 94 | try { 95 | const nextPage = currentPage.current + 1; 96 | currentPage.current = nextPage; 97 | await handleAccounts(nextPage); 98 | } catch (e) { 99 | handleErrors(e); 100 | } finally { 101 | setListPending(false); 102 | } 103 | // eslint-disable-next-line react-hooks/exhaustive-deps 104 | }, []); 105 | 106 | const handleRefresh = useCallback(() => { 107 | router.refresh(); 108 | }, [router]); 109 | 110 | const login = useCallback( 111 | (index: number, address: string) => () => { 112 | setAddress(address); 113 | handleLogin(LoginMethodsEnum.ledger, index)(); 114 | }, 115 | [handleLogin] 116 | ); 117 | 118 | useEffect(() => { 119 | if (!listPending && !accounts && !error) { 120 | resetLoginMethod(); 121 | } 122 | }, [accounts, error, listPending, resetLoginMethod]); 123 | 124 | if (listPending) { 125 | return ( 126 |
127 | 128 |
Loading addresses, please wait...
129 |
130 | ); 131 | } 132 | 133 | if (error) { 134 | return ( 135 |
136 |
{error}
137 | 140 |
141 | ); 142 | } 143 | 144 | if (chosenAddress) { 145 | return ( 146 |
147 | 148 |
Confirm on the Ledger device:
149 |
150 |
Address:
{chosenAddress} 151 |
152 |
153 | ); 154 | } 155 | 156 | if (!accounts) return null; 157 | 158 | return ( 159 |
160 |
Choose address:
161 | {accounts?.map((account: string, index: number) => ( 162 |
167 | 168 | {index + currentPage.current * ADDRESSES_PER_PAGE}: 169 | 170 | 171 | {shortenHash(account, 14)} 172 | 173 | 174 | {shortenHash(account, 10)} 175 | 176 |
177 | ))} 178 |
179 | 187 | Prev 188 | 189 | 190 | Next 191 | 192 |
193 |
194 | ); 195 | }; 196 | -------------------------------------------------------------------------------- /components/elven-ui/login-component.tsx: -------------------------------------------------------------------------------- 1 | // Login component wraps all auth services in one place 2 | // You can always use only one of them if needed 3 | import { useCallback, memo, useState } from 'react'; 4 | import { useLogin, LoginMethodsEnum } from '@useelven/core'; 5 | import { WalletConnectQRCode } from './walletconnect-qr-code'; 6 | import { WalletConnectPairings } from './walletconnect-pairings'; 7 | import { Button } from '@/components/ui/button'; 8 | import { Spinner } from '@/components/ui/spinner'; 9 | import { LedgerAccountsList } from './ledger-accounts-list'; 10 | import { getLoginMethodDeviceName } from '@/lib/get-signing-device-name'; 11 | 12 | export const LoginComponent = memo(() => { 13 | const { 14 | login, 15 | isLoggingIn, 16 | error, 17 | walletConnectUri, 18 | getHWAccounts, 19 | walletConnectPairingLogin, 20 | walletConnectPairings, 21 | walletConnectRemovePairing, 22 | } = useLogin(); 23 | 24 | const [loginMethod, setLoginMethod] = useState(); 25 | 26 | const handleLogin = useCallback( 27 | (type: LoginMethodsEnum, ledgerAccountsIndex?: number) => () => { 28 | setLoginMethod(type); 29 | login(type, ledgerAccountsIndex); 30 | }, 31 | [login] 32 | ); 33 | 34 | const handleLedgerAccountsList = useCallback(() => { 35 | setLoginMethod(LoginMethodsEnum.ledger); 36 | }, []); 37 | 38 | const resetLoginMethod = useCallback(() => { 39 | setLoginMethod(undefined); 40 | }, []); 41 | 42 | const ledgerOrPortalName = getLoginMethodDeviceName(loginMethod!); 43 | 44 | if (error) 45 | return ( 46 |
47 |
{error}
48 |
Close and try again
49 |
50 | ); 51 | 52 | return ( 53 | <> 54 | {isLoggingIn ? ( 55 |
56 |
57 | {ledgerOrPortalName ? ( 58 |
59 |
Confirmation required
60 |
61 | Approve on {ledgerOrPortalName} 62 |
63 |
64 | ) : null} 65 |
66 | 67 |
68 |
69 |
70 | ) : ( 71 |
72 | 79 | 86 | 93 | 100 | 107 |
108 | )} 109 | 110 | {loginMethod === LoginMethodsEnum.walletconnect && walletConnectUri && ( 111 |
112 | 113 |
114 | )} 115 | {loginMethod === LoginMethodsEnum.walletconnect && 116 | walletConnectPairings && 117 | walletConnectPairings.length > 0 && ( 118 | 123 | )} 124 | {loginMethod === LoginMethodsEnum.ledger && ( 125 | 130 | )} 131 | 132 | ); 133 | }); 134 | 135 | LoginComponent.displayName = 'LoginComponent'; 136 | -------------------------------------------------------------------------------- /components/elven-ui/login-modal-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { FC, useState } from 'react'; 4 | import { useLogin, useLogout } from '@useelven/core'; 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogHeader, 9 | DialogTitle, 10 | } from '@/components/ui/dialog'; 11 | import { Button } from '@/components/ui/button'; 12 | import { LoginComponent } from './login-component'; 13 | import { useEffectOnlyOnUpdate } from '@/hooks/use-effect-only-on-update'; 14 | 15 | interface LoginModalButtonProps { 16 | onClose?: () => void; 17 | onOpen?: () => void; 18 | } 19 | 20 | export const LoginModalButton: FC = ({ 21 | onClose, 22 | onOpen, 23 | }) => { 24 | const [isOpen, setIsOpen] = useState(false); 25 | const { isLoggedIn, isLoggingIn, setLoggingInState } = useLogin(); 26 | 27 | const { logout } = useLogout(); 28 | 29 | useEffectOnlyOnUpdate(() => { 30 | if (isLoggedIn) { 31 | setIsOpen(false); 32 | onClose?.(); 33 | } 34 | }, [isLoggedIn]); 35 | 36 | const onCloseComplete = (open: boolean) => { 37 | if (!open) { 38 | setIsOpen(false); 39 | setTimeout(() => { 40 | setLoggingInState('error', ''); 41 | }, 1000); 42 | } 43 | }; 44 | 45 | const handleOpen = () => { 46 | setIsOpen(true); 47 | onOpen?.(); 48 | }; 49 | 50 | return ( 51 | 52 | {isLoggedIn ? ( 53 | 56 | ) : ( 57 | 60 | )} 61 | 62 | 63 | Connect your wallet 64 | 65 |
66 | 67 |
68 |
69 |
70 | ); 71 | }; 72 | -------------------------------------------------------------------------------- /components/elven-ui/protected-page-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, PropsWithChildren, FC } from 'react'; 2 | import { useRouter } from 'next/router'; 3 | import { useLogin } from '@useelven/core'; 4 | import { Spinner } from '@/components/ui/spinner'; 5 | 6 | interface ProtectedPageWrapper { 7 | redirectPath?: string; 8 | } 9 | 10 | export const ProtectedPageWrapper: FC< 11 | PropsWithChildren 12 | > = ({ children, redirectPath = '/' }) => { 13 | const router = useRouter(); 14 | const { isLoggedIn, isLoggingIn } = useLogin(); 15 | 16 | useEffect(() => { 17 | if (!isLoggingIn && !isLoggedIn) { 18 | router.push(redirectPath); 19 | } 20 | // eslint-disable-next-line react-hooks/exhaustive-deps 21 | }, [isLoggedIn, isLoggingIn]); 22 | 23 | if (isLoggingIn) { 24 | return ( 25 |
26 | 27 |
28 | ); 29 | } 30 | 31 | if (!isLoggingIn && !isLoggedIn) { 32 | return null; 33 | } 34 | 35 | return <>{children}; 36 | }; 37 | -------------------------------------------------------------------------------- /components/elven-ui/walletconnect-pairings.tsx: -------------------------------------------------------------------------------- 1 | import { FC, MouseEventHandler } from 'react'; 2 | import { PairingTypesStruct } from '@useelven/core'; 3 | import { X } from 'lucide-react'; 4 | import { Button } from '@/components/ui/button'; 5 | 6 | interface WalletConnectPairingsProps { 7 | pairings: PairingTypesStruct[]; 8 | login: (topic: string) => Promise; 9 | remove: (topic: string) => Promise; 10 | } 11 | 12 | export const WalletConnectPairings: FC = ({ 13 | pairings, 14 | login, 15 | remove, 16 | }) => { 17 | const handleLogin = (topic: string) => () => { 18 | login(topic); 19 | }; 20 | 21 | const handleRemove = 22 | (topic: string): MouseEventHandler | undefined => 23 | (e) => { 24 | e.stopPropagation(); 25 | remove(topic); 26 | }; 27 | 28 | return ( 29 |
30 |
31 | {pairings?.length > 0 && ( 32 |
Existing pairings:
33 | )} 34 | {pairings.map((pairing) => ( 35 |
40 | 48 |
{pairing.peerMetadata?.name}
49 | {pairing.peerMetadata?.url ? ( 50 |
({pairing.peerMetadata.url})
51 | ) : null} 52 |
53 | ))} 54 |
55 |
56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /components/elven-ui/walletconnect-qr-code.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, FunctionComponent } from 'react'; 2 | import { useConfig } from '@useelven/core'; 3 | import { isMobile } from '@/lib/is-mobile'; 4 | import QRCode from 'qrcode'; 5 | import { Button } from '@/components/ui/button'; 6 | 7 | interface WalletConnectQRCodeProps { 8 | uri: string; 9 | } 10 | 11 | export const WalletConnectQRCode: FunctionComponent< 12 | WalletConnectQRCodeProps 13 | > = ({ uri }) => { 14 | const [qrCodeSvg, setQrCodeSvg] = useState(''); 15 | const { walletConnectDeepLink } = useConfig(); 16 | 17 | useEffect(() => { 18 | const generateQRCode = async () => { 19 | if (!uri) { 20 | return; 21 | } 22 | 23 | const svg = await QRCode.toString(uri, { 24 | type: 'svg', 25 | }); 26 | 27 | setQrCodeSvg(svg); 28 | }; 29 | generateQRCode(); 30 | }, [uri]); 31 | 32 | const mobile = isMobile(); 33 | 34 | return ( 35 |
36 | {mobile ? ( 37 | 50 | ) : null} 51 |
57 |
58 | ); 59 | }; 60 | -------------------------------------------------------------------------------- /components/mode-toggle.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { Moon, Sun } from 'lucide-react'; 5 | import { useTheme } from 'next-themes'; 6 | 7 | import { Button } from '@/components/ui/button'; 8 | import { 9 | DropdownMenu, 10 | DropdownMenuContent, 11 | DropdownMenuItem, 12 | DropdownMenuTrigger, 13 | } from '@/components/ui/dropdown-menu'; 14 | 15 | export const ModeToggle = () => { 16 | const { setTheme } = useTheme(); 17 | 18 | return ( 19 | 20 | 21 | 26 | 27 | 28 | setTheme('light')}> 29 | Light 30 | 31 | setTheme('dark')}> 32 | Dark 33 | 34 | setTheme('system')}> 35 | System 36 | 37 | 38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import { ThemeProvider as NextThemesProvider } from 'next-themes'; 5 | import { type ThemeProviderProps } from 'next-themes'; 6 | 7 | export const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => { 8 | return {children}; 9 | }; 10 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot } from '@radix-ui/react-slot'; 3 | import { cva, type VariantProps } from 'class-variance-authority'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | const buttonVariants = cva( 8 | 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-0 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 cursor-pointer', 9 | { 10 | variants: { 11 | variant: { 12 | default: 'bg-primary text-primary-foreground hover:bg-primary/90', 13 | destructive: 14 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90', 15 | outline: 16 | 'border border-input border-zinc-300 dark:border-zinc-700 bg-background hover:bg-zinc-100 dark:hover:bg-zinc-900 hover:text-accent-foreground', 17 | secondary: 18 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80', 19 | ghost: 'hover:bg-accent hover:text-accent-foreground', 20 | link: 'text-primary underline-offset-4 hover:underline', 21 | }, 22 | size: { 23 | default: 'h-10 px-4 py-2', 24 | sm: 'h-9 rounded-md px-3', 25 | lg: 'h-11 rounded-md px-8', 26 | icon: 'h-10 w-10', 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: 'default', 31 | size: 'default', 32 | }, 33 | } 34 | ); 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean; 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : 'button'; 45 | return ( 46 | 51 | ); 52 | } 53 | ); 54 | Button.displayName = 'Button'; 55 | 56 | export { Button, buttonVariants }; 57 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )); 18 | Card.displayName = 'Card'; 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )); 30 | CardHeader.displayName = 'CardHeader'; 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )); 45 | CardTitle.displayName = 'CardTitle'; 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )); 57 | CardDescription.displayName = 'CardDescription'; 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )); 65 | CardContent.displayName = 'CardContent'; 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )); 77 | CardFooter.displayName = 'CardFooter'; 78 | 79 | export { 80 | Card, 81 | CardHeader, 82 | CardFooter, 83 | CardTitle, 84 | CardDescription, 85 | CardContent, 86 | }; 87 | -------------------------------------------------------------------------------- /components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as DialogPrimitive from '@radix-ui/react-dialog'; 5 | import { X } from 'lucide-react'; 6 | 7 | import { cn } from '@/lib/utils'; 8 | 9 | const Dialog = DialogPrimitive.Root; 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger; 12 | 13 | const DialogPortal = ({ ...props }: DialogPrimitive.DialogPortalProps) => ( 14 | 15 | ); 16 | DialogPortal.displayName = DialogPrimitive.Portal.displayName; 17 | 18 | const DialogOverlay = React.forwardRef< 19 | React.ComponentRef, 20 | React.ComponentPropsWithoutRef 21 | >(({ className, ...props }, ref) => ( 22 | 30 | )); 31 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; 32 | 33 | const DialogContent = React.forwardRef< 34 | React.ComponentRef, 35 | React.ComponentPropsWithoutRef 36 | >(({ className, children, ...props }, ref) => ( 37 | 38 | 39 | 47 | {children} 48 | 49 | 50 | Close 51 | 52 | 53 | 54 | )); 55 | DialogContent.displayName = DialogPrimitive.Content.displayName; 56 | 57 | const DialogHeader = ({ 58 | className, 59 | ...props 60 | }: React.HTMLAttributes) => ( 61 |
68 | ); 69 | DialogHeader.displayName = 'DialogHeader'; 70 | 71 | const DialogFooter = ({ 72 | className, 73 | ...props 74 | }: React.HTMLAttributes) => ( 75 |
82 | ); 83 | DialogFooter.displayName = 'DialogFooter'; 84 | 85 | const DialogTitle = React.forwardRef< 86 | React.ComponentRef, 87 | React.ComponentPropsWithoutRef 88 | >(({ className, ...props }, ref) => ( 89 | 97 | )); 98 | DialogTitle.displayName = DialogPrimitive.Title.displayName; 99 | 100 | const DialogDescription = React.forwardRef< 101 | React.ComponentRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, ...props }, ref) => ( 104 | 109 | )); 110 | DialogDescription.displayName = DialogPrimitive.Description.displayName; 111 | 112 | export { 113 | Dialog, 114 | DialogTrigger, 115 | DialogContent, 116 | DialogHeader, 117 | DialogFooter, 118 | DialogTitle, 119 | DialogDescription, 120 | }; 121 | -------------------------------------------------------------------------------- /components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; 5 | import { Check, ChevronRight, Circle } from 'lucide-react'; 6 | 7 | import { cn } from '@/lib/utils'; 8 | 9 | const DropdownMenu = DropdownMenuPrimitive.Root; 10 | 11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; 12 | 13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group; 14 | 15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal; 16 | 17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub; 18 | 19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; 20 | 21 | const DropdownMenuSubTrigger = React.forwardRef< 22 | React.ComponentRef, 23 | React.ComponentPropsWithoutRef & { 24 | inset?: boolean; 25 | } 26 | >(({ className, inset, children, ...props }, ref) => ( 27 | 36 | {children} 37 | 38 | 39 | )); 40 | DropdownMenuSubTrigger.displayName = 41 | DropdownMenuPrimitive.SubTrigger.displayName; 42 | 43 | const DropdownMenuSubContent = React.forwardRef< 44 | React.ComponentRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, ...props }, ref) => ( 47 | 55 | )); 56 | DropdownMenuSubContent.displayName = 57 | DropdownMenuPrimitive.SubContent.displayName; 58 | 59 | const DropdownMenuContent = React.forwardRef< 60 | React.ComponentRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, sideOffset = 4, ...props }, ref) => ( 63 | 64 | 73 | 74 | )); 75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; 76 | 77 | const DropdownMenuItem = React.forwardRef< 78 | React.ComponentRef, 79 | React.ComponentPropsWithoutRef & { 80 | inset?: boolean; 81 | } 82 | >(({ className, inset, ...props }, ref) => ( 83 | 92 | )); 93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; 94 | 95 | const DropdownMenuCheckboxItem = React.forwardRef< 96 | React.ComponentRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, children, checked, ...props }, ref) => ( 99 | 108 | 109 | 110 | 111 | 112 | 113 | {children} 114 | 115 | )); 116 | DropdownMenuCheckboxItem.displayName = 117 | DropdownMenuPrimitive.CheckboxItem.displayName; 118 | 119 | const DropdownMenuRadioItem = React.forwardRef< 120 | React.ComponentRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )); 139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; 140 | 141 | const DropdownMenuLabel = React.forwardRef< 142 | React.ComponentRef, 143 | React.ComponentPropsWithoutRef & { 144 | inset?: boolean; 145 | } 146 | >(({ className, inset, ...props }, ref) => ( 147 | 156 | )); 157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; 158 | 159 | const DropdownMenuSeparator = React.forwardRef< 160 | React.ComponentRef, 161 | React.ComponentPropsWithoutRef 162 | >(({ className, ...props }, ref) => ( 163 | 168 | )); 169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; 170 | 171 | const DropdownMenuShortcut = ({ 172 | className, 173 | ...props 174 | }: React.HTMLAttributes) => { 175 | return ( 176 | 180 | ); 181 | }; 182 | DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; 183 | 184 | export { 185 | DropdownMenu, 186 | DropdownMenuTrigger, 187 | DropdownMenuContent, 188 | DropdownMenuItem, 189 | DropdownMenuCheckboxItem, 190 | DropdownMenuRadioItem, 191 | DropdownMenuLabel, 192 | DropdownMenuSeparator, 193 | DropdownMenuShortcut, 194 | DropdownMenuGroup, 195 | DropdownMenuPortal, 196 | DropdownMenuSub, 197 | DropdownMenuSubContent, 198 | DropdownMenuSubTrigger, 199 | DropdownMenuRadioGroup, 200 | }; 201 | -------------------------------------------------------------------------------- /components/ui/spinner.tsx: -------------------------------------------------------------------------------- 1 | import { Loader2, LucideProps } from 'lucide-react'; 2 | 3 | export const Spinner = (props: LucideProps) => ( 4 | 5 | ); 6 | -------------------------------------------------------------------------------- /components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as TooltipPrimitive from '@radix-ui/react-tooltip'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const TooltipProvider = TooltipPrimitive.Provider; 9 | 10 | const Tooltip = TooltipPrimitive.Root; 11 | 12 | const TooltipTrigger = TooltipPrimitive.Trigger; 13 | 14 | const TooltipContent = React.forwardRef< 15 | React.ComponentRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, sideOffset = 4, ...props }, ref) => ( 18 | 27 | )); 28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName; 29 | 30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; 31 | -------------------------------------------------------------------------------- /hooks/use-effect-only-on-update.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { DependencyList, useEffect, useRef } from 'react'; 3 | 4 | export const useEffectOnlyOnUpdate = ( 5 | callback: (deps: DependencyList) => void, 6 | dependencies: DependencyList 7 | ) => { 8 | const didMount = useRef(false); 9 | 10 | useEffect(() => { 11 | if (didMount.current) { 12 | callback(dependencies); 13 | } else { 14 | didMount.current = true; 15 | } 16 | }, dependencies); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/error-parse.ts: -------------------------------------------------------------------------------- 1 | // Basic version, extend if required 2 | 3 | export const errorParse = (err: unknown) => { 4 | if (typeof err === 'string') { 5 | return err.toUpperCase(); 6 | } else if (err instanceof Error) { 7 | return err.message; 8 | } 9 | return `Error: ${JSON.stringify(err)}`; 10 | }; 11 | -------------------------------------------------------------------------------- /lib/get-signing-device-name.ts: -------------------------------------------------------------------------------- 1 | import { LoginMethodsEnum } from '@useelven/core'; 2 | 3 | // We need two 'remote' ones, Ledger and xPortal for now 4 | export const getLoginMethodDeviceName = (type: LoginMethodsEnum) => { 5 | if (type === LoginMethodsEnum.ledger) return 'Ledger hardware wallet'; 6 | if (type === LoginMethodsEnum.walletconnect) return 'xPortal app'; 7 | return ''; 8 | }; 9 | -------------------------------------------------------------------------------- /lib/is-mobile.ts: -------------------------------------------------------------------------------- 1 | export const isMobile = () => 2 | /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( 3 | navigator.userAgent 4 | ); 5 | -------------------------------------------------------------------------------- /lib/shorten-hash.ts: -------------------------------------------------------------------------------- 1 | export const shortenHash = (address: string, charsAmount = 6) => { 2 | const firstPart = address.substring(0, charsAmount); 3 | const lastPart = address.substring( 4 | address.length - charsAmount, 5 | address.length 6 | ); 7 | return `${firstPart}...${lastPart}`; 8 | }; 9 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | webpack: (config) => { 5 | config.resolve.fallback = { fs: false }; 6 | config.externals.push('pino-pretty', 'lokijs', 'encoding', { 7 | bufferutil: 'bufferutil', 8 | 'utf-8-validate': 'utf-8-validate', 9 | }); 10 | return config; 11 | }, 12 | eslint: { 13 | dirs: ['components', 'hooks', 'lib', 'app'], 14 | }, 15 | }; 16 | 17 | module.exports = nextConfig; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-dapp-template", 3 | "version": "10.0.0", 4 | "author": "Julian Ćwirko ", 5 | "license": "MIT", 6 | "homepage": "https://github.com/xdevguild/nextjs-dapp-template", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/xdevguild/nextjs-dapp-template" 10 | }, 11 | "private": true, 12 | "scripts": { 13 | "dev": "next dev --experimental-https", 14 | "build": "next build", 15 | "start": "next start", 16 | "lint": "next lint", 17 | "prettier": "prettier --write '**/*.{js,ts,tsx,json}'" 18 | }, 19 | "dependencies": { 20 | "@multiversx/sdk-core": "13.17.2", 21 | "@radix-ui/react-dialog": "1.1.6", 22 | "@radix-ui/react-dropdown-menu": "2.1.6", 23 | "@radix-ui/react-slot": "1.1.2", 24 | "@radix-ui/react-tooltip": "1.1.8", 25 | "@useelven/core": "0.23.0", 26 | "class-variance-authority": "0.7.1", 27 | "clsx": "2.1.1", 28 | "lucide-react": "0.475.0", 29 | "next": "15.1.7", 30 | "next-themes": "0.4.4", 31 | "qrcode": "1.5.4", 32 | "react": "19.0.0", 33 | "react-dom": "19.0.0", 34 | "tailwind-merge": "3.0.1" 35 | }, 36 | "devDependencies": { 37 | "@tailwindcss/postcss": "4.0.8", 38 | "@types/node": "22.13.5", 39 | "@types/qrcode": "1.5.5", 40 | "@types/react": "19.0.10", 41 | "@types/react-dom": "19.0.4", 42 | "@typescript-eslint/eslint-plugin": "8.24.1", 43 | "@typescript-eslint/parser": "8.24.1", 44 | "eslint": "9.21.0", 45 | "eslint-config-next": "15.1.7", 46 | "eslint-config-prettier": "10.0.1", 47 | "postcss": "8.5.3", 48 | "prettier": "3.5.2", 49 | "tailwindcss": "4.0.8", 50 | "tailwindcss-animate": "1.0.7", 51 | "typescript": "5.7.3" 52 | }, 53 | "overrides": { 54 | "rxjs": "7.8.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /public/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdevguild/nextjs-dapp-template/3262045c5851254243ca286395e72e6ed5dbfddb/public/logo.png -------------------------------------------------------------------------------- /public/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdevguild/nextjs-dapp-template/3262045c5851254243ca286395e72e6ed5dbfddb/public/og-image.png -------------------------------------------------------------------------------- /public/piggybank.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdevguild/nextjs-dapp-template/3262045c5851254243ca286395e72e6ed5dbfddb/public/piggybank.wasm -------------------------------------------------------------------------------- /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": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | --------------------------------------------------------------------------------