Contract Operations
Loading a Contract
Each contract has its own zk circuits. Load them via client.loadContract():
import * as Midday from '@no-witness-labs/midday-sdk';import * as CounterContract from './contracts/counter/contract/index.js';
const loaded = await client.loadContract({ module: CounterContract, zkConfig: Midday.ZkConfig.fromPath('./contracts/counter'), privateStateId: 'my-counter',});The Two-Handle Pattern
The SDK uses two distinct contract types:
LoadedContract— Contract module loaded, ready fordeploy()orjoin(). Has no address.DeployedContract— Connected to the network with a known address. Hascall(),actions,ledgerState(), state watching, etc.
deploy() and join() return a new DeployedContract handle — they don’t mutate the loaded contract:
const loaded = await client.loadContract({ module, zkConfig, privateStateId: 'my-id' });// loaded has: deploy(), join(), effect.deploy(), effect.join()
const deployed = await loaded.deploy();// deployed has: address, actions, call(), ledgerState(), onStateChange(), etc.
console.log(deployed.address); // '0x1234...abcd'Creating a Client
Create a client (zkConfig is per-contract, not per-client):
const client = await Midday.Client.create({ seed: 'your-64-char-hex-seed', networkConfig: Midday.Config.NETWORKS.local, privateStateProvider: Midday.PrivateState.inMemoryPrivateStateProvider(),});Contract Loading Options
With Module and ZkConfig
const loaded = await client.loadContract({ module: CounterContract, zkConfig: Midday.ZkConfig.fromPath('./contracts/counter'), privateStateId: 'my-counter',});With Witnesses
Witnesses are client-side functions that provide private data to contract computations:
const loaded = await client.loadContract({ module: MyContract, zkConfig: Midday.ZkConfig.fromPath('./contracts/my-contract'), privateStateId: 'my-contract', witnesses: { my_witness_function: (context) => { return somePrivateValue; }, },});Browser (HTTP ZkConfig)
const loaded = await client.loadContract({ module: CounterContract, zkConfig: new Midday.ZkConfig.HttpZkConfigProvider('https://cdn.example.com/zk'), privateStateId: 'my-counter',});Deploying a Contract
Deploy a new instance of a contract to the network:
const deployed = await loaded.deploy();console.log(`Deployed at: ${deployed.address}`);With Timeout
const deployed = await loaded.deploy({ timeout: 60_000 }); // 60 secondsIf the timeout is exceeded, a Contract.TxTimeoutError is thrown with the operation field set to 'deploy'.
Joining an Existing Contract
Connect to a contract that’s already deployed:
const deployed = await loaded.join('0x1234...abcd');console.log(deployed.address); // '0x1234...abcd'With Timeout
const deployed = await loaded.join('0x1234...abcd', { timeout: 30_000 });Calling Contract Actions
Typed Actions (Preferred)
The actions proxy provides type-safe methods for each circuit:
await deployed.actions.increment();await deployed.actions.transfer(recipientAddress, 100n);Untyped Fallback
const result = await deployed.call('increment');console.log(`TX Hash: ${result.txHash}`);console.log(`Block: ${result.blockHeight}`);Reading Contract State
Current State
// Parsed ledger stateconst state = await deployed.ledgerState();console.log(state.counter);
// Raw stateconst raw = await deployed.getState();Historical State
const historicalState = await deployed.ledgerStateAt(blockHeight);const historicalRaw = await deployed.getStateAt(blockHeight);Watching 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;}Raw State Watching
const unsub = deployed.onRawStateChange((raw) => { console.log('Raw state changed:', raw);});
for await (const raw of deployed.watchRawState()) { console.log('Raw:', raw); break;}Effect Stream
// For Effect users — returns a Streamconst stream = deployed.effect.watchState();const rawStream = deployed.effect.watchRawState();Read-Only Contract
For dashboards and monitoring — no wallet or proof server needed:
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); // 42nComplete Example
import * as Midday from '@no-witness-labs/midday-sdk';import * as CounterContract from './contracts/counter/contract/index.js';
const client = await Midday.Client.create({ seed: Midday.Config.DEV_WALLET_SEED, networkConfig: Midday.Config.NETWORKS.local, privateStateProvider: Midday.PrivateState.inMemoryPrivateStateProvider(),});
const loaded = await client.loadContract({ module: CounterContract, zkConfig: Midday.ZkConfig.fromPath('./contracts/counter'), privateStateId: 'my-counter',});
const deployed = await loaded.deploy();await deployed.actions.increment();const state = await deployed.ledgerState();console.log(`Counter: ${state.counter}`);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: Midday.Config.DEV_WALLET_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);console.log(`Counter: ${result.counter}`);Error Handling
Contract operations can fail for various reasons:
| Error | Cause |
|---|---|
ContractError | General contract operation failure (deploy, join, call, state read) |
Contract.TxTimeoutError | Deploy or join exceeded timeout (has operation field) |
ClientError | Client initialization or configuration error |
Client.TxTimeoutError | waitForTx exceeded timeout (has txHash field) |
With the Effect API, errors are typed and can be handled explicitly:
const program = deployed.effect.call('increment').pipe( Effect.catchTag('ContractError', (error) => { console.error('Contract operation failed:', error.message); return Effect.fail(error); }));Waiting for Transactions
If you need to wait for a specific transaction to be finalized:
const result = await deployed.call('increment');const finalized = await client.waitForTx(result.txHash);console.log(`Finalized at block: ${finalized.blockHeight}`);
// With timeoutconst finalized = await client.waitForTx(result.txHash, { timeout: 30_000 });