Skip to content

feat: trezor error management#471

Open
montelaidev wants to merge 7 commits intomainfrom
feat/mul-1444
Open

feat: trezor error management#471
montelaidev wants to merge 7 commits intomainfrom
feat/mul-1444

Conversation

@montelaidev
Copy link
Contributor

@montelaidev montelaidev commented Mar 9, 2026

This PR updates the trezor keyring to use the hardware error management

Examples


Note

Medium Risk
Changes Trezor keyring error propagation to throw typed HardwareWalletErrors and adds new mapping data, which can affect how callers detect/handle failures across unlock/sign flows.

Overview
Standardizes Trezor error handling across the repo. @metamask/hw-wallet-sdk gains TREZOR_ERROR_MAPPINGS (with new tests) and the root README dependency graph is updated accordingly.

Migrates @metamask/eth-trezor-keyring to use the shared SDK. The package now depends on @metamask/hw-wallet-sdk, exports new Trezor error utilities, and adds trezor-errors/trezor-error-handler helpers that convert bridge errors into typed HardwareWalletErrors.

Updates keyring flows to use the new typed errors. unlock, signTransaction, signPersonalMessage, and signTypedData are refactored to async/await and route failures through handleTrezorTransportError (while preserving address-mismatch validation as plain Errors), with expanded unit tests and higher Jest coverage thresholds.

Written by Cursor Bugbot for commit e7616b9. This will update automatically on new commits. Configure here.

@montelaidev
Copy link
Contributor Author

@metamaskbot publish-preview

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

Preview builds have been published. See these instructions (from the core monorepo) for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/account-api": "1.0.0-543b4a0",
  "@metamask-previews/hw-wallet-sdk": "0.5.0-543b4a0",
  "@metamask-previews/keyring-api": "21.5.0-543b4a0",
  "@metamask-previews/eth-hd-keyring": "13.1.0-543b4a0",
  "@metamask-previews/eth-ledger-bridge-keyring": "11.3.0-543b4a0",
  "@metamask-previews/eth-qr-keyring": "1.1.0-543b4a0",
  "@metamask-previews/eth-simple-keyring": "11.0.0-543b4a0",
  "@metamask-previews/eth-trezor-keyring": "9.0.0-543b4a0",
  "@metamask-previews/keyring-internal-api": "10.0.0-543b4a0",
  "@metamask-previews/keyring-internal-snap-client": "9.0.0-543b4a0",
  "@metamask-previews/eth-snap-keyring": "19.0.0-543b4a0",
  "@metamask-previews/keyring-snap-client": "8.2.0-543b4a0",
  "@metamask-previews/keyring-snap-sdk": "7.2.1-543b4a0",
  "@metamask-previews/keyring-utils": "3.2.0-543b4a0"
}

@montelaidev montelaidev marked this pull request as ready for review March 11, 2026 15:02
@montelaidev montelaidev requested a review from a team as a code owner March 11, 2026 15:02
coin: 'ETH',
});
if (!response.success) {
throw new Error(response.payload?.error || 'Unknown error');
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this used to be ||, but given that we're changing that, let's fix it too:

Suggested change
throw new Error(response.payload?.error || 'Unknown error');
throw new Error(response.payload?.error ?? 'Unknown error');

Comment on lines +705 to +731
await expect(
keyring.signTypedData(
fakeAccounts[0],
{
types: { EIP712Domain: [], EmptyMessage: [] },
primaryType: 'EmptyMessage',
domain: {},
message: {},
},
{ version: SignTypedDataVersion.V4 },
),
).rejects.toThrow(HardwareWalletError);

await expect(
keyring.signTypedData(
fakeAccounts[0],
{
types: { EIP712Domain: [], EmptyMessage: [] },
primaryType: 'EmptyMessage',
domain: {},
message: {},
},
{ version: SignTypedDataVersion.V4 },
),
).rejects.toMatchObject({
code: ErrorCode.Unknown,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's be DRY here:

Suggested change
await expect(
keyring.signTypedData(
fakeAccounts[0],
{
types: { EIP712Domain: [], EmptyMessage: [] },
primaryType: 'EmptyMessage',
domain: {},
message: {},
},
{ version: SignTypedDataVersion.V4 },
),
).rejects.toThrow(HardwareWalletError);
await expect(
keyring.signTypedData(
fakeAccounts[0],
{
types: { EIP712Domain: [], EmptyMessage: [] },
primaryType: 'EmptyMessage',
domain: {},
message: {},
},
{ version: SignTypedDataVersion.V4 },
),
).rejects.toMatchObject({
code: ErrorCode.Unknown,
});
const fakeTypedData = {
types: { EIP712Domain: [], EmptyMessage: [] },
primaryType: 'EmptyMessage',
domain: {},
message: {},
};
await expect(
keyring.signTypedData(
fakeAccounts[0],
fakeTypedData,
{ version: SignTypedDataVersion.V4 },
),
).rejects.toThrow(HardwareWalletError);
await expect(
keyring.signTypedData(
fakeAccounts[0],
fakeTypedData,
{ version: SignTypedDataVersion.V4 },
),
).rejects.toMatchObject({
code: ErrorCode.Unknown,
});

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm slightly confused here, we used to those mapping in our own SDK hw-wallet-sdk, should we put this back there? Like this one: https://github.com/MetaMask/accounts/blob/main/packages/hw-wallet-sdk/src/hardware-error-mappings.ts

Copy link
Contributor

Choose a reason for hiding this comment

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

Or maybe we should do it the other way around and have the Ledger mapping in our own "ledger keyring" package. This way we can re-use constants coming from official Ledger packages.

The SDK would just be the common definitions/interface that we can use in of our HW wallet packages.

That might be a better move actually!

Comment on lines +271 to +276
const normalizedIdentifier = normalizeValue(identifier);
const mappedIdentifier = NORMALIZED_IDENTIFIER_MAP.get(normalizedIdentifier);
if (!mappedIdentifier) {
return undefined;
}
return TREZOR_ERROR_MAPPINGS[mappedIdentifier];
Copy link
Contributor

Choose a reason for hiding this comment

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

We can just use this? (we can maybe even omit the ?. part, since it would return undefined if it's not known).

Though, we can keep the if if you prefer to be explicit (I'm definitely ok with this)

Suggested change
const normalizedIdentifier = normalizeValue(identifier);
const mappedIdentifier = NORMALIZED_IDENTIFIER_MAP.get(normalizedIdentifier);
if (!mappedIdentifier) {
return undefined;
}
return TREZOR_ERROR_MAPPINGS[mappedIdentifier];
return TREZOR_ERROR_MAPPINGS?.[getTrezorErrorIdentifier(identifier)];

const errorMapping = getTrezorErrorMapping(identifier);

if (errorMapping) {
const normalizedContext = context?.trim().toLowerCase();
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's re-use the same "normalize" function we already have, in case we decide to normalize differently:

Suggested change
const normalizedContext = context?.trim().toLowerCase();
const normalizedContext = context ? normalizeValue(context) : undefined;


if (errorMapping) {
const normalizedContext = context?.trim().toLowerCase();
const normalizedMessage = errorMapping.message.toLowerCase();
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here (though, is that ok to trim this one?):

Suggested change
const normalizedMessage = errorMapping.message.toLowerCase();
const normalizedMessage = normalizeValue(errorMapping.message);

});

if (!response.success) {
throw new Error(response.payload?.error || 'Unknown error');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
throw new Error(response.payload?.error || 'Unknown error');
throw new Error(response.payload?.error ?? 'Unknown error');

Copy link

@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.

There are 2 total unresolved issues (including 1 from previous review).

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.

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.

2 participants