Revoking a Mosaic
BEGINNER
Mosaics created with the revokable flag allow their creator to reclaim units from any account ,
returning them to the creator's own account balance.
This is useful for enforcing contractual terms, reclaiming unused tokens, or correcting erroneous distributions.
This tutorial shows how to revoke mosaic units from another account.
Prerequisites
Before you start, make sure to:
Additionally, review the Transfer transaction tutorial to understand how transactions
are announced and confirmed.
For more details on revocability, see Revocability in the Textbook.
Full Code
The following is the complete code listing for this tutorial.
A detailed, step-by-step explanation follows in the next section.
import json
import os
import time
import urllib.request
from symbolchain.CryptoTypes import PrivateKey
from symbolchain.facade.SymbolFacade import SymbolFacade
NODE_URL = os . getenv ( 'NODE_URL' , 'https://reference.symboltest.net:3001' )
print ( f 'Using node { NODE_URL } ' )
# Helper function to announce a transaction
def announce_transaction ( payload , label ):
print ( f 'Announcing { label } to /transactions' )
request = urllib . request . Request (
f ' { NODE_URL } /transactions' ,
data = payload . encode (),
headers = { 'Content-Type' : 'application/json' },
method = 'PUT'
)
with urllib . request . urlopen ( request ) as announce_response :
print ( f ' Response: { announce_response . read () . decode () } ' )
# Helper function to wait for transaction confirmation
def wait_for_confirmation ( tx_hash , label ):
print ( f 'Waiting for { label } confirmation...' )
for attempt in range ( 60 ):
time . sleep ( 1 )
try :
url = f ' { NODE_URL } /transactionStatus/ { tx_hash } '
with urllib . request . urlopen ( url ) as confirm_response :
status = json . loads ( confirm_response . read () . decode ())
print ( f ' Transaction status: { status [ "group" ] } ' )
if status [ 'group' ] == 'confirmed' :
print ( f ' { label } confirmed in { attempt } seconds' )
return
if status [ 'group' ] == 'failed' :
raise RuntimeError (
f ' { label } failed: { status [ "code" ] } ' )
except urllib . error . HTTPError :
print ( ' Transaction status: unknown' )
raise TimeoutError ( f ' { label } not confirmed after 60 seconds' )
# Helper function to fetch account mosaic balances
def get_account_mosaics ( address ):
account_path = f '/accounts/ { address } '
print ( f 'Fetching account information from { account_path } ' )
with urllib . request . urlopen ( f ' { NODE_URL }{ account_path } ' ) as resp :
resp_json = json . loads ( resp . read () . decode ())
return resp_json [ 'account' ][ 'mosaics' ]
SIGNER_PRIVATE_KEY = os . getenv ( 'SIGNER_PRIVATE_KEY' ,
'0000000000000000000000000000000000000000000000000000000000000000' )
signer_key_pair = SymbolFacade . KeyPair ( PrivateKey ( SIGNER_PRIVATE_KEY ))
facade = SymbolFacade ( 'testnet' )
signer_address = facade . network . public_key_to_address (
signer_key_pair . public_key )
print ( f 'Signer address: { signer_address } ' )
SOURCE_ADDRESS = os . getenv ( 'SOURCE_ADDRESS' ,
'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA' )
print ( f 'Source address: { SOURCE_ADDRESS } ' )
MOSAIC_ID_HEX = os . getenv ( 'MOSAIC_ID' , '7AED3D514C986941' )
mosaic_id = int ( MOSAIC_ID_HEX , 16 )
print ( f 'Mosaic ID: { mosaic_id } (0x { mosaic_id : 016X } )' )
try :
# Fetch recommended fees
fee_path = '/network/fees/transaction'
print ( f 'Fetching recommended fees from { fee_path } ' )
with urllib . request . urlopen ( f ' { NODE_URL }{ fee_path } ' ) as response :
response_json = json . loads ( response . read () . decode ())
median_multiplier = response_json [ 'medianFeeMultiplier' ]
minimum_multiplier = response_json [ 'minFeeMultiplier' ]
fee_multiplier = max ( median_multiplier , minimum_multiplier )
print ( f ' Fee multiplier: { fee_multiplier } ' )
# --- CHECKING INITIAL BALANCE ---
print ( ' \n --- Checking initial balance ---' )
mosaics = get_account_mosaics ( SOURCE_ADDRESS )
for mosaic in mosaics :
if mosaic [ 'id' ] == MOSAIC_ID_HEX . upper ():
print ( f ' Mosaic ID: { mosaic [ "id" ] } ,'
f ' Amount: { mosaic [ "amount" ] } ' )
# --- REVOKING MOSAIC ---
print ( ' \n --- Revoking mosaic ---' )
revoke_tx = facade . create_transaction_from_descriptor (
{
'type' : 'mosaic_supply_revocation_transaction_v1' ,
'source_address' : SOURCE_ADDRESS ,
'mosaic' : {
'mosaic_id' : mosaic_id ,
'amount' : 7_00
}
},
signer_key_pair . public_key ,
fee_multiplier ,
2 * 60 * 60 )
# Sign and generate final payload
signature = facade . sign_transaction (
signer_key_pair , revoke_tx )
json_payload = facade . transaction_factory . attach_signature (
revoke_tx , signature )
print ( 'Built mosaic revocation transaction:' )
print ( json . dumps ( revoke_tx . to_json (), indent = 2 ))
revoke_hash = facade . hash_transaction ( revoke_tx )
print ( f 'Transaction hash: { revoke_hash } ' )
# Announce transaction
announce_transaction ( json_payload , 'mosaic revocation' )
# Wait for confirmation
wait_for_confirmation ( revoke_hash , 'mosaic revocation' )
# --- VERIFYING REVOCATION ---
print ( ' \n --- Verifying revocation ---' )
mosaics = get_account_mosaics ( SOURCE_ADDRESS )
for mosaic in mosaics :
if mosaic [ 'id' ] == MOSAIC_ID_HEX . upper ():
print ( f ' Mosaic ID: { mosaic [ "id" ] } ,'
f ' Amount: { mosaic [ "amount" ] } ' )
except Exception as e :
print ( e )
Download source
import { PrivateKey } from 'symbol-sdk' ;
import {
SymbolFacade ,
descriptors ,
models
} from 'symbol-sdk/symbol' ;
const NODE_URL = process . env . NODE_URL ||
'https://reference.symboltest.net:3001' ;
console . log ( 'Using node' , NODE_URL );
// Helper function to announce a transaction
async function announceTransaction ( payload , label ) {
console . log ( `Announcing ${ label } to /transactions` );
const response = await fetch ( ` ${ NODE_URL } /transactions` , {
method : 'PUT' ,
headers : { 'Content-Type' : 'application/json' },
body : payload
});
console . log ( ' Response:' , await response . text ());
}
// Helper function to wait for transaction confirmation
async function waitForConfirmation ( transactionHash , label ) {
console . log ( `Waiting for ${ label } confirmation...` );
for ( let attempt = 0 ; 60 > attempt ; attempt ++ ) {
await new Promise ( resolve => { setTimeout ( resolve , 1000 ); });
const response = await fetch (
` ${ NODE_URL } /transactionStatus/ ${ transactionHash } ` );
if ( ! response . ok ) {
if ( 404 === response . status ) {
console . log ( ' Transaction status: unknown' );
continue ;
}
throw new Error ( `HTTP ${ response . status } ` );
}
const status = await response . json ();
console . log ( ' Transaction status:' , status . group );
if ( 'confirmed' === status . group ) {
console . log ( ` ${ label } confirmed in` , attempt , 'seconds' );
return ;
}
if ( 'failed' === status . group )
throw new Error ( ` ${ label } failed: ${ status . code } ` );
}
throw new Error ( ` ${ label } not confirmed after 60 seconds` );
}
// Helper function to fetch account mosaic balances
async function getAccountMosaics ( address ) {
const accountPath = `/accounts/ ${ address } ` ;
console . log ( 'Fetching account information from' , accountPath );
const response = await fetch ( ` ${ NODE_URL }${ accountPath } ` );
const responseJSON = await response . json ();
return responseJSON . account . mosaics ;
}
const SIGNER_PRIVATE_KEY = process . env . SIGNER_PRIVATE_KEY ||
'0000000000000000000000000000000000000000000000000000000000000000' ;
const signerKeyPair = new SymbolFacade . KeyPair (
new PrivateKey ( SIGNER_PRIVATE_KEY ));
const facade = new SymbolFacade ( 'testnet' );
const signerAddress =
facade . network . publicKeyToAddress ( signerKeyPair . publicKey );
console . log ( 'Signer address:' , signerAddress . toString ());
const SOURCE_ADDRESS = process . env . SOURCE_ADDRESS ||
'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA' ;
console . log ( 'Source address:' , SOURCE_ADDRESS );
const MOSAIC_ID_HEX = process . env . MOSAIC_ID ||
'7AED3D514C986941' ;
const mosaicId = BigInt ( `0x ${ MOSAIC_ID_HEX } ` );
const mosaicIdHex = mosaicId . toString ( 16 )
. toUpperCase (). padStart ( 16 , '0' );
console . log (
`Mosaic ID: ${ mosaicId } (0x ${ mosaicIdHex } )` );
try {
// Fetch recommended fees
const feePath = '/network/fees/transaction' ;
console . log ( 'Fetching recommended fees from' , feePath );
const feeResponse = await fetch ( ` ${ NODE_URL }${ feePath } ` );
const feeJSON = await feeResponse . json ();
const medianMultiplier = feeJSON . medianFeeMultiplier ;
const minimumMultiplier = feeJSON . minFeeMultiplier ;
const feeMultiplier = Math . max ( medianMultiplier , minimumMultiplier );
console . log ( ' Fee multiplier:' , feeMultiplier );
// --- CHECKING INITIAL BALANCE ---
console . log ( '\n--- Checking initial balance ---' );
let mosaics = await getAccountMosaics ( SOURCE_ADDRESS );
for ( const mosaic of mosaics ) {
if ( mosaic . id === MOSAIC_ID_HEX . toUpperCase ()) {
console . log ( ` Mosaic ID: ${ mosaic . id } ,` +
` Amount: ${ mosaic . amount } ` );
}
}
// --- REVOKING MOSAIC ---
console . log ( '\n--- Revoking mosaic ---' );
const revokeTx = facade . createTransactionFromTypedDescriptor (
new descriptors . MosaicSupplyRevocationTransactionV1Descriptor (
new SymbolFacade . Address ( SOURCE_ADDRESS ),
new descriptors . UnresolvedMosaicDescriptor (
new models . UnresolvedMosaicId ( mosaicId ),
new models . Amount ( 7 _00n ))),
signerKeyPair . publicKey ,
feeMultiplier ,
2 * 60 * 60 );
// Sign and generate final payload
const signature = facade . signTransaction ( signerKeyPair , revokeTx );
const jsonPayload = facade . transactionFactory . static . attachSignature (
revokeTx , signature );
console . log ( 'Built mosaic revocation transaction:' );
console . dir ( revokeTx . toJson (), { colors : true });
const revokeHash = facade . hashTransaction ( revokeTx ). toString ();
console . log ( 'Transaction hash:' , revokeHash );
// Announce transaction
await announceTransaction ( jsonPayload , 'mosaic revocation' );
// Wait for confirmation
await waitForConfirmation ( revokeHash , 'mosaic revocation' );
// --- VERIFYING REVOCATION ---
console . log ( '\n--- Verifying revocation ---' );
mosaics = await getAccountMosaics ( SOURCE_ADDRESS );
for ( const mosaic of mosaics ) {
if ( mosaic . id === MOSAIC_ID_HEX . toUpperCase ()) {
console . log ( ` Mosaic ID: ${ mosaic . id } ,` +
` Amount: ${ mosaic . amount } ` );
}
}
} catch ( e ) {
console . error ( e . message );
}
Download source
//JAVA 21+
//DEPS org.symbol:symbol-sdk:3.3.1
import java.io.IOException ;
import java.net.URI ;
import java.net.http.HttpClient ;
import java.net.http.HttpRequest ;
import java.net.http.HttpResponse ;
import java.net.http.HttpResponse.BodyHandlers ;
import com.fasterxml.jackson.databind.JsonNode ;
import com.fasterxml.jackson.databind.ObjectMapper ;
import org.symbol.sdk.CryptoTypes ;
import org.symbol.sdk.facade.SymbolFacade ;
import org.symbol.sdk.symbol.Address ;
import org.symbol.sdk.symbol.KeyPair ;
import org.symbol.sdk.symbol.SymbolTransactionFactory ;
import org.symbol.sdk.symbol.descriptors.* ;
import org.symbol.sdk.symbol.models.* ;
public final class RevokeMosaic {
private static final ObjectMapper JSON_MAPPER = new ObjectMapper ();
private static final HttpClient HTTP_CLIENT =
HttpClient . newHttpClient ();
private final String nodeUrl = System . getenv (). getOrDefault (
"NODE_URL" , "https://reference.symboltest.net:3001" );
private final SymbolFacade facade = new SymbolFacade ( "testnet" );
private void announceTransaction (
final String payload ,
final String label
) throws IOException , InterruptedException {
System . out . printf ( "Announcing %s to /transactions%n" , label );
final HttpRequest request = HttpRequest . newBuilder (
URI . create ( nodeUrl + "/transactions" ))
. header ( "Content-Type" , "application/json" )
. PUT ( HttpRequest . BodyPublishers . ofString ( payload ))
. build ();
final HttpResponse < String > response = HTTP_CLIENT . send (
request , BodyHandlers . ofString ());
System . out . printf ( " Response: %s%n" , response . body ());
}
private void waitForConfirmation (
final String transactionHash ,
final String label
) throws IOException , InterruptedException {
System . out . printf ( "Waiting for %s confirmation...%n" , label );
for ( int attempt = 0 ; 60 > attempt ; ++ attempt ) {
Thread . sleep ( 1000 );
final String statusPath =
"/transactionStatus/" + transactionHash ;
final HttpRequest statusRequest = HttpRequest . newBuilder (
URI . create ( nodeUrl + statusPath )). GET (). build ();
final HttpResponse < String > statusResponse = HTTP_CLIENT
. send ( statusRequest , BodyHandlers . ofString ());
if ( 404 == statusResponse . statusCode ()) {
System . out . println ( " Transaction status: unknown" );
continue ;
}
if ( 2 != statusResponse . statusCode () / 100 )
throw new IOException (
"HTTP " + statusResponse . statusCode ());
final JsonNode status =
JSON_MAPPER . readTree ( statusResponse . body ());
final String group = status . get ( "group" ). asText ();
System . out . printf ( " Transaction status: %s%n" , group );
if ( "confirmed" . equals ( group )) {
System . out . printf ( "%s confirmed in %d seconds%n" ,
label , attempt );
return ;
}
if ( "failed" . equals ( group ))
throw new IOException ( String . format ( "%s failed: %s" ,
label , status . get ( "code" ). asText ()));
}
throw new IOException ( String . format (
"%s not confirmed after 60 seconds" , label ));
}
private JsonNode getAccountMosaics (
final Address address
) throws IOException , InterruptedException {
final String accountPath = "/accounts/" + address ;
System . out . printf ( "Fetching account information from %s%n" ,
accountPath );
final HttpRequest request = HttpRequest . newBuilder (
URI . create ( nodeUrl + accountPath )). GET (). build ();
final HttpResponse < String > response = HTTP_CLIENT . send (
request , BodyHandlers . ofString ());
return JSON_MAPPER . readTree ( response . body ())
. get ( "account" ). get ( "mosaics" );
}
public static void main ( final String [] args ) {
try {
new RevokeMosaic (). run ();
} catch ( final Exception ex ) {
System . out . println ( null == ex . getMessage ()
? ex . toString ()
: ex . getMessage ());
}
}
private void run () throws IOException , InterruptedException {
System . out . printf ( "Using node %s%n" , nodeUrl );
final String privateKeyString = System . getenv (). getOrDefault (
"SIGNER_PRIVATE_KEY" , "0" . repeat ( 64 ));
final KeyPair signerKeyPair = new KeyPair (
new CryptoTypes . PrivateKey ( privateKeyString ));
final Address signerAddress = facade . network . publicKeyToAddress (
signerKeyPair . getPublicKey ());
System . out . printf ( "Signer address: %s%n" , signerAddress );
final String sourceAddressString = System . getenv (). getOrDefault (
"SOURCE_ADDRESS" , "TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA" );
final Address sourceAddress = new Address ( sourceAddressString );
System . out . printf ( "Source address: %s%n" , sourceAddress );
final String mosaicIdHex = System . getenv (). getOrDefault (
"MOSAIC_ID" , "7AED3D514C986941" );
final long mosaicId = Long . parseUnsignedLong ( mosaicIdHex , 16 );
System . out . printf ( "Mosaic ID: %d (0x%016X)%n" ,
mosaicId , mosaicId );
// Fetch recommended fees
final String feePath = "/network/fees/transaction" ;
System . out . printf ( "Fetching recommended fees from %s%n" , feePath );
final HttpRequest feeRequest = HttpRequest . newBuilder (
URI . create ( nodeUrl + feePath )). GET (). build ();
final HttpResponse < String > feeResponse = HTTP_CLIENT . send (
feeRequest , BodyHandlers . ofString ());
final JsonNode feeJson = JSON_MAPPER . readTree ( feeResponse . body ());
final long feeMultiplier = Math . max (
feeJson . get ( "medianFeeMultiplier" ). asLong (),
feeJson . get ( "minFeeMultiplier" ). asLong ());
System . out . printf ( " Fee multiplier: %d%n" , feeMultiplier );
// --- CHECKING INITIAL BALANCE ---
System . out . println ( "\n--- Checking initial balance ---" );
JsonNode mosaics = getAccountMosaics ( sourceAddress );
for ( final JsonNode mosaic : mosaics ) {
if ( mosaic . get ( "id" ). asText (). equals (
mosaicIdHex . toUpperCase ()))
System . out . printf ( " Mosaic ID: %s, Amount: %s%n" ,
mosaic . get ( "id" ). asText (),
mosaic . get ( "amount" ). asText ());
}
// --- REVOKING MOSAIC ---
System . out . println ( "\n--- Revoking mosaic ---" );
final Transaction revokeTx =
facade . createTransactionFromTypedDescriptor (
new MosaicSupplyRevocationTransactionV1Descriptor (
sourceAddress ,
new UnresolvedMosaicDescriptor (
new UnresolvedMosaicId ( mosaicId ),
new Amount ( 7_00 ))),
signerKeyPair . getPublicKey (),
feeMultiplier ,
2 * 60 * 60 );
// Sign and generate final payload
final CryptoTypes . Signature signature = facade . signTransaction (
signerKeyPair , revokeTx );
final String jsonPayload = SymbolTransactionFactory
. attachSignature ( revokeTx , signature );
System . out . println ( "Built mosaic revocation transaction:" );
System . out . println ( JSON_MAPPER . writerWithDefaultPrettyPrinter ()
. writeValueAsString ( revokeTx . toJson ()));
final String revokeHash =
facade . hashTransaction ( revokeTx ). toString ();
System . out . printf ( "Transaction hash: %s%n" , revokeHash );
// Announce transaction
announceTransaction ( jsonPayload , "mosaic revocation" );
// Wait for confirmation
waitForConfirmation ( revokeHash , "mosaic revocation" );
// --- VERIFYING REVOCATION ---
System . out . println ( "\n--- Verifying revocation ---" );
mosaics = getAccountMosaics ( sourceAddress );
for ( final JsonNode mosaic : mosaics ) {
if ( mosaic . get ( "id" ). asText (). equals (
mosaicIdHex . toUpperCase ()))
System . out . printf ( " Mosaic ID: %s, Amount: %s%n" ,
mosaic . get ( "id" ). asText (),
mosaic . get ( "amount" ). asText ());
}
}
}
Download source
Code Explanation
Setting Up the Accounts
SIGNER_PRIVATE_KEY = os . getenv ( 'SIGNER_PRIVATE_KEY' ,
'0000000000000000000000000000000000000000000000000000000000000000' )
signer_key_pair = SymbolFacade . KeyPair ( PrivateKey ( SIGNER_PRIVATE_KEY ))
facade = SymbolFacade ( 'testnet' )
signer_address = facade . network . public_key_to_address (
signer_key_pair . public_key )
print ( f 'Signer address: { signer_address } ' )
SOURCE_ADDRESS = os . getenv ( 'SOURCE_ADDRESS' ,
'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA' )
print ( f 'Source address: { SOURCE_ADDRESS } ' )
MOSAIC_ID_HEX = os . getenv ( 'MOSAIC_ID' , '7AED3D514C986941' )
mosaic_id = int ( MOSAIC_ID_HEX , 16 )
print ( f 'Mosaic ID: { mosaic_id } (0x { mosaic_id : 016X } )' )
const SIGNER_PRIVATE_KEY = process . env . SIGNER_PRIVATE_KEY ||
'0000000000000000000000000000000000000000000000000000000000000000' ;
const signerKeyPair = new SymbolFacade . KeyPair (
new PrivateKey ( SIGNER_PRIVATE_KEY ));
const facade = new SymbolFacade ( 'testnet' );
const signerAddress =
facade . network . publicKeyToAddress ( signerKeyPair . publicKey );
console . log ( 'Signer address:' , signerAddress . toString ());
const SOURCE_ADDRESS = process . env . SOURCE_ADDRESS ||
'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA' ;
console . log ( 'Source address:' , SOURCE_ADDRESS );
const MOSAIC_ID_HEX = process . env . MOSAIC_ID ||
'7AED3D514C986941' ;
const mosaicId = BigInt ( `0x ${ MOSAIC_ID_HEX } ` );
const mosaicIdHex = mosaicId . toString ( 16 )
. toUpperCase (). padStart ( 16 , '0' );
console . log (
`Mosaic ID: ${ mosaicId } (0x ${ mosaicIdHex } )` );
final String privateKeyString = System . getenv (). getOrDefault (
"SIGNER_PRIVATE_KEY" , "0" . repeat ( 64 ));
final KeyPair signerKeyPair = new KeyPair (
new CryptoTypes . PrivateKey ( privateKeyString ));
final Address signerAddress = facade . network . publicKeyToAddress (
signerKeyPair . getPublicKey ());
System . out . printf ( "Signer address: %s%n" , signerAddress );
final String sourceAddressString = System . getenv (). getOrDefault (
"SOURCE_ADDRESS" , "TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA" );
final Address sourceAddress = new Address ( sourceAddressString );
System . out . printf ( "Source address: %s%n" , sourceAddress );
final String mosaicIdHex = System . getenv (). getOrDefault (
"MOSAIC_ID" , "7AED3D514C986941" );
final long mosaicId = Long . parseUnsignedLong ( mosaicIdHex , 16 );
System . out . printf ( "Mosaic ID: %d (0x%016X)%n" ,
mosaicId , mosaicId );
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 must be the original creator of the mosaic with the revokable flag.
The SOURCE_ADDRESS environment variable specifies the address of the account from which mosaic units will be revoked.
The MOSAIC_ID environment variable specifies the hexadecimal identifier of the mosaic to revoke.
See Querying Account Balance to list the mosaics held by an account.
Fetching Recommended Fees
Recommended fees are fetched from /network/fees/transaction GET , following the process described in the
Transfer Transaction tutorial.
Checking Initial Balance
Before revoking, the helper function get_account_mosaics()getAccountMosaics()getAccountMosaics() fetches the source account's current
balance for the target mosaic from the /accounts/{accountId} GET endpoint.
This provides a baseline to compare against after the revocation.
Building the Revocation Transaction
revoke_tx = facade . create_transaction_from_descriptor (
{
'type' : 'mosaic_supply_revocation_transaction_v1' ,
'source_address' : SOURCE_ADDRESS ,
'mosaic' : {
'mosaic_id' : mosaic_id ,
'amount' : 7_00
}
},
signer_key_pair . public_key ,
fee_multiplier ,
2 * 60 * 60 )
const revokeTx = facade . createTransactionFromTypedDescriptor (
new descriptors . MosaicSupplyRevocationTransactionV1Descriptor (
new SymbolFacade . Address ( SOURCE_ADDRESS ),
new descriptors . UnresolvedMosaicDescriptor (
new models . UnresolvedMosaicId ( mosaicId ),
new models . Amount ( 7 _00n ))),
signerKeyPair . publicKey ,
feeMultiplier ,
2 * 60 * 60 );
final Transaction revokeTx =
facade . createTransactionFromTypedDescriptor (
new MosaicSupplyRevocationTransactionV1Descriptor (
sourceAddress ,
new UnresolvedMosaicDescriptor (
new UnresolvedMosaicId ( mosaicId ),
new Amount ( 7_00 ))),
signerKeyPair . getPublicKey (),
feeMultiplier ,
2 * 60 * 60 );
The revocation transaction reclaims mosaic units from the source account and returns them to the creator's balance:
Type: Mosaic supply revocation transactions use the type MosaicSupplyRevocationTransactionV1 .
Source address: The address of the account holding the mosaic units to revoke.
This can be any account that currently holds units of the specified mosaic.
Mosaic: An object containing the mosaic ID and the amount to revoke.
Amount: The number of atomic units to revoke from the source account.
To find out the mosaic's divisibility ,
query the /mosaics/{mosaicId} GET endpoint.
For example, with a divisibility of 2, an amount of 700 represents 7.00 whole units (700 / 102 ).
Partial revocation
The amount does not have to match the source account's full balance.
Any amount up to the source's current holdings can be revoked in a single transaction.
Submitting the Revocation
# Sign and generate final payload
signature = facade . sign_transaction (
signer_key_pair , revoke_tx )
json_payload = facade . transaction_factory . attach_signature (
revoke_tx , signature )
print ( 'Built mosaic revocation transaction:' )
print ( json . dumps ( revoke_tx . to_json (), indent = 2 ))
revoke_hash = facade . hash_transaction ( revoke_tx )
print ( f 'Transaction hash: { revoke_hash } ' )
# Announce transaction
announce_transaction ( json_payload , 'mosaic revocation' )
// Sign and generate final payload
const signature = facade . signTransaction ( signerKeyPair , revokeTx );
const jsonPayload = facade . transactionFactory . static . attachSignature (
revokeTx , signature );
console . log ( 'Built mosaic revocation transaction:' );
console . dir ( revokeTx . toJson (), { colors : true });
const revokeHash = facade . hashTransaction ( revokeTx ). toString ();
console . log ( 'Transaction hash:' , revokeHash );
// Announce transaction
await announceTransaction ( jsonPayload , 'mosaic revocation' );
// Sign and generate final payload
final CryptoTypes . Signature signature = facade . signTransaction (
signerKeyPair , revokeTx );
final String jsonPayload = SymbolTransactionFactory
. attachSignature ( revokeTx , signature );
System . out . println ( "Built mosaic revocation transaction:" );
System . out . println ( JSON_MAPPER . writerWithDefaultPrettyPrinter ()
. writeValueAsString ( revokeTx . toJson ()));
final String revokeHash =
facade . hashTransaction ( revokeTx ). toString ();
System . out . printf ( "Transaction hash: %s%n" , revokeHash );
// Announce transaction
announceTransaction ( jsonPayload , "mosaic revocation" );
The revocation transaction is signed and announced following the same process as in the
Transfer Transaction tutorial.
The code then waits for the transaction to be confirmed by polling the
/transactionStatus/{hash} GET endpoint until the status changes to confirmed.
Verifying the Revocation
To verify the revocation, the helper function get_account_mosaics()getAccountMosaics()getAccountMosaics() fetches the source account's
balance again.
The balance should be lower than the initial balance by the revoked amount.
Output
The output shown below corresponds to a typical run of the program.
Using node https://reference.symboltest.net:3001
Signer address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Source address: TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA
Mosaic ID: 8857803461494335809 (0x7AED3D514C986941)
Fetching recommended fees from /network/fees/transaction
Fee multiplier: 100
--- Checking initial balance ---
Fetching account information from /accounts/TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA
Mosaic ID: 7AED3D514C986941, Amount: 1000
--- Revoking mosaic ---
Built mosaic revocation transaction:
{
"signature": "225719859B43C8B9FCB04432E83FA95258C15A64B60974754DC2E4CAF9E58110995F2ECB5552049C3F79632FFAF19F4B90247012A7777E79D3AF0A17507A9F0D",
"signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
"version": 1,
"network": 152,
"type": 17229,
"fee": "16800",
"deadline": "104790468507",
"source_address": "987D075454716222F609929E883174AD8C996D5828C938BC",
"mosaic": {
"mosaic_id": "8857803461494335809",
"amount": "700"
}
}
Transaction hash: C0E59A5E36FC50CC5BDF8A16EABAB791611737B64E3590D43576374B803771E6
Announcing mosaic revocation to /transactions
Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for mosaic revocation confirmation...
Transaction status: unconfirmed
Transaction status: unconfirmed
Transaction status: unconfirmed
Transaction status: unconfirmed
Transaction status: unconfirmed
Transaction status: unconfirmed
Transaction status: unconfirmed
Transaction status: unconfirmed
Transaction status: confirmed
mosaic revocation confirmed in 8 seconds
--- Verifying revocation ---
Fetching account information from /accounts/TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA
Mosaic ID: 7AED3D514C986941, Amount: 300
Some highlights from the output:
Mosaic ID (line 4): The mosaic ID 8857803461494335809 (0x7AED3D514C986941) identifies the mosaic to revoke.
Initial balance (line 10): Before the revocation, the source account holds 1000 atomic units of the mosaic.
Source address (line 22): The source_address field identifies the account from which units are revoked.
This is the hex-encoded form of the Base32 address shown on line 3.
Revoked amount (lines 24-25): The mosaic object specifies the mosaic ID in decimal format and the amount 700.
The decimal value corresponds to the hexadecimal ID shown on line 4.
Verified balance (line 45): After the revocation, the source account's balance is 300, confirming that 700
atomic units were successfully reclaimed.
The transaction hash printed in the output can be used to search for the transaction in the
Symbol Testnet Explorer .
Conclusion
This tutorial showed how to: