fix(sdk-coin-sol): resolve SOL token payment recipients during TSS verify-before-sign - #9883
rishikeshdadam136 wants to merge 1 commit into
Conversation
80ce9d3 to
ebdf11b
Compare
…rify-before-sign WCN-2113 re-introduced the pre-sign recipient verification in the EdDSA TSS sign flows. For SOL SPL payment intents the UI signs without txParams, so verification compares intent-fallback recipients (native address + tokenName from amount.symbol) against the explained transaction output (the recipient's associated token account). When the recipient's token name does not resolve in the statics map - unprefixed tokenData.tokenName ('usdt'), tokens registered outside the statics map, or a runtime where the statics lookup is unavailable - the mint cannot be resolved and every SOL token payment sign fails with 'Tx outputs does not match with expected txParams recipients'. - verifyTransaction: retry the token lookup with the chain-prefixed spelling ('usdt' -> 'sol:usdt'); when a recipient's tokenName is a genuine token NAME that still does not resolve and no tokenAddress is carried, use the mint from the explained output (the token actually moved) and prove the output is the recipient's associated token account for that mint. Recipients whose tokenName IS a mint address (unsupported-token shape) keep requiring an explicit tokenAddress. - verifyTransaction total-amount check: group per-asset totals by canonical asset key (resolved mint) on both the recipient and output sides so the name-spelling fallbacks above do not break the totals comparison. - sdk-core resolveEffectiveTxParams: carry recipient-level tokenAddress/tokenProgramId (SOL consolidateToken intents) and tokenData.tokenContractAddress (EVM-style token intents) onto intent-fallback recipients. - sdk-core IntentRecipient: declare the optional tokenAddress/tokenProgramId fields wallet-platform persists on SOL consolidateToken intent recipients. Related: WCN-2113 (verification re-introduction), WCN-2952 (consolidation follow-up, PR #9878). This is the payment-intent counterpart: SOL token payment signing is broken for MPCv2 TSS wallets signing without explicit txParams. TICKET: WCN-2952
ebdf11b to
a16ab0f
Compare
|
|
|
@claude can you review this pr |
|
Claude finished @rishikeshdadam136's task —— View job PR Review for SOL Token Payment Verification FixTodo List:
Review SummaryThis PR successfully addresses the SOL token payment verification issue for MPCv2 TSS wallets when signing without explicit txParams. The implementation includes thoughtful fallback mechanisms and proper test coverage. Code Quality Assessment: ✅ APPROVEDKey Changes Analysis1. SOL Token Name Resolution Enhancement (
|
Problem
SOL SPL token payment withdrawals (and manual sweeps-to-self) fail to sign with:
Observed in production (MPCv2 TSS hot SOL wallet, web SDK): user creates a payment intent with a native recipient address, clicks Sign — the SDK's pre-sign verification throws and no signature share is ever submitted. Platform logs show zero server-side errors: the transaction builds and rebuilds fine; only the client-side gate rejects it.
Blast radius: any SPL token, any TSS SOL wallet (MPCv1 + MPCv2 on current bundles), signing without explicit txParams — UI Sign, or any API/SDK signer that omits
txParams. The current UI and retail pins (@bitgo-beta/sdk-core8.2.1-beta.2079, published Oct 1) contain the enforcement, so the payment path is broken in production until this ships.Root cause
On Solana, a token payment delivers funds to the recipient's associated token account (ATA) — a different address than the native wallet address the user entered. The pre-sign check must therefore re-derive the ATA client-side to prove the output belongs to the intended recipient, which requires resolving the token mint from the recipient's
tokenName.The UI signs without txParams, so verification uses intent-fallback recipients (
resolveEffectiveTxParams):{ address: <native>, amount, tokenName: 'sol:usdt' }— notokenAddress, noprogramId. When the name→mint lookup fails (unprefixedtokenData.tokenName, tokens outside the statics map, or a runtime where the statics lookup is unavailable), the mint cannot be resolved and the check treats every legitimate SOL token payment as a mismatch.History: this enforcement was added to the EdDSA sign path in WCN-196 (a70114f, Jun 11), reverted Jun 25 (96658f1) for breaking signing, and re-introduced by WCN-2113 (PR #9813, Sep 24; first customer failures Sep 28). WCN-2952 (PR #9878) fixed the same regression for
consolidateintents by verifying sweep-to-base-address instead — payment intents still take the unchanged recipients comparison, which is the gap this PR closes.Changes
sdk-coin-sol—verifyTransactionrecipients check${chain}:${tokenName}(usdt→sol:usdt); wallet-platform persiststokenData.tokenNamewithout the prefix on some intent types.tokenAddressis carried, take the mint from the explained output (the token actually moved —explainTransactionfalls back to the mint astokenNameviauseTokenAddressTokenName) and prove the output is the recipient's ATA for that mint.tokenNamestring on both sides, so spelling differences (usdtvssol:usdt) produced a second, different failure (Tx total amount does not match with expected total amount field). Both sides now group by canonical asset key (resolved mint), and a recipient whose name is unresolvable adopts the output-side key.sdk-core— carry token identity onto intent-fallback recipientsresolveEffectiveTxParamsnow mapstokenAddress(from recipient-leveltokenAddress— persisted by wallet-platform on SOLconsolidateTokenintents — ortokenData.tokenContractAddressfor EVM-style token intents) andprogramId(recipient-leveltokenProgramId) onto fallback recipients.IntentRecipientdeclares these optional fields. When the data is present, the check gets the mint directly and never depends on a name lookup.Security analysis
tokenNameis a mint address, the output-mint fallback is gated off (!isValidAddress(recipientFromUser.tokenName)) — the tx side never vouches for the token, and an explicittokenAddressis still required. The existing rejection tests for that flow are unchanged and green.Test plan
New tests (all red on master, green with this PR):
should succeed to verify token transaction when recipient tokenName is not chain-prefixedshould succeed to verify token transaction for a token name outside the statics map when the explained output carries the mintshould fail to verify token transaction when the output token account belongs to a different owner(fallback still proves ownership)sdk-core: recipient-level SPL token identity (tokenAddress/tokenProgramId) andtokenData.tokenContractAddressare carried onto fallback recipientsUnchanged and green:
tokenAddress, wrong Token-2022 programId)verifysuite insdk-coin-sol/test/unit/sol.ts: 56 passingsdk-corerecipientUtils suite: 40 passingRollout
Fix reaches users via: merge → next
@bitgo-beta/sdk-core+@bitgo-beta/sdk-coin-solpublish → bump in bitgo-ui (apps/app) and bitgo-retail (apps/retail-web) → deploy. Note the beta line is cut from a release branch (WCN-2952 merged Oct 1 13:15 UTC yet the same-day 19:50 beta did not contain it) — verify the fix is present in the published tarball before declaring victory.Interim workaround for affected customers: sign via API with explicit
txParams.recipientsincludingtokenName+tokenAddress+programId— caller-supplied recipients take precedence and pass on all affected versions.Related: WCN-2113 (PR #9813 — enforcement re-introduction), WCN-2952 (PR #9878 — consolidation counterpart), WCN-196 (original enforcement + revert).