You can find the script and its source code on Github
As you can see, I did a bunch of transfers and a few smart contract interactions (methods such as Deposit, Withdraw, File Order, Swap Exact Token and so on). I'm mentioning the smart contract interactions because they are harder to process due to encoding, but that becomes irrelevant if you were to follow my guide.
All examples in this article are written in Python. I am using Python v3.10.5 and extensively taking advantage of a library called . This library allows me to interact with Ethereum and you can install it using PyPI by typing the following command in your terminal:
$ pip install web3
The first thing the script needs to do is to establish connection to the Ethereum blockchain. To do so, we will need to find an Ethereum API. If you don't run your own node, you can get a free API from , or . Choose one, I'm using Infura for my personal projects (As you can see, I pasted it in line 9).
import asyncio
from web3 import Web3, AsyncHTTPProvider
from web3.eth import AsyncEth, Eth
from web3.net import AsyncNet
async def runner():
ethereum_api_url = "//mainnet.infura.io/v3/MY_SUPER_SECRET_TOKEN"
async_web3 = Web3(
AsyncHTTPProvider(ethereum_api_url),
modules={
"eth": (AsyncEth,),
"net": (AsyncNet,),
},
middlewares=[],
)
web3_modules = get_default_modules()
web3_modules["eth"] = Eth
block = await async_web3.eth.get_block("latest")
print("Current block:", block.number)
# All code goes here
await asyncio.sleep(0)
if __name__ == "__main__":
asyncio.run(runner())
I created an asynchronous web3 provider, and I will be using it to interact with the blockchain. You might have noticed that on the snippet above I'm fetching the latest block. Even though that isn’t required to complete my task, I'm doing so to verify that my web3 provider is connected to Ethereum.
$ python runner.py
Current block: 16436276
Let's dig a bit deeper and focus on the Logs tab on Etherscan. As you can see, this transaction has one event - a Transfer event.
Let's return back to our Transfer event. You can spot it has three topics - topic 0 is the Transfer event's signature. It tells us that all Transfer events have signature 0xddf252ad...f523b3ef. This information is crucial, we will be using it to find all transfer to our wallet but I will return to this later. Let's focus on topic 1 and 2 for now.
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
...
}
As you can see, the Transfer event has two indexed arguments - from and to - and value which isn't indexed. The indexed arguments are exposed as topics, and we will be able to request logs associated with them.
Now we know that topic 1 is the from address and topic 2 is the to address. Let's request all incoming DAI transactions to my wallet from block 13,352,962 (my first DAI transaction) to block 16,436,276 (the current block).
incoming_logs = await async_web3.eth.get_logs(
{
"fromBlock": 13352962,
"toBlock": 16436276,
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", # DAI
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
None,
"0x000000000000000000000000d5e73f9199e67b6ff8dface1767a1bdadf1a7242",
],
}
)
When you look closer into this snippet, you might feel confused by two things. The first one is the address field. It isn't my wallet address but (smart contract). If you'd provide the wallet address there, you'd find nothing. Wallets don't implement any events with Transfer signature.
[
AttributeDict({
'address': '0x6B175474E89094C44Da98b954EedeAC495271d0F',
'blockHash': HexBytes('0xa67b8ceaeb79ec2592e161ee2efee6fba3fd329c87131d9335ccaa869cc857ec'),
'blockNumber': 13352962,
'data': '0x0000000000000000000000000000000000000000000000356ea11fcb4975c000',
'logIndex': 85,
'removed': False,
'topics': [
HexBytes('0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'),
HexBytes('0x00000000000000000000000021a31ee1afc51d94c2efccaa2092ad1028285549'),
HexBytes('0x000000000000000000000000d5e73f9199e67b6ff8dface1767a1bdadf1a7242')
],
'transactionHash': HexBytes('0xe3d380b3647abee2f8e2980d8e3bf6e9a43b00a0f4a388765585df13ecefaa2e'),
'transactionIndex': 41
}),
...
]
If you read it carefully, you can spot the sender and receiver of each log (topic 1 and 2). The only missing part is the transaction value, right? Actually, it is stored in the data field but it is encoded. It can be decoded by using the toInt
method from the web3.py library.
from web3 import Web3
value = Web3.toInt(hexstr="0x0000000000000000000000000000000000000000000000356ea11fcb4975c000")
print(value) # 9856490000
The script returned 9856490000
and this number feels a bit too high, right?
Not really, DAI has 18 decimals - it means you need to divide this number by 10 to the power of 18 (10^18
) to get a "human" readable number. If you do so you will find out that the value of this transaction was 985.64912368 DAI. You can confirm this number .