primaryWallet.getWalletProvider() returns the settlement half as a
midnight-js–compatible provider. Balancing and MPC signing happen inside the
wallet iframe, so no shielded, DUST, or MPC key material ever leaves it — only
public keys and serialized transactions cross the boundary.
This is for embedded Midnight wallets. External wallets (e.g. 1am) expose
their own provider surface — see
Using Midnight external wallets.
primaryWallet)
from Using Midnight embedded wallets,
plus DUST, compiled Compact artifacts, and a proof server (see Prerequisites
below).
Prerequisites
1
Fund the wallet and register DUST
Contract calls cost DUST. Fund the wallet with unshielded NIGHT, call
registerDust(), then wait for a non-zero DUST balance — see
Registering for DUST.2
Compile your contract
compact compile produces the ZK artifacts your zkConfigProvider serves:
zkir/<circuit>.bzkir, keys/<circuit>.prover, keys/<circuit>.verifier.
These are deterministic, public build outputs — not secrets.3
Point at a proof server
Your
proofProvider needs a Midnight proof server for your circuits.
The wallet handles its own shielded and DUST proofs separately, inside the
iframe.What the wallet gives you
React
walletProvider and midnightProvider in
MidnightProviders. The remaining four are yours.
Deploying and calling
React
Both calls go through
balanceTx and submitTx on the wallet provider, so the
user’s shielded coins and DUST pay for the transaction, and the unshielded
segment is MPC-signed — without your app ever holding key material.Common pitfalls
These are not obvious from themidnight-js types, and each one fails with a
misleading error.
Your zkConfigProvider must fail for built-in circuits
Your zkConfigProvider must fail for built-in circuits
A circuit that creates a shielded coin needs prover keys for Midnight’s
own
midnight/zswap/output, input and spend circuits. Those are not
in your compiled artifacts — the proof server has them.The preimage marks proving data as optional, so your provider should
throw for circuit IDs it does not own. midnight-js then omits proving
data and the server uses its own keys. If your provider returns something
for those IDs instead, it ends up in the preimage and the proof server
rejects the request with a bare 400.Validate the content type of artifact responses
Validate the content type of artifact responses
createProverKey does not inspect what you hand it. A dev server’s
single-page-app fallback returns 200 with index.html for unknown
paths, so a wrong artifact path becomes HTML embedded in your proof
preimage — and the only symptom is a bare 400 from the proof server.Reject HTML in the custom fetch, and a bad path becomes an obvious error:Deploy needs no proof; circuit calls do
Deploy needs no proof; circuit calls do
Deploying publishes state and verifier keys — there is no circuit execution
to prove, so a deploy can succeed even when no proof server is reachable.
The first
callTx is where proofProvider is actually used, which is why
a misconfigured proof server often looks like “deploy worked, calls broke”.The first call is slow
The first call is slow
getWalletProvider() initializes the wallet inside the iframe, which
includes a full sync on a cold cache. Expect well over a minute on first
use, with no intermediate progress. Later calls reuse the synced wallet.Private state is yours to keep
Private state is yours to keep
Witnesses live in your app’s private state, never in the wallet. If they are
lost, gated circuits on that contract can no longer be called by anyone —
the wallet cannot help recover them, because it never held them. Persist
anything you cannot regenerate.
Newly minted shielded coins take time to appear
Newly minted shielded coins take time to appear
A shielded coin exists on chain as soon as the transaction is included, but
the wallet has to sync the Zswap tree before it appears in your balance. A
zero balance right after a successful mint usually means sync, not failure.
Confirming a transaction landed
submitTx returns the submission identifier, which is what you poll for
inclusion. It is a different value from the canonical transaction hash that
block explorers index — querying an indexer by the wrong one returns an empty
result rather than an error, which reads like a failed transaction.
contractActions returns ContractDeploy or ContractCall with the contract
address — the simplest way to confirm a deploy and recover its address.