Skip to content

fix: decode authorization util and prefix errors#8656

Merged
matthewwalsh0 merged 1 commit intomainfrom
fix/canonical-authorization-signature
May 1, 2026
Merged

fix: decode authorization util and prefix errors#8656
matthewwalsh0 merged 1 commit intomainfrom
fix/canonical-authorization-signature

Conversation

@matthewwalsh0
Copy link
Copy Markdown
Member

@matthewwalsh0 matthewwalsh0 commented Apr 30, 2026

Explanation

Current

EIP-7702 production failures across mobile and extension are surfacing as bare, unattributable strings in our error metrics — most prominently failed to decode param in array[0] invalid JSON input from Sentinel relay.

  1. Non-canonical RLP hex in authorization signatures. signAuthorization slices raw 32-byte signature halves into r and s without canonicalizing.
  2. Bare error strings with no failure-surface attribution. When a 7702 transaction fails, the same generic message can come from any of: core's eth_sendRawTransaction, the Relay strategy's /execute POST, the non-execute relay deposit path, or the Pay publish hook. Metrics can't distinguish them, and Relay's actual server error ({ message: "Insufficient liquidity" }) is discarded by successfulFetch in favour of a generic URL-leaking template.

Solution

Canonicalize once, in core. A new exported decodeAuthorizationSignature(signature) utility in @metamask/transaction-controller decodes a 65-byte EIP-7702 authorization signature into RLP-canonical r, s, and yParity. signAuthorizationList is refactored to use it, so every signed authorization tuple emitted by the controller is canonical out of the box. Mobile/extension can read straight off txMeta.txParams.authorizationList and submit to any backend without their own strip helpers.

Layered submission error prefixes. Four prefixes, each applied at the lowest sensible layer, that cascade up the call stack to attribute every failure surface in metrics:

Layer Prefix Wraps
Core RPC RPC submit: eth_sendRawTransaction failures
Relay strategy Relay submit: the entire submitRelayQuotes body
Inner relay execute Relay execute: Relay /execute POST failures (cascades inside Relay submit:)
Pay hook MetaMask Pay: the entire TransactionPayPublishHook.#hookWrapper

A private relayFetch helper in relay-api.ts replaces successfulFetch for Relay endpoints. On non-OK responses it surfaces the response body's message or error field as <status> - <message>, falling back to <status> alone — no URL leakage, server's actual reason preserved when present.

Example errors as they appear in metrics

MetaMask Pay: Relay submit: Relay execute: 422 - Insufficient liquidity
MetaMask Pay: Relay submit: RPC submit: nonce too low
MetaMask Pay: Insufficient source token balance for relay deposit
MetaMask Pay: Relay submit: Relay execute: 500
MetaMask Pay: Relay submit: Relay execute: 400 - failed to decode param in array[0] invalid JSON input
RPC submit: replacement transaction underpriced

References

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
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
Changes transaction submission error handling and EIP-7702 signature decoding, which can affect downstream clients’ ability to submit transactions and how failures are reported. Main risk is altered error strings/prefixes and canonicalization behavior impacting integrations/tests.

Overview
Adds a new exported decodeAuthorizationSignature helper for EIP-7702 that canonicalizes r/s (strips leading zeroes, uses 0x0 for zero) and derives yParity, and refactors authorization signing to use it.

Standardizes failure-surface attribution by wrapping submission errors with layered prefixes: RPC submit: for eth_sendRawTransaction failures (including nested data.message), Relay submit: for Relay strategy execution, Relay execute: for Relay /execute POST failures, and MetaMask Pay: for Pay publish hook failures.

Replaces Relay’s successfulFetch usage with a local relayFetch that preserves server-provided non-OK details (<status> - <message|error> or <status>) without leaking request URLs, and updates tests/changelogs accordingly.

Reviewed by Cursor Bugbot for commit 1eb9b8c. Bugbot is set up for automated code reviews on this repo. Configure here.

@matthewwalsh0 matthewwalsh0 force-pushed the fix/canonical-authorization-signature branch 4 times, most recently from fa2e557 to 26b8e0d Compare May 1, 2026 07:13
@matthewwalsh0 matthewwalsh0 changed the title fix: canonicalize EIP-7702 authorization signatures and prefix publish/relay errors fix: decode authorization util and prefix errors May 1, 2026
@matthewwalsh0 matthewwalsh0 force-pushed the fix/canonical-authorization-signature branch from 26b8e0d to dfe2b38 Compare May 1, 2026 10:21
@matthewwalsh0 matthewwalsh0 force-pushed the fix/canonical-authorization-signature branch from dfe2b38 to 71a8388 Compare May 1, 2026 10:31
… EIP-7702 authorization signatures and add layered submission error prefixes

Centralizes EIP-7702 authorization signature decoding in the transaction
controller via a new `decodeAuthorizationSignature` utility that returns
`r`, `s`, and `yParity` in RLP-canonical form (no leading zero nibbles,
`0x0` for zero). go-ethereum-style decoders (Sentinel relay, public RPCs,
Relay quote endpoints) reject non-canonical integers at decode time,
surfacing as bare `failed to decode param in array[0] invalid JSON input`
errors in production metrics. With canonical decoding centralised here,
clients consume signed authorization tuples directly without needing
their own per-field strip helpers.

Adds layered submission error prefixes that cascade up the call stack to
attribute every failure surface in error-tracking metrics:

- `RPC submit: <reason>` on `eth_sendRawTransaction` failures
- `Relay submit: <reason>` wraps every error from `submitRelayQuotes`
- `Relay execute: <reason>` wraps Relay `/execute` POST failures (cascades
  inside `Relay submit:` to distinguish the gasless-execute path from the
  non-execute deposit path)
- `MetaMask Pay: <reason>` wraps every error from the Pay publish hook,
  cascading any inner prefixes

Example cascades surfaced in metrics:
- `MetaMask Pay: Relay submit: Relay execute: Insufficient liquidity`
- `MetaMask Pay: Relay submit: RPC submit: nonce too low`
- `MetaMask Pay: Insufficient source token balance for relay deposit`

Also adds a private `relayFetch` helper in `relay-api.ts` that surfaces
the Relay server's `message` or `error` response field (or status code)
on non-OK responses, replacing the previous URL-leaking generic
`Fetch failed with status '500' for request 'https://api.relay.link/...'`
message.
@matthewwalsh0 matthewwalsh0 force-pushed the fix/canonical-authorization-signature branch from 71a8388 to 1eb9b8c Compare May 1, 2026 10:32
@matthewwalsh0 matthewwalsh0 marked this pull request as ready for review May 1, 2026 10:40
@matthewwalsh0 matthewwalsh0 requested review from a team as code owners May 1, 2026 10:40
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1eb9b8c. Configure here.


successfulFetchMock
.mockResolvedValueOnce({
ok: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test mock missing ok: true in timeout scenario

Medium Severity

The .mockImplementation response object is missing ok: true. Since getRelayStatus now goes through relayFetch, which checks response.ok, this causes the second poll to throw a relayFetch error (with message "undefined") rather than returning a successful { status: 'pending' } response. The test still passes by accident — the polling loop catches the throw as a network error, and the timeout fires using lastStatus from the first poll — but it no longer verifies the intended behavior of timing out after receiving repeated valid pending responses.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1eb9b8c. Configure here.

@matthewwalsh0 matthewwalsh0 added this pull request to the merge queue May 1, 2026
Merged via the queue into main with commit 6f416e4 May 1, 2026
366 checks passed
@matthewwalsh0 matthewwalsh0 deleted the fix/canonical-authorization-signature branch May 1, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants