Skip to content

fix(sdk-coin-hbar): merge same-account entries in buildTransferData to fix ACCOUNT_REPEATED#8774

Merged
Doddanna17 merged 1 commit into
masterfrom
SI-539-hbar-self-transfer-merge-entries
May 15, 2026
Merged

fix(sdk-coin-hbar): merge same-account entries in buildTransferData to fix ACCOUNT_REPEATED#8774
Doddanna17 merged 1 commit into
masterfrom
SI-539-hbar-self-transfer-merge-entries

Conversation

@Doddanna17
Copy link
Copy Markdown
Contributor

@Doddanna17 Doddanna17 commented May 14, 2026

Summary

CoinTransferBuilder.buildTransferData() builds the protobuf accountAmounts list with separate sender and recipient entries. When sender == recipient (self-transfer for claim rewards), this produced two entries with the same accountID, which Hedera rejects with ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS (status 74).

Fix: After building the accountAmounts array, entries sharing the same accountID are merged by summing their amounts. This is a general-purpose dedup -- not a special self-transfer code path -- so it handles all cases correctly.

Also fixes: getTransferData() fallback in transaction.ts updated from isPositive() to !isNegative() to match zero-amount entries from the merged format, so toJson() correctly reports the recipient.

Cases handled

Scenario Before merge After merge Outcome
Self-transfer (sender == recipient) [{A, -1}, {A, +1}] [{A, 0}] 1 entry -- Hedera accepts, pending_reward flushed
Normal transfer (sender != recipient) [{A, -100}, {B, +100}] [{A, -100}, {B, +100}] 2 entries -- no-op, unchanged behavior
Multi-recipient with sender as recipient [{A, -150}, {B, +100}, {A, +50}] [{A, -100}, {B, +100}] 2 entries -- net amounts correct, no duplicate

Context

PR #8742 fixed the deserialization path (so toJson() doesn't crash on self-transfers), but the build/serialization path still produced duplicate entries. This PR fixes the build path. After this + SDK bump in bitgo-microservices, the full HBAR claim rewards flow (staking-service -> WP -> BitGoJS -> indexer -> Hedera) will work end-to-end.

Files changed

  • modules/sdk-coin-hbar/src/lib/coinTransferBuilder.ts -- merge same-accountID entries in buildTransferData()
  • modules/sdk-coin-hbar/src/lib/transaction.ts -- !isNegative() fallback for zero-amount self-transfer
  • modules/sdk-coin-hbar/test/unit/transactionBuilder/coinTransferBuilder.ts -- 6 tests covering all three scenarios

Test plan

  • Self-transfer produces single merged {accountID, amount: 0} entry
  • Round-trip serialization preserves self-transfer semantics
  • Signing works on self-transfer transactions
  • Normal transfers still produce two distinct entries (regression)
  • Multi-recipient with sender as recipient merges correctly (edge case)
  • Full sdk-coin-hbar test suite passes (152 passing)

Ticket: SI-539

@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 14, 2026

SI-539

…o fix self-transfer

buildTransferData() produced two separate protobuf entries for the same
accountID on self-transfer (sender == recipient), causing Hedera to
reject with ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS (status 74). Now merges
entries by accountID so self-transfer yields a single [{acct, 0}] entry.

Also updates transaction.ts getTransferData() fallback to handle
zero-amount entries from the merged format (!isNegative instead of
isPositive).

Ticket: SI-539
@Doddanna17 Doddanna17 force-pushed the SI-539-hbar-self-transfer-merge-entries branch from 3277ec8 to c273927 Compare May 15, 2026 02:48
@Doddanna17 Doddanna17 changed the title fix(sdk-coin-hbar): merge same-account entries in self-transfer to fix ACCOUNT_REPEATED fix(sdk-coin-hbar): merge same-account entries in buildTransferData to fix ACCOUNT_REPEATED May 15, 2026
@Doddanna17
Copy link
Copy Markdown
Contributor Author

Cases and outcomes

Case 1: Self-transfer (claim rewards) -- THE FIX

Sender and recipient are the same account. This is the HBAR claim rewards mechanism -- a 1-tinybar CryptoTransfer that "touches" the account, causing Hedera to flush pending_reward into the account balance.

Before this fix:

accountAmounts: [
  { accountID: 0.0.81320, amount: -1 },  // sender
  { accountID: 0.0.81320, amount: +1 },  // recipient (same account)
]

Result: Hedera rejects with ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS (status 74)

After this fix:

accountAmounts: [
  { accountID: 0.0.81320, amount: 0 },   // merged: -1 + 1 = 0
]

Result: Single entry, Hedera accepts, pending_reward flushed into balance


Case 2: Normal transfer (sender != recipient) -- NO CHANGE

Standard transfer between two different accounts. No entries share the same accountID, so the merge loop is a no-op.

Before and after (identical):

accountAmounts: [
  { accountID: 0.0.81320, amount: -100 },  // sender
  { accountID: 0.0.75861, amount: +100 },  // recipient
]

Result: 2 distinct entries, behavior unchanged


Case 3: Multi-recipient where sender is also a recipient -- EDGE CASE FIXED

Sender sends to multiple recipients, one of which is themselves. Without merge, Hedera would reject this too (sender accountID appears twice).

Before this fix:

accountAmounts: [
  { accountID: 0.0.81320, amount: -150 },  // sender (total: 100 + 50)
  { accountID: 0.0.78963, amount: +100 },  // recipient 1
  { accountID: 0.0.81320, amount: +50 },   // recipient 2 (same as sender)
]

Result: 3 entries, 0.0.81320 appears twice -- Hedera rejects

After this fix:

accountAmounts: [
  { accountID: 0.0.81320, amount: -100 },  // merged: -150 + 50 = -100
  { accountID: 0.0.78963, amount: +100 },  // unchanged
]

Result: 2 entries, net amounts correct (A loses 100, B gains 100), Hedera accepts


Implementation approach

The merge is a general-purpose dedup by accountID using a Map. It runs on every transfer but is a no-op when all account IDs are distinct (Case 2). No special code path or caller awareness needed -- WalletPlatform, staking-service, and all existing callers work transparently.

All 6 tests pass covering these three cases plus round-trip serialization and signing.

@Doddanna17 Doddanna17 marked this pull request as ready for review May 15, 2026 03:13
@Doddanna17 Doddanna17 requested a review from a team as a code owner May 15, 2026 03:13
@Doddanna17 Doddanna17 merged commit c0b28ee into master May 15, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants