At some point you turned on automation for a leveraged position. A stop loss, a repay when the health factor got thin, a Boost that levered you back up when the price ran. It did its job or it did not, and eventually you closed the position, switched the automation off, and stopped thinking about it.
The switch is off. The key is not.
We enumerated every subscription in DeFi Saver's SubStorage contract on Ethereum mainnet at block 25,882,992: 3,574 subscriptions across 1,248 distinct wallets. 2,566 of those subscriptions are inactive. 703 wallets have no active subscription at all, meaning DeFi Saver is doing nothing for them and has not been asked to. We then read the actual permission sitting on each of those 703 wallets.
695 of them still hand DeFi Saver's executor the right to run arbitrary code as them.
That is 98.9%, and it splits cleanly across both wallet types the product supports. Older DeFi Saver users hold a DSProxy with a DSGuard attached, and the guard is asked canCall. 334 of those wallets have every subscription switched off, and 332 still permit. Newer users hold a Safe with a DeFi Saver module enabled, and the Safe is asked isModuleEnabled. 369 have every subscription off, and 363 still have the module live.
Two wallet standards, two permission mechanisms, built years apart by different people. Same answer.
Deactivating a subscription writes one boolean into SubStorage. That boolean tells DeFi Saver's own bots to stop looking at you. It is a note to the operator, and it is honored.
It touches nothing on your wallet.
The permission lives somewhere else entirely. On a DSProxy it is a permit entry in your DSGuard naming DeFi Saver's ProxyAuth contract and the four bytes 0x1cff79cd, which is execute(address,bytes). On a Safe it is a module address in the Safe's own module list. Clearing either one is a separate transaction that the subscription flow never prompts for and that nothing in the interface tells you exists.
So the wallet ends up in a state its owner would not describe correctly if you asked them. Automation: off. Authority: total.
execute(address,bytes) on a DSProxy is a delegatecall. Whatever contract you name runs inside your proxy's storage, with your proxy's balances and your proxy's positions, as your proxy. It is not a permission to adjust your loan. It is a permission to be you.
A Safe module is the same shape approached from the other direction. execTransactionFromModule skips signature checking entirely, by design, because a module is meant to be logic the owners already decided to trust.
What you wanted was a rung near the bottom. Repay my Aave position from my own collateral when the health factor drops under 1.2, and nothing else, and only until I close this position. What the wallet standards could express was the top rung, so the top rung is what got written.
Neither mechanism can say less. DSGuard.permit takes a source, a destination and a function selector. It has no field for an amount, a counterparty, a condition or a date. A Safe module is an address with a bit set. There is nowhere to write "only when," and there is nowhere to write "until."
DeFi Saver does not leave that key lying around. ProxyAuth and SafeModuleAuth both check that whoever is calling them is the current StrategyExecutor, which they look up by the id 0x0c1dc439 in the DFSRegistry at 0x287778F121F134C66212FB16c9b53eC991D32f5b. That registry entry points at 0x8278DA54b4A47c0f6F4a0a4b00B6f31678f30181 today and carries a wait period of 604,800 seconds before it can point anywhere else. The registry answers to an admin vault, and the vault answers to two Safes, each of them three of six. All of this is open source and you can walk it yourself.
This is competent, and more careful than most of what ships. Read the chain from the other end anyway, because that is the end your money is at. Three signatures out of six, plus a seven day timer, is the distance between 695 wallets and a contract nobody has written yet. The timer only protects the people watching the registry, and the people most exposed here are by definition the ones who stopped paying attention to DeFi Saver years ago.
Every product with a bot has this shape somewhere. DeFi Saver is the one we could measure exactly, because they publish their registry and enumerate their subscriptions on-chain. The ones you cannot audit are not safer. They are quieter.
For each of the 695 wallets we pulled every DeactivateSub event from the subscription contract and took the most recent one, then read that block's timestamp. All 695 dated cleanly.
The median wallet switched off its last strategy 512 days ago. 424 of them have been standing over a year, 210 over two years, and 67 over three. The oldest turned everything off 1,638 days ago and the permission has been live every block since.
That is the part worth sitting with. This is not a window between using the product and cleaning up. There is no cleanup step. The wallets that stopped four years ago are in exactly the same state as the wallets that stopped last month, because nothing in the system ever expires and nothing ever reminds anyone.
Those 695 wallets hold $24.5M in tokens and ETH directly and post $56.3M of collateral to Aave v2 and v3, against $17.8M of debt. Gross reachable value is $80.8M. Net of debt it is $63.0M.
270 of the 695 hold something. 39 hold more than $100,000. Eight hold more than a million. One Safe with no active subscription holds 220 WBTC and 2,775 stETH, $23.9M at the Chainlink prices in the same block, behind a module its owner enabled for a leverage strategy and never removed.
The wallets that are actively automating hold far more, $4.25B gross across 541 wallets, and that number is fine. Those users are getting something for the key. The $80.8M is the part with nothing on the other side of the trade.
Instadapp's DSA accounts are the other widely held smart account in this category, and they are fully enumerable, so we enumerated all of them. InstaList at 0x4c8a1BEb8a87765788946D6B19C6C6355194AbEb reports 38,169 accounts. Walking every account's authority list gives 41,018 grants held by 35,662 distinct addresses. 2,346 accounts carry more than one authority and one carries fourteen.
The API for adding one is enable(address).
One argument. An authority on a DSA account can call cast with any connector in the registry and any calldata it likes, which is the same total authority arrived at by a different route. There is no second argument for scope, no third for expiry, and no version of the account that has ever had one. disable(address) exists and works, and like DeFi Saver's it is something the user has to know to go and do.
Every authority primitive in this category grants to an address. permit a caller, enable a module, enable an authority. An address names a party, and the thing you wanted to authorize was a behavior. There is no way to attach "only up to 40,000 USDC," "only against this position," or "only for the next 90 days" to a name, so those conditions get moved off-chain into the operator's own code, where the operator controls them and where switching them off changes nothing about what the chain will permit.
Once the grant is a name rather than a behavior, expiry has nowhere to live. A behavior finishes. A name does not. The permission outlives the reason it was created, and 695 wallets standing for a median of 512 days is what that looks like once a category is four years old.
A plug inverts which half gets trusted. You sign the program, with its bounds and its conditions written into the material you signed, and the signature authorizes that program and nothing else. Repay from this collateral, up to this amount, when this health factor read comes back under this number, on this position, before this date. Every clause is in the signed material and every clause is checked on-chain at execution time.
No address is ever granted anything. There is no permit entry, no module bit and no authority row, because there is no standing party to name. A solver can hand a plug a candidate for a field you deliberately left open, and the plug checks that candidate against tests you signed, which is the thing we wrote about with Pendle rollovers. The solver's only power is failing to answer.
So the question this post is about never comes up. Nothing to revoke, because nothing was ever granted. When the conditions stop being true or the deadline passes, the authorization is over, and nobody has to remember to go and end it.
We are not claiming to be more careful than DeFi Saver. They are careful, visibly and in public. The claim is narrower and harder to argue with: a permission that describes an action can end when the action ends, and a permission that names an address can only end when a human remembers to end it. Four and a half years is how long a human can fail to remember.
Four calls, and you can run them yourself with cast against any public RPC.
If you hold a DSProxy, read authority() on it. A non-zero result is a DSGuard, and canCall(0x149667b6FAe2c63D1B4317C716b0D0e4d3E2bD70, <your proxy>, 0x1cff79cd) coming back true means DeFi Saver's executor can still delegatecall as you. Take it back with forbid on the guard, called from the proxy.
If you hold a Safe, call isModuleEnabled(0x7407974DDBF539e552F1d051e44573090912CC3D). True means the module is live whatever your subscriptions say. Remove it with disableModule through the Safe.
If you hold a DSA account, call isAuth(<address>) for everything you have ever connected, and disable(<address>) for anything you no longer use.
Then do it for every automation product you have ever tried, not only these two. The list of things holding a key over your wallet is not the list of things you are currently using, and no interface anywhere is going to show you the difference.
Every number here is read from Ethereum mainnet at block 25,882,992, through public RPC endpoints, with no indexer in the path.
Subscriptions come from SubStorage.getSubsCount() followed by getSub(i) for every id from 0 to 3,573, which returns the owning wallet and the active flag. Wallets are grouped by that owner. For each wallet we read authority() and owner(). A non-zero authority is treated as a DSGuard and asked canCall(ProxyAuth, wallet, 0x1cff79cd); a zero authority is treated as a Safe and asked isModuleEnabled(SafeModuleAuth). All 673 zero-authority wallets answered isModuleEnabled, and the ones we spot-checked report Safe version 1.3.0.
Ages come from every DeactivateSub log emitted by SubStorage between its deployment at block 14,242,966 and the measurement block, read in 10,000 block windows with no gaps, matched to wallets through the subscription id.
DSA accounts come from InstaList.accounts() and accountAddr(id) for every id, with authorities walked through accountLink(id) and accountList(id, addr) until each list terminates. The walked count matches the contract's own count field on all 38,169 accounts.
Values are direct balances of ETH, WETH, USDC, USDT, DAI, WBTC, stETH and wstETH, plus getUserAccountData on the Aave v2 and v3 pools, which reports collateral and debt in USD. Prices are Chainlink feed answers read at the same block. Positions held in protocols other than Aave are not counted, so every dollar figure here is a floor.