mcpField
H
hedera-consensus-service

hedera-consensus-service

github.com/hedera-dev开发28562 次安装8 周前更新
代码仓库

关于 hedera-consensus-service

hedera-consensus-service 是介绍 Hedera Consensus Service 的 JavaScript SDK 技能,使用 `@hiero-ledger/sdk`。它说明如何创建主题、提交有序消息,以及通过镜像节点订阅带共识时间戳的消息。

主要功能

  1. 01创建和管理主题
  2. 02提交字符串或二进制消息
  3. 03通过镜像节点订阅消息
  4. 04自动拆分和重组较大消息
  5. 05使用密钥控制提交权限
  6. 06设置逐消息自定义费用

适用场景

  1. 01构建不可变的审计或事件日志
  2. 02向多个订阅者分发事件
  3. 03用共识时间戳追踪供应链消息
  4. 04限制主题的发帖权限

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/hedera-dev/hedera-skills --skill hedera-consensus-service

安装后在 Claude Code 中输入/hedera-consensus-service确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
spec.json
references/
api-reference.md

详细介绍

Hedera Consensus Service(HCS)——JavaScript SDK

HCS 提供去中心化、有序的消息日志,并附带共识时间戳。它的工作方式类似发布/订阅系统:创建主题,向其提交消息,然后通过镜像节点订阅以实时接收消息。消息不可变,按顺序排列,并由网络共识确定时间戳,可用于审计跟踪、事件日志、供应链追踪和去中心化通信。

设置

所有导入都来自 @hiero-ledger/sdk。有两种设置方式:

Client + setOperator(直接方式)
import { Client, AccountId, PrivateKey } from "@hiero-ledger/sdk";

const client = Client.forName(process.env.HEDERA_NETWORK)
    .setOperator(
        AccountId.fromString(process.env.OPERATOR_ID),
        PrivateKey.fromStringECDSA(process.env.OPERATOR_KEY),
    );
Wallet + LocalProvider(基于签名者)
import { Wallet, LocalProvider } from "@hiero-ledger/sdk";

const provider = new LocalProvider();
const wallet = new Wallet(process.env.OPERATOR_ID, process.env.OPERATOR_KEY, provider);

使用签名者模式时,请使用 freezeWithSigner(wallet)、signWithSigner(wallet)、executeWithSigner(wallet) 和 getReceiptWithSigner(wallet)。

创建主题

import { TopicCreateTransaction } from "@hiero-ledger/sdk";

const { topicId } = await (
    await new TopicCreateTransaction()
        .setTopicMemo("My event log")
        .setAdminKey(operatorKey)       // allows update/delete
        .setSubmitKey(operatorKey)      // restricts who can post
        .execute(client)
).getReceipt(client);

console.log(`Topic created: ${topicId.toString()}`);

关键行为:

  • 如果没有 adminKey,主题无法更新或删除(只能延长到期时间)。
  • 如果没有 submitKey,任何人都可以提交消息。
  • 默认自动续期时长为 90 天。
带自定义费用的主题

主题可以按条消息收取费用(以 Hbar 或代币计价):

import { TopicCreateTransaction, CustomFixedFee, Hbar } from "@hiero-ledger/sdk";

const fee = new CustomFixedFee()
    .setAmount(new Hbar(1).toTinybars())
    .setFeeCollectorAccountId(collectorId);

const { topicId } = await (
    await new TopicCreateTransaction()
        .setAdminKey(operatorKey)
        .setSubmitKey(operatorKey)
        .setFeeScheduleKey(operatorKey)
        .setCustomFees([fee])
        .addFeeExemptKey(trustedKey)  // this key skips fees
        .execute(client)
).getReceipt(client);

支付自定义费用时,提交者可以设置愿意支付的最高金额:

import { CustomFeeLimit, CustomFixedFee, Hbar, HbarUnit } from "@hiero-ledger/sdk";

