Mosaics represent assets on the NEM blockchain, such as currencies, collectibles, or access rights.
Unlike tokens on other platforms, NEM mosaics are supported directly at the protocol level
and require no additional coding to use.
Their properties are configurable to support various use cases, from simple currencies to tokens with custom supply
and transfer rules.
Every mosaic belongs to a registered namespace, which provides the first half of its
fully qualified name, such as my_namespace:token.
A namespace must therefore be registered before a mosaic can be created.
This tutorial shows how to create a mosaic under an existing namespace and configure its initial properties.
importjsonimportosimporttimeimporturllib.requestfromsymbolchain.CryptoTypesimportPrivateKeyfromsymbolchain.facade.NemFacadeimportNemFacadefromsymbolchain.ncimportAmountfromsymbolchain.nem.FeeCalculatorimport(calculate_mosaic_rental_fee,calculate_transaction_fee)NODE_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 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}')try:# Build the mosaic IDnamespace_name=os.getenv('NAMESPACE','my_namespace')mosaic_name=os.getenv('MOSAIC',f'token_{int(time.time())}')mosaic_id=f'{namespace_name}:{mosaic_name}'print(f'Creating mosaic: {mosaic_id}')# Define the mosaicmosaic_definition={'owner_public_key':signer_key_pair.public_key,'id':{'namespace_id':{'name':namespace_name},'name':mosaic_name},'description':'My tutorial mosaic','properties':[{'property_':{'name':b'divisibility','value':b'2'}},{'property_':{'name':b'initialSupply','value':b'1000'}},{'property_':{'name':b'supplyMutable','value':b'true'}},{'property_':{'name':b'transferable','value':b'true'}}]}# Build the mosaic definition transactionrental_fee=calculate_mosaic_rental_fee()print(f' Mosaic creation fee: {rental_fee/1_000_000} XEM')transaction=facade.create_transaction_from_descriptor({'type':'mosaic_definition_transaction_v1','rental_fee_sink':'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC','rental_fee':rental_fee,'mosaic_definition':mosaic_definition},signer_key_pair.public_key,0,2*60*60)# Calculate and attach the transaction feefee=calculate_transaction_fee(transaction)transaction.fee=Amount(fee)print(f' Transaction fee: {fee/1_000_000} XEM')# Sign and generate final payloadsignature=facade.sign_transaction(signer_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)print('Built mosaic definition transaction:')print(json.dumps(transaction.to_json(),indent=2))# Announce the transactionannounce_result=announce_transaction(json_payload,'mosaic definition')# Wait for confirmationif'SUCCESS'==announce_result:transaction_hash=facade.hash_transaction(transaction)print(f'Transaction hash: {transaction_hash}')wait_for_confirmation(transaction_hash,'mosaic definition')else:print(f'Transaction rejected: {announce_result}')# Retrieve the mosaicdefinition_path=f'/mosaic/definition?mosaicId={mosaic_id}'print(f'Fetching mosaic information from {definition_path}')withurllib.request.urlopen(f'{NODE_URL}{definition_path}')asresponse:mosaic_info=json.loads(response.read().decode())properties={prop['name']:prop['value']forpropinmosaic_info['properties']}print('Mosaic information:')print(f' Creator: {mosaic_info["creator"]}')print(f' Divisibility: {properties["divisibility"]}')print(f' Initial supply: {properties["initialSupply"]}')print(f' Supply mutable: {properties["supplyMutable"]}')print(f' Transferable: {properties["transferable"]}')exceptExceptionaserror:print(error)
import{PrivateKey}from'symbol-sdk';import{Address,NemFacade,calculateMosaicRentalFee,calculateTransactionFee,descriptors,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}`);constresponse=awaitfetch(`${NODE_URL}${announcePath}`,{method:'POST',headers:{'Content-Type':'application/json'},body:payload});if(!response.ok)thrownewError(`HTTP ${response.status}`);constresult=awaitresponse.json();console.log(' Result:',result.message);returnresult.message;}// 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){console.log(' Transaction status: pending');awaitnewPromise(resolve=>{setTimeout(resolve,1000);});}else{constconfirmed=awaitresponse.json();console.log(`${label} confirmed in block`,confirmed.meta.height);isConfirmed=true;break;}}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());try{// Build the mosaic IDconstnamespaceName=process.env.NAMESPACE||'my_namespace';constmosaicName=process.env.MOSAIC||`token_${Math.floor(Date.now()/1000)}`;constmosaicId=`${namespaceName}:${mosaicName}`;console.log('Creating mosaic:',mosaicId);// Define the mosaicconstmosaicDefinition=newdescriptors.MosaicDefinitionDescriptor(signerKeyPair.publicKey,newdescriptors.MosaicIdDescriptor(newdescriptors.NamespaceIdDescriptor(namespaceName),mosaicName),'My tutorial mosaic',[['divisibility','2'],['initialSupply','1000'],['supplyMutable','true'],['transferable','true']].map(([name,value])=>newdescriptors.SizePrefixedMosaicPropertyDescriptor(newdescriptors.MosaicPropertyDescriptor(name,value))));// Build the mosaic definition transactionconstrentalFee=calculateMosaicRentalFee();console.log(' Mosaic creation fee:',`${Number(rentalFee)/1_000_000} XEM`);consttransaction=facade.createTransactionFromTypedDescriptor(newdescriptors.MosaicDefinitionTransactionV1Descriptor(mosaicDefinition,newAddress('TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC'),newmodels.Amount(rentalFee)),signerKeyPair.publicKey,0n,2*60*60);// Calculate and attach the transaction feeconstfee=calculateTransactionFee(transaction);transaction.fee=newmodels.Amount(fee);console.log(' Transaction fee:',`${Number(fee)/1_000_000} XEM`);// Sign and generate final payloadconstsignature=facade.signTransaction(signerKeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);console.log('Built mosaic definition transaction:');console.dir(transaction.toJson(),{colors:true});// Announce the transactionconstannounceResult=awaitannounceTransaction(jsonPayload,'mosaic definition');// Wait for confirmationif('SUCCESS'===announceResult){consttransactionHash=facade.hashTransaction(transaction).toString();console.log('Transaction hash:',transactionHash);awaitwaitForConfirmation(transactionHash,'mosaic definition');}else{console.log('Transaction rejected:',announceResult);}// Retrieve the mosaicconstdefinitionPath=`/mosaic/definition?mosaicId=${mosaicId}`;console.log('Fetching mosaic information from',definitionPath);constdefinitionResponse=awaitfetch(`${NODE_URL}${definitionPath}`);constmosaicInfo=awaitdefinitionResponse.json();constproperties=Object.fromEntries(mosaicInfo.properties.map(prop=>[prop.name,prop.value]));console.log('Mosaic information:');console.log(' Creator:',mosaicInfo.creator);console.log(' Divisibility:',properties.divisibility);console.log(' Initial supply:',properties.initialSupply);console.log(' Supply mutable:',properties.supplyMutable);console.log(' Transferable:',properties.transferable);}catch(e){console.error(e.message,'| Cause:',e.cause?.code??'unknown');}
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's address is derived from the public key.
This account will own the created mosaic and must also own the namespace that will hold it.
// Build the mosaic IDfinalStringnamespaceName=System.getenv().getOrDefault("NAMESPACE","my_namespace");finalStringmosaicName=System.getenv().getOrDefault("MOSAIC","token_"+System.currentTimeMillis()/1000);finalStringmosaicId=namespaceName+":"+mosaicName;System.out.printf("Creating mosaic: %s%n",mosaicId);
The mosaic ID is assembled from an existing namespace and a mosaic name.
See Name in the Textbook for the naming rules.
To avoid collisions across multiple runs of the tutorial, a timestamp is added to the mosaic name.
In practice, however, programs would use a fixed name for their mosaics.
You can force the tutorial to use fixed names through the NAMESPACE and MOSAIC environment variables.
Use a namespace owned by the signer
By default, the code uses the test account referenced by SIGNER_PRIVATE_KEY and a namespace named
my_namespace.
If you come from the Registering a Root Namespace tutorial, set the
SIGNER_PRIVATE_KEY and NAMESPACE environment variables to match the account and namespace you created there,
or any other namespace that the signer owns.
: A set of key-value pairs that configure the mosaic behavior:
: The number of decimal places the mosaic supports.
For example, a value of 2 means each whole unit can be divided into 100 (102) atomic units.
See Divisibility in the Textbook.
: The number of whole units minted to the creator when the mosaic is
defined.
See Initial Supply in the Textbook.
: Whether the total supply can be changed after creation.
See Supply Mutability in the Textbook.
: Whether the mosaic can be sent between any two accounts other than the
creator.
See Transferability in the Textbook.
In this example, the mosaic is divisible to two decimal places and starts with a supply of 1000.00 whole
units.
Its supply can be changed after creation, and its units can be freely transferred between accounts.
The network rejects transactions that send the creation fee to any other address.
: The creation fee, which is 10 XEM.
The SDK's helper returns the required amount.
The network rejects transactions that pay less than this fee.
Larger amounts are accepted, but the entire amount is transferred to the sink account.
: The mosaic definition built in the previous step.
The facade adds the signer, which must own the namespace and becomes the mosaic owner, and derives the timestamp and
deadline from the two-hour deadline duration.
// Calculate and attach the transaction feeconstfee=calculateTransactionFee(transaction);transaction.fee=newmodels.Amount(fee);console.log(' Transaction fee:',`${Number(fee)/1_000_000} XEM`);
// Calculate and attach the transaction feefinallongfee=FeeCalculator.calculateTransactionFee(transaction);transaction.setFee(newAmount(fee));System.out.printf(" Transaction fee: %s XEM%n",(double)fee/1_000_000);
Finally, the transaction fee is calculated with and attached to the
transaction.
Unlike the creation fee, the transaction fee is paid to the harvester account.
Mosaic definition transactions pay a fixed transaction fee of 0.15 XEM, as shown in the
fee schedule.
# Sign and generate final payloadsignature=facade.sign_transaction(signer_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)print('Built mosaic definition transaction:')print(json.dumps(transaction.to_json(),indent=2))# Announce the transactionannounce_result=announce_transaction(json_payload,'mosaic definition')
// Sign and generate final payloadconstsignature=facade.signTransaction(signerKeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);console.log('Built mosaic definition transaction:');console.dir(transaction.toJson(),{colors:true});// Announce the transactionconstannounceResult=awaitannounceTransaction(jsonPayload,'mosaic definition');
// Sign and generate final payloadfinalCryptoTypes.Signaturesignature=facade.signTransaction(signerKeyPair,transaction);finalStringpayload=NemTransactionFactory.attachSignature(transaction,signature);System.out.println("Built mosaic definition transaction:");System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(transaction.toJson()));// Announce the transactionfinalStringresult=announceTransaction(payload,"mosaic definition");
The mosaic definition transaction is signed and announced following the same process as in the
Transfer XEM tutorial.
// Retrieve the mosaicfinalStringpath="/mosaic/definition?mosaicId="+mosaicId;System.out.printf("Fetching mosaic information from %s%n",path);finalHttpRequestrequest=HttpRequest.newBuilder(URI.create(nodeUrl+path)).GET().build();finalHttpResponse<String>response=HTTP_CLIENT.send(request,BodyHandlers.ofString());finalJsonNodeinfo=JSON_MAPPER.readTree(response.body());System.out.println("Mosaic information:");System.out.printf(" Creator: %s%n",info.get("creator").asText());for(finalJsonNodeitem:info.get("properties"))System.out.printf(" %s: %s%n",item.get("name").asText(),item.get("value").asText());
To verify the mosaic was created successfully, the code retrieves its definition from the /mosaic/definitionGET
endpoint and displays its properties.
A successful response confirms the mosaic exists on the network with the expected properties.
Mosaic lifetime
A mosaic has no duration of its own and becomes inactive when its parent namespace expires.
Extending the root namespace keeps its mosaics usable.
See Lifetime in the Textbook.
Mosaic ID (line 3): The mosaic is identified by its fully qualified name, combining the namespace
my_namespace and a timestamped mosaic name.
Search for this name in the NEM testnet explorer to view the mosaic details.
Creation fee and transaction fee (lines 4-5): The creation fee is 10 XEM, while the transaction fee is
0.15 XEM.
Verified properties (lines 66-69): The mosaic is retrieved from the network, confirming the expected
divisibility, the initial supply of 1000, and that the mosaic is both supply mutable and transferable.