Skip to content

chore: normalize wallet invocation errors across transports#312

Merged
adonesky1 merged 17 commits into
mainfrom
wapi-1521
Jun 9, 2026
Merged

chore: normalize wallet invocation errors across transports#312
adonesky1 merged 17 commits into
mainfrom
wapi-1521

Conversation

@wenfix

@wenfix wenfix commented May 28, 2026

Copy link
Copy Markdown
Contributor

Explanation

connect-multichain.invokeMethod() could surface different error shapes depending on whether a wallet error arrived as a resolved JSON-RPC error response or as a rejected transport error. That also meant connect-evm could lose provider-facing details when adapting multichain errors to EIP-1193 request errors.

This change normalizes wallet invocation failures at the RequestRouter boundary:

  • extracts wallet code, message, and JSON-RPC data from response errors and wrapped transport errors
  • walks a capped cause chain so transport/API wrappers do not hide the wallet error
  • wraps normalized wallet failures in RPCInvokeMethodErr while preserving reason, rpcCode, rpcMessage, and rpcData
  • keeps existing RPCInvokeMethodErr instances unchanged
  • keeps analytics classification using normalized wallet codes while preserving original uncoded transport errors for transport diagnostics

connect-evm now maps normalized RPCInvokeMethodErr instances into EIP-1193-facing errors that carry the wallet code, provider-facing message, and JSON-RPC data when present.

References

  • WAPI-1521

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed, highlighting breaking changes as necessary
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

@wenfix wenfix requested a review from a team as a code owner May 28, 2026 14:17
@wenfix wenfix changed the title Normalize wallet invocation errors across transports chore: normalize wallet invocation errors across transports May 28, 2026
mockCore.invokeMethod.mockRejectedValue(
new RPCInvokeMethodErr(
'RPC Request failed with code 4001: User denied transaction signature.',
'User rejected the request',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

User rejected the request.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — aligned the fixture to the canonical 'User rejected the request.' (with the trailing period) in 685619e.

);
const ancestorMessage = getFirstNonEmptyMessage([
primitiveMessage,
...ancestorObjects.map((object) => object.message),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i'm not certain it makes sense to try to resolve an error message from earlier up in the chain. Hard for me to fully understand this problem though. What were your thoughts around this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good instinct, you're right. The rpc fields were sourced from anywhere in the cause chain, so a transport/API wrapper message could leak into rpcMessage and pose as the wallet's protocol message. Fixed in 1527a1e: rpcCode/rpcMessage/rpcData now read only from the coded wallet node; reason still falls back through the chain for readable logs. When the wallet sends no message, rpcMessage is left unset.

* @param error - Unknown error thrown or returned during method execution.
* @returns Canonical fields for RPCInvokeMethodErr.
*/
function getInvocationErrorDetails(error: unknown): InvocationErrorDetails {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thoughts on moving all these newly added error helpers into their own file?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

quite a bit of complexity in this helper. How deeply nested are we expecting causes to be?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — moved them into invocationError.ts (exporting toRPCInvokeMethodErr) in b4b3448, so the router stays focused and the normalization logic is testable in isolation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Shallow in practice (wallet → transport → maybe an API wrapper, ~2–3 levels); the depth-5 cap is just a guard against cyclic chains, not an expected depth. Most of the complexity was the message juggling feeding rpcMessage — that's gone now that the rpc fields read only from the coded node (1527a1e), so the remaining walk is just "find the first node with a numeric code."

Comment thread packages/connect-multichain/src/ui/index.ts
Comment thread packages/connect-multichain/src/ui/ModalFactory.ts
@adonesky1

adonesky1 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

The error.data plumbing looks like it can't actually deliver data on either transport.

Claude traced it end to end. The wallet does send it: wallet_invokeMethod returns a top-level JSON-RPC error and the extension/mobile serializeError keeps data, so the transport receives { error: { code, message, data } }. But both SDK transports drop it before normalization:

  • DefaultTransport.request throws #parseWalletError(response.error)new Error(message) + code, no data/cause (default/index.ts).
  • MWPTransport rejects via parseWalletErrornew JsonRpcError(code, message), no data (mwp/index.ts).

Since both reject (rather than resolve an error envelope), the new if (response.error) guard in handleWithWallet never fires for them — so toRPCInvokeMethodErr only ever sees the stripped error, rpcData stays undefined, and connect-evm never sets error.data. The new router test passes only because it mocks the transport to resolve { error: { …, data } }, which neither real transport does.

If we want the changelog claim to hold, the fix would need to live in the transports' parseWalletError (pass data as the 3rd JsonRpcError arg, or set cause) so the bytes survive to the router.

EDIT: pushed what I + claude think we need here: 432d04c

DefaultTransport and MWPTransport dropped the JSON-RPC `error.data` while
constructing the rejected error, so revert reasons / custom-error bytes never
reached RequestRouter and RPCInvokeMethodErr.rpcData was always unset. Both
transports now carry `data` (validated via isValidJson), so it survives to the
router and reaches dapps via `error.data`. Adds a regression test per transport.

Also drops the now-redundant rpcData cast in EIP1193Provider.request().
@adonesky1 adonesky1 merged commit 0ef51d3 into main Jun 9, 2026
20 checks passed
@adonesky1 adonesky1 deleted the wapi-1521 branch June 9, 2026 21:15
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