const limit = new CustomFeeLimit()
    .setAccountId(payerId)
    .setFees([
        new CustomFixedFee().setAmount(Hbar.from(2, HbarUnit.Hbar).toTinybars())
    ]);

await new TopicMessageSubmitTransaction()
    .setTopicId(topicId)
    .setMessage("Hello")
    .setCustomFeeLimits([limit])
    .execute(client);

提交消息

import { TopicMessageSubmitTransaction } from "@hiero-ledger/sdk";

const response = await new TopicMessageSubmitTransaction()
    .setTopicId(topicId)
    .setMessage("Hello, Hedera!")
    .execute(client);

const receipt = await response.getReceipt(client);
console.log(`Sequence: ${receipt.topicSequenceNumber}`);

消息可以是 string 或 Uint8Array。收据包含 topicSequenceNumber(每条消息递增)和 topicRunningHash。

存在提交密钥时

如果主题有提交密钥,消息必须由该密钥签名:

await (
    await new TopicMessageSubmitTransaction()
        .setTopicId(topicId)
        .setMessage("Authorized message")
        .freezeWith(client)
        .sign(submitKey)
).execute(client);

订阅消息

TopicMessageQuery 通过镜像节点创建实时订阅。消息在达成共识后到达。

import { TopicMessageQuery } from "@hiero-ledger/sdk";

const handle = new TopicMessageQuery()
    .setTopicId(topicId)
    .setStartTime(0)  // from the beginning
    .subscribe(
        client,
        (message, error) => console.error("Error:", error),
        (message) => {
            console.log(
                `[${message.consensusTimestamp}] #${message.sequenceNumber}: ` +
                Buffer.from(message.contents).toString("utf8")
            );
        },
    );

// Later, to stop receiving:
handle.unsubscribe();

重要:创建主题后,请等待几秒再订阅——镜像节点需要时间同步新主题。

订阅选项
new TopicMessageQuery()
    .setTopicId(topicId)
    .setStartTime(startTimestamp)        // receive from this time forward
    .setEndTime(endTimestamp)            // stop after this time
    .setLimit(100)                       // max messages to receive
    .setMaxAttempts(20)                  // retry attempts (default: 20)
    .setMaxBackoff(8000)                 // max retry delay ms (default: 8000)
    .setErrorHandler((msg, err) => {})   // error callback
    .setCompletionHandler(() => {})      // fires when limit/endTime reached
    .subscribe(client, errorHandler, messageHandler);
TopicMessage 属性

每条接收到的 TopicMessage 都包含:

  • consensusTimestamp — 消息达成共识的时间
  • contents — Uint8Array 消息正文(会自动从分块中重新组装)
  • sequenceNumber — 消息在主题中的位置(从 1 开始)
  • runningHash — 截至此消息时主题的 SHA-384 累积哈希
  • chunks — 消息被分块时的各个 TopicMessageChunk[]
  • initialTransactionId — 原始交易 ID(适用于分块消息)

分块消息

大于 1024 字节的消息会自动拆分为多个块。每个块都是网络上的一笔独立交易。SDK 会在提交时处理拆分,并在订阅时重新组装。

const largeMessage = "x".repeat(5000); // 5KB message

// Option 1: execute() returns first chunk's response
const response = await new TopicMessageSubmitTransaction()
    .setTopicId(topicId)
    .setMessage(largeMessage)
    .execute(client);

// Option 2: executeAll() returns all chunk responses
const responses = await new TopicMessageSubmitTransaction()
    .setTopicId(topicId)
    .setMessage(largeMessage)
    .setMaxChunks(30)       // default: 20 (max ~20KB at 1024/chunk)
    .setChunkSize(2048)     // override chunk size (default: 1024)
    .executeAll(client);

for (const resp of responses) {
    const receipt = await resp.getReceipt(client);
    console.log(`Chunk seq: ${receipt.topicSequenceNumber}`);
}

