Recipe: Monthly Token Balances
Or, Just How Bad was my year?
We’re going to try to get a report showing the end-of-month balances for every token I’ve ever owned for my trueblocks.eth address. This is not an easy process as you’ll see, but I’m doing it permissionlessly — for free — on my laptop. All three of those attributes are really important.
Also, I’m writing this article to help me understand how to make the process better, so strap in — off we go…
Digital blockchain accounting drives me a little bit crazy:
Introduction
To get started:
- Get access to a locally running node such as Erigon running on dAppNode.
- Download and install TrueBlocks. (Figure it out — there’s a ton of information about how to install TrueBlocks: here and here)
- Decide on which address to study. We choose trueblocks.eth, for this article.
Profit and Loss Statements
We’ll start by looking at a single TrueBlocks statement (or, as we also call it, a reconciliation) for a single transaction. A “statement” is basically a profit and loss statement for the transaction and is relative to a given address.
Run this command:
chifra transactions \
--account_for trueblocks.eth \
--fmt json \
0x1a898c5448b37f693343917ea40b7ad1c43b28a4ddd37af1bd6d0bb4a0c99891
The statement is relative to the trueblocks.eth address. This produces the following reconciliation:
{
"blockNumber": 8854723,
"transactionIndex": 61,
"logIndex": 0,
"transactionHash": "0x1a898c5448b37f693343917ea40b7ad1c43b28a4ddd37af1bd6d0bb4a0c99891",
"timestamp": 1572639538,
"date": "2019-11-01 20:18:58 UTC",
"assetAddr": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"assetSymbol": "WEI",
"decimals": 18,
"spotPrice": 181.51,
"priceSource": "maker",
"accountedFor": "0xf503017d7baf7fbc0fff7492b751025c6a78179b",
"sender": "0xbb984f85bd52d78eb2fbf2c5598bae7abb98c5bc",
"recipient": "0xf503017d7baf7fbc0fff7492b751025c6a78179b",
"begBal": "",
"amountNet": "5000000000000000000",
"endBal": "5000000000000000000",
"reconciliationType": "regular-eth",
"reconciled": true,
"totalIn": "5000000000000000000",
"amountIn": "5000000000000000000",
"internalIn": "",
"selfDestructIn": "",
"minerBaseRewardIn": "",
"minerNephewRewardIn": "",
"minerTxFeeIn": "",
"minerUncleRewardIn": "",
"prefundIn": "",
"totalOut": "",
"amountOut": "",
"internalOut": "",
"selfDestructOut": "",
"gasOut": "",
"totalOutLessGas": "",
"prevAppBlk": 8854722,
"prevBal": "",
"begBalDiff": "",
"endBalDiff": "",
"endBalCalc": "5000000000000000000"
}
which, if you look carefully enough, you can see has begBal
, totalIn
, totalOut
, endBal
, and reconciled
, all of which one needs if one wishes to do accounting. You’ll also notice that it reconciles. This means
end balance at previous transaction == beg balance this transaction
and
begBal + totalIn - totalOut == endBal
Both must be true.
Notice also there are two additional fields, assetAddr,
and spotPrice
. We’re interested in assetAddr
for now. Let’s get a list of all such assetAddrs
for our account. This will, in effect, be a list of every token address we’ve ever owned.
We can do this with this command:
chifra export trueblocks.eth \
--accounting --statements --cache \
--fmt json | grep assetAddr | sort -u >tokens_list
We use the export
command because we want “every transaction in the address’s history.” The grep
component extracts only the assetAddr
field. The sort -u
removes duplicate records.
This command takes a while, so we also use the — cache
option in case we need to run it again. The next time we run it, it will be much, much faster (perhaps as much as 100 times faster).
The previous command generates data that looks something like this:
"assetAddr": "0xaec2e87e0a235266d9c5adc9deb4b2e29b54d009",
"assetAddr": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"assetAddr": "0xc12d1c73ee7dc3615ba4e37e4abfdbddfa38907e",
"assetAddr": "0xc18360217d8f7ab5e7c516566761ea12ce7f9d72",
"assetAddr": "0xc4199fb6ffdb30a829614beca030f9042f1c3992",
"assetAddr": "0xc9da518db3abdb90a82c4d1838b7cf9b0c945e7e",
...
Let’s count how many tokens different tokens we’ve owned:
cat tokens_list | wc
At the time of this writing, this wallet held, at some point in its history, 75 unique tokens. That’s not how many tokens we have now. It‘s how many tokens were ever held over the address’s history.
A Note on Unreconciled Transactions
You may notice that the above command generates “unreconciled token transfer” messages. This happens when TrueBlocks fails to reconcile a transfer.
We got 29 such notices at the time of this writing. There were 2,418 reconciliations, of which 29 failed. That means TrueBlocks is 98.8% accurate. If you know anything about blockchain accounting, you know this is really, really good. Plus, we have a list for later debugging. Don’t worry. We’re on it. Plus (did we mention?), this is on a laptop.
Getting Token Balances
Back to the task at hand…
We want to get token balances for each of the given tokens. We can do this using the file we created. Let’s clean the data a bit by pulling out just the addresses:
cat tokens_list | cut -d'"' -f4 | tee token_addrs
and now we can get current balances for these tokens for our address. We need to build a shell script. Do this:
cat token_addrs | \
sed 's/^/chifra tokens --no_zero --no_header /' | \
sed 's/$/ trueblocks.eth/' | \
grep -v 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee | \
tee get_balances.sh
This creates a long list of lines of the form:
chifra tokens --no_zero --no_header \
0xc4199fb6ffdb30a829614beca030f9042f1c3992 trueblocks.eth
chifra tokens --no_zero --no_header \
0xc9da518db3abdb90a82c4d1838b7cf9b0c945e7e trueblocks.eth
...
which will report the current balance of a given token for trueblocks.eth to the screen. The --no_zero
option squelches zero balance tokens. The --no_header
option cleans up the display by removing the header from each call.
Let’s run just one of these lins to see what fields we get:
chifra tokens --no_zero \
0xc4199fb6ffdb30a829614beca030f9042f1c3992 trueblocks.eth | head -1
We get these results:
holder address name symbol decimals balance
To make the display a bit cleaner, we can pull off address
, name
, and balance
. The holder
field is trueblocks.eth. Address
is the token.
Let’s run the entire script (and clean up the data a bit further):
source get_balances.sh 2>/dev/null | cut -f2,3,6 | tee balances
Notice even the smallest balances show. This is a list of all tokens currently owned by trueblocks.eth.
Monthly Balances?
This article was supposed to be about getting monthly balances. It’s getting a bit long, so I’ll try to finish quickly.
Many chifra
commands allow you to enter block ranges, which causes chifra
to repeatedly call the same command on blocks in that range. Ranges may be specified with a skip
parameter. Like this:
chifra when firstBlock-lastBlock:monthly
which will generate a list of the blocks most recent just prior to the beginning of each month in the range.
To illustrate, let’s find the first and last blocks in 2022:
chifra when 2022-01-01 2023-01-01
which returns
blockNumber timestamp date
13916165 1640995189 2021-12-31 23:59:49 UTC
16308189 1672531199 2022-12-31 23:59:59 UTC
This means we can use
chifra when 13916165-16308189:monthly
to get the first block of each month in 2022. We can use this knowledge to adjust the above shell script:
cat token_addrs | \
sed 's/^/chifra tokens --no_zero --no_header /' | \
sed 's/$/ trueblocks.eth 13916165-16308189:monthly/' | \
grep -v 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee | \
tee get_balances.sh | tee get_balances.sh
and then
source get_balances.sh 2>/dev/null | cut -f1,3,4,7 | tee balances
produces what we’re looking for
13717846 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
13916165 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
14116761 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
14297758 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
14497033 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
14688629 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
14881676 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
15053226 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
15253305 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
15449617 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
15649594 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
15871479 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
16086233 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
16308189 0x0000000000085d4780b73119b644ae5ecd22b376 TrueUSD "11.18"
13717846 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
13916165 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
14116761 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
14297758 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
14497033 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
14688629 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
14881676 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
15053226 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
15253305 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
15449617 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
15649594 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
15871479 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
16086233 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
16308189 0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 Rai Reflex Index "9.5"
13717846 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "24509.9332995369992192"
13916165 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "24614.2732995369992192"
14116761 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "24821.6832995369992192"
14297758 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "22630.2632995369992192"
14497033 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "18038.7032995369992192"
14688629 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "16679.540807396268977428"
14881676 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "12809.264025225954813905"
15053226 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "9.264025225954813905"
15253305 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "10763.764025225954813905"
15449617 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "10763.764025225954813905"
15649594 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "10763.764025225954813905"
15871479 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "10763.764025225954813905"
16086233 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "10763.764025225954813905"
16308189 0xde30da39c46104798bb5aa3fe8b9e0e1f348163f Gitcoin "0.000000000000000001"
balances for every token we owned during 2022 on a month-by-month basis.
[Thanks for getting this far. The above is interesting if you look closely enough.]
I told you. I’m freaking crazed about blockchain accounting…
Conclusion
There’s a lot left to do, but I’m really glad I went through this exercise as it’s taught me a huge amount about what we need to do before we can get what we really want. A quick hint for the attentive:
chifra export trueblocks.eth \
--accounting --statements --fmt json | grep spotPrice
Support Our Work
TrueBlocks is funded from our own personal funds and grants from The Ethereum Foundation (2018), Consensys (2019), Moloch DAO (2021), Filecoin/IPFS (2021), our GitCoin donors, and, of course, The Ethereum Foundation (2022).
If you like this article and wish to support our work, please donate to our GitCoin grant https://gitcoin.co/grants/184/trueblocks. Even small amounts have a big impact.
If you’d rather, feel free to send ETH or any other token to us directly at trueblocks.eth or 0xf503017d7baf7fbc0fff7492b751025c6a78179b
.