AgentSkillsCN

midnight-tooling:contract-calling

在从 Node.js 后端调用已部署的 Midnight 合约、查询合约状态、执行状态变更型交易、构建与合约交互的 API 端点,或实现合约交互的自动化时使用。

SKILL.md
--- frontmatter
name: midnight-tooling:contract-calling
description: Use when calling deployed Midnight contracts from Node.js backends, querying contract state, executing state-changing transactions, building API endpoints that interact with contracts, or automating contract interactions.

Contract Calling

Interact with deployed Midnight contracts from Node.js backends, including read-only queries and state-changing transactions with proof generation.

When to Use

  • Querying contract state from a backend service
  • Executing state-changing transactions
  • Building API endpoints that interact with contracts
  • Automating contract interactions in scripts
  • Testing contract behavior programmatically

Key Concepts

Call Types

TypeProof RequiredState ChangeGas CostUse Case
Read-only queryNoNoNoneFetching current state
State changeYesYesVariableUpdating ledger state

Contract Client Architecture

code
┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│  Your Code  │────▶│   Contract   │────▶│   Prover    │
│             │     │    Client    │     │   Service   │
└─────────────┘     └──────────────┘     └─────────────┘
                           │
                           ▼
                    ┌──────────────┐     ┌─────────────┐
                    │   Indexer    │────▶│  Midnight   │
                    │    Client    │     │   Network   │
                    └──────────────┘     └─────────────┘

Circuit Types in Compact

Compact contracts define circuits that can be invoked:

compact
// Read-only circuit - no proof needed
circuit get_balance(address: Address): Uint {
  return ledger.balances[address];
}

// State-changing circuit - requires proof
circuit transfer(to: Address, amount: Uint): Void {
  // Generates proof, updates ledger
  ledger.balances[sender] -= amount;
  ledger.balances[to] += amount;
}

References

DocumentDescription
api-client-setup.mdSDK initialization and wallet connection
error-handling.mdError types, retries, and timeout handling

Examples

ExampleDescription
read-query/Query contract state without proofs
state-change/Execute state-changing transactions

Quick Start

1. Connect to Deployed Contract

typescript
import { connectContract } from '@midnight-ntwrk/midnight-js-contracts';
import { createWallet } from '@midnight-ntwrk/midnight-js-wallet';
import { Contract } from './build/contract.cjs';

const wallet = await createWallet({ seed: process.env.WALLET_SEED! });

const contract = await connectContract({
  address: '0x1234...', // Deployed contract address
  artifact: Contract,
  wallet,
  config: {
    indexer: 'https://indexer.testnet.midnight.network',
    indexerWs: 'wss://indexer.testnet.midnight.network/ws',
    prover: 'https://prover.testnet.midnight.network',
  },
});

2. Read-Only Query

typescript
// Call read-only circuit (no proof generation)
const balance = await contract.query.get_balance({
  address: '0xabc...',
});

console.log('Balance:', balance);

3. State-Changing Transaction

typescript
// Call state-changing circuit (generates proof)
const result = await contract.call.transfer({
  to: '0xdef...',
  amount: 100n,
});

// Wait for confirmation
const confirmed = await result.waitForConfirmation();
console.log('Transfer confirmed:', confirmed.txHash);

Common Patterns

Building a Contract API Client

typescript
import { connectContract } from '@midnight-ntwrk/midnight-js-contracts';
import type { ContractMethods, ContractState } from './build/contract.d.cts';

class TokenClient {
  private contract: ConnectedContract<ContractState, ContractMethods>;

  static async connect(
    address: string,
    wallet: Wallet,
    config: NetworkConfig
  ): Promise<TokenClient> {
    const client = new TokenClient();
    client.contract = await connectContract({
      address,
      artifact: Contract,
      wallet,
      config,
    });
    return client;
  }

  async getBalance(address: string): Promise<bigint> {
    return this.contract.query.get_balance({ address });
  }

  async transfer(to: string, amount: bigint): Promise<TransactionResult> {
    const result = await this.contract.call.transfer({ to, amount });
    return result.waitForConfirmation();
  }
}

Batch Queries

typescript
async function batchGetBalances(
  contract: ConnectedContract,
  addresses: string[]
): Promise<Map<string, bigint>> {
  const results = new Map<string, bigint>();

  // Run queries in parallel
  const balances = await Promise.all(
    addresses.map((addr) =>
      contract.query.get_balance({ address: addr })
        .then((balance) => ({ addr, balance }))
    )
  );

  for (const { addr, balance } of balances) {
    results.set(addr, balance);
  }

  return results;
}

Transaction with Confirmation

typescript
interface TransactionOptions {
  timeout?: number;
  confirmations?: number;
  onProofProgress?: (progress: number) => void;
}

async function executeTransaction<T>(
  callFn: () => Promise<PendingTransaction<T>>,
  options: TransactionOptions = {}
): Promise<ConfirmedTransaction<T>> {
  const { timeout = 120000, confirmations = 1, onProofProgress } = options;

  const pending = await callFn();

  // Monitor proof generation progress
  if (onProofProgress) {
    pending.onProgress(onProofProgress);
  }

  const result = await pending.waitForConfirmation({
    timeout,
    confirmations,
  });

  if (result.status !== 'confirmed') {
    throw new Error(`Transaction failed: ${result.error || 'timeout'}`);
  }

  return result;
}

// Usage
const result = await executeTransaction(
  () => contract.call.transfer({ to: recipient, amount: 100n }),
  {
    timeout: 180000,
    onProofProgress: (p) => console.log(`Proof: ${(p * 100).toFixed(0)}%`),
  }
);

Handling Private Inputs

typescript
// For circuits with private witness data
const result = await contract.call.private_transfer({
  to: recipient,
  amount: 100n,
  // Private inputs are handled by the SDK
  // They never leave your backend
});

Error Handling

Common errors when calling contracts:

ErrorCauseSolution
Contract not foundInvalid addressVerify contract address
Proof generation failedInvalid witnessCheck circuit inputs
Insufficient balanceLow DUSTFund wallet
Circuit execution failedAssertion violatedReview circuit logic
TimeoutSlow network/proverIncrease timeout

See error-handling.md for detailed error recovery strategies.

Related Skills

  • contract-deployment - Deploying contracts before calling
  • lifecycle-management - Managing contract state
  • midnight-proofs plugin - Server-side proof optimization

Related Commands

  • /midnight:check - Verify environment is configured