Programmable Ethereum Transactions Without Smart Contracts.
Turn your manual transactions into programmable, composable, and reusable actions with Plug. No more tedious contract writing or one-off scripts. Just simple, powerful sentences at your fingertips powered by:
I've never met a person in crypto who made money by thinking the public data was cool. The few I have are no longer around to contribute their voices to the industry after attempting to monetize something that anyone can get.
Yet, here we are at Plug, building exactly that so that when your users log into your app they instantly see all the activity they are expecting, even if you had never seen that wallet until 3 seconds ago.
An activity feed your user can read at a glance, like a bank statement written by someone who was in the room, wouldn't that be nice?
While our focus is on getting you and every other protocol to immerse yourself in the world of building no-code protocols with intents, we can't ignore the simple reality that users want a phenomenal experience with their money and it doesn't really matter to them how it works. So today, let's build the feed together. I've walked this road once already; you bring the fresh eyes and I'll point out where the bodies are buried.
In the beginning of this journey, it seems so simple. A user shows up, we index their address from that point on, outstanding. Our first afternoon looks just like this:
Get an RPC.
Subscribe to a block stream.
Add your user to a table.
Use your table to filter balance changes.
Save the difference from the last balance to the new balance.
With this implementation we don't need to go back and index periods of user activity from before we were tracking that user. Even if potentially nice, it's not vital, so we can kick that bucket down the road.
If we keep it this simple it is not even a day's work. Make your employer pay for Fable and you can oneshot a transaction indexer. So, what's so hard about this? Why are there so many services attempting to solve the same problem?
We have the same question, so we set off. Quickly, we run into questions that do not have obvious answers. Where there are answers, the implementation in many cases is non-trivial or drastically outside of our original scope:
What parties were involved here?
Who is this random address?
What were my net balance changes?
Where is the metadata for these tokens?
Why was this transaction even run?
The list of questions goes on and on while we're staring at a blank slate of transaction data.
It couldn't be further from something that we as users can scan through and understand at a glance. What does any of it mean? Couldn't tell you. No one can. The data structure was not meant to be read, so why are we trying to read it? Transactions are instructions for a machine. An activity feed is a story for a person. Nothing about the former volunteers to become the latter.
So, the wisest decision is at least considering the other decisions we can make.
The best place to start is always finding people that have already solved our problem. That's probably why you're here, after all. Throughout the EVM industry there's an endless set of people doing precisely what we talked about. Run a basic RPC stream of transaction data, save it to a database, and then sell it to another person so that the cost of nodes is amortized across everyone.
But each turn around the corner reveals another room left unpainted. When it comes down to the basics of account activity, the primary difference between "activity responding" services in EVM is two-fold:
You may or may not wait up to hours.
You may or may not have access to native balances.
Some services are cheap, some are expensive, and all the others are somewhere in between. This is not a waxing poetic on the virtues of one service over another. I draw this point to highlight the fact that we are all building the same thing. It does not really matter which one we pick if we aim to have the same experience as everyone else. We grab whatever is simplest for our team and keep moving, because the only thing that matters is that what we build on top of it is better than what is already out there.
So from this point on, I am inviting you to sharpen the vision of what you want historical activity to look like as I do. We are building the same app side by side now. The mental image of yours is different than mine. Share a picture with me when you're done.
We have nice icons for every major token that sees activity, and even for things like DeFi positions we generate composite icons so that everything is always gorgeous. So, the first thing we take care of is connecting all the metadata from the tokens we have everywhere else in the app.
Because the Plug API functions on polymorphic shapes, when it returns the address involved in the transaction, it also brings the token name, symbol, decimals, and more. One response, every field already attached at the point in the graph where the data was queried.
If we squint, it's starting to look decent. But when we open our eyes a bit wider we start to feel the asymmetry: we show prices and values across the user's portfolio, and then randomly exclude them everywhere we show activity?
We have historical prices for tokens, don't we? Let's use them.
If you don't, allow me to welcome you to the Plug ecosystem. You should go read that post before this one. It will answer most of the questions you have right now.
In a normal system, we'd have to call somewhere else to get the price of the token at the time of that transaction. When using Plug, price comes right along with every single address that's returned, resolved at the block the activity happened in. ETH was $X,XXX on this date and we simply multiply the value by the price.
No extra calls or math needed, so we can focus on getting the app experience precisely how we want.
As we start to kick back and relax, we notice one big issue. This only includes transactions the user ran themselves, directly from their wallet.
Permit-based swaps.
DeFi pool deposits and withdrawals.
Bridge transactions.
Smart account transactions.
They're all missing from our standard transaction API. Oh no. If we were the kind of people who panic about not being able to deliver, now would be a good time.
We are sitting here in assless chaps. Where is all the activity?
If this is all we can get, the root of our problem is fundamental to the way we've been thinking about transaction activity. For all intents and purposes, if we are scope-limited to "transactions only" then we're done. Does stopping here feel right, to you?
I'm sure it will only take us a few minutes... right? Quite the contrary. Zapper made a whole business out of it.
While I was writing this post, Zapper announced they are shutting down in ~23 days. A legend of the industry that has shown an incredible experience can be built on top of the EVM. Salute to our fallen comrades.
If we want to build the rest of our app, we need to be able to classify transactions into the following categories:
Direct transactions: Run by the user.
Indirect transactions: Allowed by the user.
Internal transactions: Run inside an unknown contract.
Contract transactions: Landing in an unknown contract.
Cross-chain transactions: Landing on a new chain.
Multi-party transactions: Landing in multiple places.
At this point, it's no longer feasible to take care of everything ourselves unless we're willing to shoulder a scope-burden we are not prepared for. To identify a transaction, we have exactly two questions we can ask:
Do we have that address indexed?
Do we have that contract ABI?
Those are our basic primitives at the start of this problem. There is no shortcut.
So, if we want to press on, we have to account for every transaction our users are likely to run. Through all versions of all the protocols they use. If there's a token factory that creates new tokens, we have to index those addresses as well so that we can even identify them, much less price them.
Before any of the exotic classification, let's go find the floor. Forget bridges and smart accounts for a second. Take the humblest requirement on the list: the net balance change. The +0.42 ETH on the right side of the row. Surely the transfer logs give us that for free.
They do not.
A single swap emits a small crowd of Transfer events. The user's token goes to a router, the router forwards to a pool, the pool pays a second pool, a fee gets skimmed to a protocol treasury, and the output token bounces back through the router before it lands in the user's wallet. Five, ten, thirty transfers, all inside one transaction, and exactly two of them are the user's story. A transaction doesn't change one balance either; it changes many at once, and the user only cares about the handful that net out to something. To get one signed number per token, per party, we group every transfer by address, sum the deltas, and collapse every intermediate leg that nets to zero. That is bookkeeping, and bookkeeping only works if the books are complete.
The books are not complete. Transfer is an ERC-20 concept. ERC-721 shares the event signature but indexes the parameters differently. ERC-1155 uses TransferSingle and TransferBatch and encodes amounts in the data field. Fee-on-transfer tokens emit an amount that is not the amount that arrived. Rebasing tokens move balances without emitting anything at all. And the native token, the single most important asset on the chain, moves through internal calls that produce no log whatsoever. I lost a season of my life to that problem and wrote it down so we don't have to relive it here. Everything built there gets joined into every activity row we render now. The trace stream and the log stream are two halves of one ledger, and a net balance change computed from only one of them is a guess wearing a suit.
Then, once our arithmetic is right, we discover the arithmetic was the easy part. A wallet's raw event history is mostly noise. Spam airdrops nobody asked for. Dust transfers from address-poisoning bots that exist purely to sit in the feed and look like real counterparties. Router hops and flash-loan legs that are mechanically true and narratively meaningless. Relevance is a judgment call, and we are now in the business of making that judgment for every transfer, on every chain, forever, knowing that filtering too aggressively hides a user's real money and filtering too loosely buries it.
And finally, attribution. The deltas tell us what moved. They do not tell us who anyone is. The difference between a row that says Swapped 1 ETH for 3,241 USDC on Uniswap and a row that says Sent 1 ETH to 0x68b3...4a91 is knowing that 0x68b3...4a91 is a Uniswap router, which version, on which chain, deployed by which factory. Every address in every transaction needs an identity before the sentence can be written, and identity is exactly the indexing problem from the previous section, applied recursively to every counterparty our users have ever touched.
That is the floor. Net deltas across every token standard and the trace stream, filtered for relevance, attributed to named parties. Nothing about it is exotic. All of it is mandatory. And we haven't even rendered anything yet.
There is a version of this that is done. Not "done" as in the data exists in a database. Done as in the thing our user opens.
Every row is one sentence. Not a hash, not a method name, not Contract Interaction. A sentence, written server-side from the classified transaction, with a subtitle carrying the second-order detail. On the left, the parties: two icons with a bent arrow between them that reads as direction. A swap points token to token. A deposit points token to protocol. A withdrawal points protocol back to token. A transfer points token to the counterparty's face. Your eye learns the grammar in about four rows and never has to think about it again.
On the right, the two numbers this entire series has been about: the signed net balance change in the asset that mattered, and the dollar value at the block it happened in. Thirty transfers collapsed to the one number the user feels. Both already computed, both already priced, both animating in as the feed streams new blocks live.
And when a row deserves more than a sentence, it opens. Every transfer inside the transaction, each with its composite icon carrying the protocol badge, each priced individually. Every approval, summarized in plain language with the token and the spender named. Status, chain, counterparties, and the timestamp in the user's own timezone. The full depth of the transaction, one tap under a feed calm enough to read like a bank statement.
That's the bar. A list and a frame. It sounds small because everything that makes it possible is invisible, which is precisely the point. The user sees a sentence and trusts it. That trust is the same product it was two posts ago.
Now, the honest accounting of what stands behind each of those sentences.
Every sentence requires the transaction classified. Every classification requires every involved contract identified. Every identification requires the protocol integrated: its factories watched, its deployments indexed, its events decoded, across every version it has ever shipped, because our users do not politely stay on the latest deployment. Uniswap alone is four versions, thousands of pools, and a factory that mints new ones every block. Multiply by every lending market, every vault standard, every bridge, every staking derivative. Multiply again by every chain, because an integration that works on mainnet has a slightly different shape on every L2 that forked and drifted. Multiply once more by every block, forever, because activity is not a backfill we finish. It is a stream we keep up with or fall behind.
Each cell in that multiplication is real work done by a real person. A protocol integration is not a config entry; it is understanding a protocol's accounting well enough to explain it in one sentence to someone who has never heard of it. There is no economy of scale in understanding. The hundredth protocol takes about as long as the tenth.
This is the part of the map where every team that set out to "just add an activity tab" quietly turns around. This is where we are standing right now, looking at it.
And the frontier is worse than the interior. As we get further into the fringe we find ourselves incapable of pricing what we're looking at. What even is PT-sUSDeJuly2026? We could rabbithole down and discover that it's a Pendle principal token requiring a linear-decay price adapter, because it's priced unlike anything else in the industry. That is what our normal day looks like now that we want to classify transactions. Multiply that by our ability to solve one a day, against thousands of tokens and tens of chains. Will we ever catch up?
And if we want to go all the way, we will be doing everything. We've walked through classifying contract transactions together, and that's one category of six.
This is the point on the trail where I stop pretending we're standing at the same mile marker. I kept walking, years ago, and I'm writing to you from the other end. I've not gone into the other five categories today because you won't benefit from knowing the nuances of our machinations. What I can tell you is that we've taken care of everything for you on this one*.
I include an asterisk because there's one small exception. We only classify activity for protocols we have integrated into both our actions and our swap solver. Don't let me lead you astray: there is no magic bullet for the exponential scope-growth that protocol integrations bring. There is only a reason to pay for it.
Ours is this. Unlike your typical crypto data business, we don't index protocols to sell you the data. We index them because without that data we can't build very good swap routes. The solver already needs every pool, every token, every price, every protocol's internal accounting, live, on every chain. And then all of that sits in our database, fully classified, helping no one.
We would be crazy not to write the single 180-line SQL query that turns it into your activity feed, and save you from selling equity in a fundraise just to afford the tokens needed to build a codebase of this depth. And since we know there's no profitable data company in this industry, we don't bother trying to become one. The data is free, open, and already classified, because the thing that pays for it is the product sitting on top of it.
Valuing an onchain transfer is a never-ending effort. You can spend as long as you want on classification. Or you can spend the afternoon.
Three posts ago we needed every balance and every price. Two posts ago we needed the truth about native history. Today we needed all of it joined, classified, and written into sentences. It exists and sits right there for you to take advantage of.
One endpoint returns the finished row: the sentence, the subtitle, the direction, the signed net balance change, the dollar value at the block, every transfer and approval underneath, streaming live as blocks land.
Every balance, every price, every wei of native history, every classified party, compressed into sentences a person can trust at a glance.
If you'd rather keep walking the long road, you now have three posts' worth of map, and you should reach out when you hit a hard part, because I have hit all of them. But the point of building the enormous machine was that nobody should have to build it twice. Plug in.