Skip to content

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 for deploy() or join(). Has no address.
  • DeployedContract — Connected to the network with a known address. Has call(), 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 seconds

If 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 state
const state = await deployed.ledgerState();
console.log(state.counter);
// Raw state
const 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 Stream
const 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); // 42n

Complete 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}`);

Error Handling

Contract operations can fail for various reasons:

ErrorCause
ContractErrorGeneral contract operation failure (deploy, join, call, state read)
Contract.TxTimeoutErrorDeploy or join exceeded timeout (has operation field)
ClientErrorClient initialization or configuration error
Client.TxTimeoutErrorwaitForTx 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 timeout
const finalized = await client.waitForTx(result.txHash, { timeout: 30_000 });