importjsonimportosimporttimeimporturllib.requestfromsymbolchain.CryptoTypesimportPrivateKeyfromsymbolchain.facade.SymbolFacadeimportSymbolFacadefromsymbolchain.scimportAmountfromsymbolchain.symbol.Metadataimport(metadata_generate_key,metadata_update_value)fromsymbolchain.symbol.NetworkimportNetworkTimestampNODE_URL=os.getenv('NODE_URL','https://reference.symboltest.net:3001')print(f'Using node {NODE_URL}')# Helper function to announce a transactiondefannounce_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')withurllib.request.urlopen(request)asresponse:print(f' Response: {response.read().decode()}')# Helper function to wait for transaction confirmationdefwait_for_confirmation(transaction_hash,label):print(f'Waiting for {label} confirmation...')forattemptinrange(60):time.sleep(1)try:url=f'{NODE_URL}/transactionStatus/{transaction_hash}'withurllib.request.urlopen(url)asresponse:status=json.loads(response.read().decode())print(f' Transaction status: {status["group"]}')ifstatus['group']=='confirmed':print(f'{label} confirmed in {attempt} seconds')returnifstatus['group']=='failed':raiseException(f'{label} failed: {status["code"]}')excepturllib.error.HTTPError:print(' Transaction status: unknown')raiseException(f'{label} not confirmed after 60 seconds')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}')# Get mosaic ID from environmentMOSAIC_ID=os.getenv('MOSAIC_ID','6D1314BE751B62C2')mosaic_id=int(MOSAIC_ID,16)print(f'Mosaic ID: {mosaic_id} ({hex(mosaic_id)})')try:# Fetch current network timetime_path='/node/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())receive_timestamp=(response_json['communicationTimestamps']['receiveTimestamp'])timestamp=NetworkTimestamp(int(receive_timestamp))print(f' Network time: {timestamp.timestamp} ms since nemesis')# Fetch recommended feesfee_path='/network/fees/transaction'print(f'Fetching recommended fees from {fee_path}')withurllib.request.urlopen(f'{NODE_URL}{fee_path}')asresponse:response_json=json.loads(response.read().decode())median_mult=response_json['medianFeeMultiplier']minimum_mult=response_json['minFeeMultiplier']fee_mult=max(median_mult,minimum_mult)print(f' Fee multiplier: {fee_mult}')# --- ADDING NEW METADATA ---print('\n--- Adding new metadata ---')# Define metadata key and valuekey_string=f'description_{int(time.time())}'scoped_metadata_key=metadata_generate_key(key_string)metadata_value='My first mosaic'.encode('utf8')# Create the embedded metadata transactionembedded_transaction=facade.transaction_factory.create_embedded({'type':'mosaic_metadata_transaction_v1','signer_public_key':signer_key_pair.public_key,'target_address':signer_address,'target_mosaic_id':mosaic_id,'scoped_metadata_key':scoped_metadata_key,# When creating new metadata, value_size_delta# equals the value length'value_size_delta':len(metadata_value),'value':metadata_value})print('Created embedded metadata transaction:')print(json.dumps(embedded_transaction.to_json(),indent=2))# Build the aggregate transactionembedded_transactions=[embedded_transaction]transaction=facade.transaction_factory.create({'type':'aggregate_complete_transaction_v3','signer_public_key':signer_key_pair.public_key,'deadline':timestamp.add_hours(2).timestamp,'transactions_hash':facade.hash_embedded_transactions(embedded_transactions),'transactions':embedded_transactions})transaction.fee=Amount(fee_mult*transaction.size)# Sign and generate final payloadsignature=facade.sign_transaction(signer_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)# Announce and wait for confirmationtransaction_hash=facade.hash_transaction(transaction)print(f'Built aggregate transaction with hash: {transaction_hash}')announce_transaction(json_payload,'aggregate transaction')wait_for_confirmation(transaction_hash,'aggregate transaction')# --- MODIFYING EXISTING METADATA ---print('\n--- Modifying existing metadata ---')# Fetch current metadata value from networkmetadata_path=(f'/metadata?sourceAddress={signer_address}'f'&targetAddress={signer_address}'f'&scopedMetadataKey={scoped_metadata_key:016X}'f'&targetId={mosaic_id:016X}''&metadataType=1')print(f'Fetching current metadata from {metadata_path}')withurllib.request.urlopen(f'{NODE_URL}{metadata_path}')asresponse:response_json=json.loads(response.read().decode())# Get the metadata entryifnotresponse_json['data']:raiseException('Metadata entry not found')metadata_entry=response_json['data'][0]['metadataEntry']current_value=bytes.fromhex(metadata_entry['value'])print(f' Current value: {current_value.decode("utf8")}')# XOR the current and new valuesnew_value='Updated mosaic'.encode('utf8')update_value=metadata_update_value(current_value,new_value)# Create the update transaction with XOR'd valueembedded_update=facade.transaction_factory.create_embedded({'type':'mosaic_metadata_transaction_v1','signer_public_key':signer_key_pair.public_key,'target_address':signer_address,'target_mosaic_id':mosaic_id,'scoped_metadata_key':scoped_metadata_key,# value_size_delta is the difference in length# (can be negative)'value_size_delta':len(new_value)-len(current_value),'value':update_value})print('Created embedded update transaction:')print(json.dumps(embedded_update.to_json(),indent=2))# Build the aggregate for the updateembedded_transactions=[embedded_update]update_transaction=facade.transaction_factory.create({'type':'aggregate_complete_transaction_v3','signer_public_key':signer_key_pair.public_key,'deadline':timestamp.add_hours(2).timestamp,'transactions_hash':facade.hash_embedded_transactions(embedded_transactions),'transactions':embedded_transactions})update_transaction.fee=Amount(fee_mult*update_transaction.size)# Sign and announce the updatesignature=facade.sign_transaction(signer_key_pair,update_transaction)json_payload=facade.transaction_factory.attach_signature(update_transaction,signature)# Announce and wait for confirmationupdate_hash=facade.hash_transaction(update_transaction)print(f'Built aggregate transaction with hash: {update_hash}')announce_transaction(json_payload,'aggregate transaction')wait_for_confirmation(update_hash,'aggregate transaction')exceptExceptionase:print(e)
import{PrivateKey}from'symbol-sdk';import{metadataGenerateKey,metadataUpdateValue,models,NetworkTimestamp,SymbolFacade}from'symbol-sdk/symbol';constNODE_URL=process.env.NODE_URL||'https://reference.symboltest.net:3001';console.log('Using node',NODE_URL);// Helper function to announce a transactionasyncfunctionannounceTransaction(payload,label){console.log(`Announcing ${label} to /transactions`);constresponse=awaitfetch(`${NODE_URL}/transactions`,{method:'PUT',headers:{'Content-Type':'application/json'},body:payload});console.log(' Response:',awaitresponse.text());}// Helper function to wait for transaction confirmationasyncfunctionwaitForConfirmation(transactionHash,label){console.log(`Waiting for ${label} confirmation...`);for(letattempt=0;attempt<60;attempt++){awaitnewPromise(resolve=>setTimeout(resolve,1000));try{constresponse=awaitfetch(`${NODE_URL}/transactionStatus/${transactionHash}`);conststatus=awaitresponse.json();console.log(' Transaction status:',status.group);if(status.group==='confirmed'){console.log(`${label} confirmed in`,attempt,'seconds');return;}if(status.group==='failed'){thrownewError(`${label} failed: ${status.code}`);}}catch(e){if(e.message.includes('failed'))throwe;console.log(' Transaction status: unknown');}}thrownewError(`${label} not confirmed after 60 seconds`);}constSIGNER_PRIVATE_KEY=process.env.SIGNER_PRIVATE_KEY||('0000000000000000000000000000000000000000000000000000000000000000');constsignerKeyPair=newSymbolFacade.KeyPair(newPrivateKey(SIGNER_PRIVATE_KEY));constfacade=newSymbolFacade('testnet');constsignerAddress=facade.network.publicKeyToAddress(signerKeyPair.publicKey);console.log('Signer address:',signerAddress.toString());// Get mosaic ID from environmentconstMOSAIC_ID=process.env.MOSAIC_ID||'6D1314BE751B62C2';constmosaicId=BigInt(`0x${MOSAIC_ID}`);console.log('Mosaic ID:',mosaicId.toString(),`(0x${mosaicId.toString(16)})`);try{// Fetch current network timeconsttimePath='/node/time';console.log('Fetching current network time from',timePath);consttimeResponse=awaitfetch(`${NODE_URL}${timePath}`);consttimeJSON=awaittimeResponse.json();consttimestamp=newNetworkTimestamp(timeJSON.communicationTimestamps.receiveTimestamp);console.log(' Network time:',timestamp.timestamp,'ms since nemesis');// Fetch recommended feesconstfeePath='/network/fees/transaction';console.log('Fetching recommended fees from',feePath);constfeeResponse=awaitfetch(`${NODE_URL}${feePath}`);constfeeJSON=awaitfeeResponse.json();constmedianMult=feeJSON.medianFeeMultiplier;constminimumMult=feeJSON.minFeeMultiplier;constfeeMult=Math.max(medianMult,minimumMult);console.log(' Fee multiplier:',feeMult);// --- ADDING NEW METADATA ---console.log('\n--- Adding new metadata ---');// Define metadata key and valueconstkeyString=`description_${Date.now()}`;constscopedMetadataKey=metadataGenerateKey(keyString);constmetadataValue=newTextEncoder().encode('My first mosaic');// Create the embedded metadata transactionconstembeddedTransaction=facade.transactionFactory.createEmbedded({type:'mosaic_metadata_transaction_v1',signerPublicKey:signerKeyPair.publicKey.toString(),targetAddress:signerAddress.toString(),targetMosaicId:mosaicId,scopedMetadataKey,// When creating new metadata, valueSizeDelta// equals value lengthvalueSizeDelta:metadataValue.length,value:metadataValue});console.log('Created embedded metadata transaction:');console.log(JSON.stringify(embeddedTransaction.toJson(),null,2));// Build the aggregate transactionconstembeddedTransactions=[embeddedTransaction];consttransaction=facade.transactionFactory.create({type:'aggregate_complete_transaction_v3',signerPublicKey:signerKeyPair.publicKey.toString(),deadline:timestamp.addHours(2).timestamp,transactionsHash:facade.static.hashEmbeddedTransactions(embeddedTransactions),transactions:embeddedTransactions});transaction.fee=newmodels.Amount(feeMult*transaction.size);// Sign and generate final payloadconstsignature=facade.signTransaction(signerKeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);// Announce and wait for confirmationconsttransactionHash=facade.hashTransaction(transaction).toString();console.log('Built aggregate transaction with hash:',transactionHash);awaitannounceTransaction(jsonPayload,'aggregate transaction');awaitwaitForConfirmation(transactionHash,'aggregate transaction');// --- MODIFYING EXISTING METADATA ---console.log('\n--- Modifying existing metadata ---');// Fetch current metadata value from networkconstscopedKeyHex=scopedMetadataKey.toString(16).toUpperCase().padStart(16,'0');constmosaicIdHex=mosaicId.toString(16).toUpperCase().padStart(16,'0');constmetadataPath=`/metadata`+`?sourceAddress=${signerAddress}`+`&targetAddress=${signerAddress}`+`&scopedMetadataKey=${scopedKeyHex}`+`&targetId=${mosaicIdHex}`+'&metadataType=1';console.log('Fetching current metadata from',metadataPath);constmetadataResponse=awaitfetch(`${NODE_URL}${metadataPath}`);constmetadataJSON=awaitmetadataResponse.json();// Get the metadata entryif(!metadataJSON.data.length){thrownewError('Metadata entry not found');}constmetadataEntry=metadataJSON.data[0].metadataEntry;constcurrentValue=Buffer.from(metadataEntry.value,'hex');console.log(' Current value:',currentValue.toString('utf8'));// XOR the current and new valuesconstnewValue=newTextEncoder().encode('Updated mosaic');constupdateValue=metadataUpdateValue(currentValue,newValue);// Create the update transaction with XOR'd valueconstembeddedUpdate=facade.transactionFactory.createEmbedded({type:'mosaic_metadata_transaction_v1',signerPublicKey:signerKeyPair.publicKey.toString(),targetAddress:signerAddress.toString(),targetMosaicId:mosaicId,scopedMetadataKey,// valueSizeDelta is the difference in length// (can be negative)valueSizeDelta:newValue.length-currentValue.length,value:updateValue});console.log('Created embedded update transaction:');console.log(JSON.stringify(embeddedUpdate.toJson(),null,2));// Build the aggregate for the updateconstupdateEmbedded=[embeddedUpdate];constupdateTransaction=facade.transactionFactory.create({type:'aggregate_complete_transaction_v3',signerPublicKey:signerKeyPair.publicKey.toString(),deadline:timestamp.addHours(2).timestamp,transactionsHash:facade.static.hashEmbeddedTransactions(updateEmbedded),transactions:updateEmbedded});updateTransaction.fee=newmodels.Amount(feeMult*updateTransaction.size);// Sign and announce the updateconstupdateSignature=facade.signTransaction(signerKeyPair,updateTransaction);constupdatePayload=facade.transactionFactory.static.attachSignature(updateTransaction,updateSignature);// Announce and wait for confirmationconstupdateHash=facade.hashTransaction(updateTransaction).toString();console.log('Built aggregate transaction with hash:',updateHash);awaitannounceTransaction(updatePayload,'aggregate transaction');awaitwaitForConfirmation(updateHash,'aggregate transaction');}catch(e){console.error(e.message,'| Cause:',e.cause?.code??'unknown');}
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}')# Get mosaic ID from environmentMOSAIC_ID=os.getenv('MOSAIC_ID','6D1314BE751B62C2')mosaic_id=int(MOSAIC_ID,16)print(f'Mosaic ID: {mosaic_id} ({hex(mosaic_id)})')
constSIGNER_PRIVATE_KEY=process.env.SIGNER_PRIVATE_KEY||('0000000000000000000000000000000000000000000000000000000000000000');constsignerKeyPair=newSymbolFacade.KeyPair(newPrivateKey(SIGNER_PRIVATE_KEY));constfacade=newSymbolFacade('testnet');constsignerAddress=facade.network.publicKeyToAddress(signerKeyPair.publicKey);console.log('Signer address:',signerAddress.toString());// Get mosaic ID from environmentconstMOSAIC_ID=process.env.MOSAIC_ID||'6D1314BE751B62C2';constmosaicId=BigInt(`0x${MOSAIC_ID}`);console.log('Mosaic ID:',mosaicId.toString(),`(0x${mosaicId.toString(16)})`);
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.
The ID of the mosaic to which the metadata will be attached is read from the MOSAIC_ID environment variable.
This is as an unprefixed hexadecimal string, which defaults to a test value if not set.
Mosaic must exist
The mosaic must already be created and owned by the signer account,
or the transaction adding the metadata will be rejected.
# Fetch current network timetime_path='/node/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())receive_timestamp=(response_json['communicationTimestamps']['receiveTimestamp'])timestamp=NetworkTimestamp(int(receive_timestamp))print(f' Network time: {timestamp.timestamp} ms since nemesis')# Fetch recommended feesfee_path='/network/fees/transaction'print(f'Fetching recommended fees from {fee_path}')withurllib.request.urlopen(f'{NODE_URL}{fee_path}')asresponse:response_json=json.loads(response.read().decode())median_mult=response_json['medianFeeMultiplier']minimum_mult=response_json['minFeeMultiplier']fee_mult=max(median_mult,minimum_mult)print(f' Fee multiplier: {fee_mult}')
Each mosaic metadata entry is uniquely identified by:
The signer's address: the account adding the metadata
The target account's address: the mosaic owner, whose signature is required
The target mosaic ID
A scoped metadata key: a 64-bit value chosen by the metadata creator
The SDK provides a helper function that generates this key from a
human-readable string using SHA3-256 hashing.
This approach makes keys more meaningful and reduces the chance of collisions.
# Define metadata key and valuekey_string=f'description_{int(time.time())}'scoped_metadata_key=metadata_generate_key(key_string)metadata_value='My first mosaic'.encode('utf8')
// Define metadata key and valueconstkeyString=`description_${Date.now()}`;constscopedMetadataKey=metadataGenerateKey(keyString);constmetadataValue=newTextEncoder().encode('My first mosaic');
In this example, the key is derived from the string description.
For demonstration purposes, a timestamp is appended to the key string,
so each time the code is executed a new entry is added to the mosaic.
In practice, you would use a fixed key that identifies the specific metadata entry you want to create or update.
The metadata value can be any sequence of up to 1024 bytes.
In this example, the value is the string My first mosaic encoded in UTF-8.
Multiple entries with the same key
The key is only one of the 4 parts that identify a metadata entry,
so a change in any part produces a different entry.
For example, different accounts can use the same scoped metadata key on the same mosaic without conflict,
because the signer's address is different.
Each entry is independent and can only be updated by the account that originally created it.
Creating the Embedded Mosaic Metadata Transaction⚓︎
# Create the embedded metadata transactionembedded_transaction=facade.transaction_factory.create_embedded({'type':'mosaic_metadata_transaction_v1','signer_public_key':signer_key_pair.public_key,'target_address':signer_address,'target_mosaic_id':mosaic_id,'scoped_metadata_key':scoped_metadata_key,# When creating new metadata, value_size_delta# equals the value length'value_size_delta':len(metadata_value),'value':metadata_value})print('Created embedded metadata transaction:')print(json.dumps(embedded_transaction.to_json(),indent=2))
// Create the embedded metadata transactionconstembeddedTransaction=facade.transactionFactory.createEmbedded({type:'mosaic_metadata_transaction_v1',signerPublicKey:signerKeyPair.publicKey.toString(),targetAddress:signerAddress.toString(),targetMosaicId:mosaicId,scopedMetadataKey,// When creating new metadata, valueSizeDelta// equals value lengthvalueSizeDelta:metadataValue.length,value:metadataValue});console.log('Created embedded metadata transaction:');console.log(JSON.stringify(embeddedTransaction.toJson(),null,2));
A mosaic metadata transaction attaches a key-value pair to a mosaic on the blockchain.
The same transaction type handles both adding new metadata entries and updating existing ones.
Symbol requires these transactions to be inside an aggregate transaction that includes both
the signer account and the mosaic owner's signature.
This prevents unwanted metadata from being attached to a mosaic without its owner's permission.
In this tutorial, the signer is also the mosaic owner so only one signature is needed.
However, the transaction still needs to be inside an aggregate,
so the code defines the mosaic metadata transaction as an embedded transaction with these properties:
Type: Use mosaic_metadata_transaction_v1.
Signer public key: The account creating the metadata entry.
Target address: The mosaic owner's address.
When the signer differs from the mosaic owner, the owner must cosign the aggregate transaction.
Target mosaic ID: The mosaic to attach the metadata to.
Scoped metadata key: The 64-bit key used to identify this metadata entry.
Value size delta: When creating new metadata, set this to the byte length of the value.
When updating existing metadata, set this to the difference between the new and current value lengths.
Value: The metadata content as bytes.
When creating new metadata, provide the raw value.
When updating, provide a computed value (explained in the
Modifying Existing Metadata section).
# Build the aggregate transactionembedded_transactions=[embedded_transaction]transaction=facade.transaction_factory.create({'type':'aggregate_complete_transaction_v3','signer_public_key':signer_key_pair.public_key,'deadline':timestamp.add_hours(2).timestamp,'transactions_hash':facade.hash_embedded_transactions(embedded_transactions),'transactions':embedded_transactions})transaction.fee=Amount(fee_mult*transaction.size)
// Build the aggregate transactionconstembeddedTransactions=[embeddedTransaction];consttransaction=facade.transactionFactory.create({type:'aggregate_complete_transaction_v3',signerPublicKey:signerKeyPair.publicKey.toString(),deadline:timestamp.addHours(2).timestamp,transactionsHash:facade.static.hashEmbeddedTransactions(embeddedTransactions),transactions:embeddedTransactions});transaction.fee=newmodels.Amount(feeMult*transaction.size);
Since the signer is the mosaic owner, no cosignatures are required and the aggregate can be created as
complete, allowing it to be signed and announced immediately.
Adding metadata by a different account
If the signer is different from the mosaic owner, the owner must cosign the aggregate transaction to approve the
metadata entry.
For details on collecting cosignatures on-chain, see the Bonded Aggregate
tutorial.
# Sign and generate final payloadsignature=facade.sign_transaction(signer_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)# Announce and wait for confirmationtransaction_hash=facade.hash_transaction(transaction)print(f'Built aggregate transaction with hash: {transaction_hash}')announce_transaction(json_payload,'aggregate transaction')wait_for_confirmation(transaction_hash,'aggregate transaction')
// Sign and generate final payloadconstsignature=facade.signTransaction(signerKeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);// Announce and wait for confirmationconsttransactionHash=facade.hashTransaction(transaction).toString();console.log('Built aggregate transaction with hash:',transactionHash);awaitannounceTransaction(jsonPayload,'aggregate transaction');awaitwaitForConfirmation(transactionHash,'aggregate transaction');
# Fetch current metadata value from networkmetadata_path=(f'/metadata?sourceAddress={signer_address}'f'&targetAddress={signer_address}'f'&scopedMetadataKey={scoped_metadata_key:016X}'f'&targetId={mosaic_id:016X}''&metadataType=1')print(f'Fetching current metadata from {metadata_path}')withurllib.request.urlopen(f'{NODE_URL}{metadata_path}')asresponse:response_json=json.loads(response.read().decode())# Get the metadata entryifnotresponse_json['data']:raiseException('Metadata entry not found')metadata_entry=response_json['data'][0]['metadataEntry']current_value=bytes.fromhex(metadata_entry['value'])print(f' Current value: {current_value.decode("utf8")}')
// Fetch current metadata value from networkconstscopedKeyHex=scopedMetadataKey.toString(16).toUpperCase().padStart(16,'0');constmosaicIdHex=mosaicId.toString(16).toUpperCase().padStart(16,'0');constmetadataPath=`/metadata`+`?sourceAddress=${signerAddress}`+`&targetAddress=${signerAddress}`+`&scopedMetadataKey=${scopedKeyHex}`+`&targetId=${mosaicIdHex}`+'&metadataType=1';console.log('Fetching current metadata from',metadataPath);constmetadataResponse=awaitfetch(`${NODE_URL}${metadataPath}`);constmetadataJSON=awaitmetadataResponse.json();// Get the metadata entryif(!metadataJSON.data.length){thrownewError('Metadata entry not found');}constmetadataEntry=metadataJSON.data[0].metadataEntry;constcurrentValue=Buffer.from(metadataEntry.value,'hex');console.log(' Current value:',currentValue.toString('utf8'));
To retrieve the current value of a metadata entry, the code uses the /metadataGET endpoint
with filters for sourceAddress, targetAddress, scopedMetadataKey, targetId (the mosaic ID), and
metadataType (1 for mosaic metadata).
The endpoint returns the list of entries matching the filters, which in this case contains a single item.
# XOR the current and new valuesnew_value='Updated mosaic'.encode('utf8')update_value=metadata_update_value(current_value,new_value)# Create the update transaction with XOR'd valueembedded_update=facade.transaction_factory.create_embedded({'type':'mosaic_metadata_transaction_v1','signer_public_key':signer_key_pair.public_key,'target_address':signer_address,'target_mosaic_id':mosaic_id,'scoped_metadata_key':scoped_metadata_key,# value_size_delta is the difference in length# (can be negative)'value_size_delta':len(new_value)-len(current_value),'value':update_value})print('Created embedded update transaction:')print(json.dumps(embedded_update.to_json(),indent=2))
// XOR the current and new valuesconstnewValue=newTextEncoder().encode('Updated mosaic');constupdateValue=metadataUpdateValue(currentValue,newValue);// Create the update transaction with XOR'd valueconstembeddedUpdate=facade.transactionFactory.createEmbedded({type:'mosaic_metadata_transaction_v1',signerPublicKey:signerKeyPair.publicKey.toString(),targetAddress:signerAddress.toString(),targetMosaicId:mosaicId,scopedMetadataKey,// valueSizeDelta is the difference in length// (can be negative)valueSizeDelta:newValue.length-currentValue.length,value:updateValue});console.log('Created embedded update transaction:');console.log(JSON.stringify(embeddedUpdate.toJson(),null,2));
Updating an existing metadata entry requires the current value, retrieved from the network as previously shown.
To demonstrate updating metadata, the code changes the description from My first mosaic to Updated mosaic
by creating another mosaic_metadata_transaction_v1 transaction with the same scoped metadata key.
Modifying an existing metadata value differs from creating a new one in that the updated value must be defined
in terms of the current value, using the following fields:
value_size_delta: The difference in length between the new and current values.
In this example, the delta is -1 because the string Updated mosaic (14 bytes) is one byte shorter than
My first mosaic (15 bytes).
value: The XOR'd bytes computed by comparing the current and new values byte-by-byte.
The SDK provides a helper function that handles the XOR calculation.
The XOR operation compares each byte: matching bytes become zero, and differing bytes capture the change.
Note that value_size_delta represents the difference in final value lengths (new vs current),
not the length of the XOR'd bytes themselves.
Deleting a metadata entry
To delete a metadata entry, set value_size_delta to the negative of the current value length and provide the
current value as value. The XOR produces an empty result, which removes the entry from the network.
As with the initial metadata creation, this metadata modification is wrapped
in an aggregate transaction and then signed and announced.
# Build the aggregate for the updateembedded_transactions=[embedded_update]update_transaction=facade.transaction_factory.create({'type':'aggregate_complete_transaction_v3','signer_public_key':signer_key_pair.public_key,'deadline':timestamp.add_hours(2).timestamp,'transactions_hash':facade.hash_embedded_transactions(embedded_transactions),'transactions':embedded_transactions})update_transaction.fee=Amount(fee_mult*update_transaction.size)# Sign and announce the updatesignature=facade.sign_transaction(signer_key_pair,update_transaction)json_payload=facade.transaction_factory.attach_signature(update_transaction,signature)# Announce and wait for confirmationupdate_hash=facade.hash_transaction(update_transaction)print(f'Built aggregate transaction with hash: {update_hash}')announce_transaction(json_payload,'aggregate transaction')wait_for_confirmation(update_hash,'aggregate transaction')
// Build the aggregate for the updateconstupdateEmbedded=[embeddedUpdate];constupdateTransaction=facade.transactionFactory.create({type:'aggregate_complete_transaction_v3',signerPublicKey:signerKeyPair.publicKey.toString(),deadline:timestamp.addHours(2).timestamp,transactionsHash:facade.static.hashEmbeddedTransactions(updateEmbedded),transactions:updateEmbedded});updateTransaction.fee=newmodels.Amount(feeMult*updateTransaction.size);// Sign and announce the updateconstupdateSignature=facade.signTransaction(signerKeyPair,updateTransaction);constupdatePayload=facade.transactionFactory.static.attachSignature(updateTransaction,updateSignature);// Announce and wait for confirmationconstupdateHash=facade.hashTransaction(updateTransaction).toString();console.log('Built aggregate transaction with hash:',updateHash);awaitannounceTransaction(updatePayload,'aggregate transaction');awaitwaitForConfirmation(updateHash,'aggregate transaction');