Mosaics created with a mutable supply can have their total
supply increased or decreased after creation.
Only the mosaic creator can change the supply.
Supply changes affect only the creator's balance: minted units are added to it, and burned units are removed from it.
The balances of all other accounts that hold the mosaic remain unchanged.
This tutorial shows how to change a mosaic's supply by minting and burning units.
importjsonimportosimporttimeimporturllib.requestfromsymbolchain.CryptoTypesimportPrivateKeyfromsymbolchain.facade.NemFacadeimportNemFacadefromsymbolchain.ncimportAmountfromsymbolchain.nem.FeeCalculatorimportcalculate_transaction_feefromsymbolchain.nem.NetworkimportNetworkTimestampNODE_URL=os.getenv('NODE_URL','http://libertalia.nemtest.net:7890')print(f'Using node {NODE_URL}')# Helper function to announce a transactiondefannounce_transaction(payload,label):announce_path='/transaction/announce'print(f'Announcing {label} to {announce_path}')request=urllib.request.Request(f'{NODE_URL}{announce_path}',data=payload.encode(),headers={'Content-Type':'application/json'},method='POST')withurllib.request.urlopen(request)asannounce_response:result=json.loads(announce_response.read().decode())print(f' Result: {result["message"]}')returnresult['message']# Helper function to fetch the current mosaic supplydeffetch_supply(mosaic):supply_path=f'/mosaic/supply?mosaicId={mosaic}'withurllib.request.urlopen(f'{NODE_URL}{supply_path}')assupply_response:supply_info=json.loads(supply_response.read().decode())returnsupply_info['supply']# Helper function to wait for transaction confirmationdefwait_for_confirmation(tx_hash,label):status_path=f'/transaction/get?hash={tx_hash}'print(f'Waiting for {label} confirmation from {status_path}')is_confirmed=Falsefor_inrange(120):try:withurllib.request.urlopen(f'{NODE_URL}{status_path}')asstatus_response:confirmed=json.loads(status_response.read().decode())height=confirmed['meta']['height']print(f'{label} confirmed in block {height}')is_confirmed=Truebreakexcepturllib.error.HTTPError:print(' Transaction status: pending')time.sleep(1)ifnotis_confirmed:print(f'{label} confirmation took too long.')SIGNER_PRIVATE_KEY=os.getenv('SIGNER_PRIVATE_KEY','0000000000000000000000000000000000000000000000000000000000000000')signer_key_pair=NemFacade.KeyPair(PrivateKey(SIGNER_PRIVATE_KEY))facade=NemFacade('testnet')signer_address=facade.network.public_key_to_address(signer_key_pair.public_key)print(f'Signer address: {signer_address}')namespace_name=os.getenv('NAMESPACE','my_namespace')mosaic_name=os.getenv('MOSAIC','token')mosaic_id=f'{namespace_name}:{mosaic_name}'print(f'Mosaic ID: {mosaic_id}')try:# Fetch current network timetime_path='/time-sync/network-time'print(f'Fetching current network time from {time_path}')withurllib.request.urlopen(f'{NODE_URL}{time_path}')asresponse:response_json=json.loads(response.read().decode())network_time=response_json['receiveTimeStamp']//1000print(f' Network time: {network_time} s since the nemesis block')# Derived fields from network timetimestamp=NetworkTimestamp(network_time)deadline=timestamp.add_hours(2)# --- INCREASING SUPPLY (MINTING) ---print('\n--- Increasing supply (minting) ---')print(f'Supply before minting: {fetch_supply(mosaic_id)}')increase_tx=facade.transaction_factory.create({'type':'mosaic_supply_change_transaction_v1','signer_public_key':signer_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'mosaic_id':{'namespace_id':{'name':namespace_name},'name':mosaic_name},'action':'increase','delta':500})increase_tx.fee=Amount(calculate_transaction_fee(increase_tx))signature=facade.sign_transaction(signer_key_pair,increase_tx)json_payload=facade.transaction_factory.attach_signature(increase_tx,signature)print('Built supply increase transaction:')print(json.dumps(increase_tx.to_json(),indent=2))if'SUCCESS'==announce_transaction(json_payload,'supply increase'):wait_for_confirmation(facade.hash_transaction(increase_tx),'supply increase')print(f'Supply after minting: {fetch_supply(mosaic_id)}')else:print('Supply increase rejected')# --- DECREASING SUPPLY (BURNING) ---print('\n--- Decreasing supply (burning) ---')decrease_tx=facade.transaction_factory.create({'type':'mosaic_supply_change_transaction_v1','signer_public_key':signer_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'mosaic_id':{'namespace_id':{'name':namespace_name},'name':mosaic_name},'action':'decrease','delta':500})decrease_tx.fee=Amount(calculate_transaction_fee(decrease_tx))signature=facade.sign_transaction(signer_key_pair,decrease_tx)json_payload=facade.transaction_factory.attach_signature(decrease_tx,signature)print('Built supply decrease transaction:')print(json.dumps(decrease_tx.to_json(),indent=2))if'SUCCESS'==announce_transaction(json_payload,'supply decrease'):wait_for_confirmation(facade.hash_transaction(decrease_tx),'supply decrease')print(f'Supply after burning: {fetch_supply(mosaic_id)}')else:print('Supply decrease rejected')excepturllib.error.URLErrorase:print(e.reason)
import{PrivateKey}from'symbol-sdk';import{NemFacade,NetworkTimestamp,calculateTransactionFee,models}from'symbol-sdk/nem';constNODE_URL=process.env.NODE_URL||'http://libertalia.nemtest.net:7890';console.log('Using node',NODE_URL);// Helper function to announce a transactionasyncfunctionannounceTransaction(payload,label){constannouncePath='/transaction/announce';console.log(`Announcing ${label} to ${announcePath}`);constannounceResponse=awaitfetch(`${NODE_URL}${announcePath}`,{method:'POST',headers:{'Content-Type':'application/json'},body:payload});constresult=awaitannounceResponse.json();console.log(' Result:',result.message);returnresult.message;}// Helper function to fetch the current mosaic supplyasyncfunctionfetchSupply(mosaic){constsupplyPath=`/mosaic/supply?mosaicId=${mosaic}`;constsupplyResponse=awaitfetch(`${NODE_URL}${supplyPath}`);constsupplyInfo=awaitsupplyResponse.json();returnsupplyInfo.supply;}// Helper function to wait for transaction confirmationasyncfunctionwaitForConfirmation(transactionHash,label){conststatusPath=`/transaction/get?hash=${transactionHash}`;console.log(`Waiting for ${label} confirmation from`,statusPath);letisConfirmed=false;for(letattempt=1;120>=attempt;++attempt){constresponse=awaitfetch(`${NODE_URL}${statusPath}`);if(response.ok){constconfirmed=awaitresponse.json();console.log(`${label} confirmed in block`,confirmed.meta.height);isConfirmed=true;break;}console.log(' Transaction status: pending');awaitnewPromise(resolve=>{setTimeout(resolve,1000);});}if(!isConfirmed)console.warn(`${label} confirmation took too long.`);}constSIGNER_PRIVATE_KEY=process.env.SIGNER_PRIVATE_KEY||'0000000000000000000000000000000000000000000000000000000000000000';constsignerKeyPair=newNemFacade.KeyPair(newPrivateKey(SIGNER_PRIVATE_KEY));constfacade=newNemFacade('testnet');constsignerAddress=facade.network.publicKeyToAddress(signerKeyPair.publicKey);console.log('Signer address:',signerAddress.toString());constnamespaceName=process.env.NAMESPACE||'my_namespace';constmosaicName=process.env.MOSAIC||'token';constmosaicId=`${namespaceName}:${mosaicName}`;console.log('Mosaic ID:',mosaicId);try{// Fetch current network timeconsttimePath='/time-sync/network-time';console.log('Fetching current network time from',timePath);consttimeResponse=awaitfetch(`${NODE_URL}${timePath}`);consttimeJSON=awaittimeResponse.json();constnetworkTime=Math.floor(timeJSON.receiveTimeStamp/1000);console.log(' Network time:',networkTime,'s since the nemesis block');// Derived fields from network timeconsttimestamp=newNetworkTimestamp(networkTime);constdeadline=timestamp.addHours(2);// --- INCREASING SUPPLY (MINTING) ---console.log('\n--- Increasing supply (minting) ---');console.log('Supply before minting:',awaitfetchSupply(mosaicId));constincreaseTx=facade.transactionFactory.create({type:'mosaic_supply_change_transaction_v1',signerPublicKey:signerKeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,mosaicId:{namespaceId:{name:namespaceName},name:mosaicName},action:'increase',delta:500n});increaseTx.fee=newmodels.Amount(calculateTransactionFee(increaseTx));constincreaseSignature=facade.signTransaction(signerKeyPair,increaseTx);constincreasePayload=facade.transactionFactory.static.attachSignature(increaseTx,increaseSignature);console.log('Built supply increase transaction:');console.dir(increaseTx.toJson(),{colors:true});constincreaseResult=awaitannounceTransaction(increasePayload,'supply increase');if('SUCCESS'===increaseResult){awaitwaitForConfirmation(facade.hashTransaction(increaseTx).toString(),'supply increase');console.log('Supply after minting:',awaitfetchSupply(mosaicId));}else{console.log('Supply increase rejected');}// --- DECREASING SUPPLY (BURNING) ---console.log('\n--- Decreasing supply (burning) ---');constdecreaseTx=facade.transactionFactory.create({type:'mosaic_supply_change_transaction_v1',signerPublicKey:signerKeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,mosaicId:{namespaceId:{name:namespaceName},name:mosaicName},action:'decrease',delta:500n});decreaseTx.fee=newmodels.Amount(calculateTransactionFee(decreaseTx));constdecreaseSignature=facade.signTransaction(signerKeyPair,decreaseTx);constdecreasePayload=facade.transactionFactory.static.attachSignature(decreaseTx,decreaseSignature);console.log('Built supply decrease transaction:');console.dir(decreaseTx.toJson(),{colors:true});constdecreaseResult=awaitannounceTransaction(decreasePayload,'supply decrease');if('SUCCESS'===decreaseResult){awaitwaitForConfirmation(facade.hashTransaction(decreaseTx).toString(),'supply decrease');console.log('Supply after burning:',awaitfetchSupply(mosaicId));}else{console.log('Supply decrease rejected');}}catch(e){console.error(e.message,'| Cause:',e.cause?.code??'unknown');}
Changing a mosaic's supply uses the MosaicSupplyChangeTransactionV1 transaction.
This tutorial announces two of them: one to mint new units and one to burn them.
Because both transactions are submitted the same way, the snippet defines two helpers,
and , which announce a transaction
and then poll the network until it is included in a block.
A third helper, , reads the mosaic's current supply from /mosaic/supplyGET, so
that the effect of each transaction can be observed.
The snippet reads the signer's private key from the SIGNER_PRIVATE_KEY environment variable, which defaults to a test
key if not set.
The signer must be the creator of the mosaic.
The mosaic to update is read from the NAMESPACE and MOSAIC environment variables, which default to
my_namespace:token.
Use a mosaic created by the signer
By default, the code uses the test account referenced by SIGNER_PRIVATE_KEY and a mosaic named
my_namespace:token.
If you come from the Creating a Mosaic tutorial, set the SIGNER_PRIVATE_KEY, NAMESPACE,
and MOSAIC environment variables to match the account and mosaic you created there, or any other mosaic that the
signer owns.
# Fetch current network timetime_path='/time-sync/network-time'print(f'Fetching current network time from {time_path}')withurllib.request.urlopen(f'{NODE_URL}{time_path}')asresponse:response_json=json.loads(response.read().decode())network_time=response_json['receiveTimeStamp']//1000print(f' Network time: {network_time} s since the nemesis block')# Derived fields from network timetimestamp=NetworkTimestamp(network_time)deadline=timestamp.add_hours(2)
// Fetch current network timeconsttimePath='/time-sync/network-time';console.log('Fetching current network time from',timePath);consttimeResponse=awaitfetch(`${NODE_URL}${timePath}`);consttimeJSON=awaittimeResponse.json();constnetworkTime=Math.floor(timeJSON.receiveTimeStamp/1000);console.log(' Network time:',networkTime,'s since the nemesis block');// Derived fields from network timeconsttimestamp=newNetworkTimestamp(networkTime);constdeadline=timestamp.addHours(2);
Network time is fetched from /time-sync/network-timeGET, and the transaction's timestamp and deadline fields
are derived from it, following the process described in the Transfer XEM tutorial.
The mosaic in this tutorial has a divisibility of 2, so one whole unit corresponds to \(100\) atomic units and
the supply can grow up to \(9 \cdot 10^{13}\) whole units.
The transaction fee is then calculated and the transaction is signed, announced, and confirmed, following the same
process as in the Transfer XEM tutorial.
Mosaic supply change transactions pay a fixed transaction fee of 0.15 XEM, as shown in the
fee schedule.
Once confirmed, the supply is read again to show the resulting supply.
The minted units are credited to the creator's account.
constdecreaseTx=facade.transactionFactory.create({type:'mosaic_supply_change_transaction_v1',signerPublicKey:signerKeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,mosaicId:{namespaceId:{name:namespaceName},name:mosaicName},action:'decrease',delta:500n});decreaseTx.fee=newmodels.Amount(calculateTransactionFee(decreaseTx));constdecreaseSignature=facade.signTransaction(signerKeyPair,decreaseTx);constdecreasePayload=facade.transactionFactory.static.attachSignature(decreaseTx,decreaseSignature);console.log('Built supply decrease transaction:');console.dir(decreaseTx.toJson(),{colors:true});constdecreaseResult=awaitannounceTransaction(decreasePayload,'supply decrease');if('SUCCESS'===decreaseResult){awaitwaitForConfirmation(facade.hashTransaction(decreaseTx).toString(),'supply decrease');console.log('Supply after burning:',awaitfetchSupply(mosaicId));}else{console.log('Supply decrease rejected');}
To burn existing units, the same transaction type is used with set to decrease and
set to the number of whole units to remove.
The burned units are taken from the creator's account, so only units the creator still holds can be burned.
Units already distributed to other accounts remain in their balances, and the transaction fails if the creator's own
balance does not cover .
Once confirmed, the supply is read again to show the burned units.
Because this tutorial increases and then decreases the supply by the same amount, the final supply matches the value
before the changes.