@no-witness-labs/midday-sdk
v0.6.4
Published
Developer-friendly SDK for building dapps on Midnight Network
Readme
midday-sdk
Developer-friendly SDK for building dapps on Midnight Network. This project integrates with the Midnight Network.
Installation
pnpm add @no-witness-labs/midday-sdkQuick Start
Promise API
import * as Midday from '@no-witness-labs/midday-sdk';
import * as CounterContract from './contracts/counter/contract/index.js';
// Create client
const client = await Midday.Client.create({
seed: 'your-64-char-hex-seed',
networkConfig: Midday.Config.NETWORKS.local,
privateStateProvider: Midday.PrivateState.inMemoryPrivateStateProvider(),
});
// Load and deploy a contract (deploy returns a DeployedContract handle)
const loaded = await client.loadContract({
module: CounterContract,
zkConfig: Midday.ZkConfig.fromPath('./contracts/counter'),
privateStateId: 'my-counter',
});
const deployed = await loaded.deploy();
console.log(`Deployed at: ${deployed.address}`);
// Call contract actions (typed)
await deployed.actions.increment();
// Or untyped fallback
await deployed.call('increment');
// Read state
const state = await deployed.ledgerState();
console.log(state.counter);Effect API
import * as Midday from '@no-witness-labs/midday-sdk';
import * as CounterContract from './contracts/counter/contract/index.js';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const client = yield* Midday.Client.effect.create({
seed: 'your-64-char-hex-seed',
networkConfig: Midday.Config.NETWORKS.local,
privateStateProvider: Midday.PrivateState.inMemoryPrivateStateProvider(),
});
const loaded = yield* client.effect.loadContract({
module: CounterContract,
zkConfig: Midday.ZkConfig.fromPath('./contracts/counter'),
privateStateId: 'my-counter',
});
const deployed = yield* loaded.effect.deploy();
yield* deployed.effect.actions.increment();
const state = yield* deployed.effect.ledgerState();
return state;
});
const result = await Midday.Runtime.runEffectPromise(program);Effect DI (Dependency Injection)
import * as Midday from '@no-witness-labs/midday-sdk';
import { Cluster, ClusterService } from '@no-witness-labs/midday-sdk/devnet';
import { Effect, Layer } from 'effect';
// Layer composition — all lifecycle is automatic
const ClusterLayer = Cluster.managedLayer({ clusterName: 'my-app' });
const ClientFromCluster = Layer.scoped(
Midday.Client.MiddayClientService,
Effect.gen(function* () {
const cluster = yield* ClusterService;
return yield* Midday.Client.effect.createScoped({
seed: 'your-64-char-hex-seed',
networkConfig: cluster.networkConfig,
privateStateProvider: Midday.PrivateState.inMemoryPrivateStateProvider(),
});
}),
);
const AppLayer = ClientFromCluster.pipe(Layer.provide(ClusterLayer));
const program = Effect.gen(function* () {
const client = yield* Midday.Client.MiddayClientService;
const loaded = yield* client.effect.loadContract({ module: CounterContract });
const deployed = yield* loaded.effect.deploy();
yield* deployed.effect.actions.increment();
return yield* deployed.effect.ledgerState();
});
// Zero lifecycle code — cleanup is automatic (LIFO: client closes → cluster removes)
const result = await Effect.runPromise(program.pipe(Effect.provide(AppLayer)));For simpler cases without devnet management, use Client.layer() directly:
const ClientLayer = Midday.Client.layer({
seed: 'your-64-char-hex-seed',
networkConfig: Midday.Config.NETWORKS.local,
privateStateProvider: Midday.PrivateState.inMemoryPrivateStateProvider(),
});
const result = await Effect.runPromise(program.pipe(Effect.provide(ClientLayer)));Browser Usage (Lace Wallet)
import * as Midday from '@no-witness-labs/midday-sdk';
// Connect to Lace wallet
const wallet = await Midday.Wallet.fromBrowser('preview');
// Create client with connected wallet
const client = await Midday.Client.create({
wallet,
privateStateProvider: Midday.PrivateState.indexedDBPrivateStateProvider({
privateStateStoreName: 'my-app',
}),
});
// Load and deploy contract (zkConfig goes in loadContract, not in client)
const loaded = await client.loadContract({
module: CounterContract,
zkConfig: new Midday.ZkConfig.HttpZkConfigProvider('https://cdn.example.com/zk'),
privateStateId: 'my-counter',
});
const deployed = await loaded.deploy();
await deployed.actions.increment();Wallet Factories
// From seed (Node.js) — positional args
const wallet = await Midday.Wallet.fromSeed(seed, networkConfig);
const balance = await wallet.getBalance();
// From browser (Lace extension)
const wallet = await Midday.Wallet.fromBrowser('preview');
const balance = await wallet.getBalance();
// Read-only (address only, no signing)
const wallet = Midday.Wallet.fromAddress('0x...');
console.log(wallet.address);Read-Only Client
// No wallet, seed, or proof server required
const reader = Midday.Client.createReadonly({
networkConfig: Midday.Config.NETWORKS.preview,
});
const counter = reader.loadContract({ module: CounterContract });
const state = await counter.readState(contractAddress);
console.log(state.counter); // 42nConfiguration
Network Configuration
// Local network
const client = await Midday.Client.create({
networkConfig: Midday.Config.NETWORKS.local,
// ...
});
// Preview network
const client = await Midday.Client.create({
networkConfig: Midday.Config.NETWORKS.preview,
seed: 'your-64-char-hex-seed',
// ...
});
// Custom network
const client = await Midday.Client.create({
networkConfig: {
networkId: 'testnet',
indexer: 'https://indexer.testnet.midnight.network/graphql',
indexerWS: 'wss://indexer.testnet.midnight.network/graphql/ws',
node: 'wss://node.testnet.midnight.network',
proofServer: 'https://proof.testnet.midnight.network',
},
seed: 'your-64-char-hex-seed',
// ...
});Contract Operations
Deploy a New Contract
const loaded = await client.loadContract({ module, zkConfig, privateStateId: 'my-id' });
const deployed = await loaded.deploy();
console.log(`Deployed at: ${deployed.address}`);
// With timeout
const deployed = await loaded.deploy({ timeout: 60_000 });Join an Existing Contract
const loaded = await client.loadContract({ module, zkConfig, privateStateId: 'my-id' });
const deployed = await loaded.join(contractAddress);
// With timeout
const deployed = await loaded.join(contractAddress, { timeout: 60_000 });With Witnesses
const loaded = await client.loadContract({
module,
zkConfig,
privateStateId: 'my-id',
witnesses: {
my_witness_function: myImplementation,
},
});
const deployed = await loaded.deploy();Call Actions
// Typed — preferred
await deployed.actions.increment();
await deployed.actions.transfer(receiverAddress, 100n);
// Untyped fallback
const result = await deployed.call('increment');
console.log(`TX Hash: ${result.txHash}`);
console.log(`Block: ${result.blockHeight}`);Read State
// Parsed state via ledger
const state = await deployed.ledgerState();
// State at specific block
const historicalState = await deployed.ledgerStateAt(blockHeight);
// Raw state
const raw = await deployed.getState();Watch State Changes
// Callback style
const unsub = deployed.onStateChange((state) => {
console.log('Counter:', state.counter);
});
// later: unsub();
// Async iterator
for await (const state of deployed.watchState()) {
console.log('Counter:', state.counter);
if (state.counter > 10n) break;
}
// Effect Stream
const stream = deployed.effect.watchState();Transaction Lifecycle
// Wait for a transaction with timeout
await client.waitForTx(txHash, { timeout: 30_000 });
// Deploy and join support timeout
const deployed = await loaded.deploy({ timeout: 60_000 });API Reference
Namespaces
Midday.Client— Client creation, contract loading, read-only client, fee relayMidday.Contract— Contract types (LoadedContract,DeployedContract,ReadonlyContract)Midday.Config— Network configuration presets and constantsMidday.Wallet— Wallet factories (fromSeed,fromBrowser,fromAddress)Midday.PrivateState— Private state providers (in-memory, IndexedDB)Midday.ZkConfig— ZK configuration providers (fromPath,HttpZkConfigProvider)Midday.Hash— Cryptographic hash utilitiesMidday.Runtime— Effect runtime utilities (runEffectPromise,runEffectWithLogging)Midday.Utils— Hex encoding, address formatting, coin utilities
Services (Effect DI)
| Service | Layer | Description |
|---------|-------|-------------|
| Client.MiddayClientService | Client.layer(config) | Pre-initialized client (scoped — auto-closes) |
| Client.ClientService | Client.ClientLive | Client factory |
| ClusterService | Cluster.managedLayer(config?) | Managed devnet cluster (auto-starts/removes) |
Key Types
import type {
// Client
ClientConfig,
MiddayClient,
ReadonlyClient,
ReadonlyClientConfig,
WaitForTxOptions,
// Contract
LoadedContract,
DeployedContract,
ReadonlyContract,
DeployOptions,
JoinOptions,
CallResult,
ContractProviders,
// Wallet
ConnectedWallet,
ReadonlyWallet,
WalletConnection,
WalletBalance,
DustBalance,
WalletProviders,
// Config
NetworkConfig,
// Errors
ClientError,
ContractError,
WalletError,
} from '@no-witness-labs/midday-sdk';Error Types
| Error | Module | Description |
|-------|--------|-------------|
| ClientError | Client | Client creation/operation failures |
| Client.TxTimeoutError | Client | Transaction timeout (has txHash) |
| ContractError | Contract | Contract deployment/call failures |
| Contract.TxTimeoutError | Contract | Deploy/join timeout (has operation) |
| WalletError | Wallet | Wallet connection/operation failures |
License
MIT
