npx skills add ...
npx skills add kali-decoder/midnight-skills --skill react-wallet-connector
Generate a React + Vite TypeScript app that connects to a Midnight wallet via the DApp Connector API (@midnight-ntwrk/dapp-connector-api). Use when building a React frontend, bootstrapping a wallet connector, wiring connect/disconnect UI, reading window.midnight wallets, getting unshielded addresses, or asking about the DApp Connector API connection flow. Produces a minimal runnable template — styling is intentionally omitted so the user can add Tailwind, CSS modules, etc.
npx skills add kali-decoder/midnight-skills --skill react-wallet-connector
This skill generates a complete, runnable React application that connects to a Midnight wallet extension using the DApp Connector API. It covers every file needed: Vite + React + TypeScript scaffold, wallet selection, connection logic, and a WalletCard UI component.
Primary references:
docs.midnight.network — React wallet connector guide (DApp Connector API)llms.txt — full Midnight documentation index@midnight-ntwrk/dapp-connector-api@4.0.1 — latest stable connector package (Feb 2026)Key architecture notes:
InitialAPI instances on window.midnight, each keyed by a UUID — never use hardcoded keys like window.midnight.mnLaceObject.values(window.midnight); when multiple wallets exist, let the user choose@midnight-ntwrk/dapp-connector-api as a side effect to augment global window.midnight typesgetUnshieldedAddress()'undeployed' (local), 'preview', 'preprod', 'mainnet' — must match the wallet's configured networkRelationship to other skills:
1am-wallet/example-hello-world/ or midnight-js/package.jsonUse the Vite react-ts template output and ensure @midnight-ntwrk/dapp-connector-api is listed:
Pin
@midnight-ntwrk/dapp-connector-apito4.0.1unless the user specifies otherwise. Checknpm view @midnight-ntwrk/dapp-connector-api versionif install fails withETARGET.
vite.config.tstsconfig.jsontsconfig.app.jsontsconfig.node.jsonsrc/vite-env.d.tsThe @midnight-ntwrk/dapp-connector-api side-effect import in App.tsx augments window.midnight — no manual declaration needed.
src/types.tssrc/selectWallet.tsWallets inject under window.midnight with UUID keys. Always enumerate — never hardcode a wallet name.
When more than one wallet is available, render a picker using listWallets() and let the user choose. Display each wallet's name and icon safely to prevent XSS.
src/WalletCard.tsxPresentation layer — connection status, address display, connect/disconnect buttons. No CSS included; add styling as needed.
src/App.tsxConnection logic using the DApp Connector API.
selectWallet() reads window.midnight entries; throws if none found (caught by try/catch)wallet.connect(networkId) prompts the user to authorizegetUnshieldedAddress() returns the public unshielded addressgetConnectionStatus() resolves to { status: 'connected' | 'disconnected' }getConfiguration() returns service URIs (indexer, node, etc.) for downstream SDK wiringsrc/main.tsxindex.htmlOpen http://localhost:5173. Click Connect Wallet — the extension prompts for authorization. After approval, the app shows connection status and the unshielded address.
When listWallets() returns more than one entry, replace selectWallet() auto-pick with a user-facing selector:
Render wallet name and icon as text only unless you sanitize HTML. Never use dangerouslySetInnerHTML for wallet-provided icon URLs.
| Error | Cause | Fix |
|---|---|---|
window.midnight is undefined | No extension installed or page loaded before injection | Install a Midnight wallet; refresh after install |
No Midnight wallet found | Extension disabled or wrong detection pattern | Use Object.values(window.midnight) — not window.midnight.mnLace or other hardcoded keys |
| Connection rejected / fails | Network mismatch | Ensure connect('preprod') matches the wallet's active network |
| Connection hangs | Wallet locked or not synced | Unlock wallet; wait for sync |
ETARGET on install | Stale package version | Run npm view @midnight-ntwrk/dapp-connector-api version and pin the latest |
Type errors on window.midnight | Missing side-effect import | Add import '@midnight-ntwrk/dapp-connector-api' in App.tsx |
After a working connector, extend the app:
| Feature | Skill / API |
|---|---|
| Deploy & call Compact contracts | 1am-wallet/ |
| Token transfers | token-transfers/ |
| Query balances & tx history | indexer/ |
| Sign arbitrary messages | connectedApi signing methods (see DApp Connector API docs) |
| Multi-network selector | Network dropdown calling wallet.connect(selectedNetwork) |
| Full SDK provider wiring | midnight-js/ |
When generating this template for a user:
react-ts or write all files from this skill@midnight-ntwrk/dapp-connector-api@4.0.1types.ts, selectWallet.ts, WalletCard.tsx, App.tsx, main.tsx@midnight-ntwrk/dapp-connector-api in App.tsxObject.values(window.midnight) for wallet detection — never hardcode wallet keys'preprod' unless user specifies local ('undeployed') or other network{
"name": "my-wallet-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@midnight-ntwrk/dapp-connector-api": "4.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.0",
"typescript": "~5.7.0",
"vite": "^6.0.0"
}
}import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
});{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}/// <reference types="vite/client" />export interface WalletCardProps {
isConnected: boolean;
walletAddress: string | null;
onConnect: () => void;
onDisconnect: () => void;
}import type { InitialAPI } from '@midnight-ntwrk/dapp-connector-api';
export const listWallets = (): InitialAPI[] => {
const injected = window.midnight;
return injected ? Object.values(injected) : [];
};
export const selectWallet = (): InitialAPI => {
const wallets = listWallets();
if (wallets.length === 0) {
throw new Error(
'No Midnight wallet found. Please install a Midnight wallet extension.',
);
}
return wallets[0];
};import React from 'react';
import type { WalletCardProps } from './types';
const WalletCard: React.FC<WalletCardProps> = ({
isConnected,
walletAddress,
onConnect,
onDisconnect,
}) => {
return (
<div>
<div>
<h2>Connection Status</h2>
<div>{isConnected ? 'Connected' : 'Disconnected'}</div>
</div>
<div>
{isConnected && walletAddress ? (
<>
<p>Wallet Address:</p>
<p title={walletAddress}>{walletAddress}</p>
</>
) : (
<p>Please connect your wallet to proceed.</p>
)}
</div>
<div>
{isConnected ? (
<button onClick={onDisconnect}>Disconnect Wallet</button>
) : (
<button onClick={onConnect}>Connect Wallet</button>
)}
</div>
</div>
);
};
export default WalletCard;import React, { useState } from 'react';
import WalletCard from './WalletCard';
import '@midnight-ntwrk/dapp-connector-api';
import { selectWallet } from './selectWallet';
const App: React.FC = () => {
const [isConnected, setIsConnected] = useState<boolean>(false);
const [walletAddress, setWalletAddress] = useState<string | null>(null);
const handleConnect = async () => {
console.log('Connect button clicked');
let connected = false;
let address: string | null = null;
try {
const wallet = selectWallet();
// 'undeployed' for local dev; 'preprod' | 'preview' | 'mainnet' for live networks
const connectedApi = await wallet.connect('preprod');
const { unshieldedAddress } = await connectedApi.getUnshieldedAddress();
address = unshieldedAddress;
const serviceUriConfig = await connectedApi.getConfiguration();
console.log('Service URI Config:', serviceUriConfig);
const connectionStatus = await connectedApi.getConnectionStatus();
if (connectionStatus.status === 'connected') {
connected = true;
console.log('Connected to the wallet:', address);
}
} catch (error) {
console.log('An error occurred:', error);
}
setIsConnected(connected);
setWalletAddress(address);
};
const handleDisconnect = () => {
setWalletAddress(null);
setIsConnected(false);
};
return (
<div>
<header>
<h1>Midnight Wallet Connector</h1>
</header>
<main>
<WalletCard
isConnected={isConnected}
walletAddress={walletAddress}
onConnect={handleConnect}
onDisconnect={handleDisconnect}
/>
</main>
</div>
);
};
export default App;import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Midnight Wallet Connector</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>npm install
npm run dev// src/WalletPicker.tsx
import type { InitialAPI } from '@midnight-ntwrk/dapp-connector-api';
import { listWallets } from './selectWallet';
type Props = {
onSelect: (wallet: InitialAPI) => void;
};
export function WalletPicker({ onSelect }: Props) {
const wallets = listWallets();
if (wallets.length === 0) {
return <p>No Midnight wallet found. Install a wallet extension and refresh.</p>;
}
return (
<ul>
{wallets.map((wallet) => (
<li key={wallet.name}>
<button type="button" onClick={() => onSelect(wallet)}>
{wallet.name}
</button>
</li>
))}
</ul>
);
}