Skip to content

fix: throw DeviceSignerRotatedError when device key rotates between tx creation and approval#1957

Open
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1782767458-fix-device-signer-rotated-error
Open

fix: throw DeviceSignerRotatedError when device key rotates between tx creation and approval#1957
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1782767458-fix-device-signer-rotated-error

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Description

When a mobile device loses its device signer key (e.g. unreliable Android Keystore on budget devices like HOTWAV Cyber 13), the SDK's DeviceRecoveryService generates a new device key. If a transaction/signature was already created for the previous device key, approve() throws a generic InvalidSignerError with no guidance on what happened or how to recover.

This PR introduces a specific DeviceSignerRotatedError (code: wallet:signer-rotated) that is thrown instead when the SDK detects that:

  1. The pending approval requires a device:* signer
  2. The current wallet signer is also a device signer (i.e. recovery happened)
  3. The locators don't match

The new error includes both the expected (old) and actual (new) signer locators and a message guiding the developer to re-create the transaction.

Changes:

  • @crossmint/common-sdk-base: Add SIGNER_ROTATED to WalletErrorCode
  • @crossmint/wallets-sdk:
    • Add DeviceSignerRotatedError class with expectedLocator and actualLocator fields
    • approveTransactionInternal(): detect device signer mismatch → throw DeviceSignerRotatedError
    • approveSignatureInternal(): same detection logic
    • Export DeviceSignerRotatedError from package index

Refs: WAL-10931, Pylon #8810

Test plan

  • Unit tests covering three scenarios added to wallet.test.ts:
    • Transaction approval with rotated device signer → throws DeviceSignerRotatedError
    • Signature approval with rotated device signer → throws DeviceSignerRotatedError
    • Non-device signer mismatch → still throws InvalidSignerError (no regression)
  • Full wallets-sdk test suite passes (638 tests)
  • Lint passes via pnpm lint:fix

Package updates

  • @crossmint/common-sdk-base: patch (new SIGNER_ROTATED error code)
  • @crossmint/wallets-sdk: patch (new DeviceSignerRotatedError + detection logic)
  • Changeset added via .changeset/device-signer-rotated-error.md

Link to Devin session: https://crossmint.devinenterprise.com/sessions/be8e8d0513e146219fd9f723d83641eb
Requested by: @0xEmilio

…tes between tx creation and approval

When the device signer key is lost (e.g. unreliable Android Keystore on
budget devices), the SDK's DeviceRecoveryService generates a new key.
If a transaction was already created for the previous device key, the
approve() flow would throw a generic InvalidSignerError with no guidance.

Now throws DeviceSignerRotatedError (code: wallet:signer-rotated) with
both the expected and actual signer locators, guiding the developer to
re-create the transaction so it uses the current device signer.

Applies to both approveTransactionInternal and approveSignatureInternal.

Refs: WAL-10931, Pylon #8810
Co-Authored-By: Emilio <emilio@paella.dev>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author
Original prompt from Emilio

https://app.usepylon.com/support/issues/views/6843d734-fbc0-4c1d-80b5-3c69bd09c66e?issueNumber=8810

can you TAL? Ignore the sentry link he pasted the error

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@changeset-bot

changeset-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a35fe50

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 18 packages
Name Type
@crossmint/wallets-sdk Patch
@crossmint/common-sdk-base Patch
@crossmint/wallets-quickstart-devkit Patch
@crossmint/wallets-playground-react Patch
@crossmint/client-sdk-react-base Patch
@crossmint/client-sdk-react-native-ui Patch
@crossmint/client-sdk-react-ui Patch
@crossmint/client-sdk-auth Patch
@crossmint/client-sdk-base Patch
@crossmint/client-sdk-verifiable-credentials Patch
@crossmint/client-sdk-smart-wallet Patch
@crossmint/client-sdk-walletconnect Patch
@crossmint/common-sdk-auth Patch
@crossmint/server-sdk Patch
@crossmint/wallets-playground-expo Patch
@crossmint/auth-ssr-nextjs-demo Patch
@crossmint/client-sdk-nextjs-starter Patch
crossmint-auth-node Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
packages/wallets/src/utils/errors.ts:95-100
The error message refers to "this transaction" even when this error is thrown from the signature-approval path (`approveSignatureInternal`). A developer approving a `signatureId` will see a confusing message telling them to "re-create the transaction" when they should re-create the signature request instead.

