Skip to content

Recovery process with Safe as a Recoverer - feedback  #5890

Description

Bug description

I've searched and cannot see these bugs in your existing github issues.

Many bugs explained below :)

Please do reach out to me if you'd like to chat further: I'm Mike from Aztec Labs. :)

Thanks very much for reading, and hopefully this report can help improve Safe.

There's a summary of recommendations, at the end.

Background

I was experimenting with Safes.

I have set up a "Main Safe" (2 of 2).

I then successfully set up a Recoverer ("Recoverer Safe"). The recoverer is itself a Safe; a 2 of 2.

I also tried other permutations: 2 of 3, 1 of 1, etc etc.

Main Safe link: https://app.safe.global/home?safe=sep:0x3A135e4cD58F42dA5e00bCbFB272E95878bB10bE
Recoverer Safe link: https://app.safe.global/home?safe=sep:0x23Db78d842e84f212352398fA18E19fE23f70B90

Main:

Image

Recoverer:

Image

Commencing Recovery - Connecting to the Recoverer

I then wanted to test the Recovery process.

So I opened my "Main Safe", and I "switched wallet", via WalletConnect, to my "Recoverer Safe".

Here's the Recoverer Safe connected to one of the Recoverer's Signers:

Image

Here's the Main Safe connected to the Recoverer Safe via walletconnect:

Image

It's worth mentioning: it wasn't clear from docs how to connect my recoverer safe to my main safe. I had to ask the "help" chat window people (very helpful) over the weekend. It took me a very very long time to figure this out.

Commencing Recovery - Starting the Recovery Process

Image

There should be a button to "Start Recovery Process" even if my recoverer wallet is not connected to my main wallet. It was not clear to me that I should connect my Recoverer Safe in order to see this button. I just assumed the UI was broken. And it also wasn't clear to me how to connect my Recoverer Safe (and I had to ask the help desk for a couple of hours). This proposed button could live in settings, where it tells me that I have recovery set up. Then this new button could prompt me to connect my recoverer wallet (and how). I wasn't familiar with how to connect two browser tabs via walletconnect, so this should be explained via this new button.
Add a button to "Start Recovery Process" here please, even if I'm not connected to my recoverer wallet.

Once connected, I'm offered the option of starting the recovery process (but as I say above, there should be a button for this, even if I'm not connected, that would tell me what to connect and how):

Image

Starting Recovery

From the "Main Wallet" tab, I chose a replacement Signer:

Image

Next page:

Image

Image

Simulate:

Simulation fails:

Image

Tenderly link: https://dashboard.tenderly.co/public/safe/safe-apps/simulator/d577ed21-b142-4b06-a8f0-04c5ed6d554d

Execute fails:

Image

Here's the execution error message, pasted:

missing revert data (action="estimateGas", data=null, reason=null, transaction={ "data": "0x468721a70000000000000000000000003a135e4cd58f42da5e00bcbfb272e95878bb10be0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064f8dc5dd90000000000000000000000001322d86bf8d255c4b6e3407d936dc648376450ed00000000000000000000000089f70d5f1acd7ccea891a1bbf1ada633e7887c1a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000", "from": "0x23Db78d842e84f212352398fA18E19fE23f70B90", "to": "0x1ba9bDa16375E92Fb6d226dFC0DCC71aD413ac92" }, invocation=null, revert=null, code=CALL_EXCEPTION, version=6.13.4)

Safe{Wallet} – Dashboard
Confirm transaction
Sepolia Logo
Sepolia
Change Account settings

This transaction will reset the Account setup, changing the signers and threshold.

New signer
sep:0x1322d86Bf8d255c4B6E3407D936Dc648376450ED

After recovery, Safe Account transactions will require:

1 out of 1 signers.
Call
removeOwner
on
THIRD TEST

prevOwner address:
0x1322d86Bf8d255c4B6E3407D936Dc648376450ED

owner address:
0x89f70d5F1acd7CcEA891a1bbf1ada633E7887C1A

_threshold uint256:

1

Transaction data

to:
THIRD TEST
0x3A135e4cD58F42dA5e00bCbFB272E95878bB10bE

value:

0

data:
0xf8dc5dd90000000000000000000000001322d86bf8d255c4b6e340...

operation:
0 (call)

safeTxGas:
0

baseGas:
0

gasPrice:
0

gasToken:
0x0000...0000

refundReceiver:
0x0000...0000

nonce:
2

Transaction hashes

Domain hash:
0xa41a5630e5971814c29d6f6dafd89f9283f1bac8e53d41836769f106a1da6b34

