hedera-consensus-service について
hedera-consensus-serviceは、`@hiero-ledger/sdk`を使うHedera Consensus Service向けJavaScript SDKのスキルです。トピックの作成、順序付きメッセージの送信、ミラーノード経由でのコンセンサスタイムスタンプ付きメッセージの購読を説明します。
主な機能
- 01トピックの作成と管理
- 02文字列またはバイナリメッセージの送信
- 03ミラーノード経由のメッセージ購読
- 04大きなメッセージの自動分割と再構成
- 05キーによる送信権限の制御
- 06メッセージ単位のカスタム手数料設定
活用シーン
- 01改変できない監査ログやイベントログの構築
- 02複数の購読者へのイベント配信
- 03コンセンサスタイムスタンプによるサプライチェーンメッセージの追跡
- 04トピックへの投稿権限の制御
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ hedera-dev/ hedera-skills --skill hedera-consensus-serviceインストール後、Claude Code で/hedera-consensus-serviceを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonspec.jsonreferences/api-reference.md詳細
Hedera Consensus Service (HCS) — JavaScript SDK
HCS は、コンセンサスタイムスタンプ付きの分散型で順序付けられたメッセージログを提供します。これは pub/sub システムのように動作します。トピックを作成し、そこにメッセージを送信し、ミラーノードを介してリアルタイムでメッセージを受信するためにサブスクライブします。メッセージはイミュータブルで、順序付けられ、ネットワークコンセンサスによってタイムスタンプが付けられます — 監査証跡、イベントログ、サプライチェーン追跡、分散型コミュニケーションに役立ちます。
セットアップ
すべてのインポートは @hiero-ledger/sdk から行います。2 つのセットアップパターンがあります:
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);
複数のサブスクライバーによる Pub/Sub
複数のサービスが同じトピックに独立してサブスクライブできます。それぞれが 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);
よくある落とし穴
-
ミラーノードの同期遅延: トピックを作成した後、購読する前に 3〜5 秒待ってください。ミラーノードが新しいトピックをインデックス化するのに時間がかかります。
-
チャンクの再組み立ては自動: 購読すると、複数のチャンクとして送信された場合でも完全なメッセージを受け取ります。再組み立ては SDK が処理します。
-
購読に
execute()はありません:TopicMessageQueryは.execute()ではなく.subscribe()を使用します。返されるのはTransactionResponseではなくSubscriptionHandleです。 -
メッセージは不変: 一度送信されたメッセージは編集も削除もできません。これを念頭に置いてメッセージのスキーマを設計してください。
-
シーケンス番号は 1 から始まる: トピックの最初のメッセージのシーケンス番号は 0 ではなく 1 です。
-
送信キーはアクセス制御を意味する: 送信キーを設定すると、そのキーを持つユーザーだけが投稿できます。オープンなトピックにする場合は省略してください。
-
文字列と Uint8Array:
setMessage()はどちらも受け付けます。購読側でデコードするにはBuffer.from(message.contents).toString("utf8")を使用してください。 -
クリーンアップ: 終了時には必ず
handle.unsubscribe()を呼び出し、シャットダウン時にはclient.close()を呼び出してください。
参照ファイル
references/api-reference.md— すべての HCS クラスとそのメソッドおよびプロパティの完全な一覧