primaryWallet.getWalletProvider() returns the settlement half as a
midnight-js–compatible provider. Balancing and MPC signing happen inside the
wallet iframe, so shielded, DUST and MPC key material never leaves it — only
public keys and serialized transactions cross the boundary.
This is for embedded Midnight wallets. Injected wallets (1am) expose their
own provider surface — see
Using Midnight wallets.
What the wallet gives you
React
walletProvider and midnightProvider in
MidnightProviders. The remaining four are yours.
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 artefacts 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 proves its own shielded and DUST work separately, inside the
iframe.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.Things that will catch you out
These are not obvious from themidnight-js types, and each one fails in a way
that points somewhere unhelpful.
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 artefacts — 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 artefact responses
Validate the content type of artefact responses
createProverKey does not inspect what you hand it. A dev server’s
single-page-app fallback answers unknown paths with 200 and index.html,
so a wrong artefact 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 with no proof server 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() initialises 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 shows in balances. A zero
balance straight after a successful mint usually means sync, not failure.
Confirming a transaction landed
submitTx returns the submission identifier, which is what inclusion is
polled by. 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.