> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploying and calling Midnight contracts

A Midnight contract call is a **zero-knowledge proof**, not a signed
transaction. This means an embedded wallet cannot simply "sign the contract
call" — the work splits into two halves:

| Half | Who does it | What it covers |
| :- | :- | :- |
| **Contract** | Your app, via `midnight-js` | Circuit execution, witness handling, ZK proof, private state |
| **Settlement** | The Dynamic wallet | Adding coin inputs so the transaction balances, paying DUST, signing, submitting |

`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.

> [!NOTE]
>
> This is for **embedded** Midnight wallets. External wallets (e.g. 1am) expose
> their own provider surface — see
> [Using Midnight external wallets](https://docs.dynamic.xyz/docs/react/wallets/using-wallets/midnight/using-midnight-wallets.md).

Before you start: you need a typed Midnight **embedded** wallet (`primaryWallet`)
from [Using Midnight embedded wallets](https://docs.dynamic.xyz/docs/react/wallets/using-wallets/midnight/midnight-embedded-wallets.md),
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](https://docs.dynamic.xyz/docs/react/wallets/using-wallets/midnight/midnight-embedded-wallets.md#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.

> [!WARNING]
>
> Prover keys are large: expect single-digit to tens of megabytes per
> circuit. They are uploaded to the proof server on every proof, so keep
> them served from somewhere fast.

**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

```tsx React theme={"system"}
// Parameterize with the transaction types from your own installed midnight-js
// and ledger versions, and the result is assignable to `MidnightProviders`
// with no cast. The connector deliberately declares these structurally, so it
// pins no midnight-js version on your app.
const walletProvider = await primaryWallet.getWalletProvider<
  UnboundTransaction,
  FinalizedTransaction
>();

walletProvider.getCoinPublicKey();       // shielded coin public key
walletProvider.getEncryptionPublicKey(); // shielded encryption public key
walletProvider.balanceTx(tx, ttl);       // balance + pay DUST + sign, in the iframe
walletProvider.submitTx(tx);             // broadcast
```

This satisfies both `walletProvider` and `midnightProvider` in
`MidnightProviders`. The remaining four are yours.

| Provider | Supplied by | Notes |
| :- | :- | :- |
| `walletProvider` | **Dynamic** | `getWalletProvider()` |
| `midnightProvider` | **Dynamic** | the same object |
| `privateStateProvider` | you | your contract's witnesses |
| `publicDataProvider` | you | indexer; required to read contract state |
| `zkConfigProvider` | you | your compiled ZK artifacts |
| `proofProvider` | you | a Midnight proof server |

## Deploying and calling

```tsx React theme={"system"}
import { CompiledContract } from '@midnight-ntwrk/compact-js';
import { FetchZkConfigProvider } from '@midnight-ntwrk/midnight-js-fetch-zk-config-provider';
import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider';
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
import {
  deployContract,
  findDeployedContract,
} from '@midnight-ntwrk/midnight-js/contracts';
import { setNetworkId } from '@midnight-ntwrk/midnight-js/network-id';

import { Contract } from './my-contract/index.js'; // compact compile output

const PRIVATE_STATE_ID = 'my-contract-state';

// Your indexer and proof server endpoints
const indexerHttpUrl = 'https://your-indexer.example/api/v1/graphql';
const indexerWsUrl = 'wss://your-indexer.example/api/v1/graphql/ws';
const proofServerUrl = 'http://localhost:6300';

setNetworkId('preview');

// Absolute URL: FetchZkConfigProvider runs `new URL(...)` on this and rejects
// anything without an http/https scheme.
const zkArtifactBaseUrl = `${window.location.origin}/my-contract`;

const compiledContract = CompiledContract.make('my-contract', Contract)
  .withWitnesses({
    // your witness implementations
  })
  .withCompiledFileAssets(zkArtifactBaseUrl);

const walletProvider = await primaryWallet.getWalletProvider();
const accountId = walletProvider.getCoinPublicKey();

const zkConfigProvider = new FetchZkConfigProvider(
  zkArtifactBaseUrl,
  // Pass the browser's fetch explicitly — the default is `cross-fetch`, which
  // a bundler may resolve to its Node build and fail with an opaque
  // ZKConfigurationReadError. See Common pitfalls for the content-type guard.
  window.fetch.bind(window),
);

const providers = {
  privateStateProvider: levelPrivateStateProvider({
    accountId,
    privateStateStoreName: PRIVATE_STATE_ID,
  }),
  publicDataProvider: indexerPublicDataProvider(indexerHttpUrl, indexerWsUrl),
  zkConfigProvider,
  proofProvider: httpClientProofProvider(proofServerUrl, zkConfigProvider),
  walletProvider,
  midnightProvider: walletProvider,
};

// Deploy
const deployed = await deployContract(providers, {
  compiledContract,
  privateStateId: PRIVATE_STATE_ID,
  initialPrivateState: { /* your witnesses' backing state */ },
});
const { contractAddress } = deployed.deployTxData.public;

// Call a circuit on an already-deployed instance
const found = await findDeployedContract(providers, {
  compiledContract,
  contractAddress,
  privateStateId: PRIVATE_STATE_ID,
});
await found.callTx.myCircuit(/* ...circuit args */);
```

> [!NOTE]
>
> 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 the `midnight-js` types, and each one fails with a
misleading error.

> [accordion] 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`.

> [accordion] 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:
>
> ```ts theme={"system"}
> const guardedFetch = async (input, init) => {
>   const response = await window.fetch(input, init);
>   const contentType = response.headers.get('content-type') ?? '';
>   if (response.ok && contentType.includes('text/html')) {
>     throw new Error(`ZK artifact request returned HTML: ${String(input)}`);
>   }
>   return response;
> };
> ```
>
> Throwing here is also what makes the built-in Zswap circuits work, since
> those requests hit the same 404 fallback.

> [accordion] 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".

> [accordion] 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.

> [accordion] 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.

> [accordion] 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.

```graphql theme={"system"}
query ($id: HexEncoded!) {
  transactions(offset: { identifier: $id }) {
    hash
    block { height }
    ... on RegularTransaction {
      transactionResult { status }
      contractActions { __typename address }
    }
  }
}
```

`contractActions` returns `ContractDeploy` or `ContractCall` with the contract
address — the simplest way to confirm a deploy and recover its address.

## Resources

* [Using Midnight embedded wallets](https://docs.dynamic.xyz/docs/react/wallets/using-wallets/midnight/midnight-embedded-wallets.md)
* [Using Midnight external wallets](https://docs.dynamic.xyz/docs/react/wallets/using-wallets/midnight/using-midnight-wallets.md)
* [Midnight developer docs](https://docs.midnight.network/)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.