Contract Lifecycle Management
Manage deployed Midnight contracts throughout their lifecycle, including state inspection, backups, versioning, migrations, and graceful deprecation.
When to Use
- Inspecting current contract state for debugging or monitoring
- Backing up contract state before upgrades
- Planning and executing contract migrations
- Implementing versioning strategies
- Deprecating old contract versions gracefully
- Managing multi-version contract deployments
Key Concepts
Contract Lifecycle Phases
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐
│ Deploy │───▶│ Active │───▶│ Migrate │───▶│ Sunset │───▶│ Archived │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └──────────┘
│ │
│ ▼
│ ┌─────────┐
└────────▶│ Monitor │
└─────────┘
Immutability Consideration
Midnight contracts are immutable once deployed. "Upgrades" require:
- Deploying a new contract version
- Migrating state from old to new contract
- Updating references (e.g., in frontend, other contracts)
- Deprecating the old version
State Migration Patterns
| Pattern | Description | Use Case |
|---|---|---|
| Snapshot & Deploy | Export state, deploy new with state | Simple migrations |
| Proxy Pattern | Use proxy contract pointing to impl | Frequent upgrades |
| Dual-Write | Write to both old and new | Zero-downtime migrations |
| Lazy Migration | Migrate state on-demand | Large state migrations |
References
| Document | Description |
|---|---|
| state-inspection.md | Querying and analyzing contract state |
| migration-patterns.md | Strategies for contract upgrades |
Examples
| Example | Description |
|---|---|
| state-backup/ | Export and backup contract state |
| contract-upgrade/ | Migrate to new contract version |
Quick Start
1. Inspect Current State
import { connectContract } from '@midnight-ntwrk/midnight-js-contracts';
import { createIndexerClient } from '@midnight-ntwrk/midnight-js-indexer';
// Connect to contract
const contract = await connectContract({
address: CONTRACT_ADDRESS,
artifact: Contract,
wallet,
config,
});
// Query state via circuit
const state = await contract.query.get_state();
console.log('Current state:', state);
// Or query via indexer for raw state
const indexer = createIndexerClient({ url: config.indexer });
const rawState = await indexer.getContractState(CONTRACT_ADDRESS);
2. Backup State
interface StateBackup {
contractAddress: string;
blockHeight: number;
timestamp: string;
state: Record<string, unknown>;
}
async function backupState(
contract: ConnectedContract,
indexer: IndexerClient
): Promise<StateBackup> {
const state = await contract.query.get_full_state();
const info = await indexer.getContractInfo(contract.address);
return {
contractAddress: contract.address,
blockHeight: info.lastUpdateBlock,
timestamp: new Date().toISOString(),
state,
};
}
3. Deploy New Version
import { NewContract } from './build-v2/contract.cjs';
const newContract = await deployContract({
wallet,
artifact: NewContract,
initialState: {
...backupData.state,
version: 2,
},
config,
});
console.log('New version deployed at:', newContract.address);
Common Patterns
Version Registry
Track multiple contract versions:
interface ContractVersion {
version: number;
address: string;
deployedAt: string;
status: 'active' | 'deprecated' | 'archived';
}
class VersionRegistry {
private versions: ContractVersion[] = [];
addVersion(version: ContractVersion): void {
this.versions.push(version);
}
getActiveVersion(): ContractVersion | undefined {
return this.versions.find((v) => v.status === 'active');
}
deprecateVersion(version: number): void {
const v = this.versions.find((v) => v.version === version);
if (v) {
v.status = 'deprecated';
}
}
getAllVersions(): ContractVersion[] {
return [...this.versions].sort((a, b) => b.version - a.version);
}
}
Health Monitoring
interface ContractHealth {
address: string;
isResponding: boolean;
lastActivity: Date | null;
stateConsistent: boolean;
warnings: string[];
}
async function checkContractHealth(
contract: ConnectedContract,
indexer: IndexerClient
): Promise<ContractHealth> {
const warnings: string[] = [];
let isResponding = false;
let lastActivity: Date | null = null;
let stateConsistent = true;
try {
// Test responsiveness
await contract.query.get_version();
isResponding = true;
} catch {
warnings.push('Contract not responding to queries');
}
try {
// Check last activity
const info = await indexer.getContractInfo(contract.address);
lastActivity = new Date(info.lastUpdateTimestamp);
// Warn if inactive for long time
const daysSinceActivity = (Date.now() - lastActivity.getTime()) / (1000 * 60 * 60 * 24);
if (daysSinceActivity > 30) {
warnings.push(`No activity for ${daysSinceActivity.toFixed(0)} days`);
}
} catch {
warnings.push('Could not fetch contract info');
}
return {
address: contract.address,
isResponding,
lastActivity,
stateConsistent,
warnings,
};
}
Migration Script Template
interface MigrationPlan {
sourceVersion: number;
targetVersion: number;
sourceAddress: string;
steps: MigrationStep[];
}
interface MigrationStep {
name: string;
execute: () => Promise<void>;
rollback?: () => Promise<void>;
}
async function executeMigration(plan: MigrationPlan): Promise<string> {
console.log(`Migrating from v${plan.sourceVersion} to v${plan.targetVersion}`);
const completedSteps: MigrationStep[] = [];
try {
for (const step of plan.steps) {
console.log(`Executing: ${step.name}`);
await step.execute();
completedSteps.push(step);
}
console.log('Migration completed successfully');
return 'success';
} catch (error) {
console.error('Migration failed, rolling back...');
// Rollback in reverse order
for (const step of completedSteps.reverse()) {
if (step.rollback) {
try {
await step.rollback();
} catch (rollbackError) {
console.error(`Rollback failed for ${step.name}`);
}
}
}
throw error;
}
}
Graceful Deprecation
async function deprecateContract(
oldContract: ConnectedContract,
newContractAddress: string
): Promise<void> {
// 1. Set deprecation flag in old contract (if supported)
try {
await oldContract.call.set_deprecated({
successor: newContractAddress,
});
console.log('Old contract marked as deprecated');
} catch {
console.log('Contract does not support deprecation flag');
}
// 2. Log deprecation for monitoring
console.log(`Contract ${oldContract.address} deprecated`);
console.log(`Successor: ${newContractAddress}`);
// 3. Update documentation and client configurations
// (manual step or integrate with your deployment system)
}
State Inspection Queries
Common state inspection operations:
// Get all accounts with balances
const accounts = await contract.query.get_all_accounts();
// Get contract metadata
const metadata = await contract.query.get_metadata();
// Get specific state field
const owner = await contract.query.get_owner();
// Get state at specific block (via indexer)
const historicalState = await indexer.getContractStateAtBlock(
CONTRACT_ADDRESS,
blockHeight
);
Related Skills
contract-deployment- Deploying new contract versionscontract-calling- Interacting with contractsmidnight-indexerplugin - Advanced state queries
Related Commands
/midnight:check- Verify environment configuration