Skip to content

feat(sdk-coin-sui): add MPCv2 support to recoverConsolidations - #9496

Open
ralph-bitgo[bot] wants to merge 2 commits into
wci-1224-sui-mpcv2-signed-hot-recoveryfrom
WCI-1234-sui-mpcv2-recover-consolidations-pt2
Open

feat(sdk-coin-sui): add MPCv2 support to recoverConsolidations#9496
ralph-bitgo[bot] wants to merge 2 commits into
wci-1224-sui-mpcv2-signed-hot-recoveryfrom
WCI-1234-sui-mpcv2-recover-consolidations-pt2

Conversation

@ralph-bitgo

@ralph-bitgo ralph-bitgo Bot commented Aug 13, 2026

Copy link
Copy Markdown

Stack

This PR is part 2 of 2 in a stack. Review and merge in order:

  1. feat(sdk-coin-sui): support MPCv2 signed hot recovery #9493 — MPCv2 signed hot recovery in recover() (base: master)
  2. This PR — MPCv2 support in recoverConsolidations() (base: wci-1224-sui-mpcv2-signed-hot-recovery) ← you are here

What

  • Add an optional precomputedMaterial?: EddsaSigningMaterial parameter to Sui.recover(), threaded through recoverSuiToken() and signRecoveryTransaction(), so a caller can supply pre-detected signing material and skip the getEddsaSigningMaterial detection call.
  • recoverConsolidations() now detects signing material once via getEddsaSigningMaterial before its scan loop over receive-address indexes, and passes it into every recover() call, instead of letting each iteration independently decrypt the keycard.

Why

Consolidating a wide range of receive-address indexes on an MPCv2 hot wallet would decrypt the same keycard once per scanned index — wasteful and slow for the default 20-address scan window (and worse for wider ranges). This mirrors the pattern already landed for DOT (WCI-1236).

Test plan

  • Added test asserting MPCv2 signed consolidation recoveries sweep to the correct base address
  • Added test asserting getEddsaSigningMaterial is called exactly once across all scanned indexes (not per iteration)
  • Added regression test confirming MPCv1 base address derivation/signing is unchanged
  • Added test confirming the unsigned (no passphrase) cold sweep path is unchanged for MPCv2 keycards
  • yarn unit-test passes (210/210) and yarn lint is clean

Ticket: WCI-1234

Add an MPCv2-signed recovery test under the token recovery describe
block, mirroring the native-transfer MPCv2 test added in the prior
commit. Asserts getTSSSignature is not called and the resulting
signature is wrapped in SUI's 0x00-flag envelope.

The prior commit wired signRecoveryTransaction()'s MPCv2 dispatch
into both recover() and recoverSuiToken(), but only exercised the
native-transfer path in tests. The ticket explicitly calls out that
token recovery (getTokenTransferBuilder) must also handle MPCv2, so
this closes that coverage gap found during review.

Ticket: WCI-1224
Session-Id: 0c067bc3-368d-4c82-9e68-8d08c89766d5
Task-Id: 8c5b0392-4a96-4479-a83b-8ba80cd2428d
@linear-code

linear-code Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

WCI-1234

@bitgo-ai-agent-dev
bitgo-ai-agent-dev Bot force-pushed the WCI-1234-sui-mpcv2-recover-consolidations-pt2 branch from 44c478a to 0f3a34b Compare August 13, 2026 12:20
@ralph-bitgo
ralph-bitgo Bot force-pushed the WCI-1234-sui-mpcv2-recover-consolidations-pt2 branch from 0f3a34b to 95ffdf2 Compare August 13, 2026 12:20
@vibhavgo
vibhavgo changed the base branch from WCI-1234-sui-mpcv2-recover-consolidations-pt1 to wci-1224-sui-mpcv2-signed-hot-recovery August 13, 2026 18:36
@vibhavgo
vibhavgo force-pushed the WCI-1234-sui-mpcv2-recover-consolidations-pt2 branch from 95ffdf2 to 9983445 Compare August 14, 2026 06:36
@vibhavgo
vibhavgo marked this pull request as ready for review August 14, 2026 06:37
@vibhavgo
vibhavgo requested a review from a team as a code owner August 14, 2026 06:37
@vibhavgo

Copy link
Copy Markdown
Contributor

AI-generated review — auto-generated by Claude Code (wallet-platform:review-pr). Please use your own judgment before acting on any findings.


Phase 1 — Summary

This PR adds MPCv2 support to Sui.recoverConsolidations() by detecting EDDSA signing material once before the scan loop and threading it as an optional precomputedMaterial parameter through recover()recoverSuiToken()signRecoveryTransaction(). Previously each loop iteration would independently decrypt the keycard via getEddsaSigningMaterial. The change is purely additive — existing v1 and unsigned sweep paths are unaffected.

Files changed: 2 | +191 / −8

  • modules/sdk-coin-sui/src/sui.ts — production change
  • modules/sdk-coin-sui/test/unit/sui.ts — 4 new tests in new Build Consolidation Recoveries (MPCv2): describe block
