Asset Hub

Polkadot has two common-good parachains that focus on asset management, called Statemint (for Polkadot) and Statemine (for Kusama). The goal of these two common-good parachains is to streamline development and ensures that all connected parachains can trust and use a single standardized set of functionalities, rather than each parachain reinventing the wheel by implementing its own asset management capabilities.

The names of these common-good parachains is transitioning to Asset Hub instead of Statemint and Statemine, but in reality it will probably be a long time before people stop referring to them as Statemint and Statemine.

In the case of the Tether stablecoin, USDt, is created in Statemint/e by a multisig address that owns the asset and issues a call to create more. Only Tether can create/mint USDt. It can then be transferred internally on Statemint/e, transferred to other parachains, or received from other parachains via XCM. Current methods do not allow for teleportation of USDt; instead, derivatives are created during transfers, implying that the USDt remains within the ecosystem.

Thus, USDt’s total supply lives on Asset Hub and each parachain has a sovereign account that keeps track of how much USDt is on each parachain. Thus, in order to find out how much USDt is on Asset Hub, you have to start by find the total supply, then subtract how much is allocated to each parachain, and the remainder is what’s left on Assset Hub.

This may sound tricky, but that is exactly why we are writing this tutorial. We will show you how to get all the information you need from the on-chain storage functions using Python. (You could do this in TypeScript as well, but our examples will be in Python).

py-substrate-interface

We will be using the py-substrate-interface to make RPC calls from python and decode data from substate blockchains. Specifically, we will look at how to derive public keys, ss58 addresses, and sovereign accounts. As an example, we will use sovereign accounts to track the flow of USDt on substrate parachains. First thing we will do is load the necessary Python packages.

from substrateinterface import SubstrateInterface
from substrateinterface.utils.ss58 import ss58_decode, ss58_encode
from scalecodec.base import ScaleBytes
from scalecodec.types import U32
from datetime import datetime
import pandas as pd
import pandas_gbq
pd.set_option('display.max_columns', None)
pd.options.display.max_colwidth = 100

Polkadot JS app

Before we can get started, we need to identify the token id for USDt. In order to do this we will look it up using the Polkadot JS app by choosing assets.metadata. This will list all the assets registered on Statemint/e. We see that the token id is 1984:

    [
      1,984
    ]
    {
      deposit: 2,008,200,000
      name: Tether USD
      symbol: USDt
      decimals: 6
      isFrozen: false
    }
  ]

USDt Balances on Assethub

Now that we know the token id of USDt is 1984 we can use that to find the total supply of USDt on Statemine (Polkadot). We can query the Assets.Asset storage function and pass in the token id into the parameter field.

substrate = SubstrateInterface(url='wss://statemint-rpc.polkadot.io')
head = substrate.get_chain_head()
block_number = substrate.get_block_number(head)
now_time = substrate.query(module="Timestamp",storage_function = "Now", block_hash=head).value

# USDt supply on Polkadot (Statemint)
assets_asset = substrate.query(module='Assets',storage_function='Asset',block_hash=head,params = [1984])
statemint = assets_asset['supply'].value / 10**6
print(f"USDt total supply on Statemint is {statemint}")
## USDt total supply on Statemint is 17998750.120692

We see that the total supply on Polkadot is 1.799875^{7} after adjusting for the 6 decimal places.

We can do the same thing for Statemint (Kusama).

substrate = SubstrateInterface(url='wss://sys.ibp.network/statemine')
head = substrate.get_chain_head()
block_number = substrate.get_block_number(head)
now_time = substrate.query(module="Timestamp",storage_function = "Now", block_hash=head).value

# USDt supply on Kusama (Statemine)
assets_asset = substrate.query(module='Assets',storage_function='Asset',block_hash=head,params = [1984])
statemine = assets_asset['supply'].value / 10**6
print(f"USDt total supply on Statemine is {statemine}")
## USDt total supply on Statemine is 3499754.731831

