Mini Apps

Host runtime

Build custom simulator hosts with the host runtime subpath.

Use @onmax/nimiq-mini-app-kit/host-runtime when you build a custom local host shell and own both sides of the simulated Nimiq Pay bridge.

Do not use this subpath inside a normal mini app that only needs window.nimiq or window.ethereum. Use the root package or the Nuxt module instead.

Choose host-runtime over dev helpers

Use host-runtime when the host window needs to:

  • create launch payloads for a mini-app window,
  • validate mini-app-to-host messages,
  • fulfill Nimiq and Ethereum approval requests,
  • track wallet balance and transaction history,
  • send provider responses back to the mini-app window.

Use @onmax/nimiq-mini-app-kit/dev when you only need to install simulated providers in the current app window.

Open a mini app with launch state

The launch payload is serialized into window.name. The mini app consumes it during bootstrap.

host/open-mini-app.ts
import {
  createSimulatedHostRuntime,
  serializeSimulatedHostLaunchPayload,
} from '@onmax/nimiq-mini-app-kit/host-runtime'

const runtime = createSimulatedHostRuntime({
  provider: {
    privateKeyHex: '4f4e9c6d93fab8a1716dd2136bfdcf8bc6bfcb7e5fcb868f1f8f1eb8bbf4d2f0',
  },
  wallet: {
    balanceLuna: 20_000,
  },
  ethereum: {
    account: '0xf5d7f7c63cb8F6d9Db7D2674bfaB3F5D2aED2fcb',
    chainId: '0x89',
  },
})

export function openMiniApp(appUrl: string) {
  const payload = runtime.createLaunchPayload({
    hostOrigin: window.location.origin,
  })

  return window.open(appUrl, 'nimiq-pay-mini-app', serializeSimulatedHostLaunchPayload(payload))
}

Expected behavior:

  • The opened window receives the host origin and initial wallet state.
  • bootstrapMiniAppProvider() can create bridge providers from that launch payload.
  • The payload is not a production transport. It is simulator state.

Consume a launch payload in a custom app bootstrap

Use this only when you are not using bootstrapMiniAppProvider() or the Nuxt module.

mini-app/consume-launch.ts
import { consumeSimulatedHostLaunchPayload } from '@onmax/nimiq-mini-app-kit/host-runtime'

export function readSimulatorLaunch() {
  const launch = consumeSimulatedHostLaunchPayload()

  if (!launch)
    return null

  return {
    hostOrigin: launch.hostOrigin,
    balanceLuna: launch.state.wallet.balanceLuna,
    ethereumChainId: launch.state.ethereum.providerConfig.chainId,
  }
}

Expected behavior:

  • The first call returns the parsed launch payload.
  • The helper clears window.name after consuming the payload.
  • Later calls return null unless a new payload is provided.

Fulfill Nimiq approval requests

Use runtime fulfillment when your host shell receives a provider-request message and the user approves it.

host/fulfill-nimiq-request.ts
import {
  createSimulatedHostRuntime,
  createSimulatedProviderResponseMessage,
} from '@onmax/nimiq-mini-app-kit/host-runtime'
import type { SimulatedProviderRequest } from '@onmax/nimiq-mini-app-kit/dev'

const runtime = createSimulatedHostRuntime({
  provider: {
    privateKeyHex: '4f4e9c6d93fab8a1716dd2136bfdcf8bc6bfcb7e5fcb868f1f8f1eb8bbf4d2f0',
  },
  wallet: {
    balanceLuna: 20_000,
  },
})

export async function approveNimiqRequest(request: SimulatedProviderRequest) {
  const response = await runtime.fulfillProviderRequest(request)

  return {
    message: createSimulatedProviderResponseMessage(response),
    wallet: runtime.getState().wallet,
  }
}

Expected behavior after approving a send-basic-transaction-with-data request for 5000 Luna with a 100 Luna fee:

output
{
  "balanceLuna": 14900,
  "transactions": [
    {
      "kind": "wallet-transaction",
      "method": "send-basic-transaction-with-data",
      "amountLuna": 5000,
      "balanceDeltaLuna": -5100,
      "feeLuna": 100,
      "status": "approved"
    }
  ]
}

Fulfill Ethereum approval requests

Use Ethereum fulfillment for account, sign, chain, and transaction approvals.

host/fulfill-ethereum-request.ts
import {
  createSimulatedEthereumProviderResponseMessage,
  createSimulatedHostRuntime,
} from '@onmax/nimiq-mini-app-kit/host-runtime'
import type { SimulatedEthereumProviderRequest } from '@onmax/nimiq-mini-app-kit/dev'

const runtime = createSimulatedHostRuntime({
  ethereum: {
    account: '0xf5d7f7c63cb8F6d9Db7D2674bfaB3F5D2aED2fcb',
    chainId: '0x89',
  },
})

export async function approveEthereumRequest(request: SimulatedEthereumProviderRequest) {
  const response = await runtime.fulfillEthereumProviderRequest(request)

  return createSimulatedEthereumProviderResponseMessage(response)
}

Expected behavior:

  • eth_requestAccounts returns the configured account.
  • wallet_addEthereumChain records the added chain.
  • wallet_switchEthereumChain updates runtime.getState().ethereum.providerConfig.chainId.
  • Rejected Ethereum requests should be returned as Ethereum-shaped errors with an optional code.

Validate bridge messages

Use the guards before trusting postMessage data from a mini-app window.

host/messages.ts
import {
  isSimulatedHostToMiniAppMessage,
  isSimulatedMiniAppToHostMessage,
} from '@onmax/nimiq-mini-app-kit/host-runtime'

export function classifyMessage(data: unknown) {
  if (isSimulatedMiniAppToHostMessage(data))
    return `mini-app:${data.type}`

  if (isSimulatedHostToMiniAppMessage(data))
    return `host:${data.type}`

  return 'unknown'
}

Expected examples:

output
mini-app:provider-request
mini-app:ethereum-provider-request
host:provider-response
unknown

Runtime rules

  • Use exact event.origin checks around postMessage.
  • Use runtime fulfillment after your host UI records an approval decision.
  • Use createSimulatedProviderResponseMessage() and createSimulatedEthereumProviderResponseMessage() to send successful responses.
  • Use the exported error response factories when the host rejects a request.
  • Do not use host-runtime as a production wallet protocol.
Copyright © 2026