flowchart TD
    A[recoverConsolidations] -->|"userKey && walletPassphrase?"| B{detect once}
    B -->|yes| C[getEddsaSigningMaterial → signingMaterial]
    B -->|no| D[signingMaterial = undefined]
    C --> E[scan loop: idx = startingScanIndex..endingScanIndex]
    D --> E
    E -->|per iteration| F["recover(recoverParams, signingMaterial)"]
    F -->|token branch| G["recoverSuiToken(..., precomputedMaterial)"]
    F -->|SUI branch| H["signRecoveryTransaction(..., precomputedMaterial)"]
    G --> H
    H --> I["precomputedMaterial ?? getEddsaSigningMaterial(...)"]
    I --> J[sign & return tx]
Loading

Phase 2 — Ticket Alignment

Ticket: WCI-1234

PR title: feat(sdk-coin-sui): add MPCv2 support to recoverConsolidations

The implementation directly addresses what the ticket implies: extend MPCv2 signed hot recovery (landed in part 1, #9493) to the consolidation sweep path, matching the DOT pattern (WCI-1236). Alignment is strong.


Phase 3 — Index Analysis

No new database queries introduced.


Phase 4 — Type Safety

Double casts found (as unknown as) — all in test code for spying on private methods:

Location Cast
spy setup (calledOnce test) basecoin as unknown as { getEddsaSigningMaterial: unknown }
v1 regression test (×3) Sui.prototype as unknown as { getBalance/getInputCoins/getFeeEstimate: sinon.SinonStub }

These patterns are acceptable for sinon spying on private methods and are consistent with existing test patterns in the file. No as any, @ts-ignore, or production-code double casts found.

One minor note: typing getEddsaSigningMaterial as unknown in the spy cast means the spy object loses its inferred call signature — calledOnce still works because it comes from sinon.SinonSpy, but it's slightly weaker than casting to the actual method type. Not a blocker.


Phase 5 — MPCv2 Correctness & Test Quality

Correctness

Check Result
recoverConsolidations() detects signing material ONCE before the loop ✅ Detection block placed before MPC initialization and the scan loop
Detection guarded on params.walletPassphrase (skipped for unsigned sweeps) userKey && params.walletPassphrase ? await getEddsaSigningMaterial(...) : undefined
recover() short-circuits with precomputedMaterial ?? precomputedMaterial ?? (await this.getEddsaSigningMaterial(...)) in signRecoveryTransaction
precomputedMaterial threaded correctly through recoverSuiToken() and signRecoveryTransaction() ✅ Both call sites updated; recoverSuiToken passes it on to its own signRecoveryTransaction call

Test Quality

Check Result
Test asserts getEddsaSigningMaterial called EXACTLY ONCE across multi-index scan sandBox.assert.calledOnce(getEddsaMaterialSpy) with endingScanIndex: 3 (2-index scan)
Test for MPCv2 signed consolidation (correct base address) outputs[0].address.should.equal(baseAddr) where baseAddr is derived from m/0
v1 regression test for consolidations ✅ Exists; stubs real v1 keys and asserts getTSSSignature was called
Unsigned sweep consolidation test ✅ Calls recoverConsolidations with no walletPassphrase; asserts txRequests non-empty
Spy assertions are specific (calledOnce, notCalled) ⚠️ Partially — the v1 regression test uses sandBox.assert.called(getTSSSignatureSpy) (at-least-once), not calledTwice (expected for a 2-address scan). Consider tightening to calledTwice if the scan window is deterministic.

Consistency vs DOT (#9486) and ADA (#9497)

Check Result
Same detection-once pattern (guard on passphrase, ?? getEddsaSigningMaterial) ✅ PR body explicitly states it mirrors DOT (WCI-1236)
Describe block naming consistent 'Build Consolidation Recoveries (MPCv2):' matches the established describe naming convention in this file

Summary of Findings

No blocking issues. One minor test-quality flag:

  • ⚠️ v1 regression spy assertion (sandBox.assert.called(getTSSSignatureSpy)) is weaker than it could be — consider calledTwice if the 2-address scan (indexes 1–2) is deterministic, to catch any future regression that skips an iteration.

Everything else is clean: the detection-once pattern is correctly implemented, all four test scenarios are covered, and the unsigned sweep path is properly gated.


Generated by [wallet-platform:review-pr] via Claude Code

@vibhavgo
vibhavgo force-pushed the wci-1224-sui-mpcv2-signed-hot-recovery branch from 4994308 to 0349d5d Compare August 14, 2026 07:07
Add an optional precomputedMaterial parameter to Sui.recover() and thread
it through recoverSuiToken()/signRecoveryTransaction() so the caller can
supply pre-detected EddsaSigningMaterial and skip the getEddsaSigningMaterial
detection call. recoverConsolidations() now detects signing material once
via getEddsaSigningMaterial before its scan loop and passes it into every
recover() call, instead of letting each iteration independently decrypt the
keycard (as it did even after WCI-1224 added MPCv2 dispatch to recover()).

Consolidating a wide range of receive-address indexes on an MPCv2 hot wallet
would decrypt the same keycard once per scanned index, which is wasteful and
slow for the default 20-address scan window (and worse for wider ranges).
This mirrors the pattern already landed for DOT (WCI-1236).

Ticket: WCI-1234
Session-Id: bf130f11-6bd0-4a91-88c6-a9138ecc96ab
Task-Id: 179247fb-4ff6-49f3-9849-df120620c98b
@vibhavgo
vibhavgo force-pushed the WCI-1234-sui-mpcv2-recover-consolidations-pt2 branch from 9983445 to 3e17fea Compare August 14, 2026 07:18
@vibhavgo
vibhavgo requested a review from Marzooqa August 14, 2026 08:10
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