Midnight Adapter for UI Builder
npm install @openzeppelin/ui-builder-adapter-midnightA browser-based adapter for executing Midnight smart contracts in the OpenZeppelin Contracts UI Builder. This adapter enables users to upload Midnight contract artifacts, interact with contracts through the Lace wallet, and execute zero-knowledge proof transactions entirely in the browser.
- ✅ Pure Client-Side Execution - No backend server required
- ✅ ZIP Upload Workflow - Simple contract artifact loading
- ✅ Zero-Knowledge Proof Coordination - Orchestrates proof generation via external proof server
- ✅ Lace Wallet Integration - Native Midnight wallet support
- ✅ Production Ready - Static deployment identical to EVM/Stellar adapters
Export your Midnight contract as a ZIP file containing:
- Contract definition (.d.ts or .d.cts)
- Compiled contract module (.cjs or .js)
- Witness code (witnesses.js)
- ZK artifacts (.prover, .verifier, .bzkir)
``bash`
cd your-midnight-contract
compact build
zip -r contract.zip dist/
1. Navigate to Midnight network in the UI Builder
2. Click "Upload Contract"
3. Select your contract.zip file
4. Contract functions will automatically populate
1. Connect Lace wallet
2. Select a contract function
3. Enter parameters
4. Click "Execute" - proof generation and signing handled automatically
Some circuits require an organizer secret key. For security, the adapter treats this as a runtime-only credential:
- Never requested or stored during artifact upload
- Collected only at execution time (similar to a relayer API key)
- Injected into the private state provider in-memory and never persisted
How it works in the UI:
- The Builder auto-adds a form field of type runtimeSecret for functions that require an organizer key (based on adapter-provided decorations).runtimeSecret
- This field appears in the form like any other field (not in the execution settings panel). The label and helper text are adapter-defined (e.g., "Organizer Secret Key (hex)").
- Values from fields are never persisted; they are removed from contract arguments and passed to the adapter at submit time.
- The adapter seeds a runtime-only overlay of the private state so the secret is available just-in-time for proof generation and is stripped on any persistence operations.
This adapter implements a pure browser-based solution with lazy-loaded polyfills:
``
Browser Memory
├── ZIP Extraction (jszip)
├── ZK Artifacts (EmbeddedZkConfigProvider)
├── Contract Evaluation (dynamic code execution)
├── Browser Polyfills (lazy-loaded when adapter imports)
│ ├── Buffer Polyfill (runtime, via browser-init.ts)
│ └── CommonJS Polyfill (runtime, via browser-init.ts)
└── Transaction Execution (direct SDK integration)
Key Components:
- EmbeddedZkConfigProvider: In-memory storage for prover/verifier keys
- Contract Evaluator: Dynamic contract instantiation with shared WASM context
- Browser Init: Lazy-loaded polyfills (Buffer + CommonJS) only when Midnight is selected
- Monkey Patches: Minimal SDK patches for browser compatibility (see patches/)
The adapter uses lazy-loaded runtime polyfills to minimize global namespace pollution:
Runtime Polyfills - Installed when adapter is first imported
- Buffer polyfill for @dao-xyz/borsh (Midnight SDK serialization)packages/adapter-midnight/src/browser-init.ts
- CommonJS globals for dynamic contract evaluation
- Location:
- Only loads when user selects Midnight ecosystem
Why This Works:
- Polyfills install before any Midnight SDK code executes
- No impact on other ecosystems (EVM, Solana, Stellar)
- Vite's NodeGlobalsPolyfillPlugin provides additional Buffer support during dependency optimization
When you export an application from the UI Builder, all contract artifacts are automatically bundled into the exported app. This enables:
- ✅ ZK Proof Generation: All proof files included
- ✅ Contract State Queries: TypeScript definitions preserved
- ✅ Self-Contained Deployment: Deploy anywhere without external dependencies
The export process uses a lean bundling approach that keeps files small:
Bundled in the Export:
1. Original ZIP file: Stored at public/midnight/contract.zip (keeps bundle small)
2. Contract Address: Deployed contract address
3. Private State ID: Contract's private state identifier
Parsed at Runtime (from ZIP):
- Contract definition (TypeScript .d.ts interface).cjs
- Contract module ( for contract interaction)witnesses.js
- Witness code ( for zero-knowledge proofs).prover
- Verifier keys (/.verifier for proof verification)
- ZK artifacts (for browser-based proof generation)
This approach:
- ✅ Keeps exported files small (no massive expanded artifacts)
- ✅ Reuses the same ZIP parsing logic as the builder
- ✅ Maintains a single source of truth for artifact handling
- ✅ Makes exports future-proof to ZIP format changes
``
exports/your-app/
├── src/
│ ├── midnight/
│ │ └── artifacts.ts # ← ZIP data + metadata
│ ├── main.tsx # ← Auto-loads and parses ZIP on startup
│ ├── App.tsx
│ └── components/
│ └── GeneratedForm.tsx
├── package.json # ← Includes all Midnight dependencies
└── vite.config.ts # ← Configured for WASM & module deduplication
The exported main.tsx automatically loads artifacts during adapter initialization:
`typescript`
const resolveAdapter = async (nc: NetworkConfig): Promise
const adapter = new MidnightAdapter(nc);
// Load contract from URL-based ZIP (same parsing logic as builder)
await adapter.loadContractWithMetadata(midnightArtifactsSource);
return adapter;
};
The artifacts.ts file contains the ZIP metadata and URL:
`typescript`
export const midnightArtifactsSource = {
contractAddress: '0x...',
privateStateId: 'counter',
contractArtifactsUrl: '/midnight/contract.zip',
};
Important: When testing exported Midnight apps, you must test them outside the UI Builder monorepo workspace.
`bash❌ WRONG - Running in monorepo (patches won't apply)
cd /path/to/ui-builder/exports/my-app
pnpm install # Patches from adapter are ignored in nested workspaces!
pnpm dev
Why? pnpm only applies patches from
pnpm.patchedDependencies when installing published packages. In a monorepo, patches must be defined at the workspace root. The adapter's bundled patches are only activated when the adapter is installed from npm (not via workspace:*).What happens if you test in the monorepo?
- Midnight SDK patches won't be applied
- You may see errors like "No private state found" or module resolution issues
- Transaction execution will fail unexpectedly
For CI/CD or production deployments: This is not an issue. Published apps install the adapter from npm, which automatically applies all patches.
1. Export from Builder: Use the "Export" button after loading a contract
2. Install Dependencies:
cd exports/your-app && npm install
3. Run Locally: npm run dev
4. Execute Transaction: Connect Lace wallet and submit a transaction
5. Verify Offline: Artifacts load instantly without network requestsdocs/ADAPTER_ARCHITECTURE.md § 11.6.Documentation
This adapter includes comprehensive documentation covering all aspects of the Midnight integration:
$3
- TRANSACTION_IMPLEMENTATION.md - Complete implementation guide covering architecture, data flow, WASM context isolation, Buffer polyfills, ZK artifact distribution, SDK patches, and testing guidelines.
- TYPE-COVERAGE.md - Comprehensive type system reference with full coverage analysis of all Midnight types, UI component mappings, validation status against production contracts, and edge case handling.
- BROWSER_COMPATIBILITY.md - Browser compatibility strategy, polyfill implementation details, global namespace management, and debugging tips for browser-specific issues.
- CHANGELOG.md - Version history, breaking changes, new features, and migration guides for adapter updates.
$3
- Wallet Integration - Lace wallet connection, state management, and provider configuration.
$3
| Topic | Document | Key Sections |
| ---------------- | ----------------------------- | ---------------------------------- |
| Getting Started | This README | Quick Start, Architecture, Export |
| Transaction Flow | TRANSACTION_IMPLEMENTATION.md | §4-7: Providers, Execution, Proofs |
| Type Mapping | TYPE-COVERAGE.md | Type Matrix, UI Components |
| Browser Setup | BROWSER_COMPATIBILITY.md | Polyfills, CommonJS Support |
| Wallet Setup | src/wallet/README.md | Connection, State, Errors |
Requirements
- Browser: Modern browser with WebAssembly support
- Wallet: Lace wallet browser extension
- Contract: Midnight contract compiled with Compact compiler v0.9.0+
> Note: This adapter automatically installs browser polyfills (Buffer, CommonJS) when loaded. No additional setup required.
Known Limitations
1. Page Reload: Artifacts cleared on refresh (re-upload ZIP file)
2. Memory Usage: ~500KB-1MB per circuit stored in browser
3. Single Contract: One contract's artifacts at a time per session
4. Proof Server: Requires external proof generation service
Dependencies
Core:
-
@midnight-ntwrk/compact-runtime - Contract runtime
- @midnight-ntwrk/midnight-js-contracts - Contract utilities
- @midnight-ntwrk/midnight-js-http-client-proof-provider - Proof server client
- @midnight-ntwrk/midnight-js-level-private-state-provider - Private state storage
- jszip - ZIP file extractionDevelopment
`bash
Install dependencies
pnpm installRun development server
pnpm devBuild for production
pnpm build
`File Structure
`
src/
├── index.ts # Entry point + polyfill loader
├── browser-init.ts # Runtime polyfills (Buffer + CommonJS)
├── adapter.ts # Main adapter implementation
├── networks/
│ └── testnet.ts # Network configurations
├── transaction/
│ ├── eoa.ts # Transaction execution strategy
│ ├── providers.ts # SDK provider configuration
│ ├── embedded-zk-config-provider.ts # In-memory ZK artifact storage
│ ├── contract-evaluator.ts # Dynamic contract loading
│ └── witness-evaluator.ts # Witness code evaluation
└── utils/
├── artifacts.ts # Artifact validation & registration
├── zip-extractor.ts # ZIP parsing logic
└── schema-parser.ts # Contract interface parsing
`Troubleshooting
$3
Cause: Prover keys compiled with incompatible Compact compiler version.
Solution: Recompile contract with current Compact compiler:
`bash
cd your-contract
pnpm install @midnight-ntwrk/compact-cli@latest
compact build
`$3
Cause: Missing contract artifacts in ZIP file.
Solution: Ensure ZIP contains
dist/` directory with all compiled outputs.Cause: Network issues or proof server overload.
Solution:
- Check network connection
- Verify proof server URL in wallet config
- Try again after a few minutes
When modifying this adapter:
1. Test thoroughly - Midnight integration is complex
2. Update docs - Keep TRANSACTION_IMPLEMENTATION.md current
3. Maintain patches - Document any SDK monkey-patches
4. Consider upstream - Submit fixes to Midnight SDK when possible
- Midnight Documentation
- Compact Language
- Midnight SDK Reference
- Lace Wallet
MIT