Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add UvRoxx/midnight-agent-skills --skill "midnight-sdk-guide"
Install specific skill from multi-skill repository
# Description
TypeScript SDK integration guide for Midnight dApps. Use this skill when building frontends, connecting wallets, or calling contracts from TypeScript. Triggers on "SDK", "TypeScript", "wallet integration", "connect dApp", "call contract".
# SKILL.md
name: midnight-sdk-guide
description: TypeScript SDK integration guide for Midnight dApps. Use this skill when building frontends, connecting wallets, or calling contracts from TypeScript. Triggers on "SDK", "TypeScript", "wallet integration", "connect dApp", "call contract".
license: MIT
metadata:
author: webisoft
version: "1.0.0"
midnight-version: "0.27.0"
Midnight TypeScript SDK Quick Reference
Guide for integrating Midnight contracts with TypeScript applications.
When to Use
Reference this guide when:
- Building a frontend for a Midnight dApp
- Connecting the Lace wallet
- Deploying or calling contracts from TypeScript
- Handling errors from the SDK
- Building transaction flows
Installation
npm install @midnight-ntwrk/midnight-js-contracts
Core Types
Contract Deployment
import { deployContract, ContractDeployment } from '@midnight-ntwrk/midnight-js-contracts';
const deployment: ContractDeployment = await deployContract({
contract: compiledContract,
privateState: initialPrivateState,
args: constructorArgs,
});
const { contractAddress, initialState } = deployment;
Contract Interaction
import { callContract } from '@midnight-ntwrk/midnight-js-contracts';
// Call a circuit
const result = await callContract({
contractAddress,
circuitName: 'increment',
args: [amount],
privateState: currentPrivateState,
});
// Result contains new state and return value
const { newPrivateState, returnValue, proof } = result;
Providers
import {
MidnightProvider,
createMidnightProvider
} from '@midnight-ntwrk/midnight-js-contracts';
const provider = await createMidnightProvider({
indexer: 'https://indexer.testnet.midnight.network',
node: 'https://node.testnet.midnight.network',
proofServer: 'https://prover.testnet.midnight.network',
});
State Management
interface ContractState<T> {
publicState: PublicState;
privateState: T;
}
// Subscribe to state changes
provider.subscribeToContract(contractAddress, (state) => {
console.log('New state:', state);
});
Transaction Building
import { buildTransaction } from '@midnight-ntwrk/midnight-js-contracts';
const tx = await buildTransaction({
contractAddress,
circuitName: 'transfer',
args: [recipient, amount],
privateState,
});
// Sign and submit
const signedTx = await wallet.signTransaction(tx);
const txHash = await provider.submitTransaction(signedTx);
Wallet Integration
Browser Detection
declare global {
interface Window {
midnight?: {
mnLace?: MidnightProvider;
};
}
}
function isWalletAvailable(): boolean {
return typeof window !== 'undefined'
&& window.midnight?.mnLace !== undefined;
}
DApp Connector API
interface DAppConnectorAPI {
enable(): Promise<MidnightAPI>;
isEnabled(): Promise<boolean>;
apiVersion(): string;
name(): string;
icon(): string;
}
async function connectWallet(): Promise<MidnightAPI> {
if (!window.midnight?.mnLace) {
throw new Error('Midnight Lace wallet not found');
}
return await window.midnight.mnLace.enable();
}
MidnightAPI Interface
interface MidnightAPI {
getUsedAddresses(): Promise<string[]>;
getBalance(): Promise<Balance>;
signTx(tx: Transaction): Promise<SignedTransaction>;
submitTx(signedTx: SignedTransaction): Promise<TxHash>;
signData(address: string, payload: string): Promise<Signature>;
}
React Hook
export function useWallet() {
const [state, setState] = useState({
isConnected: false,
address: null as string | null,
isLoading: false,
error: null as string | null,
});
const connect = useCallback(async () => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
if (!window.midnight?.mnLace) {
throw new Error('Please install Midnight Lace wallet');
}
const api = await window.midnight.mnLace.enable();
const addresses = await api.getUsedAddresses();
setState({
isConnected: true,
address: addresses[0] || null,
isLoading: false,
error: null,
});
return api;
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: error instanceof Error ? error.message : 'Failed',
}));
throw error;
}
}, []);
return { ...state, connect };
}
Connection Flow
1. User clicks "Connect Wallet"
2. DApp calls window.midnight.mnLace.enable()
3. Wallet popup asks user to approve
4. User approves β DApp receives MidnightAPI
5. DApp can now interact with wallet
Error Handling
import { MidnightError, ContractError } from '@midnight-ntwrk/midnight-js-contracts';
try {
await callContract({ ... });
} catch (error) {
if (error instanceof ContractError) {
console.error('Contract assertion failed:', error.message);
} else if (error instanceof MidnightError) {
console.error('Network error:', error.code);
}
}
DApp Connector Errors
import { ErrorCodes } from '@midnight-ntwrk/dapp-connector-api';
// ErrorCodes.Rejected - User rejected the request
// ErrorCodes.InvalidRequest - Malformed transaction or request
// ErrorCodes.InternalError - DApp connector couldn't process request
try {
const api = await window.midnight.mnLace.enable();
} catch (error) {
if (error.code === ErrorCodes.Rejected) {
console.log('User rejected wallet connection');
}
}
ContractTypeError
// Thrown when there's a contract type mismatch
try {
const contract = await findDeployedContract(provider, address, MyContract);
} catch (e) {
if (e instanceof ContractTypeError) {
console.error('Contract type mismatch:', e.circuitIds);
}
}
Private State Witnesses
TypeScript witnesses implement the Compact witness declarations:
// counter-witnesses.ts
import type { Witnesses } from './managed/counter';
export type CounterPrivateState = {
localSecretKey: Uint8Array;
storedValues: Map<string, bigint>;
};
export const createWitnesses = (
state: CounterPrivateState
): Witnesses<CounterPrivateState> => ({
local_secret_key: () => state.localSecretKey,
store_secret_value: (value: bigint) => {
state.storedValues.set('secret', value);
return undefined; // returns []
},
get_secret_value: () => {
return state.storedValues.get('secret') ?? 0n;
},
});
export const createInitialState = (): CounterPrivateState => ({
localSecretKey: crypto.getRandomValues(new Uint8Array(32)),
storedValues: new Map(),
});
Best Practices
- Always check wallet availability before connecting
- Handle user rejection gracefully
- Store connection state in context/global state
- Provide clear loading/error feedback
- Test with Midnight Lace extension
- Keep private state secure - never log or expose it
Rules
See /rules/ directory for detailed documentation:
- wallet-integration.md - Complete wallet integration patterns
- error-handling.md - Error handling best practices
References
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.