How to Fetch User Operations (UserOps) for a Specific Wallet Address

This guide teaches you how to efficiently filter and retrieve UserOps (account abstraction transactions) pertaining to a particular user.

While Account Abstraction (ERC-4337) continues to gain momentum, so does our infrastructure evolve to be able to understand on-chain events. In a domain where transactions are not indexed conventionally, this guide teaches you how to efficiently filter and retrieve UserOps pertaining to a particular user. This is particularly useful for Account Abstraction wallet developers who want to show user activity, but it may be useful for many other development purposes.

Prerequisites

  • Familiarity with Account Abstraction.

  • Familiarity with REST APIs.

How Does an Account Abstraction (ERC-4337) Transaction Work?

First, let’s break down these new types of transactions in a simple format and clarify the terminology to help our understanding.

  1. Starting with UserOps:

    • A UserOp is a structure that describes a transaction to be sent on behalf of a user. Based on the user's instructions, their smart contract account generates UserOps detailing what they want to do (e.g., send, receive tokens, interact with a contract).

  2. Moving to Bundlers:

    • Bundlers listen to the mempool for UserOps, validate them, and bundle multiple validated ones together.

    • They package multiple UserOperation objects into a single call to a global EntryPoint contract.

  3. Execution by EntryPoint Contracts:

How to Filter UserOp Data for a Specific Wallet Address

When we view the transactions to the EntryPoint contract on Etherscan, they all have the same Method, and the From address is the address of the bundler sending the UserOps to the EntryPoint contract.

The details of the transaction are contained in the logs, including the UserOperationEvent all of which share the same topic hash: 0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f

The challenge is, with so much data all going to the same contract, how do we find and retrieve just the UserOps pertaining to a specific user?

Fortunately, there is a simple way to do this. If you look at the Topics for the UserOperationEvent, you’ll find that index_topic_2 is the sender address, i.e. the smart contract account (wallet address) of the user who originally created this UserOp.

So now, utilizing this information, let’s fetch and retrieve the UserOps for a specific wallet address using the GoldRush API.

1

Get Log Events By Topic Hash

In the Base section of the API Reference, navigate to the Get log events by topic hash(es) endpoint.
Here, change the chainName parameter by selecting the blockchain you’d like to query. On the browser, there is a sidebar to select from, but in the terminal, you can either type the chainName or its ID.
Then, input the topicHash of the UserOperationEvent. Again, they all have the same topic hash, which is 0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f.
2

Filter by User Address

Now, we adjust the query parameters. Because we know that the sender address is contained in the topics of the UserOperationEvent, we can make use of the secondary-topics query parameter for this endpoint, inputting the specific wallet address we would like to fetch UserOps for. Then, we just need to specify a block range.
3

Run

