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
## 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
## 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.