fix: muxed-address receives mislabeled as sent (#2841)#2856
Conversation
Transaction history compared the muxed (M...) destination against the base-G active public key, which can never be equal, so payments and Soroban transfers received at a muxed sub-account were labeled Sent. Muxed-from balance changes were also dropped from history entirely. Resolve every ownership check to the base account via a shared toBaseAccount() helper: classic payment direction, asset_balance_changes credit + involvement filter, and Soroban mint/transfer. The displayed counterparty still shows the M-address. Also remove an unreachable memo-validation branch in freighterApiMessageListener. Add 16 regression tests using real keypair/MuxedAccount decode and a real invoke_host_function envelope XDR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d0722258dd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const isCredit = change.to === publicKey; | ||
| // Determine if this is a credit (receiving) or debit (sending). Compare on | ||
| // the base account so a credit to a muxed (M...) destination is detected. | ||
| const isCredit = toBaseAccount(change.to) === publicKey; |
There was a problem hiding this comment.
Keep self muxed balance changes marked as sent
When asset_balance_changes contains a transfer from the active base G... account to one of its own muxed M... addresses, this now sets isCredit to true because the destination is normalized back to the same base account. That regresses the previous behavior for this scenario from Sent/- to Received/+, unlike the classic payment path just below which still guards against self-transfers by also checking the sender base account. Consider applying the same sender self-guard here before treating a muxed destination as a credit.
Useful? React with 👍 / 👎.
Summary
Fixes #2841. Payments and Soroban transfers received at a muxed (
M…)sub-account were shown as "Sent" in transaction history.
Root cause
The direction checks compared the muxed
M…destination against the activeaccount's base
G…public key. AnM…string can never equal its baseG…,so
isReceivingwas alwaysfalsefor muxed receives. Horizon already resolvesa muxed destination to its base account in
toand exposes theM…separatelyin
to_muxed(the code even documents this), but the comparison used theM…value. Second symptom: in
asset_balance_changes, a change to/from the muxedform was filtered out as "not involving this account", dropping it from history.
Change
toBaseAccount()helper resolvingM…→ baseG…(via existinggetBaseAccount);G…/C…/empty pass through unchanged.asset_balance_changesinvolvement filter + credit/debit, Soroban mint/transfer.M…address.if (!isValidatingMemo)branch infreighterApiMessageListener.Tests
16 regression tests using real
Keypair/MuxedAccountdecode and a realinvoke_host_functionenvelope XDR (classic payment received/sent/self/path,balance-change credit/debit to muxed, malformed input, decode mechanism).
Verified red-on-revert (reverting turns exactly the 6 muxed assertions red),
tscclean, prettier clean, message-listener + AssetDetail suites green (139 pass).🤖 Generated with Claude Code