Skip to content

fix(sdk-core): send empty recipients for ERC-7984 builds (CHALO-1957) - #10082

Merged
0xPrabh merged 1 commit into
masterfrom
prabhsharansingh/chalo-1957-sdk-empty-recipients
Oct 28, 2026
Merged

0xPrabh merged 1 commit into
masterfrom
prabhsharansingh/chalo-1957-sdk-empty-recipients

Conversation

@0xPrabh

@0xPrabh 0xPrabh commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #10063 (CHALO-1957): the PA-rebuild strip deleted the recipients key, but the /tx/build OpenAPI contract requires the key on every build body (TransactionBuildRequest.required: [recipients]) — a recipients-less body 400s with SchemaValidationError before the handler runs (CHALO-1991, staging-verified). An empty array is the documented server-derived shape — the spec's own recipients description already calls it out for acceleration and ERC-7984 token enablement.

For the four ERC-7984 intent types (wrapApprove | wrap | unwrap | finalizeUnwrap) the real recipients/calldata are derived server-side from the intent, so the SDK now always sends the key empty:

  • recreateAndSignTransaction (PA approve rebuild): forwards recipients: [] instead of deleting the key. The /tx/build PA-rebuild carve-out re-derives the real inputs from the pendingApprovalId approve() injects. Other PA types keep forwarding their stored recipients unchanged.
  • prebuildTransaction (direct drawer builds): defaults recipients to [] for intent types, after the TSS /txrequests early-return (TSS path untouched).
  • prebuildAndSignTransaction deny-list guard: now rejects only a populated recipients list for the intent types; the empty-array shape passes. fillNonce/acceleration behaviour unchanged.

Shared const ERC7984_INTENT_TX_TYPES moves to wallet/BuildParams.ts so the guard, prebuild and PA rebuild agree on the type set.

Server pairing

  • Minimal WP fix (no spec change): BitGo/bitgo-microservices#63897 — resolver that re-derives wrapParams/unwrapParams from the journey for the wrapApprove/unwrap rebuilds.
  • Alternative (spec-admitting) fix: BitGo/bitgo-microservices#63886 — adds dedicated oneOf branches for the recipients-less shapes. This SDK change is compatible with both: with recipients: [] present the body matches TransactionBuildRequest, and the resolver keys on pendingApprovalId, not on recipients absence.

Tests

  • Request-shape assertions flipped to recipients: []: direct wrapApprove/unwrap prebuilds and the PA approve rebuilds (wrap + finalizeUnwrap) assert the body now carries the empty key.
  • New guard cases: prebuildAndSignTransaction({ type: 'wrapApprove', recipients: [] }) passes the guard (fails later at the passphrase check, never at the deny-list); ({ type: 'fillNonce', recipients: [] }) still throws (regression).
  • Transfer-type PA regression unchanged: stored recipients still forwarded byte-identically.

Ticket: CHALO-1957

@linear-code

linear-code Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

CHALO-1957

CHALO-1991

@0xPrabh

0xPrabh commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@github-actions

This comment was marked as outdated.

@0xPrabh
0xPrabh marked this pull request as ready for review October 28, 2026 06:20
@0xPrabh
0xPrabh requested a review from a team as a code owner October 28, 2026 06:20

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@0xPrabh
0xPrabh requested review from a team as code owners October 28, 2026 06:20
@0xPrabh
0xPrabh marked this pull request as draft October 28, 2026 06:20

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread modules/sdk-core/src/bitgo/wallet/wallet.ts Outdated
Comment thread modules/sdk-core/src/bitgo/wallet/wallet.ts Outdated
@github-actions

github-actions Bot commented Oct 28, 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

@0xPrabh
0xPrabh force-pushed the prabhsharansingh/chalo-1957-sdk-empty-recipients branch from 3ac5565 to 2bf2a03 Compare October 28, 2026 06:28
@0xPrabh

0xPrabh commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@github-actions

This comment was marked as outdated.

Comment thread modules/bitgo/test/v2/unit/wallet.ts Outdated
The /tx/build OpenAPI contract requires the recipients KEY on every build
body (TransactionBuildRequest.required) - omitting the key 400s with
SchemaValidationError before the handler runs (CHALO-1991). An EMPTY array
is the documented server-derived shape (see the recipients description:
acceleration, token enablement).

For the ERC-7984 intent types (wrapApprove/wrap/unwrap/finalizeUnwrap) the
real recipients and calldata are derived server-side from the intent, so:

- recreateAndSignTransaction forwards an EMPTY recipients array instead of
  deleting the key; the /tx/build PA-rebuild carve-out re-derives the real
  inputs from the pendingApprovalId approve() injects. Other PA types keep
  forwarding their stored recipients unchanged.
