Complex Asset Transfers
Overview
Inside each address, there are two parts associated with assets as part of their state:
- A public key: to control access of the assets
- A balance: consisting of multiple boxes, the sum of the amounts contained in those boxes is the number of assets in this address
Assets in an address are transferred by signing using the private key corresponding to the public key as we have explored in the first asset tutorial.
Split Boxes
One asset box can be split into multiple asset boxes. This is mostly used to send assets to others while returning the change. Below is an example of splitting it into two. The total number of assets are conserved after the split i.e. x0 + x1 + y0 + y1 = x + y

The relevant code is listed below with self-explanatory comments inline
const BramblJS = require("brambljs);
/**
* Sends a signed transaction
*
*/
async function sendSignedTransaction(signedTransactionData, brambljs) {
let obj = {};
return new Promise((resolve, reject) => {
let e = brambljs.requests
.broadcastTx({ tx: signedTransactionData })
.then(function (result) {
obj.txId = result.result.txId;
obj.result = result.result;
resolve(obj);
})
.catch(function (err) {
console.error(err);
obj.error = err.message;
reject(err);
});
return e;
});
}
/**
* Sign transaction with tx object and private key
* @param {Object} txObject is the transaction object
* @return {(Promise|Object)} signed object with rawTransaction data to be uused for sending transaction
*/
async function signTransaction(txObject, brambljs) {
let obj = {};
return await brambljs.addSigToTx(txObject.messageToSign.result, txObject.keys).catch(function (err) {
console.error(err);
obj.error = err.message;
return obj;
});
}
/**
* First signs a transaction, then sends the signedTransaction to the network to be confirmed into a block
*
*/
async function signAndSendTransaction(txObject, brambljs) {
let obj = {};
return signTransaction(txObject, brambljs).then(function (value) {
if (value.error) {
obj.error = value.error;
return obj;
} else {
return sendSignedTransaction(value, brambljs).then(function (value) {
if (value.error) {
obj.error = value.error;
return obj;
} else {
return value;
}
});
}
});
}
/**
* Gets the raw transaction object on the asset transaction you plan on signing before sending.
* Allows verification of the asset transaction is correct as well as providing the message to sign
* @param {Object} txObject is the req.body from the service that has passed validation
* @return {Object} is the completed object that contains data about poly transactions and the message to sign.
*/
async function sendRawAssetTransaction(txObject, brambljs) {
let obj = {};
const formattedRecipients = [];
obj.keys = [txObject.senderKeyManager, txObject.issuerKeyManager]
const params = {
propositionType: txObject.propositionType,
recipients: txObject.recipients,
fee: txObject.fee,
sender: [txObject.senderKeyManager.address],
changeAddress: txObject.changeAddress,
data: txObject.data,
consolidationAddress: txObject.consolidationAddress,
minting: txObject.minting,
assetCode: txObject.assetCode,
};
return brambljs.requests
.createRawAssetTransfer(params)
.then(function (result) {
obj.messageToSign = result;
return obj;
})
.catch(function (err) {
console.error(err);
obj.err = err.message;
return obj;
});
}
async function assetTransaction(txObject, brambljs) {
// first create the rawAssetTransfer transaction
return sendRawAssetTransaction(txObject, brambljs).then(function(rpcResponse) {
if (rpcResponse.err) {
throw Error(rpcResponse.err);
}
// then sign and send the transaction
return signAndSendTransaction(rpcResponse, brambljs)
});
}
async function split(senderKeyManager,assetIssuerKeyManager, recipientAddress, assetAmount, consolidationKeyManager, consolidationAmount, fee, securityRoot, metadata, assetCode, brambljs) {
// send assetAmount of your asset to the recipient, then consolidationAmount to the consolidationAddress
const txObject = {
senderKeyManager: senderKeyManager,
issuerKeyManager: assetIssuerKeyManager,
recipients: [[
recipientAddress,
assetAmount,
securityRoot,
metadata
]],
propositionType: "PublicKeyCurve25519",
changeAddress: senderKeyManager.address,
consolidationAddress: consolidationKeyManager.address,
minting: false,
fee: fee,
assetCode: assetCode
}
const pollParams = {
"timeout": 90,
"interval": 3,
"maxFailedQueries": 10
};
// first create the rawAssetTransfer transaction
await assetTransaction(txObject, brambljs).then(res => brambljs.pollTx(res.txId, pollParams));
txObject.senderKeyManager = consolidationKeyManager
txObject.recipients = [
[
consolidationKeyManager.address,
consolidationAmount,
securityRoot,
metadata
]]
txObject.changeAddress = consolidationKeyManager.address
txObject.consolidationAddress = consolidationKeyManager.address
// second, update the securityRoot and metadata on the assets that were sent to the consolidationAddress
return await assetTransaction(txObject, brambljs).then(res => brambljs.pollTx(res.txId, pollParams));
}
// Above are helper functions, main js logic here
// please replace "yourProjectId" with your project id
const networkUrl = "https://vertx.topl.services/valhalla/{{yourProjectId}}";
// please replace "yourApiKey" with your apiKey
const apiKey = "yourApiKey";
const password = "test";
const networkPrefix = "valhalla";
// please replace with your encrypted keyfile and password
const recipientKeyManager = BramblJS.KeyManager.importKeyFileFromDisk("src/valhalla_keyfiles/your_valhalla_key_file", "yourPassword")
const brambljs = new BramblJS({
networkPrefix: networkPrefix,
password: password,
Requests: {
url: `${networkUrl}`,
apiKey: `${apiKey}`,
},
KeyManager: recipientKeyManager
});
const assetCode = brambljs.createAssetCode("covfefe");
const txObject = {
senderKeyManager: brambljs.keyManager,
issuerKeyManager: brambljs.keyManager,
recipients: [[
brambljs.keyManager.address,
10
]],
propositionType: "PublicKeyCurve25519",
changeAddress: brambljs.keyManager.address,
consolidationAddress: brambljs.keyManager.address,
minting: true,
fee: 100,
assetCode: assetCode
}
const pollParams = {
"timeout": 90,
"interval": 3,
"maxFailedQueries": 10
};
const securityRoot = "11111111111111111111111111111111"
const metadata = "metadata"
assetTransaction(txObject, brambljs).then(res => brambljs.pollTx(res.txId, pollParams)).then(res => console.log(split(brambljs.keyManager, brambljs.keyManager, "3NK4UGDB1b1E9eYfPtRKwpMXoSCQFctwq2ptkwxjCRDHgxK9nt4P", 5, brambljs.keyManager, 5,100,
securityRoot, metadata, assetCode, brambljs
)).then(function(result) {
return result;
});
Demo
Above we have the code to generate the asset, then split the assets.
Step 1: Issue Polys
- Polys need to be added to the sending account of the transaction, there will need to be at least 300 nanopolys in your address in order for the code above to work properly. You can find out how to load your address with polys at the Adding Polys page
Step 2: Issue Assets
- 10 Assets are issued in the code above
Step 3: Split Asset Box
- The Asset Box of 10 assets are split into two asset boxes: one containing 5 assets, and the other box containing 5 assets. Those boxes are owned by the recipientAddress and the consolidationAddress respectively
Step 4: Make sure that the securityRoot and metadata are attached to the box owned by the consolidationAddress
- By default, there is no securityRoot and metadata attached to the boxes that are sent to the consolidationAddress. Thus, we need to make another transaction to add that information back to the box.
Updated over 1 year ago