The MultiversX dApp Template, built with React 18, TypeScript and Vite. It is the canonical reference implementation of @multiversx/sdk-dapp, demonstrating:
- wallet authentication (browser extension, xPortal / WalletConnect, web wallet, Ledger, passkeys, and a custom in-memory provider)
- transaction signing, sending, and tracking (single + batch transactions)
- message signing and native auth
- smart-contract interaction (a Ping-Pong contract)
See Template dApp for a live demo.
Looking for another framework? The same dApp exists for Next.js, Vue, Angular, SolidJS, plain-JavaScript React, and React Native — see Other templates.
- Node.js 20+
- pnpm 10+ (the repo ships a
pnpm-lock.yaml— use pnpm, not npm or yarn)
pnpm installpnpm start-devnet # or start-testnet / start-mainnetOpen https://localhost:3000 (note https — the dev server uses a self-signed certificate, because wallet providers only work on secure origins; accept the browser warning).
The page reloads on edits, and lint errors show in the console.
pnpm build-devnet # or build-testnet / build-mainnetThe output goes to the build/ folder, ready to deploy to any static host.
| Script | Description |
|---|---|
pnpm start-devnet / start-testnet / start-mainnet |
Copy the network config, then run the Vite dev server on https://localhost:3000 |
pnpm build-devnet / build-testnet / build-mainnet |
Type-check, copy the network config, and build to build/ |
pnpm copy-devnet-config (also testnet / mainnet) |
Copy src/config/config.<network>.ts over src/config/index.ts |
pnpm test |
Run the Jest unit tests |
pnpm run-playwright-test |
Run the Playwright E2E suite (auto-starts the devnet dev server) |
pnpm run-playwright-test-ui |
Playwright in interactive UI mode |
pnpm lint |
ESLint with --fix over src |
There is no .env file. The active network (devnet / testnet / mainnet) is selected at dev/build time by copying one of the per-network config files over the generated index:
src/config/config.devnet.ts,config.testnet.ts,config.mainnet.ts— per-network values (API URL, contract address,environment)src/config/sharedConfig.ts— values common to all networks (WalletConnect project ID, batch-transaction contracts, etc.)src/config/index.ts— generated by thecopy-*-configscripts; never edit it by hand, it is overwritten on everystart-*/build-*
The environment exported by the active config is passed to sdk-dapp's initApp in src/initConfig.ts, so the copy mechanism is the single source of truth for the network.
src/
├── assets/ # images, icons
├── components/ # shared presentational components
├── config/ # per-network configs (see Network configuration)
├── contracts/ # Ping-Pong contract ABI + query/transaction helpers
├── helpers/ # generic utilities
├── hooks/ # shared React hooks
├── lib/ # SDK re-export layer — the only place with deep @multiversx/* imports
├── localConstants/ # route names, misc constants
├── pages/ # Home, Dashboard (feature widgets), Unlock, Disclaimer, PageNotFound
├── provider/ # custom InMemoryProvider (login without an external wallet)
├── routes/ # declarative route array consumed by App.tsx
├── styles/ # Tailwind v4 styles + theme CSS variables
├── types/ # shared TypeScript types
├── wrappers/ # app-wide providers (AxiosInterceptors, BatchTransactions, AuthRedirect)
├── initConfig.ts # the InitAppType config passed to sdk-dapp's initApp
└── index.tsx # entry: awaits initApp(config), then renders <App />
- Bootstrap —
src/index.tsxcallsinitApp(config)(config insrc/initConfig.ts: environment, native auth, theme, custom providers) and renders the app only after it resolves. - SDK import layer — app code never imports
@multiversx/sdk-*deep paths directly; everything is funneled throughsrc/lib/(sdkCore,sdkDapp,sdkDappUI,sdkDappUtils). Add new SDK symbols to the matchinglibre-export and import fromlib. - Login — the Unlock page opens sdk-dapp's
UnlockPanelManager, which lists all available providers (including the customInMemoryProviderregistered ininitConfig.ts). - Transactions — widgets build
Transactionobjects with sdk-core, sign viagetAccountProvider().signTransactions(), then send and track them throughTransactionManager(toast notifications included). See the Dashboardwidgets/for canonical examples. - State — account, network, and transaction-session data come from sdk-dapp's store via React hooks (
useGetAccount,useGetNetworkConfig,useGetIsLoggedIn, ...).
- Unit tests — Jest (
@swc/jest, jsdom):pnpm test. Run a single file withpnpm test -- path/to/File.test.tsxor filter by name withpnpm test -- -t "name". - E2E tests — Playwright specs in
tests/covering connect-wallet flows (memory provider, MetaMask snap, web wallet) and transaction cancel flows:pnpm run-playwright-test. The runner auto-starts the devnet dev server.
MUST configure hosts file for passkey functionality:
# Add to /etc/hosts (macOS/Linux) or C:\Windows\System32\drivers\etc\hosts (Windows):
127.0.0.1 localhost.multiversx.com
# Edit hosts file:
# macOS/Linux:
sudo nano /etc/hosts
# Windows (run as Administrator):
notepad C:\Windows\System32\drivers\etc\hostsCRITICAL: WebAuthn requires valid HTTPS certificates to prevent TLS certificate errors:
# Install mkcert (if not already installed)
brew install mkcert
# Install the local CA in your system trust store
mkcert -install
# Generate certificates for localhost.multiversx.com
mkcert localhost.multiversx.com localhost 127.0.0.1 ::1This creates two files in certificates folder:
localhost.multiversx.com+3.pem(certificate)localhost.multiversx.com+3-key.pem(private key)
Update vite.config.ts to use port 443:
import fs from 'fs';
// import basicSsl from '@vitejs/plugin-basic-ssl'; // Remove this - use mkcert certificates instead
const https = {
key: fs.readFileSync('./certificates/localhost.multiversx.com-key.pem'),
cert: fs.readFileSync('./certificates/localhost.multiversx.com.pem')
};
export default defineConfig({
server: {
port: Number(process.env.PORT) || 443,
strictPort: true,
https,
host: true
}
});With proper certificates, you can use regular Chrome. For additional debugging, use Chrome with security flags:
# Close all Chrome instances first, then run:
open -a Google\ Chrome --args --ignore-certificate-errors --ignore-urlfetcher-cert-requests --disable-web-security --user-data-dir=/tmp/chrome_dev_passkeypnpm start-devnet --forceTest URLs:
- Template dApp: https://localhost.multiversx.com
If you encounter NotAllowedError: WebAuthn is not supported on sites with TLS certificate errors, ensure:
- ✅ mkcert is installed and CA is trusted (
mkcert -install) - ✅ Certificates are generated for localhost.multiversx.com
- ✅ vite.config.ts uses the certificate files
- ✅ Browser shows a valid HTTPS lock icon
- ✅ No mixed content warnings in DevTools
This template comes with three built-in themes:
- TealLab (mvx:dark-theme)
- VibeMode (mvx:vibe-theme)
- BrightLight (mvx:light-theme)
But you can customize the appearance of your project by defining your own theme using CSS variables. Follow these steps to set it up.
This is the main place where you customize your theme. You add your project specific colors and update CSS variables that style your elements, according to your theme.
Define your color palette in the :root section:
:root {
--mvx-custom-primary-color: #your-color;
--mvx-custom-secondary-color: #your-color;
}Next, configure your theme-specific variables:
:root[data-mvx-theme='mvx:your-theme'],
[data-mvx-theme='mvx:your-theme'] {
--mvx-bg-color-primary: var(--mvx-custom-primary-color);
--mvx-bg-color-secondary: var(--mvx-custom-secondary-color);
}This hook registers and manages all available themes in your project.
It maintains a list of all theme options, tracks the currently active theme and
provides a handleThemeSwitch function that updates the data-mvx-theme attribute.
const allThemeOptions: ThemeOptionType[] =
[
{ identifier: 'mvx:dark-theme', label: 'TealLab' },
{ identifier: 'mvx:vibe-theme', label: 'VibeMode' },
{ identifier: 'mvx:light-theme', label: 'BrightLight' },
{ identifier: 'mvx:your-theme', label: 'Your Theme Label' }
];This allows you to see the theme options available in the project. They are listed in a tooltip dropdown in header with visual color previews for each theme.
const themeDotColors: Record<string, string[]> =
{
'mvx:dark-theme': ['#23F7DD', '#262626', '#B6B3AF', '#FFFFFF'],
'mvx:vibe-theme': ['#471150', '#5A2A62', '#D200FA', '#FFFFFF'],
'mvx:light-theme': ['#000000', '#A5A5A5', '#E2DEDC', '#F3EFED'],
'mvx:your-theme': ['#color1', '#color2', '#color3', '#color4']
};Add your icon in assets folder and import it as:
import { ReactComponent as YourThemeIcon } from 'assets/icons/your-theme-icon.svg';Add a background image for your theme in public folder and reference it in tailwind.css:
@theme {
--background-image-your-theme: url('/your-theme-bg.png');
}And then update themeExtraProperties object with your values. These properties are used for
customizing your hero section from home page. It adds background image and icon + title for the
theme switch section in hero.
const themeExtraProperties: Record<
string,
Omit<HomeThemeOptionType, keyof ThemeOptionType>
> = {
// existing themes
'mvx:your-theme': {
icon: YourThemeIcon,
title: 'Your Title',
backgroundClass: 'bg-your-theme'
}
};dAppConfig: {
theme: 'mvx:your-theme',
}Now the project will start with your configured theme.
All variables will have the colors you have set. If you don't set custom colors, the default ones will apply.
You can see the current theme in data-mvx-theme attribute in browser inspector.
The same template dApp is implemented across several frameworks. If another stack suits your project better, start from one of these instead:
| Template | Stack | Repository |
|---|---|---|
| React (TypeScript) ← this repo | React 18 · TypeScript · Vite | mx-template-dapp |
| React (JavaScript) | React 19 · JSX · Vite | mx-template-dapp-reactjs |
| Next.js | Next.js 16 (App Router) · TypeScript | mx-template-dapp-nextjs |
| SolidJS | SolidJS · TypeScript · Vite | mx-template-dapp-solidjs |
| Vue | Vue 3 · TypeScript · Vite | mx-template-dapp-vue |
| Angular | Angular 20 · TypeScript | mx-template-dapp-angular |
| React Native | React Native | mx-template-dapp-react-native |
- @multiversx/sdk-dapp on GitHub · on npm
- MultiversX developer docs
- Migration guide (sdk-dapp 4.x → 5.x)
See the open issues for a list of proposed features (and known issues).
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.
One can contribute by creating pull requests, or by opening issues for discovered bugs or desired features.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request