After hitting Run, you should see all the UserOps pertaining to the wallet address you put in the query parameters.
Here is a snippet of a sample response:
As you can see, the wallet address of the user shows up in the decoded logs as the indexed address sender value, not as the sender_address at the top of the response which is actually the EntryPoint contract.
"sender_contract_decimals": 0,
"sender_name": null,
"sender_contract_ticker_symbol": null,
"sender_address": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
"sender_address_label": null,
"sender_logo_url": "https://logos.covalenthq.com/tokens/59144/0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789.png",
"raw_log_data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000fcf9332dac8600000000000000000000000000000000000000000000000000000000000809ca",
"decoded": {
  "name": "UserOperationEvent",
  "signature": "UserOperationEvent(indexed bytes32 userOpHash, indexed address sender, indexed address paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed)",
  "params": [
    {
      "name": "userOpHash",
      "type": "bytes32",
      "indexed": true,
      "decoded": true,
      "value": "HIXgb/lNamvDtxKsuxC+tJEDtGy3M8n3xDBYU1Af0Ss="
    },
    {
      "name": "sender",
      "type": "address",
      "indexed": true,
      "decoded": true,
      "value": "0x15ce59897fa557c51250ace9dc82490f16a13b42"
    },
    {
      "name": "paymaster",
      "type": "address",
      "indexed": true,
      "decoded": true,
      "value": "0x3b912be0270b59143985cc5c6aab452d99e2b4bb"
    },
...

How to Get UserOp Transactions (with Logs) by Address

Okay, so what we’ve covered so far allows you to fetch just the UserOps pertaining to a wallet address. However, this UserOp is only one event log in a complete transaction, and doesn’t provide the full context of a user’s on-chain activities. So, what we might really be interested in are the transactions containing UserOps, for a given wallet address. To get these, we’re going to use GoldRush’s transactions endpoints.

1

Get Transactions for Address

In the Transactions section of the API Reference, there are a few different options for fetching transactions by wallet address:
Recent transactions provides less than 100 of the user’s recent transactions to date, paginated transactions allows you to paginate through transactions (100 items per page) using a page number parameter, and the bulk endpoint allows you to fetch transactions based on 15-minute intervals. For this example, we’re just going to use the recent transactions endpoint.
Configure the path parameters with the blockchain and wallet you want to query. In this example, I’m using one on Linea.
2

Run

After hitting Run, you should see a list of that user’s recent transactions with all their decoded event logs. Here is a sample showing some of the log events for a particular transaction, In the API response it is obvious which logs events belong to which transactions, but even in this snippet you can verify that they have the same tx_hash (transaction hash).
log_events: [
	{
		block_signed_at: "2023-11-01T19:21:29Z",
		block_height: 763416,
		tx_offset: 3,
		log_offset: 6,
		tx_hash: "0x9a3718ef39a2b1e227c69661b2f7f5ccf167fe0d444ddab532942fc6995c75ae",
		raw_log_topics: [
			"0x2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4",
			"0x000000000000000000000000b5c7e1c90b3f048240127385924f62fdeb17c221"
		],
		sender_contract_decimals: 0,
		sender_name: null,
		sender_contract_ticker_symbol: null,
		sender_address: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
		sender_address_label: null,
		sender_logo_url: "<https://logos.covalenthq.com/tokens/59144/0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789.png>",
		raw_log_data: "0x000000000000000000000000000000000000000000000000000391bce512eee5",
		decoded: {
			name: "Deposited",
			signature: "Deposited(indexed address user, uint256 amount)",
			params: [
				{
					name: "user",
					type: "address",
					indexed: true,
					decoded: true,
					value: "0xb5c7e1c90b3f048240127385924f62fdeb17c221"
				},
				{
					name: "amount",
					type: "uint256",
					indexed: false,
					decoded: true,
					value: "1004665413234405"
				}
			]
		}
	},

	...

	{
		block_signed_at: "2023-11-01T19:21:29Z",
		block_height: 763416,
		tx_offset: 3,
		log_offset: 9,
		tx_hash: "0x9a3718ef39a2b1e227c69661b2f7f5ccf167fe0d444ddab532942fc6995c75ae",
		raw_log_topics: [
			"0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f",
			"0x11fe51bb1c9ef29fca870ebd36626a0bb0f5f0de7e273c5069db98b799f22979",
			"0x000000000000000000000000b5c7e1c90b3f048240127385924f62fdeb17c221",
			"0x0000000000000000000000000000000000000000000000000000000000000000"
		],
		sender_contract_decimals: 0,
		sender_name: null,
		sender_contract_ticker_symbol: null,
		sender_address: "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789",
		sender_address_label: null,
		sender_logo_url: "<https://logos.covalenthq.com/tokens/59144/0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789.png>",
		raw_log_data: "0x000000000000000000000000000000000000000000000000000000000000037e0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000221b696dd9b800000000000000000000000000000000000000000000000000000000000028d60",
		decoded: {
			name: "UserOperationEvent",
			signature: "UserOperationEvent(indexed bytes32 userOpHash, indexed address sender, indexed address paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed)",
			params: [
				{
					name: "userOpHash",
					type: "bytes32",
					indexed: true,
					decoded: true,
					value: "Ef5Ruxye8p/Khw69NmJqC7D18N5+JzxQaduYt5nyKXk="
				},
				{
					name: "sender",
					type: "address",
					indexed: true,
					decoded: true,
					value: "0xb5c7e1c90b3f048240127385924f62fdeb17c221"
				},
				{
					name: "paymaster",
					type: "address",
					indexed: true,
					decoded: true,
					value: "0x0000000000000000000000000000000000000000"
				},
				
				...

				}
			]
		}
	}
]
3

Filter by Account Abstraction Transactions

So now we see that we can get transactions containing UserOps through GoldRush’s transactions endpoints. These are known as “Account Abstraction transactions” or “4337 transactions”. However, GoldRush’s endpoints will give you ALL the transactions for an address, so what if we just want to get the Account Abstraction transactions, without capturing other activity like basic token transfers etc?
Recall, all UserOps are executed by a single EntryPoint contract. This means we can find the EntryPoint contract address involved in all Account Abstraction transactions. In the event logs provided by the GoldRush API, the EntryPoint contract can be found as the sender_address value 0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789. You can verify this in the sample response above.
Therefore, to filter by Account Abstraction transactions, simply adapt your frontend to check for the EntryPoint contract in the sender_address field of the GoldRush API response!

Conclusion

Utilizing the powerful capabilities of the GoldRush API, you can now deftly sift through Account Abstraction data, extracting UserOps specific to individual wallet addresses, or all a user’s transactions containing UserOps. This newfound knowledge paves the way for enhanced transaction insights and new use cases!

Read more