Last active
January 5, 2021 13:23
-
-
Save eezis/1ae5050a524d7c14229db2658143ad4a to your computer and use it in GitHub Desktop.
Get Ethereum and ERC20 positions from an Ethereum address
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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__) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.