importjsonimportosimporttimeimporturllib.requestfromsymbolchain.CryptoTypesimportPrivateKeyfromsymbolchain.facade.NemFacadeimportNemFacadefromsymbolchain.ncimportAmountfromsymbolchain.nem.FeeCalculatorimport(calculate_namespace_rental_fee,calculate_transaction_fee)fromsymbolchain.nem.NetworkimportNetworkTimestampNODE_URL=os.getenv('NODE_URL','http://libertalia.nemtest.net:7890')print(f'Using node {NODE_URL}')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:# 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)# Build the namespace namenamespace_name=os.getenv('ROOT_NAMESPACE',f'ns_{int(time.time())}')print(f'Creating root namespace: {namespace_name}')# Build the transactionrental_fee=calculate_namespace_rental_fee(True)print(f' Namespace lease fee: {rental_fee/1_000_000} XEM')transaction=facade.transaction_factory.create({'type':'namespace_registration_transaction_v1','signer_public_key':signer_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'rental_fee_sink':'TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35','rental_fee':rental_fee,'name':namespace_name})# Calculate and attach the transaction feefee=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))# Announce the transactionannounce_path='/transaction/announce'print(f'Announcing namespace registration to {announce_path}')announce_request=urllib.request.Request(f'{NODE_URL}{announce_path}',data=json_payload.encode(),headers={'Content-Type':'application/json'},method='POST')withurllib.request.urlopen(announce_request)asresponse:announce_result=json.loads(response.read().decode())print(f' Result: {announce_result['message']}')# Wait for confirmationif'SUCCESS'==announce_result['message']:status_path=(f'/transaction/get?hash={facade.hash_transaction(transaction)}')print(f'Waiting for confirmation from {status_path}')is_confirmed=Falseforattemptinrange(120):try:withurllib.request.urlopen(f'{NODE_URL}{status_path}')asresponse:confirmed=json.loads(response.read().decode())height=confirmed['meta']['height']print(f'Transaction confirmed in block {height}')is_confirmed=Truebreakexcepturllib.error.HTTPError:print(' Transaction status: pending')time.sleep(1)ifnotis_confirmed:print('Confirmation took too long.')else:print(f'Transaction rejected: {announce_result['message']}')# Retrieve the namespacenamespace_path=f'/namespace?namespace={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"]}')excepturllib.error.URLErrorase:print(e.reason)
import{PrivateKey}from'symbol-sdk';import{NemFacade,NetworkTimestamp,calculateNamespaceRentalFee,calculateTransactionFee,models}from'symbol-sdk/nem';constNODE_URL=process.env.NODE_URL||'http://libertalia.nemtest.net:7890';console.log('Using node',NODE_URL);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{// 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);// Build the namespace nameconstnamespaceName=process.env.ROOT_NAMESPACE||`ns_${Math.floor(Date.now()/1000)}`;console.log('Creating root namespace:',namespaceName);// Build the transactionconstrentalFee=calculateNamespaceRentalFee(true);console.log(' Namespace lease fee:',`${Number(rentalFee)/1_000_000} XEM`);consttransaction=facade.transactionFactory.create({type:'namespace_registration_transaction_v1',signerPublicKey:signerKeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,rentalFeeSink:'TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35',rentalFee,name:namespaceName});// Calculate and attach the transaction feeconstfee=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});// Announce the transactionconstannouncePath='/transaction/announce';console.log('Announcing namespace registration to',announcePath);constannounceResponse=awaitfetch(`${NODE_URL}${announcePath}`,{method:'POST',headers:{'Content-Type':'application/json'},body:jsonPayload});constannounceResult=awaitannounceResponse.json();console.log(' Result:',announceResult.message);// Wait for confirmationif('SUCCESS'===announceResult.message){consttransactionHash=facade.hashTransaction(transaction).toString();conststatusPath=`/transaction/get?hash=${transactionHash}`;console.log('Waiting for confirmation from',statusPath);letisConfirmed=false;for(letattempt=1;120>=attempt;++attempt){constresponse=awaitfetch(`${NODE_URL}${statusPath}`);if(response.ok){constconfirmed=awaitresponse.json();console.log('Transaction confirmed in block',confirmed.meta.height);isConfirmed=true;break;}console.log(' Transaction status: pending');awaitnewPromise(resolve=>{setTimeout(resolve,1000);});}if(!isConfirmed)console.warn('Confirmation took too long.');}else{console.log('Transaction rejected:',announceResult.message);}// Retrieve the namespaceconstnamespacePath=`/namespace?namespace=${namespaceName}`;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 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 registered namespace.
# 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.
// Build the namespace nameconstnamespaceName=process.env.ROOT_NAMESPACE||`ns_${Math.floor(Date.now()/1000)}`;console.log('Creating root namespace:',namespaceName);
A namespace is identified by its name, which the transaction reserves on the network for one year.
See Name in the Textbook for the naming rules.
To avoid collisions across multiple runs of the tutorial, a timestamp is added to the name.
In practice, however, programs would use a fixed name for their namespaces.
You can force the tutorial to use a fixed name through the ROOT_NAMESPACE environment variable.
The network rejects transactions that send the lease fee to any other address.
: The lease fee, which is 100 XEM for root namespaces.
The SDK's helper returns the required amount.
The argument requests the fee for a root namespace.
The network rejects transactions that pay less than this fee.
Larger amounts are accepted, but the entire amount is transferred to the sink account.
// Calculate and attach the transaction feeconstfee=calculateTransactionFee(transaction);transaction.fee=newmodels.Amount(fee);console.log(` Transaction fee: ${Number(fee)/1_000_000} XEM`);
Finally, the transaction fee is calculated with and attached to the
transaction.
Unlike the lease fee, the transaction fee is paid to the harvester account.
Namespace registration transactions pay a fixed transaction fee of 0.15 XEM, as shown in the
fee schedule.
# 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))# Announce the transactionannounce_path='/transaction/announce'print(f'Announcing namespace registration to {announce_path}')announce_request=urllib.request.Request(f'{NODE_URL}{announce_path}',data=json_payload.encode(),headers={'Content-Type':'application/json'},method='POST')withurllib.request.urlopen(announce_request)asresponse:announce_result=json.loads(response.read().decode())print(f' Result: {announce_result['message']}')
// 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});// Announce the transactionconstannouncePath='/transaction/announce';console.log('Announcing namespace registration to',announcePath);constannounceResponse=awaitfetch(`${NODE_URL}${announcePath}`,{method:'POST',headers:{'Content-Type':'application/json'},body:jsonPayload});constannounceResult=awaitannounceResponse.json();console.log(' Result:',announceResult.message);
The transaction is signed and announced following the same process as in the
Transfer XEM tutorial.
# Wait for confirmationif'SUCCESS'==announce_result['message']:status_path=(f'/transaction/get?hash={facade.hash_transaction(transaction)}')print(f'Waiting for confirmation from {status_path}')is_confirmed=Falseforattemptinrange(120):try:withurllib.request.urlopen(f'{NODE_URL}{status_path}')asresponse:confirmed=json.loads(response.read().decode())height=confirmed['meta']['height']print(f'Transaction confirmed in block {height}')is_confirmed=Truebreakexcepturllib.error.HTTPError:print(' Transaction status: pending')time.sleep(1)ifnotis_confirmed:print('Confirmation took too long.')else:print(f'Transaction rejected: {announce_result['message']}')
// Wait for confirmationif('SUCCESS'===announceResult.message){consttransactionHash=facade.hashTransaction(transaction).toString();conststatusPath=`/transaction/get?hash=${transactionHash}`;console.log('Waiting for confirmation from',statusPath);letisConfirmed=false;for(letattempt=1;120>=attempt;++attempt){constresponse=awaitfetch(`${NODE_URL}${statusPath}`);if(response.ok){constconfirmed=awaitresponse.json();console.log('Transaction confirmed in block',confirmed.meta.height);isConfirmed=true;break;}console.log(' Transaction status: pending');awaitnewPromise(resolve=>{setTimeout(resolve,1000);});}if(!isConfirmed)console.warn('Confirmation took too long.');}else{console.log('Transaction rejected:',announceResult.message);}
The code then waits for the transaction to be confirmed by polling the /transaction/getGET endpoint until the
transaction is included in a block.
Using node http://libertalia.nemtest.net:7890
Signer address: TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP
Fetching current network time from /time-sync/network-time
Network time: 355503793 s since the nemesis block
Creating root namespace: ns_1783091378
Namespace lease fee: 100.0 XEM
Transaction fee: 0.15 XEM
Built transaction:
{
"type": 8193,
"version": 1,
"network": 152,
"timestamp": 355503793,
"signer_public_key": "462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE",
"signature": "D15220D1888AC85CE205D4D2B1AF3540CA415716DD266A29646FE0DEFAFBED924F0B698C4F6EEAA706AA836FA82516552D54B5BCE91D841D6F1C865633EED50D",
"fee": "150000",
"deadline": 355510993,
"rental_fee_sink": "54414D4553504143455748344D4B464D42435646455244504F4F5034464B374D54444A4559503335",
"rental_fee": "100000000",
"name": "6e735f31373833303931333738"
}
Announcing namespace registration to /transaction/announce
Result: SUCCESS
Waiting for confirmation from /transaction/get?hash=D56EDC5946F1A41304CDDC2BC322793C558DFB57831EF7D45024DE46D6AD827F
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction confirmed in block 685363
Fetching namespace information from /namespace?namespace=ns_1783091378
Namespace information:
Name: ns_1783091378
Owner: TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP
Registration height: 685363
Some highlights from the output:
Namespace name (line 5): The chosen name ns_1783091378 includes a timestamp to ensure uniqueness.
Search for this name in the NEM testnet explorer to view the namespace details.
Lease fee and transaction fee (lines 6-7): The lease fee is 100 XEM because this is a root namespace
(Subnamespaces pay 10 XEM instead), while the transaction fee is 0.15 XEM.
Namespace information (lines 31-33): The registered namespace, its owner (the signer's address), and the
registration height, which is the block at which the lease began.