fix(sdk-core): add EVM staking types to TSS signing bypass list#8658
fix(sdk-core): add EVM staking types to TSS signing bypass list#8658mrdanish26 wants to merge 2 commits intomasterfrom
Conversation
6f997d9 to
9ef7266
Compare
…ents BSC/BNB hot wallet staking breaks after WAL-375 SDK bump because the staking wallet calls signTransaction without txParams, and staking intentType strings were absent from the recipient bypass set. Instead of enumerating every staking intentType string (which drifts as new coins add staking support), detect staking intents generically via the presence of stakingRequestId on the intent — a required field on BaseStakeIntent in @bitgo/public-types that all staking intents inherit. Fix: - NO_RECIPIENT_TX_TYPES retains only the 8 non-staking ECDSA types - resolveEffectiveTxParams checks stakingRequestId as a generic staking signal; throws only if no recipients, not staking, and not an ECDSA no-recipient type - Keep intent.intentType fallback so txType propagates to downstream verifyTssTransaction callers - Revert verifyTssTransaction bypass list in abstractEthLikeNewCoins.ts back to 8 original ECDSA types - Remove stale verifyTssTransaction override from bsc.ts (parent handles) - Tests: updated makeTxRequest to accept stakingRequestId; realistic tests using delegate/stake + stakingRequestId; negative test confirms delegate without stakingRequestId still throws Refs: WAL-756 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> TICKET: WAL-756
9ef7266 to
467ff30
Compare
|
@claude Please do a thorough review of this PR. Focus especially on:
Please call out concrete issues clearly and separate them by severity if possible. |
ashutoshkumar-6
left a comment
There was a problem hiding this comment.
Overall the direction of centralizing NO_RECIPIENT_TX_TYPES and the staking detection via stakingRequestId is good. However, there is a critical gap: the fix in resolveEffectiveTxParams prevents InvalidTransactionError from throwing, but the downstream verifyTssTransaction guards in every affected coin (bsc.ts, bscToken.ts, xdc.ts, xdcToken.ts, evmCoin.ts, and the parent abstractEthLikeNewCoins.ts) will still throw 'missing txParams' for staking types — see inline comments for details.
| // Use its presence as a generic staking signal — no need to enumerate every intentType. | ||
| const isStakingIntent = !!(txRequest.intent as any)?.stakingRequestId; | ||
|
|
||
| if (!effectiveTxParams.recipients?.length && !isStakingIntent && !NO_RECIPIENT_TX_TYPES.has(txType)) { |
There was a problem hiding this comment.
[Critical] This fix prevents InvalidTransactionError here, but the staking bypass does not propagate to verifyTssTransaction.
The full call chain after this function returns for a staking intent (stakingRequestId present, no recipients) is:
resolveEffectiveTxParams returns { type: 'delegate' } ← fixed here
→ baseCoin.verifyTransaction({ txParams: { type: 'delegate' } })
→ verifyTssTransaction: !txParams?.recipients && !NO_RECIPIENT_TX_TYPES.has('delegate')
→ throws 'missing txParams' ← NOT fixed
'delegate', 'undelegate', 'stake', 'unstake' are not in NO_RECIPIENT_TX_TYPES, so every verifyTssTransaction override (bsc.ts:79, bscToken.ts:57, xdc.ts:79, xdcToken.ts:81, evmCoin.ts:119, abstractEthLikeNewCoins.ts:3112) will still throw for BSC staking.
The fix moves the error from InvalidTransactionError (here) to Error('missing txParams') (in verifyTssTransaction) but does not eliminate it. The staking flow remains broken end-to-end.
| txParams.prebuildTx?.consolidateId || | ||
| (txParams.type && ['acceleration', 'fillNonce', 'transferToken', 'tokenApproval'].includes(txParams.type)) | ||
| ) | ||
| !(txParams.prebuildTx?.consolidateId || (txParams.type && NO_RECIPIENT_TX_TYPES.has(txParams.type))) |
There was a problem hiding this comment.
[Critical] This guard still throws for BSC staking transactions.
After resolveEffectiveTxParams returns effectiveTxParams = { type: 'delegate' } (no recipients), this evaluates as:
!txParams?.recipients→truetxParams.prebuildTx?.consolidateId→undefined(no prebuildTx on staking tx)NO_RECIPIENT_TX_TYPES.has('delegate')→false- Result: throws
'missing txParams'
The staking types propagated via intent.intentType are not in NO_RECIPIENT_TX_TYPES. Same issue exists in bscToken.ts, xdc.ts, xdcToken.ts, and evmCoin.ts:119.
Possible fixes:
- Check for a staking signal here too (e.g. set a flag on
effectiveTxParamslikeeffectiveTxParams.isStakingIntent = trueinresolveEffectiveTxParams, then check it here), or - Pass staking types through
NO_RECIPIENT_TX_TYPESwith a comment, or - Remove this override entirely and let the parent handle it (as the commit message
467ff30dsays was intended — but the override is still present in the PR)
| 'enableToken', | ||
| 'customTx', | ||
| ].includes(txParams.type)) | ||
| (txParams.type && NO_RECIPIENT_TX_TYPES.has(txParams.type)) |
There was a problem hiding this comment.
[Critical] Same issue as in bsc.ts: NO_RECIPIENT_TX_TYPES does not contain staking types (delegate, undelegate, stake, unstake, etc.). When a staking transaction arrives here with txParams = { type: 'delegate' } (returned by resolveEffectiveTxParams), this check throws 'missing txParams'.
Note that this parent class is also what bsc.ts would fall through to if the override were removed (as the commit message for 467ff30d states). Either way, the staking bypass needs to be present at this layer too.
There was a problem hiding this comment.
@mrdanish26 let's also add the stakingRequestId, it should be in the prebuildTx object. @noel-bitgo if you can confirm.
| // All staking intents (BSC delegate/undelegate, CELO stake/unstake, etc.) carry | ||
| // stakingRequestId as a required field on BaseStakeIntent (@bitgo/public-types). | ||
| // Use its presence as a generic staking signal — no need to enumerate every intentType. | ||
| const isStakingIntent = !!(txRequest.intent as any)?.stakingRequestId; |
There was a problem hiding this comment.
[High] as any cast is unsafe. PopulatedIntent in baseTypes.ts does not define stakingRequestId, so TypeScript cannot verify this field exists at compile time. If BaseStakeIntent in @bitgo/public-types renames or removes stakingRequestId, this silently returns false at runtime with no compile-time error.
Consider importing BaseStakeIntent from @bitgo/public-types and writing a proper type guard:
function isBaseStakeIntent(intent: unknown): intent is BaseStakeIntent {
return typeof (intent as any)?.stakingRequestId === 'string';
}
const isStakingIntent = isBaseStakeIntent(txRequest.intent);Or extend the PopulatedIntent union in baseTypes.ts to include a staking variant.
TICKET: WAL-756
Summary
1. WAL-756 — BNB/BSC staking broken
Staking wallets call
signTransactionwithouttxParams. After WAL-375,resolveEffectiveTxParams()throwsInvalidTransactionErrorbecauseeffectiveTxParams.typeis undefined and staking intentType strings were not inNO_RECIPIENT_TX_TYPES.2. XDC/BSC token enable/approve
Coin-specific
verifyTssTransactionoverrides inxdcToken.ts,bscToken.ts,xdc.ts,bsc.ts, andevmCoin.tshad stale hardcoded bypass lists and never updated. When the parent class addedtokenApproval,enableToken,bridgeFunds, etc., these overrides were left behind — causing"missing txParams"errors on app.bitgo-test.com when enabling tokens on XDC.Solution
Staking fix (
recipientUtils.ts):stakingRequestIdpresence onthe intent (a required field on
BaseStakeIntentin@bitgo/public-types)rather than enumerating staking intentType strings that drift over time
intent.intentTypefallback sotxTypepropagates to downstreamverifyTssTransactioncallers whentxParams.typeis absentCentralization (all coin modules):
NO_RECIPIENT_TX_TYPESfrom sdk-core public API (tss/index.ts)as the single source of truth
NO_RECIPIENT_TX_TYPES.has()in:
abstractEthLikeNewCoins.ts,bsc.ts,bscToken.ts,xdc.ts,xdcToken.ts,evmCoin.tsverifyTssTransactionoverrides preservedTesting
staking-bnb-hotE2E test in bitgo-retail passes after SDK bump