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 used to pay for shielded operations.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.
Midnight wallets are connected today through an injected browser extension (the 1am wallet). Embedded (social-auth) Midnight wallets are not yet available. Until then, the 1am extension is the supported path and a user who signs in with social auth will not have a derived Midnight wallet.

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 richer shielded handles (coin / encryption public keys), read them from the connector:
React
const connector = primaryWallet.connector;

const { shieldedAddress, shieldedCoinPublicKey, shieldedEncryptionPublicKey } =
  await connector.getShieldedAddresses();
const { unshieldedAddress } = await connector.getUnshieldedAddress();
const { dustAddress } = await connector.getDustAddress();

Reading Balances

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

// shieldedBalance   -> shielded NIGHT, e.g. "12.5"
// unshieldedBalance -> unshielded NIGHT, e.g. "3.0"
// dustBalance       -> { balance, cap } for DUST generation, undefined if empty
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