Repository navigation
Conversation
Contributor
Contributor
|
|
SOL token transfers on EdDSA MPCv2 wallets fail at sign time with 'Tx outputs does not match with expected txParams recipients' (WCI-2009). The MPCv2 sign flow verifies the server-supplied transaction against recipients rebuilt from the persisted intent when the caller passes no txParams (UI Sign / re-sign / pending-approval paths). Two recipient shapes then leave the token unresolvable at the coin layer: token names persisted without the chain prefix ('usdc' instead of 'tsol:usdc'), and token-wallet recipients whose amount.symbol equals getChain() — which is the token's own name on a token wallet — so the existing symbol !== chainName guard drops the tokenName entirely. resolveEffectiveTxParams now derives token names correctly for chain-aware callers: it keeps amount.symbol when chainName is itself a registered token (token wallets, the check PR #9884 proposed), and resolves the statics-registered spelling of the derived name, trying the chain-scoped form first ('usdc' -> 'tsol:usdc'). The chain-scoped spelling is the unambiguous reading because statics also registers bare-name mainnet aliases (e.g. the Ethereum 'usdc' token) that a bare lookup would silently match. Names that resolve nowhere pass through unchanged so verification fails closed at the coin layer. The change is gated on the chainName argument, which only EddsaMPCv2Utils.signRequestBase passes; the ECDSA sign paths and coin-level verification are untouched. Out of scope: MPCv2 SOL consolidation signing (intent amount snapshot drift, WCN-2952). Ticket: WCI-2009
Marzooqa
force-pushed
the
marzooqanaeemakather/wci-2009-fix-sol-token-transfer-failures-in-the-mpcv2-flow
branch
from
October 21, 2026 13:47
c966f80 to
3fce1af
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
SOL token transfers on EdDSA MPCv2 (TSS) wallets fail at sign time with
Tx outputs does not match with expected txParams recipients(WCI-2009). The API path fails the same way whenever signing runs without explicit txParams.Root cause. The MPCv2 sign flow verifies the server-supplied transaction against recipients rebuilt from the persisted intent when the caller passes no txParams (UI Sign button / re-sign / pending-approval paths —
EddsaMPCv2Utils.signRequestBase→resolveEffectiveTxParams). Two recipient shapes leave the token unresolvable at the coin layer:'usdc'instead of'tsol:usdc');tokenNameat all — WP storesamount.symbol = getChain(), and on a token walletgetChain()is the token's own name, so the existingsymbol !== chainNameguard drops the tokenName entirely.Fix (scoped to the MPCv2 path).
resolveEffectiveTxParamsnow derives token names correctly for chain-aware callers:amount.symbolwhenchainNameis itself a registered token (token wallets) — the same check proposed in fix(sdk-core): keep intent tokenName for token-wallet TSS signing #9884;'usdc'→'tsol:usdc'). The chain-scoped spelling is the unambiguous reading: statics also registers bare-name mainnet aliases (e.g. the Ethereumusdctoken) that a bare lookup silently matches — a plain retry-after-bare-lookup canonicalizes to the wrong chain's token;The change is gated on the
chainNameargument, which onlyEddsaMPCv2Utils.signRequestBasepasses (ecdsa.ts/ecdsaMPCv2.tscall the resolver without it), so the ECDSA sign paths and all coin-level verification used by non-MPCv2 flows are untouched. Supersedes the resolver check in #9884 (which covers only shape 2 and is based on a three-week-old master).Out of scope: MPCv2 SOL consolidation signing, which remains broken due to intent amount-snapshot drift (WCN-2952; the sweep-to-base-address fix, PR #9878, was reverted in #9885). Related: WCI-2010 (add SOL token transfers to the MPCv2 QA suite).
Issue Number
WCI-2009 · related: WCI-2010
Type of change
How Has This Been Tested?
sdk-core/test/.../recipientUtils.ts, 8 cases): unprefixedtokenData.tokenNameandamount.symbolcanonicalize via the chain prefix; token-wallet symbol equal tochainNameis kept; already-registered names unchanged; native transfers stay withouttokenName; unresolvable names pass through; unregisteredchainNameequal to symbol stays dropped (fail-closed); legacy ECDSA callers (nochainName) are unchanged.sdk-coin-sol/test/unit/sol.ts) exercising the real sign path — intent →resolveEffectiveTxParams→Sol.verifyTransaction— for an unprefixed payment intent, a token-wallet withdrawal, and a tampered recipient (still rejected with the exact production error).sdk-corefocused suites pass 184/184 and the fullsdk-coin-solunit suite passes 878/878 (BITGOJS_TEST_PASSWORDset).tsc --buildon both modules: error sets byte-identical to master baselines (59 pre-existing, none in changed files).eslint --quietandprettier --checkclean.Checklist