- prebuildTransaction defaults the OUTGOING body's recipients to [] for
  direct intent builds (the drawer flow), after the TSS txrequests
  early-return; the caller's params object is never mutated (a prebuild +
  prebuildTx sign flow would otherwise trip the prebuildTx check).
- the prebuildAndSignTransaction deny-list guard now rejects only a
  POPULATED recipients list for the intent types; the empty-array shape
  passes. fillNonce/acceleration behaviour is unchanged.

Shared const ERC7984_INTENT_TX_TYPES moves to wallet/BuildParams.ts so the
guard, prebuild and PA rebuild agree on the type set.

Tests: request-shape assertions flipped to recipients: [] for direct
wrapApprove/unwrap (and wrap/finalizeUnwrap) prebuilds and the PA approve
rebuilds; guard cases (empty array allowed for wrapApprove, still rejected
for fillNonce); no caller-params mutation pinned; transfer-type PA
regression unchanged.

Ticket: CHALO-1957
@0xPrabh
0xPrabh force-pushed the prabhsharansingh/chalo-1957-sdk-empty-recipients branch from 2bf2a03 to 816eb19 Compare October 28, 2026 06:35
@0xPrabh

0xPrabh commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @0xPrabh's task in 19s —— View job


Review of PR #10082

No blocking issues. I read the diff at HEAD (816eb19) and did not run the tests or tsc.

Earlier findings are fixed at HEAD

  • both_prebuildtx_and_recipients_specified (🔴): prebuildTransaction now writes the default to whitelistedParams.recipients, after getExtraPrebuildParams. The caller's params is no longer mutated. A prebuild followed by sendMany({ ...params, prebuildTx }) no longer trips the check.
  • hop: true with an intent type (🟡): getExtraPrebuildParams sees the unmutated params, so recipients is still undefined and the hop branch is skipped, as on master.
  • Test type error (🔴): callerParams in modules/bitgo/test/v2/unit/wallet.ts:657 now declares recipients?: unknown, so callerParams.recipients at line 678 type-checks. It avoids any, as CLAUDE.md asks.

What I checked

  • pendingApproval.ts: The four ERC-7984 intent types rebuild with recipients = []. Other pending-approval types keep forwarding their stored recipients.
  • prebuildTransaction: The empty default is added after the TSS /txrequests early-return, so the TSS path is untouched.
  • prebuildAndSignTransaction guard: An empty array is allowed only for the intent types. fillNonce and acceleration with [] still throw.
  • ERC7984_INTENT_TX_TYPES: It now lives in BuildParams.ts, and all three call sites use === true, so a type like 'toString' can't match through the prototype chain.

Non-blocking notes

  1. The guard still throws recipients_not_allowed_for_fillnonce_and_acceleration_tx_type for the ERC-7984 types. The name is misleading, but renaming it would break the API, so leaving it is reasonable.
  2. A direct prebuildTransaction call with a populated recipients for an intent type is forwarded as-is. Only prebuildAndSignTransaction rejects it. This is unchanged from before, so the server should validate it.
  3. The Node 26 unit-test failure is non-blocking per CI. I did not look at its cause, so check it isn't related to this change.
    · branch prabhsharansingh/chalo-1957-sdk-empty-recipients

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-reviewed the latest push and found no new bugs. The three issues I raised earlier look addressed. This changes the shared wallet build path, so a review from the code owners is still worthwhile.

What was reviewed:

  • prebuildTransaction now puts recipients: [] only on the outgoing /tx/build body, and it does so after getExtraPrebuildParams. The caller's params are no longer mutated, and the ETH hop branch still sees recipients as undefined.
  • The new callerParams test now declares an optional recipients field, so it should type-check.
  • The prebuildAndSignTransaction guard: an empty array passes only for the four ERC-7984 types, and fillNonce/acceleration still reject []. The pending-approval (PA) rebuild forwards [] for those types and leaves other types' stored recipients unchanged.
Extended reasoning...

The change touches transaction building in sdk-core (wallet.ts prebuildTransaction and prebuildAndSignTransaction, the PendingApproval rebuild) and moves ERC7984_INTENT_TX_TYPES to BuildParams.ts. It does not touch auth, crypto or key handling directly, but it changes the /tx/build request shape for ERC-7984 intent types. Commit 816eb19 fixes the caller-params mutation, the hop interaction and the test type error that were raised earlier. The paths are covered by the wallet-core CODEOWNERS entry, which rules out auto-approval.

@0xPrabh
0xPrabh marked this pull request as ready for review October 28, 2026 06:44
@0xPrabh
0xPrabh merged commit c0679bd into master Oct 28, 2026
28 checks passed
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.

3 participants