Skip to content

Instantly share code, notes, and snippets.

@eezis
Last active January 5, 2021 13:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eezis/1ae5050a524d7c14229db2658143ad4a to your computer and use it in GitHub Desktop.
Save eezis/1ae5050a524d7c14229db2658143ad4a to your computer and use it in GitHub Desktop.
Get Ethereum and ERC20 positions from an Ethereum address
"""
see my comment below the gist
"""
import re
import os
from requests_html import HTMLSession
BASE_ETHERSCAN_URL = "https://etherscan.io/address/"
def get_environment_variable(name):
env_var = os.getenv(name)
if env_var is None:
print()
print(f"ERROR: The environment variable for {name} has not been found")
print(
f"You can set the variable at the command line using export {name}=ethereum_address"
)
print(f"To remove the value you can use unset {name}")
print("You can also set the variable in your .bashrc or .zshrc script")
print()
return None
return env_var
class EthereumPositions:
"""
This class takes an Ethereum address then scrapes the total Ethereum balance and all the
ERC20 positions as well. The Ethereum address MUST BE SET AS AN ENVIRONMEENTAL VARIABLE
e.g. export metamask=...2e81928e313b2fa9acc65b1c73....
given that that environmental variable use like this:
eb = EthereumPositions("metamask", verbose=[True|False])
for p in eb.positions:
print(p)
If you are using pandas, create a dataframe:
import pandas as pd
eb = EthereumPositions("metamask", verbose=False)
eth_df = pd.DataFrame(eb.positions)
eth_df
"""
def __init__(self, eth_address_env_var, verbose=True):
"""
intialize this class with the name of the environmental variable that
exports the ethereum address you wish to check
Args:
eth_address_env_var [str]: the name of the export variable
verbose [bool]: True if you want human readable output in
addition to the list of dictionary entries in the positions
property.
Returns:
this class object with the positions property populated
"""
self.positions = []
self.__verbose = verbose
# get the ethernet address from the environmental variable
eth_address_from_env_var = get_environment_variable(eth_address_env_var)
if eth_address_from_env_var is not None:
self.get_balances(eth_address_from_env_var)
def get_balances(self, eth_address):
etherscan_url_and_address = f"{BASE_ETHERSCAN_URL}{eth_address}"
# print(etherscan_url_and_address)
# use requests-html to get the HTML and parse it
session = HTMLSession()
r = session.get(etherscan_url_and_address)
total_value = r.html.find("#availableBalanceDropdown")
total_value = total_value[0].text.split()[0]
if self.__verbose:
print()
print(
# f"Total Balance for addess {eth_address[0:3]}...{eth_address[-3:]} is: {total_value}"
f"Total Balance: {total_value}"
)
print()
# The ethereum piece is separate from the ERC20 tokens
# so we need to handle that first
ethereum = r.html.find(".col-md-8")
# the second entry has the value, with a dollar sign, remove the $
# and convert to a float
value = float(ethereum[1].text.split()[0].replace("$", ""))
# the total ETH position ins in the first clss
coins = ethereum[0].text.split()[0]
datum = {}
datum["symbol"] = "ETH"
datum["tokens"] = coins
datum["balance"] = value
self.positions.append(datum)
# get the number of coins
tokens = r.html.find(".list-amount")
# get the total value of each coin
values = r.html.find(".list-usd-value")
if self.__verbose:
print("Symbol Tokens Balance")
print("-" * 44)
total = 0.0
for (t, v) in zip(tokens, values):
# s = f"{t} {v}"
# print(s.split(" ")[0])
tokens = t.text.split()[0].replace(",", "")
tokens = tokens.replace(",", "")
if v.text != "":
balance = v.text.replace("$", "").replace(",", "")
else:
balance = "0.0"
datum = {}
datum["symbol"] = t.text.split()[1]
datum["tokens"] = float(tokens)
datum["balance"] = float(balance)
# print(datum)
self.positions.append(datum)
if v.text != "":
balance = re.sub("[$|,]", "", v.text)
else:
balance = "0.0"
total += float(balance)
if self.__verbose:
print(
f"{datum['symbol']:<9} {datum['tokens']:15,.2f} {datum['balance']:15,.2f}"
)
if self.__verbose:
print("=" * 44)
print(f"{total_value:>44}")
print()
"""
Usage . . .
I have the line `export metamask=0xE....c84` in my .zshrc (Mac)
and .bashrc (ubuntu) scripts -- don't forget to source them if you edit them from
the terminal `source ~/.bashrc`. To test if the environment variable loaded
successfully, `echo $env_variable_name_you_used`
"""
eb = EthereumPositions("metamask", verbose=True)
for p in eb.positions:
print(p)
# print(eb.__doc__)
# print(eb.__init__.__doc__)
@eezis
Copy link
Author

eezis commented Jan 5, 2021

I needed to get a balance from an Ethereum wallet that also included ERC20 tokens, and had version conflict in my install packages that prevented me from installing and using Web3.py so I wrote this class to patch that need. It works fine for me for now, but be warned, it is potentially brittle: this solution is dependent upon etherscan if etherscan.io were to update their HTML classes this code would need to get updated too. Please see the usage comment on the bottom: 1) get etherscan.io eth address. 2) make that an environment variable, 3) init class with the variable name.

export var_name=eth_address

eb = EthereumPositions(var_name, verbose=True)

See the doc string in __init__ if you want to use with pandas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment