importjsonimportosimporttimeimporturllib.requestfromsymbolchain.CryptoTypesimportPrivateKeyfromsymbolchain.facade.NemFacadeimportNemFacadefromsymbolchain.ncimportAmountfromsymbolchain.nem.FeeCalculatorimport(calculate_namespace_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:# Choose the subnamespace nameroot_namespace_name=os.getenv('ROOT_NAMESPACE','ns_root')child_namespace_name=os.getenv('SUBNAMESPACE',f'sub_{int(time.time())}')full_namespace_name=(f'{root_namespace_name}.{child_namespace_name}')print(f'Creating subnamespace: {full_namespace_name}')# Build the transactionrental_fee=calculate_namespace_rental_fee(False)print(f' Namespace lease fee: {rental_fee/1_000_000} XEM')transaction=facade.create_transaction_from_descriptor({'type':'namespace_registration_transaction_v1','rental_fee_sink':'TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35','rental_fee':rental_fee,'name':child_namespace_name,'parent_name':root_namespace_name},signer_key_pair.public_key,0,2*60*60)fee=calculate_transaction_fee(transaction)transaction.fee=Amount(fee)print(f' Transaction fee: {fee/1_000_000} XEM')# Sign transaction and generate final payloadsignature=facade.sign_transaction(signer_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)print('Built transaction:')print(json.dumps(transaction.to_json(),indent=2))transaction_hash=facade.hash_transaction(transaction)print(f'Transaction hash: {transaction_hash}')# Announce the transactionannounce_result=announce_transaction(json_payload,'namespace registration')# Wait for confirmationif'SUCCESS'==announce_result:wait_for_confirmation(transaction_hash,'namespace registration')else:print(f'Transaction rejected: {announce_result}')# Retrieve the namespacenamespace_path=f'/namespace?namespace={full_namespace_name}'print(f'Fetching namespace information from {namespace_path}')withurllib.request.urlopen(f'{NODE_URL}{namespace_path}')asresponse:namespace_info=json.loads(response.read().decode())print('Namespace information:')print(f' Name: {namespace_info["fqn"]}')print(f' Owner: {namespace_info["owner"]}')print(f' Registration height: {namespace_info["height"]}')exceptExceptionaserror:print(error)
import{PrivateKey}from'symbol-sdk';import{Address,NemFacade,calculateNamespaceRentalFee,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{// Choose the subnamespace nameconstrootNamespaceName=process.env.ROOT_NAMESPACE||'ns_root';constchildNamespaceName=process.env.SUBNAMESPACE||`sub_${Math.floor(Date.now()/1000)}`;constfullNamespaceName=`${rootNamespaceName}.${childNamespaceName}`;console.log('Creating subnamespace:',fullNamespaceName);// Build the transactionconstrentalFee=calculateNamespaceRentalFee(false);console.log(' Namespace lease fee:',`${Number(rentalFee)/1_000_000} XEM`);consttransaction=facade.createTransactionFromTypedDescriptor(newdescriptors.NamespaceRegistrationTransactionV1Descriptor(newAddress('TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35'),newmodels.Amount(rentalFee),childNamespaceName,rootNamespaceName),signerKeyPair.publicKey,0n,2*60*60);constfee=calculateTransactionFee(transaction);transaction.fee=newmodels.Amount(fee);console.log(` Transaction fee: ${Number(fee)/1_000_000} XEM`);// Sign transaction and generate final payloadconstsignature=facade.signTransaction(signerKeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);console.log('Built transaction:');console.dir(transaction.toJson(),{colors:true});consttransactionHash=facade.hashTransaction(transaction).toString();console.log('Transaction hash:',transactionHash);// Announce the transactionconstannounceResult=awaitannounceTransaction(jsonPayload,'namespace registration');// Wait for confirmationif('SUCCESS'===announceResult)awaitwaitForConfirmation(transactionHash,'namespace registration');elseconsole.log('Transaction rejected:',announceResult);// Retrieve the namespaceconstnamespacePath=`/namespace?namespace=${fullNamespaceName}`;console.log('Fetching namespace information from',namespacePath);constnamespaceResponse=awaitfetch(`${NODE_URL}${namespacePath}`);constnamespaceInfo=awaitnamespaceResponse.json();console.log('Namespace information:');console.log(' Name:',namespaceInfo.fqn);console.log(' Owner:',namespaceInfo.owner);console.log(' Registration height:',namespaceInfo.height);}catch(e){console.error(e.message,'| Cause:',e.cause?.code??'unknown');}
The code follows the same pattern as the Registering a Root Namespace tutorial.
This section focuses only on the key differences.
For detailed explanations of the common steps (setting up the account, creating a transaction, announcing) and the
transaction descriptor fields shared with a root namespace,
see Registering a Root Namespace.
// Choose the subnamespace namefinalStringrootNamespaceName=System.getenv().getOrDefault("ROOT_NAMESPACE","ns_root");finalStringchildNamespaceName=System.getenv().getOrDefault("SUBNAMESPACE","sub_"+System.currentTimeMillis()/1000);finalStringfullNamespaceName=rootNamespaceName+"."+childNamespaceName;System.out.printf("Creating subnamespace: %s%n",fullNamespaceName);
A subnamespace is identified by its full name, which joins the parent namespace name and the child name with a dot,
such as ns_root.product.
See Name in the Textbook for the naming rules.
To avoid collisions across multiple runs of the tutorial, a timestamp is added to the child name.
In practice, however, programs would use a fixed name for their subnamespaces.
You can force the tutorial to use fixed names through the ROOT_NAMESPACE and SUBNAMESPACE environment variables.
Use a parent namespace owned by the signer
By default, the code uses the test account referenced by SIGNER_PRIVATE_KEY and a parent namespace named
ns_root.
If you come from the Registering a Root Namespace tutorial, set the
SIGNER_PRIVATE_KEY and ROOT_NAMESPACE environment variables to match the account and namespace you created
there, or any other namespace that the signer owns.
The main difference when registering a subnamespace is in the transaction descriptor:
: The name of the subnamespace, chosen in the previous step.
Note that this is just the name of the subnamespace, not the full path.
For example, to create company.product, where company is the root, you would set
and .
: The name of the parent namespace, defined in the previous step.
It can be a root namespace or another subnamespace.
: The lease fee, which is 10 XEM for subnamespaces, paid to the same
sink account as root namespaces.
The SDK's helper returns the required amount.
The argument requests the fee for a subnamespace.
The transaction is then signed, announced, and confirmed following the same process as in the
Registering a Root Namespace tutorial.
The facade adds the signer and derives the timestamp and deadline from the two-hour deadline duration.
// Retrieve the namespacefinalStringpath="/namespace?namespace="+fullNamespaceName;System.out.printf("Fetching namespace 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("Namespace information:");System.out.printf(" Name: %s%n",info.get("fqn").asText());System.out.printf(" Owner: %s%n",info.get("owner").asText());System.out.printf(" Registration height: %s%n",info.get("height").asText());
To verify the subnamespace was registered, the code retrieves it from the network using the /namespaceGET endpoint
and displays its properties.
The subnamespace is queried by its full name, which joins the parent and child names with a dot
(for example, ns_root.sub_1783411728).
A successful response confirms the subnamespace is registered and active.
The response also shows the registration height, which is the block in which the root namespace was registered,
because subnamespaces inherit their root namespace's lease.
Subnamespace duration
A subnamespace expires when its root namespace expires and cannot be renewed on its own.
Renewing the root namespace also renews the subnamespace.
Full namespace path (line 3): ns_root.sub_1783411728 combines the parent namespace ns_root with the
subnamespace name set in the transaction.
Lease fee and transaction fee (lines 4-5): The lease fee is 10 XEM because this is a subnamespace
(root namespaces pay 100 XEM instead), while the transaction fee is 0.15 XEM.
Namespace information (lines 31-33): The registered subnamespace, its owner (the signer's address), and the
registration height, which is the block at which the root namespace's lease began, inherited by the subnamespace.