> ## Documentation Index
> Fetch the complete documentation index at: https://docs.defiloops.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Safe wallets, and the one thing they cannot do

> Why a Safe cannot take a flash loan without a module, and what we do instead

Why a Safe cannot take a flash loan without a module, and what we do instead.

Safe is the most widely held smart wallet in DeFi, so "can a Safe just use this
directly?" is the first question anyone asks. The answer splits in two, and the
two halves get opposite answers.

Everything below was measured against a real Safe v1.4.1 on Base, not reasoned
about. The tests are in `contracts/test/SafeDelegateFlash.t.sol`.

## The half that works

A Safe can run an arbitrary batch of calls **with nothing installed on it**. The
owners sign one ordinary `execTransaction` with `operation = 1`, which is a
delegatecall, and the batch executes in the Safe's own context: `address(this)`
is the Safe, the storage is the Safe's, and `msg.sender` at every target is the
Safe.

This is not a trick. It is exactly what Safe's own `MultiSend` is, and it is why
"batch these calls" needs no module, no extension and no setup.

## The half that does not

A flash loan cannot be taken this way. Not "is awkward to": cannot.

**The lender calls back.** Every flash loan is two steps: the lender sends the
money, then calls a function on the recipient to let it do the work before
repaying. Balancer calls `receiveFlashLoan`. Morpho calls `onMorphoFlashLoan`.
Aave calls `executeOperation`.

That callback is a **fresh external call to the Safe** for a function the Safe
does not have. Solidity does not care that a delegatecall frame is still open
further down the stack; the call arrives from outside and is dispatched by the
Safe's `fallback`, which forwards it to the Safe's **fallback handler**.

And a fallback handler cannot help, for a reason that is easy to miss: Safe
reaches its handler with `call`, not `delegatecall`. The handler therefore runs
in **its own** context. Even a custom handler that implemented `receiveFlashLoan`
perfectly would be a different contract holding no tokens and having no authority
over the Safe.

<Warning>
  Nominating a different flash recipient does not rescue this. Balancer, Morpho and
  Aave all let you name the recipient, so a helper contract can receive the
  callback, but that helper then has no authority over the Safe, which is the
  thing you needed.
</Warning>

Nor can an approval-based helper substitute. Given an allowance, a helper can
`supply` and `repay` on a Safe's behalf, because those take an `onBehalfOf`. It
can never `withdraw` (Aave's withdraw acts on `msg.sender`'s own position) and
it cannot `borrow` without separate credit delegation. Withdrawing collateral is
half of every unwind, so this route stops before it starts.

## Which leaves three options

|                                        | What it costs                            | What it grants                                              |
| -------------------------------------- | ---------------------------------------- | ----------------------------------------------------------- |
| **A Safe module**                      | a module install, approved by the owners | permanent, `execTransaction`-equivalent power over the Safe |
| **A custom fallback handler**          | one storage slot on the Safe             | nothing useful: the handler still cannot act as the Safe    |
| **A Kernel account owned by the Safe** | nothing installed on the Safe at all     | the full adapter set, bounded per operation                 |

So: **if the position must live inside the Safe itself, a module is the only
way.** That is a real cost. A Safe module can call `execTransaction`-equivalent
functions on the Safe for as long as it is installed, its power is not scoped per
operation, and it is one more contract in the trust boundary of a wallet whose
whole appeal is that it has a small one.

## What we do instead

**The Safe owns a Kernel account.** Nothing is installed on the Safe. The
position lives in the Kernel account, and the Safe is its root owner.

This works because of one substitution in the root validator. The stock validator
**recovers** a signer from the signature and compares it to the owner, and a
Safe's address can never be the answer to a recovery, because a Safe has no
private key. `ERC1271RootValidator` **asks** the owner instead: it hands the
signature to the owner contract and accepts whatever that contract says.

A contract owner then answers under its own rules, which for a Safe means its
threshold.
Two further properties of that checker matter as much as the delegation:

* **It does not revert.** `ECDSA.recover` reverts on anything that is not 64 or
  65 bytes, and a Safe's multi-owner blob is 65 bytes per owner. A revert inside
  `validateUserOp` surfaces as a bundler rejection rather than a clean signature
  failure.
* **It falls back to ECDSA when the owner has no code**, so an EOA owner keeps
  working. The validator is a superset of the stock one, which is what lets an
  account be re-owned by a Safe later without changing which validator is root.

The validator is also an `IHook`, which keeps the owner's **direct-call exit**
alive: a Safe can drive the account with no bundler, no paymaster and no ETH in
the account, because being the caller is the proof.

### What this gets you that a module does not

The Safe delegates **bounded** authority rather than total authority. Every
adapter is installed per operation with its own spending caps and its own venue
list, and an operation the owner did not authorise is refused by the adapter
rather than by convention. A Safe module has no such granularity, see
[the kernel](/kernel) for what A, B and R may each do.

### What it costs

<Warning>
  Adopting this validator **changes the address of every account not yet derived**.
  The validator id is the first 21 bytes of the account's init data, and the Kernel
  factory hashes that into the CREATE2 salt. There is no migration for an address
  already published.
</Warning>

Two more, stated plainly:

* **Signing is two domains deep.** Kernel wraps the hash in its own EIP-712
  domain, and the Safe wraps that in `SafeMessage`. Anything signing for the
  account must reproduce both.
* **The validator is unaudited**, and deliberately minimal. It has no ERC-7739
  nested-signature defence, so a Safe owning two Kernel accounts answers the same
  digest for both; no owner rotation; and no guard against an owner contract that
  later upgrades into something else.

It is deployed immutably, by plain CREATE2 behind no proxy: unlike every other
genesis contract here. Whoever can replace a root validator's code can authorise
any operation on every account naming it, which is a strictly larger power than
the proxy admin holds anywhere else. Making that answer "nobody" is worth the
cost that a bug is fixed by deploying a new validator.


## Related topics

- [Adapters](/adapters.md)
- [DeFiLoops](/index.md)
- [Every tool](/tools/reference.md)
- [FAQ](/faq.md)
- [Non-custodial, and unilateral control](/kernel.md)
