fix(sdk-coin-hbar): merge same-account entries in buildTransferData to fix ACCOUNT_REPEATED#8774
Conversation
…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
3277ec8 to
c273927
Compare
Cases and outcomesCase 1: Self-transfer (claim rewards) -- THE FIXSender 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 Before this fix: Result: Hedera rejects with After this fix: Result: Single entry, Hedera accepts, Case 2: Normal transfer (sender != recipient) -- NO CHANGEStandard transfer between two different accounts. No entries share the same accountID, so the merge loop is a no-op. Before and after (identical): Result: 2 distinct entries, behavior unchanged Case 3: Multi-recipient where sender is also a recipient -- EDGE CASE FIXEDSender sends to multiple recipients, one of which is themselves. Without merge, Hedera would reject this too (sender accountID appears twice). Before this fix: Result: 3 entries, 0.0.81320 appears twice -- Hedera rejects After this fix: Result: 2 entries, net amounts correct (A loses 100, B gains 100), Hedera accepts Implementation approachThe merge is a general-purpose dedup by All 6 tests pass covering these three cases plus round-trip serialization and signing. |
Summary
CoinTransferBuilder.buildTransferData()builds the protobufaccountAmountslist with separate sender and recipient entries. When sender == recipient (self-transfer for claim rewards), this produced two entries with the sameaccountID, which Hedera rejects withACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS(status 74).Fix: After building the
accountAmountsarray, entries sharing the sameaccountIDare 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 intransaction.tsupdated fromisPositive()to!isNegative()to match zero-amount entries from the merged format, sotoJson()correctly reports the recipient.Cases handled
[{A, -1}, {A, +1}][{A, 0}][{A, -100}, {B, +100}][{A, -100}, {B, +100}][{A, -150}, {B, +100}, {A, +50}][{A, -100}, {B, +100}]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 inbuildTransferData()modules/sdk-coin-hbar/src/lib/transaction.ts--!isNegative()fallback for zero-amount self-transfermodules/sdk-coin-hbar/test/unit/transactionBuilder/coinTransferBuilder.ts-- 6 tests covering all three scenariosTest plan
{accountID, amount: 0}entryTicket: SI-539