Skip to content

TopicUpdateTransaction clear methods silently fail to clear fields on the network #4190

Description

@Jexsie

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

  1. Install @hiero-ledger/sdk and configure a client against test/local-net.
  2. 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);
  3. 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);
  4. Query the topic:
    const info = await new TopicInfoQuery().setTopicId(topicId).execute(client);
    console.log(info.submitKey); // expected: null  — actual: the original PublicKey
  5. 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

Metadata

Metadata

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions