| name | midnight-dapp:state-management |
| description | Use when reading public ledger state, implementing reactive UI that updates with chain state, caching state for performance, or understanding public vs private state in Midnight DApps. |
State Management
Read, sync, and cache contract state in Midnight DApps with proper handling of the dual-state model.
When to Use
- Reading public ledger state from deployed contracts
- Implementing reactive UI that updates with chain state
- Caching state for performance optimization
- Understanding when to use public vs private state
- Syncing state across multiple components
Key Concepts
Dual-State Model
Midnight contracts have two types of state:
| State Type | Storage Location | Access Method | Visibility |
|---|
| Public Ledger State | On-chain (indexer) | contract.state.* | Anyone can read |
| Private Local State | Browser (LevelDB) | WitnessContext.privateState | Only local user |
This is fundamentally different from Ethereum where all state is public on-chain.
State Access Patterns
const totalSupply = await contract.state.total_supply();
const balance = await contract.state.balances.get(address);
const witnesses = {
get_secret: ({ privateState }) => privateState.secretKey,
};
Chain Synchronization
State on-chain can change from other transactions. DApps must handle:
- Initial Load - Fetch current state when component mounts
- Polling - Periodically check for updates
- WebSocket - Subscribe to real-time updates
- Optimistic Updates - Update UI before confirmation
References
Examples
Quick Start
1. Read Simple State Values
const contract = await deployedContract();
const totalSupply: bigint = await contract.state.total_supply();
const balance: bigint | undefined = await contract.state.balances.get(userAddress);
const isMember: boolean = await contract.state.members.has(userAddress);
2. Create a State Hook
function useContractState<T>(
contract: Contract,
accessor: (state: ContractState) => Promise<T>
) {
const [value, setValue] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function fetchState() {
setLoading(true);
const result = await accessor(contract.state);
if (!cancelled) {
setValue(result);
setLoading(false);
}
}
fetchState();
return () => { cancelled = true; };
}, [contract, accessor]);
return { value, loading };
}
3. Subscribe to Updates
useEffect(() => {
const interval = setInterval(async () => {
const newBalance = await contract.state.balances.get(address);
setBalance(newBalance);
}, 5000);
return () => clearInterval(interval);
}, [contract, address]);
Common Patterns
Type-Safe State Reading
interface ContractState {
total_supply(): Promise<bigint>;
balances: {
get(address: string): Promise<bigint | undefined>;
};
members: {
has(address: string): Promise<boolean>;
};
}
Error Handling
async function safeStateRead<T>(
accessor: () => Promise<T>,
fallback: T
): Promise<T> {
try {
return await accessor();
} catch (error) {
console.error('State read failed:', error);
return fallback;
}
}
const balance = await safeStateRead(
() => contract.state.balances.get(address),
0n
);
Optimistic Updates
const [balance, setBalance] = useState<bigint>(0n);
async function transfer(to: string, amount: bigint) {
setBalance(prev => prev - amount);
try {
await contract.callTx.transfer(to, amount, witnesses);
} catch (error) {
setBalance(prev => prev + amount);
throw error;
}
}
Related Skills
wallet-integration - Required for setting up providers
proof-handling - Witness implementation that accesses private state
transaction-flows - Submitting state-changing transactions
error-handling - Handling state read errors
Related Commands
/dapp-check - Validates provider configuration
/dapp-debug state - Debug state synchronization issues