合约调用Skill midnight-tooling:contract-calling

该技能用于从Node.js后端程序与Midnight区块链上已部署的智能合约进行交互。核心功能包括:执行无需生成零知识证明的只读查询以获取合约状态,以及发起需要生成证明的状态变更交易来更新链上数据。适用于构建与合约交互的API服务、自动化脚本测试以及程序化验证合约逻辑。关键词:Midnight合约调用,Node.js后端交互,零知识证明,智能合约查询,状态变更交易,区块链API开发。

智能合约 0 次安装 6 次浏览 更新于 2/26/2026

name: midnight-tooling:contract-calling description: 用于从Node.js后端调用已部署的Midnight合约、查询合约状态、执行状态变更交易、构建与合约交互的API端点或自动化合约交互。

合约调用

从Node.js后端与已部署的Midnight合约进行交互,包括无需证明的只读查询和需要生成证明的状态变更交易。

使用场景

  • 从后端服务查询合约状态
  • 执行状态变更交易
  • 构建与合约交互的API端点
  • 在脚本中自动化合约交互
  • 以编程方式测试合约行为

核心概念

调用类型

类型 是否需要证明 状态变更 燃料成本 使用场景
只读查询 获取当前状态
状态变更 可变 更新账本状态

合约客户端架构

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   你的代码   │────▶│   合约客户端   │────▶│   证明器服务   │
│             │     │              │     │             │
└─────────────┘     └──────────────┘     └─────────────┘
                           │
                           ▼
                    ┌──────────────┐     ┌─────────────┐
                    │   索引器客户端   │────▶│   Midnight   │
                    │              │     │   网络      │
                    └──────────────┘     └─────────────┘

Compact语言中的电路类型

Compact合约定义了可被调用的电路:

// 只读电路 - 无需证明
circuit get_balance(address: Address): Uint {
  return ledger.balances[address];
}

// 状态变更电路 - 需要证明
circuit transfer(to: Address, amount: Uint): Void {
  // 生成证明,更新账本
  ledger.balances[sender] -= amount;
  ledger.balances[to] += amount;
}

参考资料

文档 描述
api-client-setup.md SDK初始化和钱包连接
error-handling.md 错误类型、重试和超时处理

示例

示例 描述
read-query/ 无需证明查询合约状态
state-change/ 执行状态变更交易

快速开始

1. 连接到已部署的合约

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...', // 已部署的合约地址
  artifact: Contract,
  wallet,
  config: {
    indexer: 'https://indexer.testnet.midnight.network',
    indexerWs: 'wss://indexer.testnet.midnight.network/ws',
    prover: 'https://prover.testnet.midnight.network',
  },
});

2. 只读查询

// 调用只读电路(无需生成证明)
const balance = await contract.query.get_balance({
  address: '0xabc...',
});

console.log('余额:', balance);

3. 状态变更交易

// 调用状态变更电路(生成证明)
const result = await contract.call.transfer({
  to: '0xdef...',
  amount: 100n,
});

// 等待确认
const confirmed = await result.waitForConfirmation();
console.log('转账已确认:', confirmed.txHash);

常用模式

构建合约API客户端

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();
  }
}

批量查询

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

  // 并行运行查询
  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;
}

带确认的交易

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();

  // 监控证明生成进度
  if (onProofProgress) {
    pending.onProgress(onProofProgress);
  }

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

  if (result.status !== 'confirmed') {
    throw new Error(`交易失败: ${result.error || '超时'}`);
  }

  return result;
}

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

处理私有输入

// 对于包含私有见证数据的电路
const result = await contract.call.private_transfer({
  to: recipient,
  amount: 100n,
  // 私有输入由SDK处理
  // 它们永远不会离开你的后端
});

错误处理

调用合约时的常见错误:

错误 原因 解决方案
未找到合约 地址无效 验证合约地址
证明生成失败 见证无效 检查电路输入
余额不足 DUST不足 为钱包充值
电路执行失败 断言违反 检查电路逻辑
超时 网络/证明器慢 增加超时时间

详细错误恢复策略请参阅 error-handling.md

相关技能

  • contract-deployment - 在调用前部署合约
  • lifecycle-management - 管理合约状态
  • midnight-proofs 插件 - 服务器端证明优化

相关命令

  • /midnight:check - 验证环境配置