Skip to main content
Midnight is a privacy-focused chain. Unlike most chains, a functional Midnight wallet is not a single address — it exposes three distinct surfaces:
SurfaceWhat it isWhere Dynamic exposes it
UnshieldedPublic address/state. The wallet’s main address.wallet.address
ShieldedPrivate (shielded) address/state. A separate token pool.wallet.additionalAddresses (midnight_shielded)
DUSTFee-generation state. DUST pays for Midnight transactions.wallet.additionalAddresses (midnight_dust)
NIGHT exists as both a shielded and an unshielded asset — they are different token types in different pools, not the same balance shown twice.
This page covers Midnight wallets connected through an injected browser extension (the 1am wallet). For embedded Midnight wallets created via social or email login, see Using Midnight embedded wallets.

Enabling Midnight

1

Enable Midnight in the dashboard

Enable Midnight under Chains & Networks.
2

Install the connector

npm i @dynamic-labs/midnight
3

Add the connector to DynamicContextProvider

React
import { DynamicContextProvider, DynamicWidget } from '@dynamic-labs/sdk-react-core';
import { MidnightWalletConnectors } from '@dynamic-labs/midnight';

const App = () => (
  <DynamicContextProvider
    settings={{
      environmentId: 'YOUR_ENVIRONMENT_ID',
      walletConnectors: [MidnightWalletConnectors],
    }}
  >
    <DynamicWidget />
  </DynamicContextProvider>
);

export default App;

Checking if a Wallet is a Midnight Wallet

React
import { isMidnightWallet } from '@dynamic-labs/midnight';

if (!isMidnightWallet(wallet)) {
  throw new Error('This wallet is not a Midnight wallet');
}

Retrieving the Address Surfaces

The unshielded address is the wallet’s main address. The shielded and DUST addresses are exposed through additionalAddresses, keyed by WalletAddressType.
React
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { isMidnightWallet } from '@dynamic-labs/midnight';
import { WalletAddressType } from '@dynamic-labs/sdk-api-core';

const { primaryWallet } = useDynamicContext();

if (!primaryWallet || !isMidnightWallet(primaryWallet)) {
  throw new Error('This wallet is not a Midnight wallet');
}

// Unshielded — the main address
const unshieldedAddress = primaryWallet.address;

// Shielded and DUST — from additionalAddresses
const shieldedAddress = primaryWallet.additionalAddresses?.find(
  (a) => a.type === WalletAddressType.MidnightShielded,
)?.address;

const dustAddress = primaryWallet.additionalAddresses?.find(
  (a) => a.type === WalletAddressType.MidnightDust,
)?.address;
Deposits go to either the unshielded or shielded address depending on which pool the sender is paying into — surface both in your deposit UI. DUST is generated, not deposited to.
If you need the cryptographic public keys for the shielded surface (coin and encryption keys required by some dApps), read them from the connector:
React
const { shieldedCoinPublicKey, shieldedEncryptionPublicKey } =
  await primaryWallet.connector.getShieldedAddresses();

Reading Balances

getFormattedBalances() returns display-ready strings for all three surfaces in one call:
React
const { unshieldedBalance, shieldedTokenCount, dustBalance } =
  await primaryWallet.getFormattedBalances();

// unshieldedBalance  -> unshielded NIGHT as a display string, e.g. "3.0" (undefined if none)
// shieldedTokenCount -> number of distinct token types with a positive shielded balance
// dustBalance        -> { balance: string, cap: string } (undefined if no DUST)
Individual getters are also available:
React
await primaryWallet.getShieldedBalance();   // shielded NIGHT
await primaryWallet.getUnshieldedBalance(); // unshielded NIGHT
await primaryWallet.getDustBalance();       // { balance, cap }
For raw, per-token amounts (a pool can hold more than just NIGHT), use getBalances(), which returns { shielded, unshielded, dust } keyed by token type:
React
const { shielded, unshielded, dust } = await primaryWallet.getBalances();

Sending

sendBalance routes to the correct pool automatically based on the recipient address prefix (mn_shield... = shielded, otherwise unshielded). Cross-pool transfers are not supported — sender and recipient must be in the same pool.
React
await primaryWallet.sendBalance({
  toAddress: 'mn_shield...', // shielded recipient
  amount: '1.5',             // human-readable NIGHT
});

Ownership Boundaries

When integrating, it helps to know who owns what:
  • Dynamic — connection lifecycle, the MidnightWalletConnectors, the MidnightWallet object, address/balance accessors, and send routing.
  • 1am wallet (extension) — key custody, address derivation, proving, message signing, and transaction submission. Dynamic talks to it through the official @midnight-ntwrk/dapp-connector-api.
  • Your app — the deposit/receive UX (surface unshielded and shielded), which pool to display, and how you present DUST generation.

Resources

Last modified on June 29, 2026