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:

To get started:

  1. Get access to a locally running node such as Erigon running on dAppNode.
  2. Download and install TrueBlocks. (Figure it out — there’s a ton of information about how to install TrueBlocks: here and here)
  3. Decide on which address to study. We choose trueblocks.eth, for this article.

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.

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.

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.

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…

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.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Thomas Jay Rush

Blockchain Enthusiast, Founder TrueBlocks, LLC and Philadelphia Ethereum Meetup, MS Computer Science UPenn