Generate a report

In this tutorial we present how to generate a report using the Ledger Enterprise API.

We explain how to retrieve information about your accounts including addresses, currencies and balances and how to generate a report from it.

Requirements

Before we start, you need to have access to your workspace's Ledger Authentication Module (LAM) and have the necessary credentials, i.e. an API account and API key (if applicable).

To generate a global report, your API account must also have a VIEW ALL permission to be able to fetch data from all the accounts on the workspace.

Below we define the LAM address and headers we will use to send API requests:

Copy
Copied
LAM = '<your_LAM_address>'
headers = {'X-Ledger-API-User': '<your_api_username>', 'X-Ledger-API-Key': '<your_api_key>'}

Get list of active accounts

The first step will be to fetch the list of all the active accounts on your workspace. For that, we can execute a GET /accounts request, which will return the list of accounts.

If the list of accounts does not fit in a single response page, one needs to iterate over the pages to get the full list. See the pagination section of the API documentation.

The function defined below automatically iterates over the pages of the GET /accounts request, and builds the list of all ACTIVE accounts in the current workspace:

Copy
Copied
import requests

def get_all_accounts(page_size=30):
    active_accounts = []
    page = 1
    has_next_page = True
    while has_next_page:
        r = requests.get(LAM + '/accounts', headers=headers, params={'page': page,
                                                                     'page_size': page_size,
                                                                     'status': 'ACTIVE'})
        accounts = r.json()['edges']
        active_accounts += [a['node'] for a in accounts]
        has_next_page = r.json()['page_info']['has_next_page']
        page += 1
    return active_accounts

Using this function, we fetch the list of all the accounts on our workspace:

Copy
Copied
accounts = get_all_accounts(page_size=30)

The list we obtain provides enough information to build a basic report which would include the account names, addresses and raw balances.

But it misses some information such as the corresponding token names, tickers and numbers of decimals.

In the next section, we build a dictionary of currencies and tokens to further refine our report content.

Build currencies and tokens dictionary

To get more information regarding the different currencies and tokens of our accounts, we can query the /currencies and /currencies/tokens endpoints, which will return the complete lists of currencies and tokens supported on the Ledger Enterprise platform.

Let's first get the list of all native currencies:

Copy
Copied
r = requests.get(LAM + '/currencies', headers=headers)

From the API response, we extract the currency names, tickers and corresponding numbers of decimals, and gather this data in a dictionary which we will use later to build our report. In that case, the dictionary keys are simply the currency/chain names:

Copy
Copied
currencies_list = r.json()['currencies']

currencies = {currency['name']: {'name': currency['name'].replace('_', ' ').capitalize(),
                                 'ticker': currency['units'][-1]['code'],
                                 'decimals': currency['units'][-1]['magnitude']}
              for currency in currencies_list}

which returns a dictionary as below:

Copy
Copied
{'bitcoin': {'name': 'Bitcoin', 'ticker': 'BTC', 'decimals': 8},
 'bitcoin_cash': {'name': 'Bitcoin cash', 'ticker': 'BCH', 'decimals': 8},
 'bitcoin_gold': {'name': 'Bitcoin gold', 'ticker': 'BTG', 'decimals': 8},
 'bitcoin_testnet': {'name': 'Bitcoin testnet', 'ticker': 'tBTC', 'decimals': 8},
 ...
 'polygon': {'name': 'Polygon', 'ticker': 'MATIC', 'decimals': 18},
 'ripple': {'name': 'Ripple', 'ticker': 'XRP', 'decimals': 6},
 'solana': {'name': 'Solana', 'ticker': 'SOL', 'decimals': 9},
 ...
}

We now do the same for the list of ERC20/BEP20 tokens supported on the platform. This time, we build a dictionary of tokens where keys are defined as a tuples (token_address, token_chain):

Copy
Copied
r = requests.get(LAM + '/currencies/tokens', headers=headers)

tokens_list = r.json()['tokens']

tokens = {(token['contract_address'].lower(), token['parent_currency']): 

          {'name': token['name'],
           'ticker': token['units'][0]['code'],
           'decimals': token['units'][0]['magnitude']} 

          for token in tokens_list}

Finally, we merge the token dictionary with the previously built currency dictionary to get a single dictionary gathering all the useful information about native currencies and tokens:

Copy
Copied
currencies.update(tokens)

Create report

Finally, we combine the list of accounts we fetched in the first section with the currencies and tokens information we generated in the second section to build our report.

To build the report, we iterate over the list of accounts and apply the following process:

  • First, we exclude accounts which we don't want to be added in the report. In this example, we exclude Ethereum PoW and Ethereum Ropsten accounts from the report.
  • For each UTXO-based account, we define the account address as its xpub. For other accounts, we simply use their blockchain addresses.
  • For each account, we fetch the corresponding currency or token information, including the currency/token's name, ticker and number of decimals.
  • For each account, we adjust the account's raw balance based on the number of decimals to get the account balance expressed in the currency/token main unit.
  • Finally, for each account we record the following fields which we want to be part of the report:
    • chain : the chain/network on which the corresponding account lives.
    • currency : the name of the account's currency/token.
    • ticker : the ticker/code of the account's currency/token.
    • id : the unique id of the account on the Ledger Enterprise platform.
    • name : the account's name on the Ledger Enterprise platform.
    • balance : the account's balance, expressed in the main currency/token unit.
    • address : the account's address or xpub.

To build the report, we use pandas and create a DataFrame out of the account fields we recorded in the loop:

Copy
Copied
import pandas as pd

report = []

for account in accounts:
    account_chain = account['currency']
    if account_chain not in ['ethereum_pow', 'ethereum_ropsten']: 

        if account['address'] is None:  # If UTXO-based currency
            account_address = account['xpub']
        else:
            account_address = account['address']

        if account['contract_address'] is not None:  # If token
            currency_key = (account['contract_address'].lower(), account_chain)
        else:  # If native currency
            currency_key = account_chain

        currency_name = currencies[currency_key]['name']
        currency_decimals = currencies[currency_key]['decimals']
        currency_ticker = currencies[currency_key]['ticker']

        balance = round(int(account['balance']) / pow(10, currency_decimals), 2)

        report.append({'chain': account_chain,
                       'currency': currency_name,
                       'ticker': currency_ticker,
                       'id': account['id'],
                       'name': account['name'],
                       'balance': str(balance) + ' ' + currency_ticker,
                       'address': account_address})

report_df = pd.DataFrame(report)
report_df.sort_values(by=['id'], inplace=True, ignore_index=True)

The report can easily be saved as a csv or excel file using .to_csv() or .to_excel() methods.

Copy
Copied
report_df.to_csv('ledger-enterprise-report.csv')

The report can also be converted into markdown format using:

Copy
Copied
report_df.to_markdown(index=False)

which renders as follows on this tutorial page:

