Each block on NEM is produced by a single harvester account.
The entire reward for harvesting a block comes from the transaction fees collected in that block, and these fees are
paid in full to the harvester that produced it.
This tutorial shows how to query any block, identify its harvester, and sum the transaction fees that form the reward.
importjsonimportosimporturllib.requestfromsymbolchain.CryptoTypesimportPublicKeyfromsymbolchain.facade.NemFacadeimportNemFacadeNODE_URL=os.getenv('NODE_URL','http://libertalia.nemtest.net:7890')print(f'Using node {NODE_URL}')BLOCK_HEIGHT=os.getenv('BLOCK_HEIGHT','661258')facade=NemFacade('testnet')try:# Fetch the block at the given heightblock_url=f'{NODE_URL}/block/at/public'request=urllib.request.Request(block_url,data=json.dumps({'height':int(BLOCK_HEIGHT)}).encode(),headers={'Content-Type':'application/json'})withurllib.request.urlopen(request)asresponse:block=json.loads(response.read())transactions=block['transactions']print(f'Block height: {BLOCK_HEIGHT}')print(f'Transactions: {len(transactions)}')# Identify the harvesterharvester=facade.network.public_key_to_address(PublicKey(block['signer']))print(f'Harvester: {harvester}')# Sum the transaction feestotal_reward=0print('\nTransaction fees:')fortransactionintransactions:fee=int(transaction['fee'])total_reward+=feeprint(f' Fee: {fee/1e6:,.6f} XEM')# Total rewardprint(f'\nTotal block reward: {total_reward/1e6:,.6f} XEM')exceptExceptionaserror:print(error)
import{PublicKey}from'symbol-sdk';import{NemFacade}from'symbol-sdk/nem';constfmt=v=>(Number(v)/1e6).toLocaleString('en-US',{minimumFractionDigits:6});constNODE_URL=process.env.NODE_URL||'http://libertalia.nemtest.net:7890';console.log(`Using node ${NODE_URL}`);constBLOCK_HEIGHT=process.env.BLOCK_HEIGHT||'661258';constfacade=newNemFacade('testnet');try{// Fetch the block at the given heightconstblockUrl=`${NODE_URL}/block/at/public`;constresponse=awaitfetch(blockUrl,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({height:parseInt(BLOCK_HEIGHT,10)})});if(!response.ok)thrownewError(`HTTP error! status: ${response.status}`);constblock=awaitresponse.json();consttransactions=block.transactions;console.log(`Block height: ${BLOCK_HEIGHT}`);console.log(`Transactions: ${transactions.length}`);// Identify the harvesterconstharvester=facade.network.publicKeyToAddress(newPublicKey(block.signer));console.log(`Harvester: ${harvester}`);// Sum the transaction feeslettotalReward=0n;console.log('\nTransaction fees:');for(consttransactionoftransactions){constfee=BigInt(transaction.fee);totalReward+=fee;console.log(` Fee: ${fmt(fee)} XEM`);}// Total rewardconsole.log(`\nTotal block reward: ${fmt(totalReward)} XEM`);}catch(error){console.log(error);}
//JAVA 21+//DEPS org.symbol:symbol-sdk:3.3.3importjava.io.IOException;importjava.math.BigInteger;importjava.net.URI;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.net.http.HttpResponse.BodyHandlers;importjava.util.Locale;importcom.fasterxml.jackson.databind.JsonNode;importcom.fasterxml.jackson.databind.ObjectMapper;importcom.fasterxml.jackson.databind.node.ObjectNode;importorg.symbol.sdk.CryptoTypes;importorg.symbol.sdk.facade.NemFacade;importorg.symbol.sdk.nem.Address;publicfinalclassQueryBlockRewards{privatestaticfinalHttpClientHTTP_CLIENT=HttpClient.newHttpClient();privatestaticfinalObjectMapperJSON_MAPPER=newObjectMapper();privatestaticfinalStringNODE_URL=System.getenv().getOrDefault("NODE_URL","http://libertalia.nemtest.net:7890");privatestaticfinalStringBLOCK_HEIGHT=System.getenv().getOrDefault("BLOCK_HEIGHT","661258");// Format an atomic amount as whole XEM with integer math,// since amounts can exceed float precision.privatestaticStringfmt(finalBigIntegervalue){finalBigIntegerdivisor=BigInteger.valueOf(1_000_000);finalBigInteger[]parts=value.divideAndRemainder(divisor);returnString.format(Locale.US,"%,d.%06d",parts[0],parts[1]);}publicstaticvoidmain(finalString[]args){try{newQueryBlockRewards().run();}catch(finalExceptionex){System.out.println(null==ex.getMessage()?ex.toString():ex.getMessage());}}privatevoidrun()throwsIOException,InterruptedException{System.out.printf("Using node %s%n",NODE_URL);finalNemFacadefacade=newNemFacade("testnet");// Fetch the block at the given heightfinalObjectNoderequestBody=JSON_MAPPER.createObjectNode();requestBody.put("height",Long.parseLong(BLOCK_HEIGHT));finalHttpRequestrequest=HttpRequest.newBuilder(URI.create(NODE_URL+"/block/at/public")).header("Content-Type","application/json").POST(HttpRequest.BodyPublishers.ofString(requestBody.toString())).build();finalHttpResponse<String>response=HTTP_CLIENT.send(request,BodyHandlers.ofString());if(200!=response.statusCode())thrownewIOException("HTTP error! status: "+response.statusCode());finalJsonNodeblock=JSON_MAPPER.readTree(response.body());finalJsonNodetransactions=block.get("transactions");System.out.printf("Block height: %s%n",BLOCK_HEIGHT);System.out.printf("Transactions: %d%n",transactions.size());// Identify the harvesterfinalAddressharvester=facade.network.publicKeyToAddress(newCryptoTypes.PublicKey(block.get("signer").asText()));System.out.printf("Harvester: %s%n",harvester);// Sum the transaction feesBigIntegertotalReward=BigInteger.ZERO;System.out.println("\nTransaction fees:");for(finalJsonNodetransaction:transactions){finalBigIntegerfee=newBigInteger(transaction.get("fee").asText());totalReward=totalReward.add(fee);System.out.printf(" Fee: %s XEM%n",fmt(fee));}// Total rewardSystem.out.printf("\nTotal block reward: %s XEM%n",fmt(totalReward));}}
The code fetches a block by height and derives the harvester address from the block's signer public key.
It then sums the fees of every transaction in the block to obtain the total reward.
# Fetch the block at the given heightblock_url=f'{NODE_URL}/block/at/public'request=urllib.request.Request(block_url,data=json.dumps({'height':int(BLOCK_HEIGHT)}).encode(),headers={'Content-Type':'application/json'})withurllib.request.urlopen(request)asresponse:block=json.loads(response.read())transactions=block['transactions']print(f'Block height: {BLOCK_HEIGHT}')print(f'Transactions: {len(transactions)}')
// Fetch the block at the given heightconstblockUrl=`${NODE_URL}/block/at/public`;constresponse=awaitfetch(blockUrl,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({height:parseInt(BLOCK_HEIGHT,10)})});if(!response.ok)thrownewError(`HTTP error! status: ${response.status}`);constblock=awaitresponse.json();consttransactions=block.transactions;console.log(`Block height: ${BLOCK_HEIGHT}`);console.log(`Transactions: ${transactions.length}`);
// Fetch the block at the given heightfinalObjectNoderequestBody=JSON_MAPPER.createObjectNode();requestBody.put("height",Long.parseLong(BLOCK_HEIGHT));finalHttpRequestrequest=HttpRequest.newBuilder(URI.create(NODE_URL+"/block/at/public")).header("Content-Type","application/json").POST(HttpRequest.BodyPublishers.ofString(requestBody.toString())).build();finalHttpResponse<String>response=HTTP_CLIENT.send(request,BodyHandlers.ofString());if(200!=response.statusCode())thrownewIOException("HTTP error! status: "+response.statusCode());finalJsonNodeblock=JSON_MAPPER.readTree(response.body());finalJsonNodetransactions=block.get("transactions");System.out.printf("Block height: %s%n",BLOCK_HEIGHT);System.out.printf("Transactions: %d%n",transactions.size());
The /block/at/publicPOST endpoint returns information about the block at the requested height,
including the list of transactions present in the block.
// Identify the harvesterfinalAddressharvester=facade.network.publicKeyToAddress(newCryptoTypes.PublicKey(block.get("signer").asText()));System.out.printf("Harvester: %s%n",harvester);
The signer field holds the public key of the account that harvested the block.
The method converts this public key into the corresponding testnet address.
The signer is not always the account that earns the reward
# Sum the transaction feestotal_reward=0print('\nTransaction fees:')fortransactionintransactions:fee=int(transaction['fee'])total_reward+=feeprint(f' Fee: {fee/1e6:,.6f} XEM')
// Sum the transaction feeslettotalReward=0n;console.log('\nTransaction fees:');for(consttransactionoftransactions){constfee=BigInt(transaction.fee);totalReward+=fee;console.log(` Fee: ${fmt(fee)} XEM`);}
// Sum the transaction feesBigIntegertotalReward=BigInteger.ZERO;System.out.println("\nTransaction fees:");for(finalJsonNodetransaction:transactions){finalBigIntegerfee=newBigInteger(transaction.get("fee").asText());totalReward=totalReward.add(fee);System.out.printf(" Fee: %s XEM%n",fmt(fee));}
Each transaction in the block has a fee field expressed in atomic units.
XEM has a divisibility of 6, so 350000 atomic units represent 0.350000 XEM.
Adding the fees of every transaction gives the total reward for the block.
// Total rewardSystem.out.printf("\nTotal block reward: %s XEM%n",fmt(totalReward));
The total block reward equals the sum of all transaction fees, paid in full to the harvester.
An empty block has no fees, and therefore no reward.
Alternative: Query rewards by account
This tutorial calculates the reward for a specific block by summing the transaction fees it contains.
If you are interested in the rewards earned by a particular account instead, use the
/account/harvestsGET endpoint.
It returns one entry per harvested block, including a totalFee field with the reward earned for that block.