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- 验证环境配置