Web3 SQL Weekly #8: Analyzing Account Abstraction (ERC4337)
Let's look at the top contracts and interactions from smart accounts on Polygon (and other EVM chains)!
Welcome back! I’ll be breaking down one (community submitted) query a week into byte-sized bits, making both the SQL and Blockchain concepts more digestible.
Here’s the video version of this article:
Continue your web3 data learning journey by subscribing now!
If you’re a complete beginner, you can reference this guide for any terms that are unfamiliar.
ELI5 on Account Abstraction
If you weren’t confused enough by hot wallets, cold wallets, MPC wallets, and smart contract wallets - it’s time to introduce another: abstracted accounts (ERC4337).
Let me try and explain the hierarchy as simply as possible:
You’re probably most used to externally owned accounts (EOAs), which is when you have a single seed phrase/private key that you can port around to different key storage hosts like Metamask in your browser or the Ledger hardware wallet. Some wallets like Coinbase use the MPC tech to share the key.
Smart contract wallets have existed for many years, pioneered by Argent. A smart contract is deployed to represent your account, and it can only accept transactions from the Argent relayer (at the start). You can generate and socially recover an encrypted key on your phone to sign transactions, which is then used when submitting transactions to the relayer. The relayer is an EOA that actually calls the smart contract wallet with the signed transaction. Note that this encrypted key is NOT the same as a normal private key.
ERC4337 abstracted accounts still deploy a smart contract to represent your account, but they go further in splitting the relayer structure. Anyone can create their own verification structure like a simple username and password, the transaction (called a user operation) gets sent to the bundler, and the gas can be paid by someone else (a paymaster) so that the end user transacts for free. While you currently send your transaction to just one bundler, it will become a mempool with multiple bundlers in the future.
From here on out, I’ll refer to abstracted accounts as smart accounts.
Trends in ERC4337 Adoption
We can see that there are already 29k users transacting with this new account structure across six EVM chains, interacting over 77k times.
Polygon is really leading the way here (with the help of Biconomy), and I want to dive deeper into what these smart account users are doing! You can find the query below showing the top interactions and number of smart accounts behind them.
While this has now solved the issue of bundling token approval and transfer, there are many other cool examples of user operations from Polygon:
0x2a2e… is CapX, building IOU token distribution. They’ve distributed tokens 44k times to 21.6k smart accounts.
Fantazy is a fantasy cricket mobile app, which has given out Matic rewards to over 1000 smart accounts.
Dexwin has enabled 300 smart accounts to perform sports betting over 1000 times.
Be sure to check out the dashboard to see what’s going on on other chains as well!
Explaining the User Operations Query
You can’t just use “from” column anymore in polygon.transactions for filtering for who has called a contract from a smart account.
Let’s look at an example mint from LMBTHENOISE, where we’ll see everything from the account creation to the actual mint being called.
The transaction is submitted by the bundler (the user already approved the transaction off-chain), and then the user has an account contract deployed for them at 0x951 which is called the “sender”. This 0x951 address is the smart account.
All of the user operations are passed into handleOps function, and then each operation is executed by innerHandleOp function which tells the newly deployed sender account 0x951 to execute the mint on the LMBTHENOISE contract.
So, we know that we need to get the trace called by the sender account for each innerHandleOp call. The confusing part is in the join, basically I am taking all traces where the sender account is the traces “from” and then using a row_number() window function to only keep the first trace for each innerHandleOp call_trace_address. We only keep the first trace since some functions like mint() have an onERC721Recieved callback that would duplicate user operation counts.
My traces join uses trace_address, so the size has to be at least 1 or more. Technically I could make it be greater than 1, but I know that it takes at least depth of 4 for the user operation to be sent so I make it greater than 3 to filter out more results and make the query run faster.
I recommend plugging in an example transaction hash and understanding this base CTE, before trying to understand the aggregated query table.
You might be wondering, “why can’t we just get all deployed smart accounts and then filter for them in traces?” The answer right now is that there are more than just one factory/implementation of these smart accounts (though they all have the same base bundle/entrypoint logic), and we are still working on cleanly aggregating them.
I’m very excited about the new users and communities we can reach with ERC4337 technology. It’s great to see infrastructure and usage heating up in the space, with Biconomy, Stackup, and Alchemy leading the way.
It will also be really important to watch what ERC4337 supported wallets are built, so that smart accounts are still portable between applications and not just locked in to one app provider.
If you are building a 4337 wallet or application, please don’t hesitate to reach out - my friends and I would love to chat, help, and possibly invest!