wallet: Account balance

- Related to https://github.com/status-im/status-bot/issues/16
- Get account balance
- Convert account balance into fiat currency
- Make properties private
This commit is contained in:
Nick Ninov
2026-04-04 10:42:58 +03:00
parent b2c28851c9
commit df2d8662fb
5 changed files with 136 additions and 18 deletions
+73 -11
View File
@@ -56,7 +56,7 @@ class Account:
# All tokens in Status Backend
self.__http_base_url = f"http{'s' if is_secure else ''}://{domain}:{port}/statusgo/"
self.__ws_base_url = f"ws://{domain}:{port}/"
self.urls = {
self.__urls = {
"http": {
"initialize": f"{self.__http_base_url}InitializeApplication",
"login": f"{self.__http_base_url}LoginAccount",
@@ -71,7 +71,7 @@ class Account:
"signals": f"{self.__ws_base_url}signals"
}
}
self.__signal = Signal(self.urls["socket"]["signals"])
self.__signal = Signal(self.__urls["socket"]["signals"])
# Initialize profile
self.available_accounts
# In case if there is a hanging logged in session
@@ -147,12 +147,14 @@ class Account:
# Wallet usage
if infura_token:
params["infuraToken"] = infura_token
self.__is_wallet_set = True
if coingecko_api_key:
params["coingeckoApiKey"] = coingecko_api_key
url = self.urls["http"][url_key]
if infura_token and coingecko_api_key:
self.__is_wallet_set = True
url = self.__urls["http"][url_key]
response = requests.post(url, json=params)
signal_event = self.__signal.get("node.login")
if signal_event["is_error"]:
@@ -188,7 +190,7 @@ class Account:
"""
Logout of Status app. In a way this method behaves as a Status cleaner
"""
response = requests.post(self.urls["http"]["logout"])
response = requests.post(self.__urls["http"]["logout"])
self.__info = {}
self.__is_messenger_launched = False
self.__is_wallet_set = False
@@ -203,7 +205,7 @@ class Account:
"""
All locally available accounts
"""
response = requests.post(self.urls["http"]["initialize"], json={
response = requests.post(self.__urls["http"]["initialize"], json={
"dataDir": self.__docker_data_folder
})
data: dict = response.json()
@@ -432,6 +434,67 @@ class Account:
self.__chains = {chain[key]["chainId"]: chain[key]["chainName"] for chain in result if chain.get(key)}
return self.__chains
@property
def balance(self) -> pd.DataFrame:
"""
Get the account's balance
"""
empty = pd.DataFrame(columns=["timestamp", "address", "chain_id", "amount", "symbol"])
params = [[self.info["wallet_address"]], True]
results = self.__call_rpc("wallets", "fetchOrGetCachedWalletBalances", params).get("result", {}).get(self.info["wallet_address"].lower(), [])
if not results:
return empty.copy()
balance = pd.DataFrame(results)
column_mapping = {"tokenAddress": "address", "tokenChainId": "chain_id", "balance": "amount", "hasError": "error"}
balance = balance.rename(columns=column_mapping)[list(column_mapping.values())]\
.astype({"chain_id": "int8", "amount": "float64"})
query = (balance["amount"] != 0) & (~balance["error"])
if query.sum() == 0:
return empty.copy()
redundant_columns = ["error", "decimals", "cross_chain_id", "source_id"]
available_tokens = self.get_tokens()
balance = balance.loc[query].merge(available_tokens, "left", ["address", "chain_id"])\
.drop(redundant_columns, axis=1)\
.drop_duplicates()\
.sort_values("chain_id", ascending=True)\
.reset_index(drop=True)\
balance.insert(0, "timestamp", datetime.datetime.now())
return balance.copy()
def __getitem__(self, key: str) -> pd.DataFrame:
"""
Get the fiat currency balance
"""
ccy = key.upper()
if ccy not in self.__get_fiat_ccy():
raise Exception(f"{ccy} is an invalid fiat (ISO 4217) currency code...")
balance = self.balance
tokens = (balance["chain_id"].astype(str) + "-" + balance["address"]).to_list()
result = self.__call_rpc("wallets", "fetchPrices", [tokens, [ccy]]).get("result", {})
if not result:
return pd.DataFrame()
rates = pd.DataFrame([
{
"chain_id": int(address.split("-")[0]),
"address": address.split("-")[1],
"rate": price,
"ccy": ccy,
}
for address, prices in result.items()
for ccy, price in prices.items()
])
balance = balance.merge(rates, "left", ["chain_id", "address"])
balance["fiat_value"] = balance["amount"] * balance["rate"]
return balance.copy()
def send_message(self, chat_id: str, message: str):
"""
Send a message to the given chat.
@@ -597,7 +660,7 @@ class Account:
- the Docker backup path (linked to a volume). The file name is unique per account.
"""
self.info
response = requests.post(self.urls["http"]["create_backup"])
response = requests.post(self.__urls["http"]["create_backup"])
result: dict = response.json()
file_path = result.get("filePath")
@@ -637,7 +700,6 @@ class Account:
return self.__available_tokens.copy()
def get_balance(self, token_addresses: Union[list[str], str], chain_ids: Union[list[int], int] = 1, wallets: Optional[Union[list[str], str]] = None, ccy: Optional[Union[str, list[str]]] = None) -> pd.DataFrame:
"""
Get the current amount for the provided token addresses, chain IDs and wallets.
@@ -841,7 +903,7 @@ class Account:
"filePath": os.path.join(self.__docker_backup_folder, file_name)
}
self.logger.info(f"Trying to load {file_name}")
response = requests.post(self.urls["http"]["load_backup"], json=params)
response = requests.post(self.__urls["http"]["load_backup"], json=params)
error: str = response.json().get("error", "")
if len(error) == 0:
self.__signal.get("messages.new")
@@ -870,7 +932,7 @@ class Account:
raise ValueError(f"Name {name} does not exist... Available options: {list(self.__prefix_mapping.keys())}")
if name == "wallet" and not self.__is_wallet_set:
raise Exception(f"Cannot use this method without setting an `infura_token` when calling `login`.")
raise Exception(f"Cannot use this method without setting an `infura_token` and `coingecko_api_key` when calling `login`.")
data = {
'jsonrpc': '2.0',
@@ -881,7 +943,7 @@ class Account:
if params:
data["params"] = params
response = requests.get(self.urls["http"]["rpc"], json=data)
response = requests.get(self.__urls["http"]["rpc"], json=data)
return response.json()
def __get_fiat_ccy(self) -> list[str]:
+63 -7
View File
@@ -423,7 +423,8 @@ account = Account()
params = {
"display_name": "status-app-bot",
"password": "SNTPUMP",
"infura_token" : "token from https://www.infura.io/"
"infura_token" : "token from https://www.infura.io/",
"coingecko_api_key": "API key from https://www.coingecko.com/"
}
account.login(**params)
available_tokens = account.get_tokens()
@@ -454,7 +455,8 @@ account = Account()
params = {
"display_name": "status-app-bot",
"password": "SNTPUMP",
"infura_token" : "token from https://www.infura.io/"
"infura_token" : "token from https://www.infura.io/",
"coingecko_api_key": "API key from https://www.coingecko.com/"
}
account.login(**params)
@@ -479,7 +481,8 @@ account = Account()
params = {
"display_name": "status-app-bot",
"password": "SNTPUMP",
"infura_token" : "token from https://www.infura.io/"
"infura_token" : "token from https://www.infura.io/",
"coingecko_api_key": "API key from https://www.coingecko.com/"
}
account.login(**params)
@@ -505,7 +508,8 @@ account = Account()
params = {
"display_name": "status-app-bot",
"password": "SNTPUMP",
"infura_token" : "token from https://www.infura.io/"
"infura_token" : "token from https://www.infura.io/",
"coingecko_api_key": "API key from https://www.coingecko.com/"
}
account.login(**params)
@@ -535,7 +539,8 @@ account = Account()
params = {
"display_name": "status-app-bot",
"password": "SNTPUMP",
"infura_token" : "token from https://www.infura.io/"
"infura_token" : "token from https://www.infura.io/",
"coingecko_api_key": "API key from https://www.coingecko.com/"
}
account.login(**params)
@@ -876,7 +881,11 @@ Channel permissions:
from bot import Account
account = Account()
account.login("status-app-bot", "SNTPUMP")
params = {
"display_name": "status-app-bot",
"password": "SNTPUMP"
}
account.login(**params)
for community in account.communities:
print(community["name"], community["members"])
@@ -938,8 +947,55 @@ account = Account()
params = {
"display_name": "status-app-bot",
"password": "SNTPUMP",
"infura_token" : "token from https://www.infura.io/"
"infura_token" : "token from https://www.infura.io/",
"coingecko_api_key": "API key from https://www.coingecko.com/"
}
account.login(**params)
print(account.chains)
```
#### `balance`
Retrieve the current **non-zero balances** token balances for the **logged-in account wallet** across all supported chains.
Returns `pd.DataFrame`.
| Column | Type | Description |
|--------|------|-------------|
| `timestamp` | `datetime.datetime` | Timestamp when the balance was fetched. |
| `address` | `str` | Token contract address. |
| `chain_id` | `int` | Chain ID where the token exists. |
| `amount` | `float` | Token balance (adjusted using token decimals). |
| `symbol` | `str` | Token symbol (e.g. `ETH`, `USDT`). |
```python
from bot import Account
account = Account()
params = {
"display_name": "status-app-bot",
"password": "SNTPUMP",
"infura_token" : "token from https://www.infura.io/",
"coingecko_api_key": "API key from https://www.coingecko.com/"
}
account.login(**params)
print(account.balance)
```
You can convert the current balance into fiat currency by using a [ISO 4217 currency code](https://www.iso.org/iso-4217-currency-codes.html) in the `[]` accessor:
```python
from bot import Account
account = Account()
params = {
"display_name": "status-app-bot",
"password": "SNTPUMP",
"infura_token" : "token from https://www.infura.io/",
"coingecko_api_key": "API key from https://www.coingecko.com/"
}
account.login(**params)
print(account["GBP"])
```
Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 137 KiB