Create & Manage Assets in MOI
Prerequisites
To effectively utilise this guide, we recommend reading our guide on Submitting an Interaction and the documentation regarding, Interactions, Participants and Assets
Getting Started
This is a walkthrough of the different capabilities of Assets in MOI. We use the JS-MOI-SDK to submit Interactions and make RPC calls to the network. This can also be done directly with the JSON-RPC API or with MOI Voyage.
This walkthrough assumes that your code is already set up with VoyageProvider
and that the wallet signer for
sending Interactions is initialized. Refer to the section on Setting up JS-MOI-SDK.
Creating an Asset
We must first create an Asset in order to transfer and manage it. We can do this using the AssetCreate
Interaction
and specify the appropriate parameters and metadata. For the sake of this guide, we will first create an
MAS1 Asset (Non-Fungible Token) with the symbol CANDYLAND001.
- Code
- Output
const createAsset = async() => {
// Submit the AssetCreate Interaction and await the response
const response = await wallet.sendInteraction({
fuel_price: 1,
fuel_limit: 200,
ix_operations: [
{
type: OpType.ASSET_CREATE,
payload: {
standard: AssetStandard.MAS1,
symbol: "CANDYLAND001",
supply: 1 // The supply for MAS1 Assets must always be 1
}
}
]
})
// Obtain the Interaction Hash and print it
const ixhash = response.hash;
console.log("Interaction Hash: ", ixhash)
// Poll for the Interaction Receipt and print it
const receipt = await response.wait()
console.log("Interaction Receipt: ", receipt)
}
// Console Output
Interaction Hash: 0x2b442890ce3429aaab0aca675b70671e85cd47e1efb44c1a9ab484e0f6d71e79
Interaction Receipt: {
ix_hash: '0x2b442890ce3429aaab0aca675b70671e85cd47e1efb44c1a9ab484e0f6d71e79',
status: 0,
fuel_used: '0x64',
ix_operations: [
{
tx_type: '0x3',
status: 0,
data: {
asset_id: '0x0000000108ffe861df53c4c0c7d3cddeffd65070b90706d535a651bd64a6842bbded00b9',
address: '0x08ffe861df53c4c0c7d3cddeffd65070b90706d535a651bd64a6842bbded00b9'
}
}
],
from: '0x45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08',
ts_hash: '0xd7d81ffe706dff181b49b45c22c31b685745fbd0c68065f263de72e4c0ac6d2e',
participants: [
{
address: '0x08ffe861df53c4c0c7d3cddeffd65070b90706d535a651bd64a6842bbded00b9',
height: '0x0',
transitive_link: '0x0000000000000000000000000000000000000000000000000000000000000000',
prev_context: '0x0000000000000000000000000000000000000000000000000000000000000000',
latest_context: '0x877c6d9faa6a0323be70303559f5fbab923046e0002b7484c8cdfdde4c0c88a6',
context_delta: {
behavioural_nodes: [
'3WvobsLdcc3mi8dvtBdeTjEfgGZmJj1B65Mh9aW9EUJAb8xRuboD.16Uiu2HAmS15QRQdSB1iujqe6ufE3Y3B6Rv6gsPj65H1p2rhS8zjP'
],
random_nodes: [
'3Ww2HiS1ckNyMd28ng8rRs2Y8KHmB829AYbubY9AZgg2scPsvbFd.16Uiu2HAkvTBmuphPwjPa7eeeNZxWjXQDUtx9PHxeDtoi29Qrxw2M'
],
replaced_nodes: null
},
state_hash: '0x9afcad8fe3dfe11cf75d47aa97eb362694bf93063c6c1b67a8f0edb641c1be72'
},
{
address: '0x45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08',
height: '0x5',
transitive_link: '0x9e6baa663b1dbffa76c13af3696868294901f38f7d31ff62668ebbf7c0114e89',
prev_context: '0x637104288d0eb688bc7e8332f06db6e68a27b6427ed4458a6649ac2410f891ec',
latest_context: '0x637104288d0eb688bc7e8332f06db6e68a27b6427ed4458a6649ac2410f891ec',
context_delta: null,
state_hash: '0x55548a7ca5387866fc5bb18d37831dbb2c52d25f407215957a431f44e3c5656c'
},
{
address: '0xa6ba9853f131679d00da0f033516a2efe9cd53c3d54e1f9a6e60e9077e9f9384',
height: '0x6',
transitive_link: '0x9e6baa663b1dbffa76c13af3696868294901f38f7d31ff62668ebbf7c0114e89',
prev_context: '0x348561859bb43cb835672e29f9b0e5d8ec79c1c08fc5b9553f9c9fa73c0ff900',
latest_context: '0x348561859bb43cb835672e29f9b0e5d8ec79c1c08fc5b9553f9c9fa73c0ff900',
context_delta: null,
state_hash: '0xf65ea8ef201b434290699bebd89ff3ed04791f453189f83ecb520352b6939921'
}
]
}
Bingo! We have now successfully created an NFT called CANDYLAND001. We can see from the receipt that it has been created with the following address and Asset ID:
[
{
"asset_id": "0x0000000108ffe861df53c4c0c7d3cddeffd65070b90706d535a651bd64a6842bbded00b9",
"address": "0x08ffe861df53c4c0c7d3cddeffd65070b90706d535a651bd64a6842bbded00b9"
}
]
Retrieving Asset Information
Now that we have successfully created our Asset, let us retrieve some information about it with an RPC Call. We can also check that CANDYLAND001 is available in the spendable balances of the sender’s account.
Retrieving the Metadata of an Asset
- Code
- Output
const getAssetInfo = async() => {
// We get the Asset ID of CANDYLAND001 from the receipt of the Asset Create Interaction
const asset_id = "0x0000000108ffe861df53c4c0c7d3cddeffd65070b90706d535a651bd64a6842bbded00b9"
// Use the moi.AssetInfoByAssetID RPC to fetch Asset Metadata
const asset_info = await provider.getAssetInfoByAssetID(asset_id)
console.log("Asset Metadata: ", asset_info)
}
// Console Output
Asset Metadata: {
dimension: '0x0',
is_logical: false,
is_stateful: false,
operator: '0x45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08',
standard: '0x1',
supply: '0x1',
symbol: 'CANDYLAND001'
}
Retrieving the Balance of an Asset for a Participant
const getAssetBalance = async() => {
// We get the address of the participant who created the Asset,
// the initial supply of the Asset would have been credited to them
const address = await wallet.getAddress();
// We get the Asset ID of CANDYLAND001 from the receipt of the Asset Create Interaction
const asset_id = "0x0000000108ffe861df53c4c0c7d3cddeffd65070b90706d535a651bd64a6842bbded00b9"
// Use the moi.Balance RPC to fetch the balance of an Asset for a
// specific Asset ID. The moi.TDU RPC can be used to fetch all balances
const balance = await provider.getBalance(address, asset_id)
console.log("Asset Balance: ", balance)
}
// Console Output
Asset Balance: 1
Transferring an Asset between Participants
Now that we have a functional Asset, let's transfer this NFT to another Participant. MOI allows Participants to
transfer multiple assets at once because all Assets are recognized natively in the account’s balances. While
transferring assets with the AssetTransfer
Interaction, only the token ownership moves between Participants,
the operatorship of the Asset is not modified. The code for transferring the CANDYLAND001 Asset is as follows:
- Code
- Output
const transferAssets = async() => {
// Submit the AssetTransfer Interaction and await the response
const response = await wallet.sendInteraction({
fuel_price: 1,
fuel_limit: 200,
ix_operations: [
{
type: OpType.ASSET_TRANSFER,
payload: {
beneficiary: "0x6ef7715969a7a99edf06957e94494e40203b4be2b0ffb325196677a82de6fcb6",
asset_id: "0x0000000108ffe861df53c4c0c7d3cddeffd65070b90706d535a651bd64a6842bbded00b9",
amount: 1
}
}
]
})
// Obtain the Interaction Hash and print it
const ixhash = response.hash;
console.log("Interaction Hash: ", ixhash)
// Poll for the Interaction Receipt and print it
const receipt = await response.wait()
console.log("Interaction Receipt: ", receipt)
}
// Console Output
Interaction Hash: 0x8b885068cb62864539177507a2687e5f066d26d2cd17f1c16639fdd2d04a2e13
Interaction Receipt: {
ix_hash: '0x8b885068cb62864539177507a2687e5f066d26d2cd17f1c16639fdd2d04a2e13',
status: 0,
fuel_used: '0x64',
ix_operations: [ { tx_type: '0x1', status: 0, data: null } ],
from: '0x45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08',
ts_hash: '0x71f4a213a0c8aefb98b52c7cd8d5173973a808a694b11adb67daa5d2e0c28b00',
participants: [
{
address: '0x45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08',
height: '0x6',
transitive_link: '0xd7d81ffe706dff181b49b45c22c31b685745fbd0c68065f263de72e4c0ac6d2e',
prev_context: '0x637104288d0eb688bc7e8332f06db6e68a27b6427ed4458a6649ac2410f891ec',
latest_context: '0x637104288d0eb688bc7e8332f06db6e68a27b6427ed4458a6649ac2410f891ec',
context_delta: null,
state_hash: '0x474183d0deeccd59f2fcc2ba2a995cda096eced59e6d30c64bcb0473941ae3ff'
},
{
address: '0x6ef7715969a7a99edf06957e94494e40203b4be2b0ffb325196677a82de6fcb6',
height: '0x0',
transitive_link: '0x0000000000000000000000000000000000000000000000000000000000000000',
prev_context: '0x0000000000000000000000000000000000000000000000000000000000000000',
latest_context: '0x5c2f5281df903d082e46349558c8ea26bdddeeca502a14b344e104c163c930e6',
context_delta: {
behavioural_nodes: [
'3Wvdsur7X92s2bkwgWomxxnYZnXbU4xfuv1HS4ELqTwaNqcdKTq9.16Uiu2HAmRKggu3i2N26J7ofAHnfV6Sn3WTzRznkN2HierXvL9AxB'
],
random_nodes: [
'3WyySN6RKQ2iWpzjoBKpzSrkLzuye3Jm7nEEmCzyvwrovZ4C6ekw.16Uiu2HAmQwpuc5d6GYoogPfatowFddPeCzVMmGzwEUuRN94ksnh7'
],
replaced_nodes: null
},
state_hash: '0x723c9975b99dce3c3b833e1d5e87b827f04418039c0c1da8be4c2d3121a3bdcd'
},
{
address: '0xa6ba9853f131679d00da0f033516a2efe9cd53c3d54e1f9a6e60e9077e9f9384',
height: '0x7',
transitive_link: '0xd7d81ffe706dff181b49b45c22c31b685745fbd0c68065f263de72e4c0ac6d2e',
prev_context: '0x348561859bb43cb835672e29f9b0e5d8ec79c1c08fc5b9553f9c9fa73c0ff900',
latest_context: '0x348561859bb43cb835672e29f9b0e5d8ec79c1c08fc5b9553f9c9fa73c0ff900',
context_delta: null,
state_hash: '0x4a3e1d03b73e8f1486767b72963091bd6b5872bdaab0c6adbcbbddec0a5269dc'
}
]
}
SUCCESS! We have successfully transferred the CANDYLAND001 Asset from the sender 0x9c6..30a
to
the receiver 0x6ef..cb6
.
Transferring Operatorship of an Asset
When an Asset is created, the creator is marked as its operator. Only the operator can perform privileged actions such as modulating its supply. Currently, the operatorship of Assets is static and cannot be transferred, but this will change in the coming months.
Adjusting the Supply of an Asset
Supply Modulation of Assets involves the creation (minting) or destruction (burning) of tokens to manipulate its total supply. This action can only be performed on Assets of the MAS0 standard are also only capable of being performed by the operator of the Asset. Any minted or burned tokens are credited to or debited from the spendable balances of the operator.
The CANDYLAND001 Asset we have used until now does not fit support supply modulation. So we will now create
another Asset of the MAS0 standard called MYTOKEN with a 100 million tokens as the initial supply with
AssetCreate
Interaction.
- Code
- Output
const createAsset = async() => {
// Submit the AssetCreate Interaction and wait for the response
const response = await wallet.sendInteraction({
fuel_price: 1,
fuel_limit: 200,
ix_operations: [
{
type: OpType.ASSET_CREATE,
payload: {
standard: AssetStandard.MAS0,
symbol: "MYTOKEN",
supply: 100000000
}
}
]
})
// Poll for the Interaction Receipt
const receipt = await response.wait()
// We get the Asset ID of MYTOKEN from the receipt and print it
const asset_id = receipt.ix_operations[0].data.asset_id
console.log("Asset Created: ", asset_id)
// Use the moi.AssetInfoByAssetID RPC to fetch Asset Metadata
const asset_info = await provider.getAssetInfoByAssetID(asset_id)
console.log("Asset Supply: ", asset_info.supply)
}
// Console Output
Asset Created: 0x00000000a314461000bbc64ad953b468abfd2c39103862334ce6cbc301c906391d1bbb4a
Asset Supply: 0x5f5e100 // 100,000,000
Minting New Tokens (Increasing the Supply)
We will now increase the supply of MYTOKEN by 10,000 tokens using the AssetMint
Interaction and
verify that the supply has adjusted accordingly.
- Code
- Output
const mintAsset = async() => {
const response = await wallet.sendInteraction({
fuel_price: 1,
fuel_limit: 200,
ix_operations: [
{
type: OpType.ASSET_MINT,
payload: {
asset_id: asset_id,
amount: 10000
}
}
]
})
// Poll for the Interaction Receipt
const receipt = await response.wait()
// We get the updated supply of MYTOKEN from the receipt and print it
const new_supply = receipt.ix_operations[0].data.total_supply
console.log("New Supply: ", new_supply)
}
// Console Output
New Supply: 0x5f60810 // 100,010,000
Burning Existing Tokens (Decreasing the Supply)
The current supply of MYTOKEN is 100,010,00 tokens. We will now decrease the supply by 100,000 using
the AssetBurn
Interaction and verify that the supply adjusted accordingly.
- Code
- Output
const burnAsset = async() => {
const response = await wallet.sendInteraction({
fuel_price: 1,
fuel_limit: 200,
ix_operations: [
{
type: OpType.ASSET_BURN,
payload: {
asset_id: asset_id,
amount: 100000
}
}
]
})
// Poll for the Interaction Receipt
const receipt = await response.wait()
// We get the updated supply of MYTOKEN from the receipt and print it
const new_supply = receipt.ix_operations[0].data.total_supply
console.log("New Supply: ", new_supply)
}
// Console Output
New Supply: 0x5f48170 // 99,910,000