Loading...
Loading...
How to create topics, submit messages, and subscribe to real-time message streams on Hedera using the Hiero JavaScript SDK (@hiero-ledger/sdk). Use this skill whenever the user wants to work with Hedera Consensus Service (HCS), including topic creation, message submission, pub/sub messaging, mirror node subscriptions, chunked large messages, topic fees, or any consensus-related operation in JavaScript or TypeScript. Also trigger when users mention @hashgraph/sdk topic operations, event logging on Hedera, decentralized messaging, audit trails, or ordered message streams.
npx skill4agent add hedera-dev/hedera-skills hedera-consensus-service@hiero-ledger/sdkimport { 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),
);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()}`);adminKeysubmitKeyimport { 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}`);stringUint8ArraytopicSequenceNumbertopicRunningHashawait (
await new TopicMessageSubmitTransaction()
.setTopicId(topicId)
.setMessage("Authorized message")
.freezeWith(client)
.sign(submitKey)
).execute(client);TopicMessageQueryimport { 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);TopicMessageconsensusTimestampcontentsUint8ArraysequenceNumberrunningHashchunksTopicMessageChunk[]initialTransactionIdconst 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}`);
}setChunkSize()setMaxChunks()TopicMessageimport { 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}`);references/api-reference.mdTopicInfo| Key | Purpose |
|---|---|
| Update/delete the topic; rotate other keys |
| Authorize message submission (if absent, open to all) |
| Update custom fee schedule |
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);execute()TopicMessageQuery.subscribe().execute()SubscriptionHandleTransactionResponsesetMessage()Buffer.from(message.contents).toString("utf8")handle.unsubscribe()client.close()references/api-reference.md