Description
The HAPI specification requires sentinel values to clear fields — empty KeyList for keys, "" for memo, AccountID(0, 0, 0) for the auto-renew account. The SDK's clearX() methods instead set the local field to null, and the protobuf serializer drops null fields entirely. Because absent fields in ConsensusUpdateTopicTransactionBody mean "do not modify", the network never clears them.
The bug is visible directly in the SDK source src/topic/TopicUpdateTransaction.js
clearTopicMemo() { this._topicMemo = null; }
clearAdminKey() { this._adminKey = null; }
clearSubmitKey() { this._submitKey = null; }
clearFeeScheduleKey() { this._feeScheduleKey = null; }
clearAutoRenewAccountId() { this._autoRenewAccountId = null; }
…and in _makeTransactionData(), every one of those fields is guarded with != null, so a null field is never serialized into the protobuf:
adminKey: this._adminKey != null ? this._adminKey._toProtobufKey() : null,
submitKey: this._submitKey != null ? this._submitKey._toProtobufKey() : null,
feeScheduleKey: this._feeScheduleKey != null ? this._feeScheduleKey._toProtobufKey() : null,
memo: this._topicMemo != null ? { value: this._topicMemo } : null,
autoRenewAccount: this._autoRenewAccountId != null ? this._autoRenewAccountId._toProtobuf() : null,
clearFeeExemptKeys() and clearCustomFees() are not affected — they set their fields to [], which is the documented sentinel.
Steps to reproduce
- Install
@hiero-ledger/sdk and configure a client against test/local-net.
- Create a topic with both an admin key and a submit key:
const adminKey = PrivateKey.generateED25519();
const submitKey = PrivateKey.generateED25519();
const createTx = await new TopicCreateTransaction()
.setAdminKey(adminKey.publicKey)
.setSubmitKey(submitKey.publicKey)
.freezeWith(client)
.sign(adminKey);
const { topicId } = await (await createTx.execute(client)).getReceipt(client);
- Submit an update that calls
clearSubmitKey():
const updateTx = await new TopicUpdateTransaction()
.setTopicId(topicId)
.clearSubmitKey()
.freezeWith(client)
.sign(adminKey);
await (await updateTx.execute(client)).getReceipt(client);
- Query the topic:
const info = await new TopicInfoQuery().setTopicId(topicId).execute(client);
console.log(info.submitKey); // expected: null — actual: the original PublicKey
- Repeat with
clearAdminKey(), clearFeeScheduleKey(), clearAutoRenewAccountId(), and clearTopicMemo() — all reproduce the same no-op behaviour.
A workaround that does clear the fields:
new TopicUpdateTransaction()
.setTopicId(topicId)
.setSubmitKey(new KeyList()) // clears submitKey
.setAdminKey(new KeyList()) // clears adminKey
.setFeeScheduleKey(new KeyList()) // clears feeScheduleKey
.setAutoRenewAccountId("0.0.0") // clears autoRenewAccountId
.setTopicMemo(""); // clears topicMemo
Additional context
No response
Hedera network
testnet
Version
v2.85.0
Operating system
macOS
Description
The HAPI specification requires sentinel values to clear fields — empty
KeyListfor keys,""for memo,AccountID(0, 0, 0)for the auto-renew account. The SDK'sclearX()methods instead set the local field tonull, and the protobuf serializer dropsnullfields entirely. Because absent fields inConsensusUpdateTopicTransactionBodymean "do not modify", the network never clears them.The bug is visible directly in the SDK source
src/topic/TopicUpdateTransaction.js…and in
_makeTransactionData(), every one of those fields is guarded with!= null, so anullfield is never serialized into the protobuf:clearFeeExemptKeys()andclearCustomFees()are not affected — they set their fields to[], which is the documented sentinel.Steps to reproduce
@hiero-ledger/sdkand configure a client against test/local-net.clearSubmitKey():clearAdminKey(),clearFeeScheduleKey(),clearAutoRenewAccountId(), andclearTopicMemo()— all reproduce the same no-op behaviour.A workaround that does clear the fields:
Additional context
No response
Hedera network
testnet
Version
v2.85.0
Operating system
macOS