chain currency ticker id name balance address
bitcoin_testnet Bitcoin testnet tBTC 6 Reserve-BTC 0.02 tBTC tpubDCs9Krqrkuey5sZvF5yqpcVTSQWvqdgqf9wYodECWZ1W7Fo2Dm6wXYLMGJjzxTmxUtgeCkg7FTxtL6C6mbe6LEDhVX9aqy26nfNKVHVF9BA
bitcoin_testnet Bitcoin testnet tBTC 7 Liquidity-BTC 0.01 tBTC tpubDDko6ESpH2GUQfBQ35Hrn3jVnmvrJk6AuBZZpdzZ63Ukgi3RPnbSd6cD9QiMHuyH6keLxa3GrPMtjVF5dEgnow8jXZKQzqw9V2buPGk8s5V
ethereum Ethereum ETH 10 Ethereum-SCI-NFT 0.07 ETH 0xB6b171F2E253cD991eB98091ee8C258753083C5C
ethereum TrueUSD TUSD 11 TUSD 1.0 TUSD 0xB6b171F2E253cD991eB98091ee8C258753083C5C
tezos Tezos XTZ 15 Tezos1 0.72 XTZ tz2CVMDVA16dD9A7kpWym2ptGDhs5zUhwWXr
polkadot Polkadot DOT 17 DOT1 22.36 DOT 14WUAWaLejoiWPurTgdhsQ7qLpQLdBqUtTfUFrnZ8BJG9eAn
bitcoin Bitcoin BTC 20 ClientA-BTC 0.0 BTC xpub6DHkDqUp6Js1d4gs4gmnAP5uUsKV14nP1wodXfiga8JzTzkw6U3Lfq7moGsnJxGoR1XNP68oBWswvQ6d89oLEAZNpe81g5euWkFXUDX2jvW
bitcoin_testnet Bitcoin testnet tBTC 27 bitcoin-testnet3 0.0 tBTC tpubDCTf2NtMVx6N7s8nxQ9cVFSG7ByJpX6o12SBQUDFGfNzYefXDnZMHGxkViuBqE5xEsVE8QDQshuBGigxdiXyB29JQSoE7pAvrULLhqaZVpG
bitcoin_testnet Bitcoin testnet tBTC 28 ClientA-BTC-Testnet 0.0 tBTC tpubDDTePbrCyqaTtxtQFkm8cwPmRPz1kEy5cMp5SK5hR48DnHr6Q7kJTcLoqN6AYHVxXFKATpyhin4UmH4qxWXsxLtsoJLE9cRKbAdc3A3p3pu
ethereum Ethereum ETH 29 Liquidity-Ethereum 0.0 ETH 0x95dBFF72d215e4a0834Aed61A189882876116751
ethereum Ethereum ETH 31 Reserve-Ethereum 0.0 ETH 0x630D83b5Db5b8216bd2E13D5b3d2f89b845f304e
ethereum Tether USD USDT 32 Reserve-USDT 7.0 USDT 0x630D83b5Db5b8216bd2E13D5b3d2f89b845f304e
ethereum Ethereum ETH 33 Ethereum-Test 0.0 ETH 0xD5882DeCf61E23Db16bfbe036CE9d9707F1e7d40
bitcoin_testnet Bitcoin testnet tBTC 34 UFC-Athlete-BTC 0.0 tBTC tpubDCXWunXGeWzWSMu8xa7HqdUtgJbNsBsMVFrCGoLGkStyQLXVt7LuZj23zdFsTxrYxrdk1Ni44W8FRWbw3n6DEgVoYfaMNaHYST6f63S55LX
bitcoin_testnet Bitcoin testnet tBTC 35 UFC-Agent-BTC 0.0 tBTC tpubDDCGcfmnL3P5Nhpqz5kLJD3BpGk7x9xyyPiVdaoxWxgoq8zYRYSCxwfGyF9BWURivFWs3bZQ1UZHtNTPp53xVgQi39NpM5UgtoYYJNqDEL9
bitcoin_testnet Bitcoin testnet tBTC 36 UFC-BlueChip-BTC 0.0 tBTC tpubDDYj7naXAg3foHuTxEk8Ugwsi3BRZ9kt8hFY3E2Q4M94nj74KJrToyXno34nc6FbV4Jtbym8ptRFacpAqghgyw6EU5G8DB3d1LhPPo1dW8R
ethereum Tether USD USDT 37 Liquidity-USDT 0.09 USDT 0x95dBFF72d215e4a0834Aed61A189882876116751
polygon Polygon MATIC 38 Polygon-SCI-NFT 0.0 MATIC 0xB6b171F2E253cD991eB98091ee8C258753083C5C
polygon Polygon MATIC 39 Liquidity-Polygon 1.0 MATIC 0x95dBFF72d215e4a0834Aed61A189882876116751
polygon Matic Token MATIC 40 Liquidity-MATIC 1.0 MATIC 0x95dBFF72d215e4a0834Aed61A189882876116751
polygon Wrapped Ether WETH 41 Liquidity-wETH 0.0 WETH 0x95dBFF72d215e4a0834Aed61A189882876116751
digibyte Digibyte DGB 42 DGB 167.57 DGB xpub6CAYMUHEKiJogZYC6Ssk3c8qt9ZpTP2LxTyPAXJatejanXrXJGnFw8kj2hjGySkjpcTygfTZXJDX4v7YX6bQ8z4QFX4ZNsFtjCK6nM4aw4f
digibyte Digibyte DGB 43 DGB2 37.3 DGB xpub6CA4kXVgZaY2YLt1gAeApqvW9HCcDKjG6JyZNbLVbjhryLw1pSdnbT5wvgGjvJV4mjKu5tDxm76FFHvDERXatQZz9eLfz2B9xNHiKEbc4jK
ethereum_goerli Ethereum goerli gETH 44 ETH-RESERVE 0.22 gETH 0x2CE3895Fa3ad9a4EAcC37feD6ac8c35e5AE8c5cF
ethereum_goerli USDC USDC 46 USDC-RESERVE 0.9 USDC 0x2CE3895Fa3ad9a4EAcC37feD6ac8c35e5AE8c5cF
solana_devnet Solana devnet tSOL 47 SOL-DEV 11.23 tSOL fZ3im6BP7jpaqYBA4cBAtZrPKsT3PR3d8fwDyHnyh1e
ethereum Tether USD USDT 48 SCI-USDT 0.1 USDT 0xB6b171F2E253cD991eB98091ee8C258753083C5C
polygon USD Coin (PoS) USDC 51 Liquidity-USDC 0.25 USDC 0x95dBFF72d215e4a0834Aed61A189882876116751
solana Solana SOL 55 SOL1 0.15 SOL fZ3im6BP7jpaqYBA4cBAtZrPKsT3PR3d8fwDyHnyh1e
polygon SAND SAND 56 Liquidity-SAND 0.0 SAND 0x95dBFF72d215e4a0834Aed61A189882876116751
polygon Polygon MATIC 58 Reserve-Polygon 0.0 MATIC 0x630D83b5Db5b8216bd2E13D5b3d2f89b845f304e
ethereum_goerli Ethereum goerli gETH 61 ETH-Goerli-Staking 0.16 gETH 0x128075552e4C6dC64Bca2Cf9ca46ee688629e4CD
ethereum Matic MATIC 64 Ethereum-SCI-MATIC 1.2 MATIC 0xB6b171F2E253cD991eB98091ee8C258753083C5C
bsc Bsc BNB 65 Liquidity-BSC 0.0 BNB 0x95dBFF72d215e4a0834Aed61A189882876116751
polygon USD Coin (PoS) USDC 68 account58_0x2791Bca 0.01 USDC 0x630D83b5Db5b8216bd2E13D5b3d2f89b845f304e

Bonus - Send report by email

As a bonus, we show how to send the report by email.

To do that, one option is to convert the report DataFrame to a HTML table which we can then include in an email.

Copy
Copied
from pretty_html_table import build_table

report_html = build_table(report_df, color='blue_light', text_align='center', escape=False)

In the example below, we send an email containing the report as a HTML table, using smtplib and via a properly configured Gmail account.

The gmail account must be configured to authorize such operation, especially a specific email App password must be set up.

Copy
Copied
import smtplib, ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

sender_email = '<sender_gmail_address>'
receiver_email = '<receiver_email_address>'
password = '<gmail_app_password>'

# Build email object
message = MIMEMultipart()
message['Subject'] = "Ledger Vault - Daily balances report"
message['From'] = sender_email
message['To'] = receiver_email
message.attach(MIMEText(report_html, 'html'))

# Create secure connection with server and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL('smtp.gmail.com', 465, context=context) as server:
    server.login(sender_email, password)
    server.sendmail(from_addr=sender_email, to_addrs=receiver_email, msg=message.as_string())
Copyright © Ledger Enterprise Platform 2023. All right reserved.