Most tutorials create transactions from descriptors through the facade.
This is the recommended approach: typed descriptors provide compile-time or type-checking support and better editor
assistance in languages that support them, and the facade fills the signer, timestamp, and deadline from a duration.
For completeness, this tutorial shows the lower-level alternative: creating a transaction manually with the
transaction factory.
The example mirrors the Transfer XEM tutorial, but replaces descriptor-based creation with manual
field assignment. It explicitly fetches network time, assigns the timestamp and absolute deadline, and calculates the
NEM transaction fee.
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 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))RECIPIENT_ADDRESS=os.getenv('RECIPIENT_ADDRESS','TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4')facade=NemFacade('testnet')# Define the amount of XEM to transferxem=float(os.getenv('XEM_AMOUNT','1'))amount=round(xem*1_000_000)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 transactiontransaction=facade.transaction_factory.create({'type':'transfer_transaction_v2','signer_public_key':signer_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'recipient_address':RECIPIENT_ADDRESS,'amount':amount})# 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_result=announce_transaction(json_payload,'transaction')# Wait for confirmationif'SUCCESS'==announce_result:transaction_hash=facade.hash_transaction(transaction)print(f'Transaction hash: {transaction_hash}')wait_for_confirmation(transaction_hash,'transaction')else:print(f'Transaction rejected: {announce_result}')exceptExceptionaserror:print(error)
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}`);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));constRECIPIENT_ADDRESS=process.env.RECIPIENT_ADDRESS||'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4';constfacade=newNemFacade('testnet');// Define the amount of XEM to transferconstxem=parseFloat(process.env.XEM_AMOUNT||'1');constamount=BigInt(Math.round(xem*1_000_000));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 transactionconsttransaction=facade.transactionFactory.create({type:'transfer_transaction_v2',signerPublicKey:signerKeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,recipientAddress:RECIPIENT_ADDRESS,amount});// 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 transactionconstannounceResult=awaitannounceTransaction(jsonPayload,'transaction');// Wait for confirmationif('SUCCESS'===announceResult){consttransactionHash=facade.hashTransaction(transaction).toString();console.log('Transaction hash:',transactionHash);awaitwaitForConfirmation(transactionHash,'transaction');}else{console.log('Transaction rejected:',announceResult);}}catch(e){console.error(e.message,'| Cause:',e.cause?.code??'unknown');}
//JAVA 21+//DEPS org.symbol:symbol-sdk:3.3.3importjava.io.IOException;importjava.net.URI;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.net.http.HttpResponse.BodyHandlers;importjava.util.Map;importcom.fasterxml.jackson.databind.JsonNode;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.symbol.sdk.CryptoTypes;importorg.symbol.sdk.facade.NemFacade;importorg.symbol.sdk.nem.FeeCalculator;importorg.symbol.sdk.nem.KeyPair;importorg.symbol.sdk.nem.NemTransactionFactory;importorg.symbol.sdk.nem.NetworkTimestamp;importorg.symbol.sdk.nem.models.Amount;importorg.symbol.sdk.nem.models.Transaction;publicfinalclassManualTransactionCreation{privatestaticfinalObjectMapperJSON_MAPPER=newObjectMapper();privatestaticfinalHttpClientHTTP_CLIENT=HttpClient.newHttpClient();privatefinalStringnodeUrl=System.getenv().getOrDefault("NODE_URL","http://libertalia.nemtest.net:7890");privatefinalNemFacadefacade=newNemFacade("testnet");// Helper method to announce a transactionprivateStringannounceTransaction(finalStringpayload,finalStringlabel)throwsIOException,InterruptedException{finalStringannouncePath="/transaction/announce";System.out.printf("Announcing %s to %s%n",label,announcePath);finalHttpRequestrequest=HttpRequest.newBuilder(URI.create(nodeUrl+announcePath)).header("Content-Type","application/json").POST(HttpRequest.BodyPublishers.ofString(payload)).build();finalHttpResponse<String>response=HTTP_CLIENT.send(request,BodyHandlers.ofString());if(2!=response.statusCode()/100)thrownewIOException("HTTP "+response.statusCode());finalStringresult=JSON_MAPPER.readTree(response.body()).get("message").asText();System.out.printf(" Result: %s%n",result);returnresult;}// Helper method to wait for transaction confirmationprivatevoidwaitForConfirmation(finalStringtransactionHash,finalStringlabel)throwsIOException,InterruptedException{finalStringstatusPath=String.format("/transaction/get?hash=%s",transactionHash);System.out.printf("Waiting for %s confirmation from %s%n",label,statusPath);booleanisConfirmed=false;for(intattempt=1;120>=attempt;++attempt){finalHttpRequestrequest=HttpRequest.newBuilder(URI.create(nodeUrl+statusPath)).GET().build();finalHttpResponse<String>response=HTTP_CLIENT.send(request,BodyHandlers.ofString());if(2!=response.statusCode()/100){System.out.println(" Transaction status: pending");Thread.sleep(1000);}else{finalJsonNodeconfirmed=JSON_MAPPER.readTree(response.body());System.out.printf("%s confirmed in block %s%n",label,confirmed.get("meta").get("height").asText());isConfirmed=true;break;}}if(!isConfirmed)System.out.printf("%s confirmation took too long.%n",label);}publicstaticvoidmain(finalString[]args){try{newManualTransactionCreation().run();}catch(finalExceptionex){System.out.println(null==ex.getMessage()?ex.toString():ex.getMessage());}}privatevoidrun()throwsIOException,InterruptedException{System.out.printf("Using node %s%n",nodeUrl);finalStringsignerPrivateKey=System.getenv().getOrDefault("SIGNER_PRIVATE_KEY","0".repeat(64));finalKeyPairsignerKeyPair=newKeyPair(newCryptoTypes.PrivateKey(signerPrivateKey));finalStringrecipientAddress=System.getenv().getOrDefault("RECIPIENT_ADDRESS","TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4");// Define the amount of XEM to transferfinaldoublexem=Double.parseDouble(System.getenv().getOrDefault("XEM_AMOUNT","1"));finallongamount=Math.round(xem*1_000_000);// Fetch current network timefinalStringtimePath="/time-sync/network-time";System.out.printf("Fetching current network time from %s%n",timePath);finalHttpRequesttimeRequest=HttpRequest.newBuilder(URI.create(nodeUrl+timePath)).GET().build();finalHttpResponse<String>timeResponse=HTTP_CLIENT.send(timeRequest,BodyHandlers.ofString());finalJsonNodetimeJson=JSON_MAPPER.readTree(timeResponse.body());finallongnetworkTime=timeJson.get("receiveTimeStamp").asLong()/1000;System.out.printf(" Network time: %d s since the nemesis block%n",networkTime);finalNetworkTimestamptimestamp=newNetworkTimestamp(networkTime);finallongdeadline=timestamp.addHours(2).timestamp;// Build the transactionfinalTransactiontransaction=facade.transactionFactory.create(Map.of("type","transfer_transaction_v2","signerPublicKey",signerKeyPair.getPublicKey().toString(),"timestamp",timestamp.timestamp,"deadline",deadline,"recipientAddress",recipientAddress,"amount",amount));// 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);// Sign transaction and generate final payloadfinalCryptoTypes.Signaturesignature=facade.signTransaction(signerKeyPair,transaction);finalStringjsonPayload=NemTransactionFactory.attachSignature(transaction,signature);System.out.println("Built transaction:");System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(transaction.toJson()));// Announce the transactionfinalStringannounceResult=announceTransaction(jsonPayload,"transaction");// Wait for confirmationif("SUCCESS".equals(announceResult)){finalStringtransactionHash=facade.hashTransaction(transaction).toString();System.out.printf("Transaction hash: %s%n",transactionHash);waitForConfirmation(transactionHash,"transaction");}else{System.out.printf("Transaction rejected: %s%n",announceResult);}}}
// Define the amount of XEM to transferfinaldoublexem=Double.parseDouble(System.getenv().getOrDefault("XEM_AMOUNT","1"));finallongamount=Math.round(xem*1_000_000);
The amount is expressed in atomic units. XEM has a divisibility of 6, so the default transfer of 1 XEM is
1_000_000 atomic units.
# 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);
// Fetch current network timefinalStringtimePath="/time-sync/network-time";System.out.printf("Fetching current network time from %s%n",timePath);finalHttpRequesttimeRequest=HttpRequest.newBuilder(URI.create(nodeUrl+timePath)).GET().build();finalHttpResponse<String>timeResponse=HTTP_CLIENT.send(timeRequest,BodyHandlers.ofString());finalJsonNodetimeJson=JSON_MAPPER.readTree(timeResponse.body());finallongnetworkTime=timeJson.get("receiveTimeStamp").asLong()/1000;System.out.printf(" Network time: %d s since the nemesis block%n",networkTime);finalNetworkTimestamptimestamp=newNetworkTimestamp(networkTime);finallongdeadline=timestamp.addHours(2).timestamp;
Manual transaction creation requires an explicit timestamp and an absolute deadline, both expressed as the number
of seconds since the NEM nemesis block.
The snippet fetches the current network time from /time-sync/network-timeGET.
The endpoint returns milliseconds, so the value is divided by 1000 before it is wrapped in .
The deadline is set two hours after the timestamp.
Descriptor-based facade creation instead accepts a deadline duration in seconds and derives both values using the local
clock, so it does not require this request.
# Build the transactiontransaction=facade.transaction_factory.create({'type':'transfer_transaction_v2','signer_public_key':signer_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'recipient_address':RECIPIENT_ADDRESS,'amount':amount})
// Build the transactionconsttransaction=facade.transactionFactory.create({type:'transfer_transaction_v2',signerPublicKey:signerKeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,recipientAddress:RECIPIENT_ADDRESS,amount});
// Build the transactionfinalTransactiontransaction=facade.transactionFactory.create(Map.of("type","transfer_transaction_v2","signerPublicKey",signerKeyPair.getPublicKey().toString(),"timestamp",timestamp.timestamp,"deadline",deadline,"recipientAddress",recipientAddress,"amount",amount));
The transaction is created with , which accepts a plain descriptor dictionary or object.
Unlike the facade creation method, this lower-level factory does not fill common transaction fields.
// 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);
The low-level factory does not calculate NEM's fixed-schedule fee.
The snippet calls after construction and assigns the result to
transaction.fee before signing.
# 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))
// 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});
// Sign transaction and generate final payloadfinalCryptoTypes.Signaturesignature=facade.signTransaction(signerKeyPair,transaction);finalStringjsonPayload=NemTransactionFactory.attachSignature(transaction,signature);System.out.println("Built transaction:");System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(transaction.toJson()));
The transaction is signed with .
The signature is attached with , producing a JSON payload ready for announcement.
Using node http://libertalia.nemtest.net:7890
Fetching current network time from /time-sync/network-time
Network time: 351947283 s since the nemesis block
Transaction fee: 0.05 XEM
Built transaction:
{
type: 257,
version: 2,
network: 152,
timestamp: 351947283,
signerPublicKey: '462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE',
signature: '17FF7D37A63F3CFC43C790300A7B7F1C8A2A2B1C5D64546007C7D02C771516EE8872A6BBAE414486DC2D4750BBAACB41B3140D45CC319F51B7CBC1D524545C06',
fee: '50000',
deadline: 351954483,
recipientAddress: '5442554C4541554732435A51495355523434324857413655414B47574958484441424A5649505334',
amount: '1000000',
mosaics: []
}
Announcing transaction to /transaction/announce
Result: SUCCESS
Transaction hash: 436A43BDD3CE1F0A5A5EFC40A9027D3732D59AB3C507818A1B436EC7B32BF099
Waiting for transaction confirmation from /transaction/get?hash=436A43BDD3CE1F0A5A5EFC40A9027D3732D59AB3C507818A1B436EC7B32BF099
Transaction status: pending
Transaction status: pending
transaction confirmed in block 626588
The first two highlighted lines show the explicit network-time request.
The output also shows the calculated fee, the manually assigned signer and absolute deadline, the announcement result,
and the transaction hash used for confirmation.