Summary
Querying a node directly for all transactions is difficult due to the way the blockchain stores data. Blockchains like Ethereum aren't relational databases themselves and are not designed for easy querying. They are a string a blocks connected to each other, a sort of linked list.
Because of this, if all you have is a node, you would need to "brute force" go through each block, gather a list of all transactions, and check if the transaction originates or is sent to the address you are looking for. Additionally, you'd need an archive node to do this.
This is often time-consuming, so a faster method that's commonly used would be to use a service that has its own database and sorts transactions into it to be easily queried.
Let's look at both, and examples of each.
How we will do this
For our example, we will use the Ethereum Mainnet Aave token, as it's a proxy contract and we can also show some event querying aspects like "how many times it's been upgraded by governance".
Brute Force
Here is an example of brute force getting transactions with web3.py. We add a start block and end block, because otherwise you're going to make a bajillion calls to your node.
import os
from web3 import Web3
START_BLOCK = 17950195
END_BLOCK = 17950197
def main():
w3 = Web3(Web3.HTTPProvider(os.getenv("RPC_URL")))
for block in range(START_BLOCK, END_BLOCK):
block = w3.eth.get_block(block, full_transactions=True)
for transaction in block.transactions:
print(transaction["hash"])
if __name__ == "__main__":
main()
The "brute force" way would involve going block-by-block transaction-by-transaction till you find all the events you're looking for, and can take a very long time.
And here is the worst part this list is non-exhaustive. If your address is a contract, contracts can make transactions to and from each other in a single transaction, but will not be the main "to" or "from".
For example:
person A calls "doStuff" on -> contract B
"from" == person A
"to" == contract B
If "doStuff" on contract B calls -> contract C
"from" and "to" will still just be A & B even though we interacted with C.
The python code above would miss this contract to contract call.
To get all of those transactions, you'd have to debug each transaction, keep track of the stack and keep track of which opcodes interact with other contracts.
It's a pretty laborious and time-intensive process. So the python code shown above is actually just a subset of all transactions that interact with an address.
You could write all this, but most op to go with indexing transactions into a easier to query database, or use a service that makes it easier.
Centralized / Service Techniques
Etherscan (UI)
One of the easist ways to get a list of transactions (and internal transactions) is to use a block explorer like etherscan.
For our Aave token, it's very easy to see a list of transactions. And if we select internal
and toggle onadvanced
we can see a list of contracts that have interacted with Etherscan.
On Etherscan's back end, they are indexing all this data for us, so we can "see" it easier.
They also have an advanced filtered mode, but getting exactly what you want can be a little tricky.
Dune Analytics
Dune analytics indexes blockchain "stuff" so that you can query it as if it's a SQL database. For example, we can easily see all transactions of our Aave token.
SELECT
*
FROM ethereum.traces
WHERE
"from" = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9
or "to" = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9
Dune comes with a table known as ethereum.traces which includes all these contract -> contract transactions. The query above will find EVERY transaction that interacted with the Aave token, or the Aave token interacted with.
Alchemy / QuickNode / Etc
Note: For this we are using a Python SDK I made to make it easier to work with the Alchemy API, but you could just as easily just call the Alchemy API directly with your language of choice.
Another popular method would be to use something like Alchemy's getAssetTransfers which does something similar.
They similarly have options to get all regular transactions, traces (which they call internal
) etc.
import os
from alchemy_sdk_py import Alchemy
ALCHEMY_API_KEY = os.getenv("ALCHEMY_API_KEY")
CHAIN_ID = 1
MY_ADDRESS = "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9" # Aave token on ETH Mainnet
def main():
alchemy = Alchemy(network=CHAIN_ID)
transfers = alchemy.get_asset_transfers(to_address=MY_ADDRESS)
# this is a paginated version, we could add `get_all_flag=True` but we'd make a LOT of API calls!
print(transfers)
if __name__ == "__main__":
main()
Bonus: How to get all transactions that emitted a certain event
Let's say I want to find all the times the Aave tokens was upgraded by governance and emitted the Upgraded(address)
event.
First, we'd get the topic0
of the event (the hash of the event signature). If I use foundry's cast
I get:
cast keccak "Upgraded(address)
0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b
Brute Force
Skipped, but you'd do something similar to above.
Dune
SELECT
*
FROM ethereum.logs
WHERE
"contract_address" = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9
AND topic0 = from_hex('bc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b')
Etherscan
I'm not sure at this time.
Alchemy
import os
from alchemy_sdk_py import Alchemy
ALCHEMY_API_KEY = os.getenv("ALCHEMY_API_KEY")
CHAIN_ID = 1
MY_ADDRESS = "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9" # Aave token on ETH Mainnet
def main():
alchemy = Alchemy(network=CHAIN_ID)
upgraded_events = alchemy.get_logs(
contract_address=MY_ADDRESS,
topics=["0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b"],
)
print(upgraded_events)
if __name__ == "__main__":
main()
filter.stopWatching
executes before the callback has a chance to work. The way it's laid out suggests an expectation that those commands will execute in order, but it's not the case with JavaScript. I would consider commenting outfilter.stopWatching
and start from block 0, then build up complexity after I see it work. Hope it helps.