importjsonimportosimporturllib.requestNODE_URL=os.getenv('NODE_URL','http://libertalia.nemtest.net:7890')print(f'Using node {NODE_URL}')defget_mosaic_balances(address):""" Fetch all mosaic balances owned by an account. Args: address: The account address Returns: List of mosaics, each with a structured mosaicId and quantity """balances_path=f'/account/mosaic/owned?address={address}'withurllib.request.urlopen(f'{NODE_URL}{balances_path}')asresponse:balances_info=json.loads(response.read().decode())returnbalances_info['data']defget_mosaic_definitions(address):""" Fetch mosaic definitions for every mosaic owned by an account. Args: address: The account address Returns: Dictionary mapping "namespace:name" to the mosaic definition """definitions_path='/account/mosaic/owned/definition'withurllib.request.urlopen(f'{NODE_URL}{definitions_path}?address={address}')asresponse:definitions_info=json.loads(response.read().decode())# Build a dictionary mapping "namespace:name" to its definitiondefinitions_map={}forentryindefinitions_info['data']:entry_id=entry['id']entry_key=f'{entry_id["namespaceId"]}:{entry_id["name"]}'definitions_map[entry_key]=entryreturndefinitions_mapdefformat_amount(amount,divisibility):""" Format an atomic amount with decimal places. Args: amount: The atomic amount as an integer divisibility: Number of decimal places Returns: Formatted amount as a string """ifdivisibility==0:returnstr(amount)whole_part=amount//(10**divisibility)fractional_part=amount%(10**divisibility)returnf'{whole_part}.{fractional_part:0{divisibility}d}'# The account address to queryADDRESS=os.getenv('ADDRESS','TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP')print(f'Fetching balances for {ADDRESS}')try:# Fetch mosaic balances and definitions for the accountaccount_mosaics=get_mosaic_balances(ADDRESS)mosaic_definitions=get_mosaic_definitions(ADDRESS)ifnotaccount_mosaics:print('Account holds no mosaics')else:print(f'Account holds {len(account_mosaics)} mosaic(s):')formosaic_entryinaccount_mosaics:mosaic_id=mosaic_entry['mosaicId']key=f'{mosaic_id["namespaceId"]}:{mosaic_id["name"]}'balance=int(mosaic_entry['quantity'])# Get mosaic divisibility from the definitiondefinition=mosaic_definitions[key]properties={p['name']:p['value']forpindefinition['properties']}mosaic_divisibility=int(properties.get('divisibility','0'))# Format and display the balanceformatted_balance=format_amount(balance,mosaic_divisibility)print(f'- Mosaic {key}')print(f' Balance: {formatted_balance}')print(f' Balance (atomic): {balance}')print(f' Divisibility: {mosaic_divisibility}')excepturllib.error.URLErrorase:print(e.reason)
constNODE_URL=process.env.NODE_URL||'http://libertalia.nemtest.net:7890';console.log('Using node',NODE_URL);/** * Fetch all mosaic balances owned by an account. * @param {string} address - Account address * @returns {Promise<object[]>} List of mosaics with id and quantity */asyncfunctiongetMosaicBalances(address){constpath=`/account/mosaic/owned?address=${address}`;constresponse=awaitfetch(`${NODE_URL}${path}`);constinfo=awaitresponse.json();returninfo.data;}/** * Fetch mosaic definitions for every mosaic owned by an account. * @param {string} address - Account address * @returns {Promise<Map>} Map of "namespace:name" to mosaic definition */asyncfunctiongetMosaicDefinitions(address){constpath=`/account/mosaic/owned/definition?address=${address}`;constresponse=awaitfetch(`${NODE_URL}${path}`);constinfo=awaitresponse.json();// Build a map from "namespace:name" to mosaic definitionconstdefinitionsMap=newMap();for(constentryofinfo.data){constkey=`${entry.id.namespaceId}:${entry.id.name}`;definitionsMap.set(key,entry);}returndefinitionsMap;}/** * Format an atomic amount with decimal places. * @param {bigint} amount - The atomic amount * @param {number} divisibility - Number of decimal places * @returns {string} The formatted amount */functionformatAmount(amount,divisibility){if(0===divisibility)returnamount.toString();constdivisor=10n**BigInt(divisibility);constwholePart=amount/divisor;constfractionalPart=amount%divisor;constfractionalStr=fractionalPart.toString().padStart(divisibility,'0');return`${wholePart}.${fractionalStr}`;}// The account address to queryconstADDRESS=process.env.ADDRESS||'TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP';console.log('Fetching balances for',ADDRESS);try{// Fetch mosaic balances and definitions for the accountconstaccountMosaics=awaitgetMosaicBalances(ADDRESS);constmosaicDefinitions=awaitgetMosaicDefinitions(ADDRESS);if(0===accountMosaics.length){console.log('Account holds no mosaics');}else{console.log(`Account holds ${accountMosaics.length} mosaic(s):`);for(constmosaicEntryofaccountMosaics){const{mosaicId}=mosaicEntry;constkey=`${mosaicId.namespaceId}:${mosaicId.name}`;constbalance=BigInt(mosaicEntry.quantity);// Get mosaic divisibility from the definitionconstdefinition=mosaicDefinitions.get(key);constproperties=Object.fromEntries(definition.properties.map(p=>[p.name,p.value]));constdivisibility=parseInt(properties.divisibility||'0',10);// Format and display the balanceconstformattedBalance=formatAmount(balance,divisibility);console.log(`- Mosaic ${key}`);console.log(` Balance: ${formattedBalance}`);console.log(` Balance (atomic): ${balance.toString()}`);console.log(` Divisibility: ${divisibility}`);}}}catch(e){console.error(e.message,'| Cause:',e.cause?.code??'unknown');}
defget_mosaic_balances(address):""" Fetch all mosaic balances owned by an account. Args: address: The account address Returns: List of mosaics, each with a structured mosaicId and quantity """balances_path=f'/account/mosaic/owned?address={address}'withurllib.request.urlopen(f'{NODE_URL}{balances_path}')asresponse:balances_info=json.loads(response.read().decode())returnbalances_info['data']
/** * Fetch all mosaic balances owned by an account. * @param {string} address - Account address * @returns {Promise<object[]>} List of mosaics with id and quantity */asyncfunctiongetMosaicBalances(address){constpath=`/account/mosaic/owned?address=${address}`;constresponse=awaitfetch(`${NODE_URL}${path}`);constinfo=awaitresponse.json();returninfo.data;}
The /account/mosaic/ownedGET endpoint returns every mosaic the account holds, together with its quantity in
atomic units.
defget_mosaic_definitions(address):""" Fetch mosaic definitions for every mosaic owned by an account. Args: address: The account address Returns: Dictionary mapping "namespace:name" to the mosaic definition """definitions_path='/account/mosaic/owned/definition'withurllib.request.urlopen(f'{NODE_URL}{definitions_path}?address={address}')asresponse:definitions_info=json.loads(response.read().decode())# Build a dictionary mapping "namespace:name" to its definitiondefinitions_map={}forentryindefinitions_info['data']:entry_id=entry['id']entry_key=f'{entry_id["namespaceId"]}:{entry_id["name"]}'definitions_map[entry_key]=entryreturndefinitions_map
/** * Fetch mosaic definitions for every mosaic owned by an account. * @param {string} address - Account address * @returns {Promise<Map>} Map of "namespace:name" to mosaic definition */asyncfunctiongetMosaicDefinitions(address){constpath=`/account/mosaic/owned/definition?address=${address}`;constresponse=awaitfetch(`${NODE_URL}${path}`);constinfo=awaitresponse.json();// Build a map from "namespace:name" to mosaic definitionconstdefinitionsMap=newMap();for(constentryofinfo.data){constkey=`${entry.id.namespaceId}:${entry.id.name}`;definitionsMap.set(key,entry);}returndefinitionsMap;}
To format mosaic balances correctly, the snippet fetches their definitions from the network.
The key property required is divisibility, which defines how many decimal places a mosaic supports.
The /account/mosaic/owned/definitionGET endpoint returns the definition for every mosaic owned by the account in a
single request, including divisibility and other properties.
defformat_amount(amount,divisibility):""" Format an atomic amount with decimal places. Args: amount: The atomic amount as an integer divisibility: Number of decimal places Returns: Formatted amount as a string """ifdivisibility==0:returnstr(amount)whole_part=amount//(10**divisibility)fractional_part=amount%(10**divisibility)returnf'{whole_part}.{fractional_part:0{divisibility}d}'
/** * Format an atomic amount with decimal places. * @param {bigint} amount - The atomic amount * @param {number} divisibility - Number of decimal places * @returns {string} The formatted amount */functionformatAmount(amount,divisibility){if(0===divisibility)returnamount.toString();constdivisor=10n**BigInt(divisibility);constwholePart=amount/divisor;constfractionalPart=amount%divisor;constfractionalStr=fractionalPart.toString().padStart(divisibility,'0');return`${wholePart}.${fractionalStr}`;}
This utility function converts atomic amounts into human-friendly representations:
Atomic amount: The raw value stored on the blockchain, expressed as an integer.
Formatted amount: The display format with decimal places determined by the mosaic's divisibility.
The formatting splits the atomic amount into whole and fractional parts by dividing and taking the remainder with
respect to \(10^{\text{divisibility}}\).
The fractional part is then zero-padded to ensure it always displays the correct number of decimal places.
# The account address to queryADDRESS=os.getenv('ADDRESS','TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP')print(f'Fetching balances for {ADDRESS}')try:# Fetch mosaic balances and definitions for the accountaccount_mosaics=get_mosaic_balances(ADDRESS)mosaic_definitions=get_mosaic_definitions(ADDRESS)ifnotaccount_mosaics:print('Account holds no mosaics')else:print(f'Account holds {len(account_mosaics)} mosaic(s):')formosaic_entryinaccount_mosaics:mosaic_id=mosaic_entry['mosaicId']key=f'{mosaic_id["namespaceId"]}:{mosaic_id["name"]}'balance=int(mosaic_entry['quantity'])# Get mosaic divisibility from the definitiondefinition=mosaic_definitions[key]properties={p['name']:p['value']forpindefinition['properties']}mosaic_divisibility=int(properties.get('divisibility','0'))# Format and display the balanceformatted_balance=format_amount(balance,mosaic_divisibility)print(f'- Mosaic {key}')print(f' Balance: {formatted_balance}')print(f' Balance (atomic): {balance}')print(f' Divisibility: {mosaic_divisibility}')excepturllib.error.URLErrorase:print(e.reason)
// The account address to queryconstADDRESS=process.env.ADDRESS||'TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP';console.log('Fetching balances for',ADDRESS);try{// Fetch mosaic balances and definitions for the accountconstaccountMosaics=awaitgetMosaicBalances(ADDRESS);constmosaicDefinitions=awaitgetMosaicDefinitions(ADDRESS);if(0===accountMosaics.length){console.log('Account holds no mosaics');}else{console.log(`Account holds ${accountMosaics.length} mosaic(s):`);for(constmosaicEntryofaccountMosaics){const{mosaicId}=mosaicEntry;constkey=`${mosaicId.namespaceId}:${mosaicId.name}`;constbalance=BigInt(mosaicEntry.quantity);// Get mosaic divisibility from the definitionconstdefinition=mosaicDefinitions.get(key);constproperties=Object.fromEntries(definition.properties.map(p=>[p.name,p.value]));constdivisibility=parseInt(properties.divisibility||'0',10);// Format and display the balanceconstformattedBalance=formatAmount(balance,divisibility);console.log(`- Mosaic ${key}`);console.log(` Balance: ${formattedBalance}`);console.log(` Balance (atomic): ${balance.toString()}`);console.log(` Divisibility: ${divisibility}`);}}}catch(e){console.error(e.message,'| Cause:',e.cause?.code??'unknown');}
The main code reads the ADDRESS environment variable to determine which account to query.
If no value is provided, it uses a default sample address.
It orchestrates the helper functions to:
Fetch the mosaic balances for the account.
Retrieve the mosaic definitions to determine each mosaic's divisibility.
Iterate through each mosaic and format its balance with the appropriate number of decimal places.