Local simulator
Use @onmax/nimiq-mini-app-kit/dev when local code needs Nimiq Pay-like provider behavior without the real host app.
Do not ship simulator providers in production bundles. Production apps should read providers published by Nimiq Pay.
Choose the simulator mode
Use installSimulatedNimiqProvider() or installSimulatedEthereumProvider() when a browser app expects window.nimiq or window.ethereum.
Use createSimulatedNimiqProvider() or createSimulatedEthereumProvider() in unit tests where you can pass a provider directly.
Use createSimulatedHostRuntime() when you are building a local host shell with approval UI and wallet state.
Use blocking providers when you want quick browser confirm() prompts without a custom host shell.
Install providers in development only
Install simulated providers behind a development guard before app code reads them.
import {
installSimulatedEthereumProvider,
installSimulatedNimiqProvider,
} from '@onmax/nimiq-mini-app-kit/dev'
const NIMIQ_PRIVATE_KEY = '4f4e9c6d93fab8a1716dd2136bfdcf8bc6bfcb7e5fcb868f1f8f1eb8bbf4d2f0'
const ETHEREUM_ACCOUNT = '0xf5d7f7c63cb8F6d9Db7D2674bfaB3F5D2aED2fcb'
export async function installLocalMiniAppProviders() {
if (!import.meta.env.DEV)
return
await installSimulatedNimiqProvider({
privateKeyHex: NIMIQ_PRIVATE_KEY,
blockNumber: 1_000_000,
consensusEstablished: true,
})
await installSimulatedEthereumProvider({
account: ETHEREUM_ACCOUNT,
chainId: '0x89',
connected: true,
tokenContracts: {
'0xc2132d05d31c914a87c6611c10748aeb04b58e8f': {
decimals: 6,
symbol: 'USDT',
balancesByAddress: {
[ETHEREUM_ACCOUNT.toLowerCase()]: '1000000',
},
},
},
})
}
Expected behavior:
window.nimiqis available after the first installer resolves.window.ethereumis available after the second installer resolves.- The Ethereum provider announces itself as
Nimiq Pay Simulatorthrough EIP-6963.
Test delayed injection and missing providers
Use injectionDelayMs and missingProvider to prove unavailable-provider UI.
import { expect, it } from 'vitest'
import {
installSimulatedNimiqProvider,
waitForInjectedNimiqProvider,
} from '@onmax/nimiq-mini-app-kit/dev'
it('waits for delayed window.nimiq injection', async () => {
const source = { window: {} as Window }
const installed = installSimulatedNimiqProvider({
source,
injectionDelayMs: 20,
})
expect(source.window.nimiq).toBeUndefined()
await installed
await expect(waitForInjectedNimiqProvider({
source: () => source.window.nimiq,
timeoutMs: 100,
intervalMs: 5,
})).resolves.toBe(source.window.nimiq)
})
Expected behavior:
- Before the installer resolves, no provider exists.
- After the delay,
waitForInjectedNimiqProvider()resolves to the injected provider. - With
missingProvider: true, the installer removeswindow.nimiq.
Drive error states from a controller
Use a controller when a test needs to mutate provider behavior between assertions.
import { expect, it } from 'vitest'
import { createSimulatedNimiqProviderController } from '@onmax/nimiq-mini-app-kit/dev'
it('shows a rejected signature state', async () => {
const controller = createSimulatedNimiqProviderController()
controller.setConfig({ signError: 'Rejected in host shell' })
await expect(controller.provider.sign('hello')).resolves.toEqual({
error: {
type: 'SIGN_ERROR',
message: 'Rejected in host shell',
},
})
})
Simulate Ethereum reads and writes
Use createSimulatedEthereumProvider() for deterministic EIP-1193 behavior in unit tests.
import { expect, it } from 'vitest'
import { createSimulatedEthereumProvider } from '@onmax/nimiq-mini-app-kit/dev'
it('updates native balances after a simulated transfer', async () => {
const account = '0xf5d7f7c63cb8F6d9Db7D2674bfaB3F5D2aED2fcb'
const recipient = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
const provider = createSimulatedEthereumProvider({
account,
connected: true,
chainId: '0x89',
nativeBalances: {
[account.toLowerCase()]: '0x64',
},
})
await provider.request({
method: 'eth_sendTransaction',
params: [{ from: account, to: recipient, value: '0xa' }],
})
await expect(provider.request({
method: 'eth_getBalance',
params: [account, 'latest'],
})).resolves.toBe('0x5a')
await expect(provider.request({
method: 'eth_getBalance',
params: [recipient, 'latest'],
})).resolves.toBe('0xa')
})
Use blocking prompts for a fast manual loop
Use blocking providers when you want direct browser confirmation dialogs and do not need a simulator sidecar.
import {
createSimulatedBlockingEthereumProvider,
createSimulatedBlockingNimiqProvider,
} from '@onmax/nimiq-mini-app-kit/dev'
export function installBlockingProviders() {
if (!import.meta.env.DEV)
return
window.nimiq = createSimulatedBlockingNimiqProvider({
source: window,
appName: 'Dino',
appOrigin: window.location.origin,
})
window.ethereum = createSimulatedBlockingEthereumProvider({
source: window,
appName: 'Dino',
appOrigin: window.location.origin,
input: {
account: '0xf5d7f7c63cb8F6d9Db7D2674bfaB3F5D2aED2fcb',
},
})
}
Expected behavior:
listAccounts()asks to connect the Nimiq account.eth_requestAccountsasks to connect the Ethereum account.- Sign, chain, and transaction requests use browser confirmation dialogs.
Simulator coverage
- Nimiq account reads, signatures, transaction hashes, consensus state, and block height.
- Nimiq transaction previews for basic, data, and staking methods.
- Ethereum account, sign, chain, read-only RPC, native transfer, and ERC-20 transfer scenarios.
- EIP-6963 provider announcement.
- Host bridge messages for custom simulator shells.