限制:

  • 默认块大小:1024 字节
  • 默认最大块数:20(因此默认最大消息大小约为 20KB)
  • 可通过 setChunkSize() 和 setMaxChunks() 配置
  • 订阅者会自动将分块重新组装为单个 TopicMessage

更新主题

import { TopicUpdateTransaction } from "@hiero-ledger/sdk";

await new TopicUpdateTransaction()
    .setTopicId(topicId)
    .setTopicMemo("Updated memo")
    .setSubmitKey(newSubmitKey)
    .execute(client);

所有更新操作都需要管理员密钥。特定密钥的更新方式:

  • 更改管理员密钥需要旧管理员密钥和新管理员密钥共同签名
  • 设置新的自动续期账户需要该账户签名
  • 可以使用 clearAdminKey()、clearSubmitKey() 等清除密钥
更新主题费用
await new TopicUpdateTransaction()
    .setTopicId(topicId)
    .setCustomFees([newFee])
    .addFeeExemptKey(anotherKey)
    .execute(client);

删除主题

import { TopicDeleteTransaction } from "@hiero-ledger/sdk";

await new TopicDeleteTransaction()
    .setTopicId(topicId)
    .execute(client);

需要管理员密钥。删除后,对该主题的所有操作都将失败。

查询主题信息

import { TopicInfoQuery } from "@hiero-ledger/sdk";

const info = await new TopicInfoQuery()
    .setTopicId(topicId)
    .execute(client);

console.log(`Memo: ${info.topicMemo}`);
console.log(`Sequence: ${info.sequenceNumber}`);
console.log(`Admin key: ${info.adminKey}`);
console.log(`Submit key: ${info.submitKey}`);

完整的 TopicInfo 属性列表请参阅 references/api-reference.md。

密钥角色

密钥用途
adminKey更新/删除主题;轮换其他密钥
submitKey授权提交消息(如果不存在,则任何人都可以提交)
feeScheduleKey更新自定义费用表

常见模式

事件日志 / 审计跟踪

为每个实体或事件类型创建一个主题。提交结构化 JSON 消息。从某个服务订阅,以构建读取模型。

const event = JSON.stringify({
    type: "ORDER_PLACED",
    orderId: "12345",
    timestamp: Date.now(),
    data: { items: 3, total: 99.99 },
});

await new TopicMessageSubmitTransaction()
    .setTopicId(ordersTopic)
    .setMessage(event)
    .execute(client);
多订阅者发布/订阅

多个服务可以独立订阅同一主题。每个服务都通过 setStartTime 维护自己的游标。

// Service A: process all messages from the beginning
new TopicMessageQuery()
    .setTopicId(topicId)
    .setStartTime(0)
    .subscribe(client, null, processMessage);

// Service B: only new messages from now
new TopicMessageQuery()
    .setTopicId(topicId)
    .subscribe(client, null, processMessage);

常见注意事项

  1. 镜像节点同步延迟:创建主题后,请等待 3-5 秒再订阅。镜像节点需要时间为新主题建立索引。

  2. 分块会自动重组:订阅时,即使消息是以多个分块提交的,您也会收到完整消息。SDK 会处理重组。

  3. 订阅不使用 execute():TopicMessageQuery 使用 .subscribe(),而非 .execute()。它返回 SubscriptionHandle,而非 TransactionResponse。

  4. 消息不可变:消息提交后,无法编辑或删除。设计消息架构时请考虑这一点。

  5. 序列号从 1 开始:主题上的第一条消息序列号为 1,而非 0。

  6. 提交密钥用于访问控制:如果设置了提交密钥,则只有持有该密钥的用户才能发布消息。对于开放主题,请省略该密钥。

  7. String 与 Uint8Array:setMessage() 接受这两种类型。在订阅端使用 Buffer.from(message.contents).toString("utf8") 进行解码。

  8. 清理:操作完成后,请务必调用 handle.unsubscribe();关闭时请调用 client.close()。

参考文件

  • references/api-reference.md — HCS 所有类及其方法和属性的完整列表

评论

支持 Markdown