We see that the total supply on Kusama is 3.4997547^{6} after adjusting for the 6 decimal places.

Sovereign accounts

Based on this stackexchange question we see how to derive a parachain’s sovereign account based on its parachain id. The function below shows how this is done. This function can be used to generate parent (para) accounts and sibling (sibl) accounts. My understanding is that para accounts used for connecting to between the relay chain and its parachains while sibl accounts are used when one parachain is connecting directly to another parachain (without going through the relay chain). Normally when we use this function we would use chain_type of para, but to generate sovereign accounts, we use chain_type of sibl.
It is worth pointing out that in substrate each account has a public key that can be converted into an ss58 account for each parachain.

Fun fact, all parachain para public keys start with 0x70617261 and all parachain sibl public keys start with 0x7369626c. Try using this online converter and convert para to hex and you will get 0x70617261. Likewise, if you convert sibl to hex you will get 0x7369626c. See, now it is not so mysterious.

# https://substrate.stackexchange.com/questions/1200/how-to-calculate-sovereignaccount-for-parachain
# 0x70617261 = b"para" (up/down) or 0x7369626c = b"sibl" (side-to-side)
def get_parachain_sovereign_account(para_id, ss58_format, chain_type='sibl'):
    scale_encoded = chain_type.encode().hex()
    scale_encoded += U32(ScaleBytes(bytearray())).encode(para_id).to_hex()[2:]
    public_key = f"0x{scale_encoded + ''.join(['0' * (64 - len(scale_encoded))])}"
    ss58_address = ss58_encode(public_key, ss58_format = ss58_format)
    return public_key, ss58_address
  
polkadot_ss58 = 0 # This is Polkadot's ss58 format code
p_public_key, p_ss58 = get_parachain_sovereign_account(para_id = '2000', ss58_format = polkadot_ss58, chain_type='sibl')
print(f"Acala's public key is {p_public_key}")
## Acala's public key is 0x7369626cd0070000000000000000000000000000000000000000000000000000
print(f"Acala's sovereign account is {p_ss58}")
## Acala's sovereign account is 13cKp89Msu7M2PiaCuuGr1BzAsD5V3vaVbDMs3YtjMZHdGwR
kusama_ss58 = 2 # This is Kusama's ss58 format code
k_public_key, k_ss58 = get_parachain_sovereign_account(para_id = '2000', ss58_format = kusama_ss58, chain_type='sibl')
print(f"Karura's public key is {k_public_key}")
## Karura's public key is 0x7369626cd0070000000000000000000000000000000000000000000000000000
print(f"Karura's sovereign account is {k_ss58}")
## Karura's sovereign account is FBeL7EAeUroLWXW1yfKboiqTqVfbRBcsUKd6QqVf4kGBySS

USDt balance for each account

We will now use py-subtrate-interface to query the USDt balance for every account in Statemint.

substrate = SubstrateInterface(url='wss://statemint-rpc.polkadot.io')
usdt_balances = substrate.query_map(module='Assets',storage_function='Account',block_hash=None,params = [1984])
bal = []
for a,b in usdt_balances:
  outi = {'account': a.value,
          'balance': b['balance'].value}
  bal.append(outi)

bal_df = pd.DataFrame(bal)

bal_df[bal_df['account'] == p_ss58]
##                                               account   balance
## 506  13cKp89Msu7M2PiaCuuGr1BzAsD5V3vaVbDMs3YtjMZHdGwR  63087190
acala = bal_df[bal_df['account'] == p_ss58]['balance'] / 10**6
print(f"Acala's balance of USDt is {acala}")
## Acala's balance of USDt is 506    63.08719
## Name: balance, dtype: float64

Adjusting for the number of decimal places of USDt (6), we see that Acala has 63.08719 USDt.

Doing this calculation for every parachain would give us the complete balances held by the parachains, which we could substract from the USDt total supply to find out how much was left over to be attributed to Asset Hub.

We could do the same exercise for Kusama, but I will leave that for you as a take home assignment.