```suggestion
        super(
            `The device signer was rotated since this request was created. ` +
                `The pending approval requires signer '${expectedLocator}', but the current device signer is '${actualLocator}'. ` +
                `Please re-create the transaction or signature request so it uses the current device signer.`,
            WalletErrorCode.SIGNER_ROTATED
        );
```

### Issue 2 of 2
packages/wallets/src/wallets/wallet.ts:1227-1234
Duplicated device-signer rotation detection block — the same 8-line guard appears identically in both `approveSignatureInternal` (line ~1168) and `approveTransactionInternal` (here). Extracting it into a small helper (e.g. `throwIfDeviceSignerRotated(signers, pendingApproval)`) would keep the two call-sites in sync and make future changes (different condition, different fields on the error) a single-place edit.

Reviews (1): Last reviewed commit: "fix(wallets-sdk): throw DeviceSignerRota..." | Re-trigger Greptile

Comment on lines +95 to +100
super(
`The device signer was rotated since this transaction was created. ` +
`The transaction requires signer '${expectedLocator}', but the current device signer is '${actualLocator}'. ` +
`Please re-create the transaction so it uses the current device signer.`,
WalletErrorCode.SIGNER_ROTATED
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The error message refers to "this transaction" even when this error is thrown from the signature-approval path (approveSignatureInternal). A developer approving a signatureId will see a confusing message telling them to "re-create the transaction" when they should re-create the signature request instead.

Suggested change
super(
`The device signer was rotated since this transaction was created. ` +
`The transaction requires signer '${expectedLocator}', but the current device signer is '${actualLocator}'. ` +
`Please re-create the transaction so it uses the current device signer.`,
WalletErrorCode.SIGNER_ROTATED
);
super(
`The device signer was rotated since this request was created. ` +
`The pending approval requires signer '${expectedLocator}', but the current device signer is '${actualLocator}'. ` +
`Please re-create the transaction or signature request so it uses the current device signer.`,
WalletErrorCode.SIGNER_ROTATED
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/wallets/src/utils/errors.ts
Line: 95-100

Comment:
The error message refers to "this transaction" even when this error is thrown from the signature-approval path (`approveSignatureInternal`). A developer approving a `signatureId` will see a confusing message telling them to "re-create the transaction" when they should re-create the signature request instead.

```suggestion
        super(
            `The device signer was rotated since this request was created. ` +
                `The pending approval requires signer '${expectedLocator}', but the current device signer is '${actualLocator}'. ` +
                `Please re-create the transaction or signature request so it uses the current device signer.`,
            WalletErrorCode.SIGNER_ROTATED
        );
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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 from the cold-blooded code crawler 🐍 — fixed in a35fe50. The message now says "request" instead of "transaction" so it makes sense from both approval paths.

Comment on lines +1227 to +1234
const deviceSigner = signers.find((s) => s.type === "device");
if (
pendingApproval.signer.locator.startsWith("device:") &&
deviceSigner != null &&
deviceSigner.locator() !== pendingApproval.signer.locator
) {
throw new DeviceSignerRotatedError(pendingApproval.signer.locator, deviceSigner.locator());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Duplicated device-signer rotation detection block — the same 8-line guard appears identically in both approveSignatureInternal (line ~1168) and approveTransactionInternal (here). Extracting it into a small helper (e.g. throwIfDeviceSignerRotated(signers, pendingApproval)) would keep the two call-sites in sync and make future changes (different condition, different fields on the error) a single-place edit.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/wallets/src/wallets/wallet.ts
Line: 1227-1234

Comment:
Duplicated device-signer rotation detection block — the same 8-line guard appears identically in both `approveSignatureInternal` (line ~1168) and `approveTransactionInternal` (here). Extracting it into a small helper (e.g. `throwIfDeviceSignerRotated(signers, pendingApproval)`) would keep the two call-sites in sync and make future changes (different condition, different fields on the error) a single-place edit.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

Fair point from the slithering style inspector 🦎, but this is a 6-line guard in two places — extracting a helper here would be over-engineering for the scope of this fix. If this pattern grows or gains more callers, happy to refactor then.

The error is thrown from both transaction and signature approval paths,
so the message now says 'request' instead of 'transaction'.

Co-Authored-By: Emilio <emilio@paella.dev>
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "fix: use generic wording in DeviceSigner..." | Re-trigger Greptile

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.

0 participants