Skip to content

fix(sdk-core): resolve intent token names for EdDSA MPCv2 signing - #10020

Draft
Marzooqa wants to merge 1 commit into
masterfrom
marzooqanaeemakather/wci-2009-fix-sol-token-transfer-failures-in-the-mpcv2-flow
Draft

Marzooqa wants to merge 1 commit into
masterfrom
marzooqanaeemakather/wci-2009-fix-sol-token-transfer-failures-in-the-mpcv2-flow

Conversation

@Marzooqa

@Marzooqa Marzooqa commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

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:

  1. wallet-platform persists token names without the chain prefix on some intents ('usdc' instead of 'tsol:usdc');
  2. token-wallet recipients carry no tokenName at all — WP stores amount.symbol = getChain(), and on a token wallet getChain() is the token's own name, so the existing symbol !== chainName guard drops the tokenName entirely.

Fix (scoped to the MPCv2 path). resolveEffectiveTxParams now derives token names correctly for chain-aware callers:

  • keeps amount.symbol when chainName is itself a registered token (token wallets) — the same check proposed in fix(sdk-core): keep intent tokenName for token-wallet TSS signing #9884;
  • 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: statics also registers bare-name mainnet aliases (e.g. the Ethereum usdc token) that a bare lookup silently matches — a plain retry-after-bare-lookup canonicalizes to the wrong chain's token;
  • names that resolve nowhere pass through unchanged, so verification still fails closed at the coin layer.

The change is gated on the chainName argument, which only EddsaMPCv2Utils.signRequestBase passes (ecdsa.ts / ecdsaMPCv2.ts call 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

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • New resolver unit tests (sdk-core/test/.../recipientUtils.ts, 8 cases): unprefixed tokenData.tokenName and amount.symbol canonicalize via the chain prefix; token-wallet symbol equal to chainName is kept; already-registered names unchanged; native transfers stay without tokenName; unresolvable names pass through; unregistered chainName equal to symbol stays dropped (fail-closed); legacy ECDSA callers (no chainName) are unchanged.
  • New end-to-end regression tests (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).
  • RED/GREEN proven: with the resolver fix stashed, 3 resolver tests and 2 end-to-end tests fail; with the fix, sdk-core focused suites pass 184/184 and the full sdk-coin-sol unit suite passes 878/878 (BITGOJS_TEST_PASSWORD set).
  • tsc --build on both modules: error sets byte-identical to master baselines (59 pre-existing, none in changed files). eslint --quiet and prettier --check clean.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My code compiles correctly for both Node and Browser environments
  • I have commented my code, particularly in hard-to-understand areas
  • My commits follow Conventional Commits and I have properly described any BREAKING CHANGES
  • The ticket or github issue was included in the commit message as a reference
  • I have made corresponding changes to the documentation and on any new/updated functions and/or methods
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@linear-code

linear-code Bot commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

WCI-2009

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Unit tests are failing on Node 26.x (Current release line, non-blocking). This is not an LTS version yet, so it does not block merge, but it signals an incompatibility to fix before Node 26.x becomes LTS.

View run

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
Marzooqa force-pushed the marzooqanaeemakather/wci-2009-fix-sol-token-transfer-failures-in-the-mpcv2-flow branch from c966f80 to 3fce1af Compare October 21, 2026 13:47
@Marzooqa Marzooqa changed the title fix(sdk-coin-sol): resolve SOL token recipient names in MPCv2 verify-before-sign fix(sdk-core): resolve intent token names for EdDSA MPCv2 signing Oct 21, 2026

This branch has not been deployed

No deployments
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.

1 participant