Message hash:
0x10f417016c4fb516023ab1ace5c646aaa05d76c6a8126e16a6a45d6390c03759

safeTxHash:
0xb12a1298ae63ed7888931e2a8ac3af5962c5a31e0bbd42963e3b26fa9c595657
Transaction checks

Run a simulation
Powered by Tenderly

Error
execute

You're about to execute this transaction.
Error submitting the transaction. Please try again.

missing revert data (action="estimateGas", data=null, reason=null, transaction={ "data": "0x468721a70000000000000000000000003a135e4cd58f42da5e00bcbfb272e95878bb10be0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064f8dc5dd90000000000000000000000001322d86bf8d255c4b6e3407d936dc648376450ed00000000
Recovery will be possible in 5 minutes after this transaction is executed.

Transaction status

    Create
    Confirmed (0 of 2)+1
    Execute

Simulation failed

The transaction failed during the simulation throwing error GS026 in the contract at 0x29fcb43b46531bca003ddc8fcb67ffe91900c762. Full simulation report is available [on Tenderly](https://dashboard.tenderly.co/public/safe/safe-apps/simulator/d577ed21-b142-4b06-a8f0-04c5ed6d554d).

Woah, that pasted more than I expected.

Please keep reading... because we can make further progress with a few tweaks.

Criticisms up until this point

Is there a way for it to know that my recoverer is a safe. "Execute" is the wrong prompt here, since I first need to call to my Recoverer Safe for two Signatures. In fact, I'll never "Execute" from this "Main Safe" browser tab... Once I've signed on my "Recoverer Safe" browser tab (see below for how), I'll be executing from that Recoverer Safe browser tab. This is not obvious from the UI prompts, and I think they could guide me better in how to Recover through both the Main Safe browser tab and the Recoverer Safe browser tab - especially since Recoverers will usually be inexperienced users who might never have done anything blockchainy before.

Trying again, with a small change

Notice in the screenshots above that my "Recoverer Safe" has no Sepolia ETH balance. We'll try again after sending some ETH to this Recoverer Safe, and we'll see that we can progress further. We'll still see some bad bugs, but we'll get further.

My understanding is that the "Recoverer Safe" should not need a > 0 balance. The Recoverer Safe is for signing; it doesn't need to be the eth address that actually "executes" (i.e. to send an actual tx to the network and spend gas).

Anyway, let's fund it with some ETH. This was a suggestion of a colleague as we sat down trying many different things until it worked.

Image

Sending ETH to the Recoverer Safe address

Image

Address before

Image

Address after - we're rich!

Recover again:

Image

Start account recovery:

Image

It shows me this, same as before:

Image

Some comments:

I think the "Confirmed 0 of 2" is referring to the "Main Safe", when in this kind of tx (a recovery tx), the "Main Safe" does not need to contribute any signatures. Perhaps this is a bug.

I don't know what the "+1" in green means:

Image

Simulate again:

Simulation Fails again:

Image

Tenderly link: https://dashboard.tenderly.co/public/safe/safe-apps/simulator/11f57f21-7816-4833-971c-912e2e4d5d01

The Execute Button

But if we press "Execute", we progress further than last time!! Look at the browser tab of the recoverer wallet:

Image

It's flashing with this ^^^

Recommendation: Make this flash more frequently (if you have control over that), with more red colour. It's not noticeable enough, and I didn't spot it at first.

As mentioned before: this Recoverer Safe should not need to be funded with ETH. This flow should succeed without any eth in the Recoverer Safe.

Clicking over to the Recoverer Safe's tab:

Image

This looks promising.

Simulation succeeds!

Image

Tenderly successful simulation link: https://dashboard.tenderly.co/public/safe/safe-apps/simulator/55c02efb-95ec-47ea-bac2-b0331ebe52b7

Sign 1

Let's Sign:

Image

That worked!

Image

Interestingly, signing with a Ledger Flex was pain. It didn't display all of the calldata. It showed some of the calldata, and then just had ..., even if I pressed "more". Insane. It also didn't show a message hash! That's a Ledger problem, I guess?

Sign 2

Let's switch wallet to the 2nd signer of the Recoverer Safe and sign a 2nd time:

Simulation succeeded again: https://dashboard.tenderly.co/public/safe/safe-apps/simulator/6830d3ea-0f2c-4d55-ac62-66256d66a503

It's nice that it shows 1 of 2 now, although I still don't know what that green "+1" means?

Image

Ok, I clicked and signed it a 2nd time. Great.

Still in the Recoverer Safe browser tab:

Image

Execute:

I switched to a different wallet; a wallet that is not one of the signers of the Recoverer Safe; but just any old wallet that has enough ETH to send the tx. As mentioned above, this is why I wouldn't have expected the following:

  • I wouldn't expect the "Main Safe" browser tab to have offered me an "Execute" button, because that tab _is not the tab that is executing".
  • I wouldn't expect the "Recoverer Safe" address to be funded with any ETH (but I was forced to fund it to overcome the above bug), because that Recoverer Safe address is not the address that will be spending gas on this tx.

Image

Switched wallet.

2 of 2 signatures confirmed. About to execute the tx from my unrelated wallet:

Image

Simulation with tenderly succeeds: https://dashboard.tenderly.co/public/safe/safe-apps/simulator/494a1b6e-da95-4565-871f-9cad75c3282d

Dangerous, wrong data being shown by Safe UI

It's important that I show the full tx data here, because there's a big and dangerous bug. The data I'm being shown is not the data that I am about to approve via my unrelated wallet in order to send the tx.

I'm being shown the data that my two Recoverer Safe signers needed to sign. This is not the same as the calldata that I now need to approve.

Image

.

Image

.

Image

.

Image

Another nit: this data could all appear on one laptop screen without scrolling. As a user, I'm more prone to make mistakes if I have to scroll in order to read through the data that I'm about to sign. And it's mildly annoying to have to scroll.

Let's compare the raw data that Safe showed me, vs the raw data that Rabby is showing me:

Safe data:

{
  "to": "0x1ba9bDa16375E92Fb6d226dFC0DCC71aD413ac92",
  "value": "0",
  "data": "0x468721a70000000000000000000000003a135e4cd58f42da5e00bcbfb272e95878bb10be0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064f8dc5dd90000000000000000000000001322d86bf8d255c4b6e3407d936dc648376450ed00000000000000000000000089f70d5f1acd7ccea891a1bbf1ada633e7887c1a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000",
  "operation": 0,
  "baseGas": "0",
  "gasPrice": "0",
  "gasToken": "0x0000000000000000000000000000000000000000",
  "refundReceiver": "0x0000000000000000000000000000000000000000",
  "nonce": 0,
  "safeTxGas": "0"
}

Rabby data:

{
    "chainId": 11155111,
    "data": "0x6a7612020000000000000000000000001ba9bda16375e92fb6d226dfc0dcc71ad413ac920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000124468721a70000000000000000000000003a135e4cd58f42da5e00bcbfb272e95878bb10be0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064f8dc5dd90000000000000000000000001322d86bf8d255c4b6e3407d936dc648376450ed00000000000000000000000089f70d5f1acd7ccea891a1bbf1ada633e7887c1a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082c23517d667b78f9a21e3ed5fd9c41f96407a2e6b12b4af64399f9d354113ffb129a72c7c553d796bd288f9e79b0f60a52f68b93775625548a3732edf853a14041b00003ac62f9739ef0edeb2283446f3f3c97250d992e3f2318c41883c0cf453e42fba120b24681098ccbb0eb5f35a01694e8ec8367e8ced626f984c83ea9a1b6d1b000000000000000000000000000000000000000000000000000000000000",
    "from": "0x1322d86bf8d255c4b6e3407d936dc648376450ed",
    "gas": "0x3fbac",
    "gasPrice": "0x3678fd81",
    "nonce": "0x13",
    "to": "0x23Db78d842e84f212352398fA18E19fE23f70B90"
}

It's completely different data!!!

How are users meant to validate this? Especially inexperienced Recoverers.

The Safe UI is showing me the message that my Signers signed, but at this stage I don't want that data; I want the data of the tx that I'm about to send to Ethereum.

The Safe UI could do a much better job of telling me exactly the calldata that my wallet (Rabby/Metamask/Ledger/Trezor/...) is about to show me. I mean the raw, ABI-encoded calldata.

Rabby also, helpfully, shows me the raw ABI-encoded calldata that I'm about to sign:

0x6a7612020000000000000000000000001ba9bda16375e92fb6d226dfc0dcc71ad413ac920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000124468721a70000000000000000000000003a135e4cd58f42da5e00bcbfb272e95878bb10be0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064f8dc5dd90000000000000000000000001322d86bf8d255c4b6e3407d936dc648376450ed00000000000000000000000089f70d5f1acd7ccea891a1bbf1ada633e7887c1a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082c23517d667b78f9a21e3ed5fd9c41f96407a2e6b12b4af64399f9d354113ffb129a72c7c553d796bd288f9e79b0f60a52f68b93775625548a3732edf853a14041b00003ac62f9739ef0edeb2283446f3f3c97250d992e3f2318c41883c0cf453e42fba120b24681098ccbb0eb5f35a01694e8ec8367e8ced626f984c83ea9a1b6d1b000000000000000000000000000000000000000000000000000000000000

The SafeUtils can also show me this raw abi encoding, but I think the Safe UI should also show it.

I've tried with Rabby, Ledger Flex, Trezor Safe 5, and the only commonality at this stage of signing a tx is the raw ABI encoding of the data.

When I first encountered all this last week, I actually wrote a small script to format the raw abi encodings according to Ledger Flex and Trezor Safe 5: https://github.com/iAmMichaelConnor/hw_wallet_calldata_formatter

Safe really must show me the correct data. As I say, the data that Safe is showing me is wrong; it's showing me the data that I signed earlier; not the data I'm about to sign. Big bug.

If I navigate back to the "Transactions" tab, to look at the queue, I get some data that corresponds to what Rabby is showing me, e.g. the two signatures. But even so, it's not in a universally-useful format, and it's missing key information vs the raw abi encoding.

Image

Clicking "Execute" one last time

This will succeed, and the Recovery will work (yay!).

I didn't proceed with clicking "Execute" in this example, in case you wanted to experiment and play with this exact combination of Safes. Having said that, I've spoiled the reproducibility of one of the bugs, by funding the Recoverer address with ETH. So to reproduce, you'll likely need to create your own pair of Safes, ensuring not to fund the Recoverer with ETH.

It is worth noting, that when I click back to my "Main Safe" screen, it tells me that this failed. I experimented, and it will tell me that this fails even if I do proceed with the full recovery process from my Recoverer Safe. (I've repeated this experiment so many times with many new Safes).

This failure message is extremely misleading; especially for a novice.

Image

could not coalesce error (error={ "message": "Request expired. Please try again." }, payload={ "id": 7, "jsonrpc": "2.0", "method": "eth_sendTransaction", "params": [ { "data": "0x468721a70000000000000000000000003a135e4cd58f42da5e00bcbfb272e95878bb10be0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064f8dc5dd90000000000000000000000001322d86bf8d255c4b6e3407d936dc648376450ed00000000000000000000000089f70d5f1acd7ccea891a1bbf1ada633e7887c1a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000", "from": "0x23db78d842e84f212352398fa18e19fe23f70b90", "gas": "0x198ee", "maxFeePerGas": "0x948ea45c", "maxPriorityFeePerGas": "0xf4240", "to": "0x1ba9bda16375e92fb6d226dfc0dcc71ad413ac92" } ] }, code=UNKNOWN_ERROR, version=6.13.4)

I get this message even if Recovery succeeds.

The "Main Safe" should be able to gracefully handle the very slow process of signing via a Recoverer Safe. Indeed, the act of signing via a Recoverer safe could take days, and the Main Safe should never give the impression that recovery has failed during that time.

I have to refresh the Main Safe to see that Recovery has begun. Perhaps as a quick fix, you can say "Hey, since your Recoverer is itself a Safe, you'll need to refresh this Main tab once you're done with the recovery.

Summary of problems

  • It wasn't clear from docs how to connect my recoverer safe to my main safe.
  • There should be a button to "Start Recovery Process" even if my recoverer wallet is not connected to my main wallet. This button could live in settings, where it tells me that I have recoery set up. Then this new button could prompt me to connect my recoverer wallet (and how). I wasn't familiar with how to connect two browser tabs via walletconnect, so this should be explained via this new button.
  • When starting recovery, it offers me the option to "Simulate" from the Main Safe browser tab. This simulation will always fail; this tab should not offer me the option of Simulating, nor Executing, because this Main Safe browser tab is not the tab that will be doing Signing nor Executing.
  • Is there a way for it to know that my recoverer is a safe? "Execute" is the wrong prompt here, since I first need to call to my Recoverer Safe for two Signatures. In fact, I'll never "Execute" from this "Main Safe" browser tab... Once I've signed on my "Recoverer Safe" browser tab (see below for how), I'll be executing from that Recoverer Safe browser tab. This is not obvious from the UI prompts, and I think they could guide me better in how to Recover through both the Main Safe browser tab and the Recoverer Safe browser tab.
  • Given the differences of using a Safe for Recovery vs a single EoA, I think the Main Safe should be told (through some config) that the Recoverer address is a Safe. I believe the current flow is not well-designed for a Recoverer that is a Safe wallet.
  • The "Execute" process fails from the "Main" browser tab if my Recoverer Safe has 0 ETH. This should not happen. I should never have to fund my Recoverer Safe with ETH.
  • I don't know what the "+1" in green means.
  • Liaise with the Ledger team and tell them it's unacceptable to truncate the calldata on their Ledger Flex device with .... :)
  • Liaise with the Ledger team and tell them to show a message hash, when signing a message.
  • Given the time to sign with a Recoverer Safe (which might be days), the Main Safe should be aware that its recoverer is itself a Safe, so that it doesn't timeout. The timeout error on the Main Safe was very confusing. Novice users might not think to refresh the page. In fact, they might try to commence recovery all over again, even though it succeeded.
  • Nit: if on a laptop, use more of the screen:
    • Wider.
    • Avoid scrollbar if there's space for me to view everything at once.
  • The data being shown at the time of "Execute" is wrong.
    • I have noticed this for any kind of Safe tx. Even for simply sending ETH or USDC.
    • To reproduce: Sign, Sign, but defer execution until "Later".
      • I always defer execution until later, so as to use a completely unrelated address to actually send the tx and spend gas.
    • The data being shown at the time of "Execute" is the earlier data from the Signing step. It should not be this data: it should show me the data that I'm about to approve, which is a much larger string of calldata.
    • I'd describe this bug as "Dangerous".

Thanks very much for reading!

Environment

I've tried all of the following permutations during my experiments:

  • Browser:
    • Chrome for both Main Safe Wallet & Recoverer Safe Wallet.
    • Firefox for both Main Safe Wallet & Recoverer Safe Wallet.
    • Firefox for one of the Safes, and Chrome for the other Safe.
  • Wallet:
    • Ledger
    • Rabby
    • MetaMask
  • Chain:
    • Ethereum mainnet
    • Sepolia

Steps to reproduce

See above.

Expected result

Obtained result

Screenshots


🤖 Refined and Investigated by Claude — 2026-07-16

Session: Wallet Hygiene cloud routine, run 2026-07-16

Labels added: Type → Bug, Tech → Frontend

Investigation notes:

  • Code: Confirmed apps/web/src/features/recovery/ exists as its own feature module (README, RecoverySettings, useRecoveryTxNotification, Storybook stories), confirming this is a real, actively-maintained web feature area — not a stale/removed flow.
  • Notion: Found "Safe as an Recoverer: create recovery tx form is not updated and manual closing is required" — a separate internal doc describing a related-but-distinct recovery-tx UI bug, and "Safe{RecoveryHub} - Technical Deep-dive" for architecture context. Neither appears to directly address the "wrong calldata shown at Execute" issue this ticket calls "Dangerous."
  • Datadog/Mixpanel: not applicable (no error-rate/usage signal readily tied to this UX/data-display bug).
  • Unclear/missing: could not verify from code search alone whether the "data shown at Execute time is the earlier Sign-step data, not the actual tx data" bug is still present — would need to reproduce in the live app.

Flagged for a human:

  • This is a GitHub-attachment-linked issue (safe-wallet-monorepo#5890) but createdById is a named human (Simon Abehsera) with a "Migrated" label — per the exclusion rule this is an internal migration artifact, not external, so it was processed normally (not skipped).
  • Ticket bundles many distinct bugs/UX issues into one report (confusing "Execute" prompt when recoverer is itself a Safe, forced ETH funding of the Recoverer Safe, misleading "Confirmed 0 of 2" count, misleading post-recovery timeout error, and the "Dangerous" wrong-calldata-shown-at-Execute issue). Recommend splitting into separate tracked tickets, prioritizing the "Dangerous" data-integrity item (user is shown different calldata at Execute time than what they're about to approve) given its severity.
  • No "Features" label added — "Account recovery" isn't in the current Features taxonomy; closest existing code area is apps/web/src/features/recovery. Left unset rather than force-fitting an approximate match (e.g. "Multi-sig" or generic "Wallet").
  • Ticket has bounced between Backlog and Canceled multiple times since May 2025 (state history shows repeated Backlog→Canceled→Backlog cycles) and has sat over a year without resolution — may need an explicit triage decision (fix vs. close as won't-fix).

Auto-generated best-effort draft. Edit freely. React 👎 on this issue or ping to flag misses so I learn.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions