Batched calls: yes
A Safe can run a batch of calls with nothing installed on it. It needs no
module, extension or setup.
Flash loans: no
A Safe cannot take a flash loan without installing a module. There is no way
around this.
Everything below was measured against a real Safe on Base.
The half that works
The owners sign one ordinary transaction that runs a batch in the Safe’s own context. Everything the batch touches sees the Safe as the caller, and the Safe’s own storage is what changes. Safe’s own batching contract works the same way, which is why “batch these calls” needs no setup.The half that does not
The protocol calls back. Every flash loan is two steps. The protocol sends the money, then calls a function on the recipient to let it do the work before repaying. That callback is a fresh call into the Safe, for a function the Safe does not have. The language does not care that a batch is still running further down the stack. The call arrives from outside, and the Safe forwards it to its fallback handler. A fallback handler cannot help either. The Safe reaches its handler with a plain call, not with the borrowing mechanism that shares its context. The handler runs in its own context. Even a handler that implemented the callback perfectly would be a different contract, holding no tokens, with no authority over the Safe.Which leaves three options
If the position must live inside the Safe itself, a module is the only way. That
is a real cost. A module keeps full power for as long as it is
installed, and that power is not scoped per operation. It is also one more
contract inside the trust boundary of a wallet whose whole appeal is a small
one.
What we do instead
The Safe owns an account. Nothing is installed on the Safe. The position lives in the account, and the Safe is its owner. This works because of one substitution. The standard owner check recovers a signer from the signature and compares it to the owner. A Safe’s address can never be the answer to a recovery, because a Safe has no private key. Our checker 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 matter as much as the delegation:- It does not fail on a long signature. Standard recovery rejects anything that is not one signature’s length, and a Safe’s multi-owner signature is one length per owner. A failure inside validation surfaces as a rejected transaction rather than a clean “wrong signature”.
- It falls back to standard recovery when the owner has no code, so an ordinary wallet keeps working. Our checker is a superset of the standard one, which is what lets an account be re-owned by a Safe later without changing which checker is in charge.
What this gets you that a module does not
The Safe delegates bounded authority rather than total authority. The agent can reach only the operations your account allows, each with its own checks, and you can set a daily limit per token. If the owner did not authorise an operation, the operation itself refuses it. A module cannot be limited this way. → Your account and your keys New accounts are also proved accounts, and that holds for a Safe-owned account too. A plan can pay its proceeds back to your Safe, because the Safe is an owner that can act for itself. An address whose key was discarded cannot.What it costs
It has two more costs:- Signing is two layers deep. The account wraps the message in its own envelope, and the Safe wraps that in another. Anything signing for the account must reproduce both.
- The checker is deliberately minimal. It has no defence against the same message being valid for two accounts, so a Safe owning two accounts answers the same message for both. It has no owner rotation, and no guard against an owner contract that later upgrades into something else.
It is deployed permanently, and cannot be replaced, unlike every other shared
contract here. Whoever could replace an owner checker could authorise anything on
every account that uses it, which is a larger power than anything else here
grants. We chose to make that “nobody”, so a bug is fixed by deploying a new
checker.