Ofline
Every developer building on early-stage blockchain SDKs has lived through this: you run
npm install after a team member updates the lockfile, and suddenly nothing compiles. Midnight's SDK is actively evolving, and breaking changes are frequent enough that you need a systematic approach to handling them.This guide covers practical strategies for managing Midnight SDK upgrades — from detecting breaking changes before they break production, to a step-by-step migration workflow, to maintaining backward compatibility when you can't upgrade immediately.
Why Midnight SDK Changes Break More Than Normal Libraries
Midnight SDK upgrades are more disruptive than typical library updates for a structural reason: proving keys are cryptographically bound to specific circuit versions.
When you upgrade the SDK, even a minor version bump can:
- Change the circuit constraint system
- Invalidate your existing proving keys
- Require regeneration of proving and verification keys
- Break existing proofs generated against the old keys
Unlike a typical API change where you update a function call, a circuit change means any proofs generated before the upgrade are incompatible with contracts deployed after it.
This creates a hard migration surface on two dimensions:
- Your development environment: compilation and proof generation
- Your deployment state: on-chain contracts vs. client-generated proofs
Step 1: Track Changes Before Upgrading
Never upgrade blindly. Before changing any
@midnight-ntwrk/* version:Check the changelog:
Код:
# In your repo, view the current versions
cat package.json | grep "@midnight-ntwrk"
# On npm, check what changed between versions
npx npm-check-updates --filter "@midnight-ntwrk/*" --dry-run
Read the release notes directly:
The Midnight SDK publishes changelogs at:
https://github.com/midnight-ntwrk/midnight-js/releasesLook specifically for entries labeled:
BREAKING CHANGE— requires code changesCircuit change— requires key regenerationAPI rename— function/interface names changedType change— TypeScript types modified (common source of silent breakage)
Check the migration guide:
If a migration guide exists, read it in full before starting. The 15 minutes of reading saves hours of debugging.
Step 2: Pin Exact Versions (Never Use ^ or ~)
Код:
// DANGEROUS — floating versions
{
"dependencies": {
"@midnight-ntwrk/compact-runtime": "^0.14.0",
"@midnight-ntwrk/midnight-js-types": "~0.14.0"
}
}
// CORRECT — pinned exact versions
{
"dependencies": {
"@midnight-ntwrk/compact-runtime": "0.14.0",
"@midnight-ntwrk/midnight-js-types": "0.14.0",
"@midnight-ntwrk/midnight-js-contracts": "0.14.0",
"@midnight-ntwrk/midnight-js-network-id": "0.14.0"
}
}
Lockfile discipline:
Commit
package-lock.json (or yarn.lock). Never .gitignore it. Your CI should install with npm ci (not npm install) to use the exact lockfile versions.Step 3: Upgrade in an Isolated Branch
Never upgrade the Midnight SDK directly on
main. Create an upgrade branch:
Код:
git checkout -b upgrade/midnight-sdk-0.15.0
This lets you:
- Run the full migration without disrupting other work
- Revert cleanly if something breaks badly
- Get a code review of the upgrade diff before merging
Step 4: Update Versions and Recompile Contracts
Update
package.json manually (don't use npm update with floating versions):
Код:
# Update to specific new version
npm install @midnight-ntwrk/compact-runtime@0.15.0 \
@midnight-ntwrk/midnight-js-types@0.15.0 \
@midnight-ntwrk/midnight-js-contracts@0.15.0 \
--save-exact
Then recompile all Compact contracts:
Код:
# Recompile all .compact files
find ./contracts -name "*.compact" | while read contract; do
echo "Compiling: $contract"
npx compactc "$contract" -o "$(dirname "$contract")/build"
done
If compilation fails, the error messages from
compactc are usually clear about what changed. Common patterns:
Код:
Error: 'disclose' function signature changed in 0.15.0
# Fix: Update all disclose() calls to new signature
Error: Type 'Uint64' is no longer assignable to 'Field'
# Fix: Use explicit conversion via toField()
Step 5: Regenerate Proving Keys
After any compilation success, regenerate proving and verification keys. Even if the circuit didn't visibly change, don't trust old keys.
Код:
# Script to regenerate all keys
#!/bin/bash
set -euo pipefail
CONTRACTS_DIR="./contracts"
KEYS_DIR="./keys"
mkdir -p "$KEYS_DIR"
for contract_dir in "$CONTRACTS_DIR"/*/build; do
contract_name=$(basename "$(dirname "$contract_dir")")
echo "Generating keys for: $contract_name"
npx compact-cli keygen \
--circuit "$contract_dir/circuit.json" \
--proving-key "$KEYS_DIR/${contract_name}.pk" \
--verification-key "$KEYS_DIR/${contract_name}.vk"
echo "✅ Keys generated for $contract_name"
done
echo "All keys regenerated. Commit the new keys."
Important: Proving keys must be committed to your repository. They're large (often 50-200MB), so use Git LFS if needed:
Код:
git lfs track "*.pk"
git lfs track "*.vk"
echo "*.pk filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
echo "*.vk filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
git add .gitattributes
git lfs install
Step 6: Update the Witness Implementation
After recompiling, review your TypeScript witness implementations. These are the most failure-prone area because TypeScript doesn't always catch circuit-level mismatches at compile time.
What to look for:
Код:
// Old pattern (pre-0.15.0)
import { buildTransferWitness } from '@midnight-ntwrk/compact-runtime';
// New pattern (post-0.15.0) — function renamed
import { createTransferWitness } from '@midnight-ntwrk/compact-runtime';
Run the TypeScript compiler as a first check:
Код:
npx tsc --noEmit 2>&1 | head -50
TypeScript errors reveal API changes. Fix all TypeScript errors before running tests.
Step 7: Run the Full Test Suite
After compilation and TypeScript checks pass, run your tests:
Код:
npm test
Common test failure modes after upgrades:
Constraint violations in tests: If tests fail with "constraint not satisfied," the witness implementation doesn't match the new circuit. Compare the old and new circuit constraint counts.
Proof verification failures: Old proofs in test fixtures are invalid. Regenerate test fixtures.
Timeout failures: A new proving system may be slower. Increase test timeouts:
Код:
// In jest.config.ts
export default {
testTimeout: 120000, // 2 minutes for proof generation
};
- Network interaction failures: If the SDK changed how it connects to the network, update your test setup.
Step 8: Test on Local Devnet, Then Testnet
After local tests pass:
- Deploy to local devnet (if Midnight provides one)
- Run integration tests against the devnet
- Deploy to testnet
- Run smoke tests on testnet before committing to the upgrade
If you have existing testnet deployments, understand that you may need to redeploy contracts if the circuit changed. Old contracts with new client code will produce proof verification failures.
Handling "I Can't Upgrade Right Now" Situations
Sometimes you learn about a breaking SDK change but you can't migrate immediately (active user base, other ongoing work, etc.).
Strategy 1: Lock the environment completely
Prevent accidental upgrades at the system level:
Код:
# .npmrc — add to repo root
package-lock=true
engine-strict=true
# package.json engines field
"engines": {
"node": ">=18.0.0 <20.0.0"
}
Strategy 2: Document the freeze with a clear exit criteria
Create
UPGRADE_BLOCKED.md in the repo:
Код:
# Midnight SDK Upgrade Blocked
Current version: 0.14.0
Target version: 0.15.0
Blocked since: 2026-01-15
Blocker: Active testnet deployment with 200+ users
Exit criteria: Testnet migration window scheduled for 2026-02-01
Owner: @yourname
## What breaks in 0.15.0
- [List specific breaking changes]
- [Associated code areas]
## Migration plan
[Brief migration plan when the window opens]
Strategy 3: Run parallel environments
For longer freezes, maintain two deployment environments with different SDK versions. Route traffic based on contract version detected from on-chain state.
Diagnosing Proof Generation Failures After Upgrade
If proof generation fails after a successful upgrade, here's the diagnostic checklist:
1. Check proving key freshness
Код:
# Get the modification time of the compiled circuit
stat contracts/my-contract/build/circuit.json
# Compare with proving key
stat keys/my-contract.pk
# If circuit is newer than key: regenerate the key
2. Verify circuit input types
Код:
# Inspect the circuit's public inputs
cat contracts/my-contract/build/circuit.json | python3 -c "
import json, sys
circuit = json.load(sys.stdin)
print('Public inputs:')
for name, type_info in circuit.get('public_inputs', {}).items():
print(f' {name}: {type_info}')
print('Private inputs:')
for name, type_info in circuit.get('private_inputs', {}).items():
print(f' {name}: {type_info}')
"
Compare against your witness implementation and verify the types match exactly.
3. Test with minimal inputs
Isolate the failing constraint with a minimal test:
Код:
it('should prove minimal transfer', async () => {
const proof = await proveTransfer({
from: testKey,
to: testAddress,
amount: 1n, // minimum amount
balance: 1n, // exactly equal to amount
});
expect(proof).toBeDefined();
});
4. Enable verbose proving output
Код:
COMPACT_PROOF_VERBOSE=1 npm test 2>&1 | grep -E "constraint|witness|error"
Automating Upgrade Detection
Set up a CI job to detect new SDK versions:
Код:
# .github/workflows/check-sdk-updates.yml
name: Check Midnight SDK Updates
on:
schedule:
- cron: '0 9 * * 1' # Every Monday 9AM
jobs:
check-updates:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Check for SDK updates
run: |
npm install -g npm-check-updates
UPDATES=$(ncu --filter "@midnight-ntwrk/*" --jsonUpgraded 2>/dev/null || echo "{}")
if [ "$UPDATES" != "{}" ] && [ "$UPDATES" != "null" ]; then
echo "New SDK versions available:"
echo $UPDATES
# Optionally create a GitHub issue or send a notification
fi
Midnight SDK migrations are painful the first time but routine once you have the workflow down. The key principles:
- Never upgrade without reading the changelog
- Always regenerate proving keys after any SDK change
- Test witness implementations explicitly — TypeScript won't catch circuit mismatches
- Branch, test, review before merging
The SDK will stabilize as Midnight matures, but for the next 12-18 months, treat every
@midnight-ntwrk/* version bump as a potential major event.