dynamic-midnight-wallet
Skill18 starsChanged 18 days agoMITView on GitHub
Scaffold a React + Vite TypeScript app with Dynamic.xyz Midnight wallets (@dynamic-labs/midnight). Covers external wallets (1AM extension via MidnightWalletConnectors) and embedded MPC wallets (social/email via DynamicWaasMidnightConnectors): three surfaces (unshielded, shielded, DUST), balances, sendBalance routing, registerDust, and embedded contract settlement via getWalletProvider(). Use when the user asks about Dynamic + Midnight, DynamicWidget Midnight, shielded/unshielded/DUST addresses in Dynamic, or replacing hand-rolled window.midnight with Dynamic.
- Reads credentials
- Installs packages
In this repository36 other files it gives its agents.
- AGENTS.mdAGENTS.md
- 1am-walletSkill
- android-example-votingSkill
- ankr-midnightSkill
- compactSkill
- example-counterSkill
- example-hello-worldSkill
- example-leaderboard-dappSkill
- example-locker-dappSkill
- example-passport-dappSkill
- example-payment-dappSkill
- example-private-party-dappSkill
- example-private-reserve-auction-dappSkill
- example-zk-loan-applicationSkill
- gsap-coreSkill
- gsap-utilsSkill
- indexerSkill
- midnight-consensusSkill
- midnight-cryptographySkill
- midnight-environment-setupSkill
- midnight-jsSkill
- midnight-onchain-logicSkill
- midnight-p2p-networkingSkill
- midnight-passportSkill
- midnight-rpcSkill
- midnightskillSkill
- midnight-storageSkill
- midnight-transactionsSkill
- multinetworkSkill
- nftSkill
- react-wallet-connectorSkill
- midnight-securitySkill
- testingSkill
- token-transfersSkill
- why-midnightSkill
- zk-vs-fhe-vs-mpcSkill
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 2 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 40
Always guard with isMidnightWallet(wallet) before Midnight methods
- Very specific: line 54
Add DynamicContextProvider + MidnightWalletConnectors (external) or DynamicWaasMidnightConnectors (embedded)
- Distinctive: line 55
Read three address surfaces.
- Very specific: line 56
Read balances; send with sendBalance.
- Very specific: line 158
Use the standard Vite react-ts template configs (same as react-wallet-connector/ §§6–8).
- Distinctive: line 363
No CSS — add Tailwind or your preferred styling after scaffolding.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Dynamic Midnight Wallet
Generate a React + Vite + TypeScript app that connects Midnight wallets through Dynamic.xyz (@dynamic-labs/midnight). Prefer this over hand-rolled window.midnight when the product already uses Dynamic auth/wallet UI.
Primary references:
- Using Midnight external wallets — 1AM extension
- Using Midnight embedded wallets — social/email MPC
- Deploying and calling Midnight contracts — embedded settlement provider
- Dynamic docs index
- Pin:
@dynamic-labs/midnight@5.12.2+@dynamic-labs/sdk-react-core@5.12.2(+@dynamic-labs/sdk-api-core@0.104.0forWalletAddressType)
Key architecture notes:
A Midnight wallet is not a single address — three surfaces:
Surface Role Dynamic API Unshielded Public address/state wallet.addressShielded Private token pool wallet.additionalAddresses(MidnightShielded/midnight_shielded)DUST Fee-generation state wallet.additionalAddresses(MidnightDust/midnight_dust)NIGHTexists as both shielded and unshielded — different pools, not duplicate balances- Dynamic — connection lifecycle, connectors, address/balance accessors, send routing
- 1AM (external) — key custody, derivation, proving, signing, submission via
@midnight-ntwrk/dapp-connector-api - Your app — deposit UX: surface both unshielded and shielded; DUST is generated, not deposited to
sendBalanceroutes by recipient prefix:mn_shield…→ shielded, otherwise unshielded. No cross-pool transfers- Always guard with
isMidnightWallet(wallet)before Midnight methods - Contract deploy/call with Dynamic's settlement provider is documented for embedded wallets (
getWalletProvider()). External wallets use their own provider surface (see1am-wallet/)
Relationship to other skills:
- Raw DApp Connector without Dynamic →
react-wallet-connector/ - Contract deploy + circuit calls via 1AM directly →
1am-wallet/ - Token pools / NIGHT units →
token-transfers/
Workflow
- Enable Midnight (and Private Key Exports if embedded) in Dynamic console
- Scaffold Vite React-TS; install Dynamic packages at matching versions
- Add
DynamicContextProvider+MidnightWalletConnectors(external) orDynamicWaasMidnightConnectors(embedded) - Read three address surfaces; show both deposit addresses in UI
- Read balances; send with
sendBalance; for embedded addregisterDust/ optional contract provider
1) Project structure
my-dynamic-midnight-app/
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.node.json
├── vite.config.ts
├── .env.example
└── src/
├── main.tsx
├── App.tsx
├── MidnightWalletPanel.tsx
├── hooks/
│ └── useMidnightWallet.ts
└── vite-env.d.ts2) Prerequisites
- Node.js 18+
- Dynamic developer console account
- Environment ID from the dashboard
- Enable Midnight under Chains & Networks
- External (1AM): 1AM browser extension installed
- Embedded (social/email): enable Private Key Exports under Embedded Wallets → Security (403 on all ops if off)
3) Scaffold and install
npm create vite@latest my-dynamic-midnight-app -- --template react-ts
cd my-dynamic-midnight-app
npm install @dynamic-labs/sdk-react-core@5.12.2 @dynamic-labs/midnight@5.12.2 @dynamic-labs/sdk-api-core@0.104.0Create .env:
VITE_DYNAMIC_ENVIRONMENT_ID=your-environment-id-herePin
@dynamic-labs/midnightand@dynamic-labs/sdk-react-coreto the same major.minor.patch.sdk-api-corefollows Midnight's dependency (currently0.104.0for5.12.2). Checknpm view @dynamic-labs/midnight versionif install fails.
4) package.json
{
"name": "my-dynamic-midnight-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@dynamic-labs/midnight": "5.12.2",
"@dynamic-labs/sdk-api-core": "0.104.0",
"@dynamic-labs/sdk-react-core": "5.12.2",
"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"
}
}5) vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
});6) TypeScript configs
Use the standard Vite react-ts template configs (same as react-wallet-connector/ §§6–8).
7) src/vite-env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_DYNAMIC_ENVIRONMENT_ID: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}8) src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);9) Enable Midnight + connectors
External wallets (1AM extension) — default
import { DynamicContextProvider, DynamicWidget } from '@dynamic-labs/sdk-react-core';
import { MidnightWalletConnectors } from '@dynamic-labs/midnight';
import MidnightWalletPanel from './MidnightWalletPanel';
const environmentId = import.meta.env.VITE_DYNAMIC_ENVIRONMENT_ID;
if (!environmentId) {
throw new Error('Set VITE_DYNAMIC_ENVIRONMENT_ID in .env');
}
export default function App() {
return (
<DynamicContextProvider
settings={{
environmentId,
walletConnectors: [MidnightWalletConnectors],
}}
>
<header>
<h1>Midnight + Dynamic</h1>
<DynamicWidget />
</header>
<main>
<MidnightWalletPanel />
</main>
</DynamicContextProvider>
);
}Embedded wallets (social / email MPC)
import { DynamicWaasMidnightConnectors } from '@dynamic-labs/midnight';
<DynamicContextProvider
settings={{
environmentId,
walletConnectors: [DynamicWaasMidnightConnectors],
}}
>Dashboard: Chains & Networks → Midnight on, and Embedded Wallets → Security → Private Key Exports on.
10) src/hooks/useMidnightWallet.ts
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { isMidnightWallet } from '@dynamic-labs/midnight';
import { WalletAddressType } from '@dynamic-labs/sdk-api-core';
export function useMidnightWallet() {
const { primaryWallet } = useDynamicContext();
if (!primaryWallet || !isMidnightWallet(primaryWallet)) {
return { wallet: null, isConnected: false as const };
}
const shieldedAddress = primaryWallet.additionalAddresses?.find(
(a) => a.type === WalletAddressType.MidnightShielded,
)?.address;
const dustAddress = primaryWallet.additionalAddresses?.find(
(a) => a.type === WalletAddressType.MidnightDust,
)?.address;
return {
wallet: primaryWallet,
isConnected: true as const,
unshieldedAddress: primaryWallet.address,
shieldedAddress,
dustAddress,
};
}11) Checking wallet type
import { isMidnightWallet } from '@dynamic-labs/midnight';
if (!isMidnightWallet(wallet)) {
throw new Error('This wallet is not a Midnight wallet');
}Narrow before getFormattedBalances(), sendBalance(), or connector methods.
12) Address surfaces
// Unshielded — main address
const unshieldedAddress = primaryWallet.address;
// Shielded + DUST — additionalAddresses
const shieldedAddress = primaryWallet.additionalAddresses?.find(
(a) => a.type === WalletAddressType.MidnightShielded,
)?.address;
const dustAddress = primaryWallet.additionalAddresses?.find(
(a) => a.type === WalletAddressType.MidnightDust,
)?.address;Deposit UX: show unshielded and shielded. DUST is generated, not deposited to.
Cryptographic keys for circuits (external connector):
const { shieldedCoinPublicKey, shieldedEncryptionPublicKey } =
await primaryWallet.connector.getShieldedAddresses();13) Reading balances
External wallets
const { unshieldedBalance, shieldedTokenCount, dustBalance } =
await primaryWallet.getFormattedBalances();
// unshieldedBalance -> e.g. "3.0" (undefined if none)
// shieldedTokenCount -> distinct shielded token types with positive balance
// dustBalance -> { balance: string, cap: string } | undefinedEmbedded wallets (extra fields)
const { unshieldedBalance, shieldedTokenCount, dustBalance, dustSyncing } =
await primaryWallet.getFormattedBalances();
if (dustSyncing) {
setTimeout(() => void refreshBalances(), 2500);
}Individual getters:
await primaryWallet.getShieldedBalance(); // shielded NIGHT
await primaryWallet.getUnshieldedBalance(); // unshielded NIGHT
await primaryWallet.getDustBalance(); // { balance, cap }Raw per-token amounts:
const { shielded, unshielded, dust } = await primaryWallet.getBalances();14) src/MidnightWalletPanel.tsx
No CSS — add Tailwind or your preferred styling after scaffolding.
import { useCallback, useEffect, useState } from 'react';
import { useMidnightWallet } from './hooks/useMidnightWallet';
type Formatted = {
unshieldedBalance?: string;
shieldedTokenCount?: number;
dustBalance?: { balance: string; cap: string };
dustSyncing?: boolean;
};
export default function MidnightWalletPanel() {
const { wallet, isConnected, unshieldedAddress, shieldedAddress, dustAddress } =
useMidnightWallet();
const [balances, setBalances] = useState<Formatted | null>(null);
const [shieldedNight, setShieldedNight] = useState<string | undefined>();
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const refreshBalances = useCallback(async () => {
if (!wallet) return;
setLoading(true);
setError(null);
try {
const formatted = await wallet.getFormattedBalances();
setBalances(formatted);
setShieldedNight(await wallet.getShieldedBalance());
if (formatted.dustSyncing) {
setTimeout(() => void refreshBalances(), 2500);
}
} catch (e) {
setError(String(e));
} finally {
setLoading(false);
}
}, [wallet]);
useEffect(() => {
void refreshBalances();
}, [refreshBalances]);
if (!isConnected || !wallet) {
return <p>Connect your Midnight wallet with the widget above.</p>;
}
return (
<div>
<h2>Wallet surfaces</h2>
<dl>
<dt>Unshielded (public)</dt>
<dd>{unshieldedAddress}</dd>
<dt>Shielded (private)</dt>
<dd>{shieldedAddress ?? '—'}</dd>
<dt>DUST (fees)</dt>
<dd>{dustAddress ?? '—'}</dd>
</dl>
<h2>Balances</h2>
{loading ? <p>Loading…</p> : null}
{error ? <p role="alert">{error}</p> : null}
{balances ? (
<ul>
<li>Unshielded NIGHT: {balances.unshieldedBalance ?? '0'}</li>
<li>Shielded NIGHT: {shieldedNight ?? '0'}</li>
<li>Shielded token types: {balances.shieldedTokenCount ?? 0}</li>
<li>
DUST:{' '}
{balances.dustBalance
? `${balances.dustBalance.balance} / cap ${balances.dustBalance.cap}`
: '—'}
</li>
</ul>
) : null}
<button type="button" onClick={() => void refreshBalances()}>
Refresh balances
</button>
<h2>Deposit</h2>
<p>
Send to the <strong>unshielded</strong> address for public NIGHT, or the{' '}
<strong>shielded</strong> address for private NIGHT. DUST is generated from
registered unshielded NIGHT — do not deposit to the DUST address.
</p>
</div>
);
}15) Sending
sendBalance routes by recipient prefix (mn_shield… = shielded, otherwise unshielded). Same pool only.
External (human-readable NIGHT amounts in docs)
await primaryWallet.sendBalance({
toAddress: 'mn_shield...',
amount: '1.5',
});Embedded (atomic units unless token.decimals is set)
await primaryWallet.sendBalance({
toAddress: 'mn_addr...',
amount: '100000000',
});
await primaryWallet.sendBalance({
toAddress: 'mn_shield...',
amount: '50',
token: { address: 'tokenTypeHex', decimals: 6 },
});Embedded step-by-step
const { serializedTransaction } = await primaryWallet.createTransferTransaction({
transfers: [
{
type: 'unshielded', // or 'shielded'
recipientAddress: 'mn_addr...',
amount: '100000000',
},
],
});
const finalizedTransaction =
await primaryWallet.signTransaction(serializedTransaction);
const { txHash } = await primaryWallet.submitTransaction(finalizedTransaction);Stuck inputs after aborted builds:
await primaryWallet.revertTransaction(serializedTransaction);
await primaryWallet.revertAllPending();
// last resort: await primaryWallet.resetWalletCache();16) Embedded: register DUST
const result = await primaryWallet.registerDust();
// 'registered' | 'already_registered' | 'already_has_dust' | 'no_utxos'Fund unshielded NIGHT first. Registration does not mint DUST — it accrues over time. Poll getFormattedBalances() until dustBalance appears.
Embedded signing:
const signature = await primaryWallet.signMessage('Hello Midnight');17) Embedded: contracts via settlement provider
Contract half (circuits, witnesses, ZK proof) runs in your app via midnight-js. Settlement (balance, DUST, sign, submit) stays in the Dynamic iframe:
const walletProvider = await primaryWallet.getWalletProvider();Wire into midnight-js providers (walletProvider + midnightProvider from Dynamic; you supply private state, indexer, zk config, proof server). Full pattern: Dynamic midnight-contracts + skills midnight-js/, 1am-wallet/.
For external 1AM, use the extension's own provider surface (1am-wallet/) — Dynamic's settlement provider docs are embedded-only.
18) index.html / .env.example / run
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Midnight + Dynamic</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html># .env.example
VITE_DYNAMIC_ENVIRONMENT_ID=cp .env.example .env
# paste Environment ID
npm install
npm run devOpen http://localhost:5173 → DynamicWidget → connect → panel shows three surfaces and balances.
19) Ownership boundaries
| Layer | Owns |
| Dynamic | Connection lifecycle, connectors, MidnightWallet, address/balance accessors, send routing |
| 1AM (external) | Key custody, derivation, ZK proving, signing, submission |
| Your DApp | Deposit/receive UX (unshielded and shielded), pool display, DUST messaging |
20) Troubleshooting
| Symptom | Cause | Fix |
| Midnight missing in widget | Chain disabled | Enable Midnight → Chains & Networks |
| 403 on all methods (embedded) | Private Key Exports off | Embedded Wallets → Security → enable |
isMidnightWallet false | Wrong connector | Use MidnightWalletConnectors or DynamicWaasMidnightConnectors |
| Shielded / DUST missing | Still syncing | Retry; poll dustSyncing (embedded) |
| Send fails cross-pool | mn_addr ↔ mn_shield | Same pool only |
| Version mismatch | Mixed @dynamic-labs/* | Align midnight + sdk-react-core versions |
| Empty DUST | Not registered / low NIGHT | registerDust() after funding unshielded NIGHT (embedded) |
| Deploy works, calls fail | Proof server / ZK artifacts | See midnight-contracts pitfalls; first callTx needs proof server |
21) Agent checklist
- Scaffold Vite
react-tsor write files from this skill - Install Dynamic packages (pinned versions above)
- Set
VITE_DYNAMIC_ENVIRONMENT_ID - Enable Midnight in console; Private Key Exports if embedded
MidnightWalletConnectors(external) orDynamicWaasMidnightConnectors(embedded)- Include
DynamicWidget - Surface unshielded, shielded, and DUST
- Guard with
isMidnightWallet - Omit CSS unless requested
- Point to
1am-wallet//midnight-js/for contract deploy beyond connection
22) Related skills
| Need | Skill |
| DApp Connector without Dynamic | react-wallet-connector/ |
| Contract deploy + circuits (1AM) | 1am-wallet/ |
| Token units & pools | token-transfers/ |
| SDK providers | midnight-js/ |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

