Skip to main content

Submitting an Interaction

Prerequisites

To effectively utilise this guide, we recommend reading the docs regarding Interactions.

Getting Started

This is a beginner’s guide to sending a MOI Interaction using the JS-MOI-SDK. Interactions can also be fired with the JSON-RPC API but users would have to manage the serialisation and signing manually. Alternatively, MOI Voyage offers a simple GUI playground to make RPC Calls and Send Interactions.

Setting up JS-MOI-SDK

The JS-MOI-SDK package is a client library for interacting with the MOI Network using its JSON-RPC interface and can handle POLO serialization and Interaction signing as well. The JS-MOI-SDK package is published on NPM and can be installed from NPM.

npm i js-moi-sdk

To access the JSON-RPC API of the protocol, we usually use the JsonRpcProvider provider but this is applicable only if you are connecting to a node you have access to. The MOI Voyage service provides access to Babylon Protocol RPC with gated and rate-limited access to it when using the VoyageProvider provider.

import { VoyageProvider } from "js-moi-sdk";

// Setup the Voyage JSON-RPC Provider for the Babylon Network
const provider = new VoyageProvider('babylon');

To sign Interactions with JS-MOI-SDK, we need to set up the wallet signer for the sender account. This can be done from a private key mnemonic (for testing) or with a wallet keystore (for production).

Initialize the Wallet with a Private Key Mnemonic

When first registering with MOI Voyage, it will generate 3 different key pairs in your HD wallet that can be used to work with the Babylon Network. Each of these key pairs are derived from the master private key of the wallet with derivation path. You should be able to view this path listed along side the each of the 3 available accounts in Voyage.

For testing and development, we can directly derive the private key of the account from which we wish to send Interaction using its master private key mnemonic and derivation path to the key pair.

// Declare the private key mnemonic for the wallet
const mnemonic = "dizzy soft dwarf ice club crouch mutual outside month shrimp whisper dad";

// Declare the HD wallet path. This path is obtained from your MOI Voyage Dashboard
const path = "m/44'/6174'/7020'/0/0"

Lastly, we define a helper function to load the wallet signer instance for a given provider. This function can be called to return a new instance of the wallet signer for the account parameters we discussed above.

import { Wallet } from "js-moi-sdk";

const loadWallet = async (provider, mnemonic) => {
const wallet = await Wallet.fromMnemonic(mnemonic, path);
wallet.connect(provider);
return wallet;
};

Initialize the Wallet from its Keystore

We can use the wallet keystore to initialize the wallet signer. The keystore is a JSON data structure.

const keystore = `{
"cipher": "aes-128-ctr",
"ciphertext": "...",
"cipherparams": {
"IV": "..."
},
"kdf": "scrypt",
"kdfparams": {
"n": 4096,
"r": 8,
"p": 1,
"dklen": 32,
"salt": "..."
},
"mac": "..."
}`;

We can then use the Wallet.fromKeystore method to initialize the wallet signer from the keystore.

import { Wallet } from "js-moi-sdk";

const loadWalletFromKeystore = async (provider, keystore, password) => {
const wallet = Wallet.fromKeystore(keystore, password);
wallet.connect(provider);
return wallet;
};

Steps to Submit an Interaction

In this guide, we will discuss firing a simple AssetTransfer Interaction to transfer 50 tokens of the Asset ID 00000000b8fe9f7f6255a6fa08f668ab632a8d081ad87983c77cd274e48ce450f0b349fd to the receiver 0x916f62f7bdf311ba46d9df465283aa4e97a5dcf36e4c0761bc7d87d32557f8cc.

Refer to the JSON-RPC Documentation for the payload format of other Interaction types.

1. Constructing an Interaction

To submit an Interaction, we need to construct a payload object which must then be POLO-serialized and then signed by the private key of the sender’s account. This raw Interaction data and the signature are sent to the network with moi.SendInteraction RPC call.

We construct the Interaction payload as follows:

// AssetTransfer Interaction to transfer 50 tokens of an Asset 0x..49fd 
// from the initialized wallet to the address, 0x916..8cc.

const interaction = {
fuel_price: 1,
fuel_limit: 100,
nonce: 1,
ix_operations: [
{
type: OpType.ASSET_TRANSFER,
payload: {
beneficiary: "0x916f62f7bdf311ba46d9df465283aa4e97a5dcf36e4c0761bc7d87d32557f8cc",
asset_id: "0x00000000d657e031456f57d8aec05a624006a77ed7b9c2b5e6fc5368d4151e3fdf7724c2",
amount: 100
}
}
]
}

2. Serializing & Signing the Interaction

This interaction must be serialized and signed by private key of the wallet to generate an object payload for the moi.SendInteraction RPC Call. This can be done with the following code:

// Serialize the Interaction and sign it with the wallet

const wallet = await loadWallet(provider, mnemonic);

const payload = wallet.signInteraction(interaction);
console.log("Payload:", payload);
Payload: {
"ix_args": "0ebf0206860483089308a308be08ae12ae25802f802f45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f0800000000000000000000000000000000000000000000000000000000000000000301641f0e3f068309303030303030303064363537653033313435366635376438616563303561363234303036613737656437623963326235653666633533363864343135316533666466373732346332641f0e2f0316010e7f068604860883110000000000000000000000000000000000000000000000000000000000000000916f62f7bdf311ba46d9df465283aa4e97a5dcf36e4c0761bc7d87d32557f8cc303030303030303064363537653033313435366635376438616563303561363234303036613737656437623963326235653666633533363864343135316533666466373732346332643f0ede043f06830445b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08013f068304916f62f7bdf311ba46d9df465283aa4e97a5dcf36e4c0761bc7d87d32557f8cc01",
"signature": "0146304402202c7991471c63099e818fe6dd298e495ea82a01002cbc9d086b96302fd2383114022051d216f0810811efb3e38b47f7c3b17f863e8b48cd26f36f18aab087794bcb4a03"
}
info

This process of preparing the final payload is handled automatically by the JS-MOI-SDK and does not need to be performed by the developer. We can now proceed to submit the Interaction.

3. Submitting the Interaction

We now submit the interaction to the network and get the Interaction Hash for the submitted Interaction as a response. This Interaction Hash is cryptographically generated and acts as a unique identifier for the submitted Interaction.

When sending the Interaction with wallet.sendInteraction, the nonce and sender fields are automatically added and do not need to be manually entered as we showed above.

// Submit the Interaction to the network
const ix = await wallet.sendInteraction(interaction)

// Obtain the Interaction Hash from the response and print it
console.log("Interaction Hash: ", ix.hash)
// Console Output
Interaction Hash: 0xedf953a3769450911755765f090b75aedad14ee42a20bab485927b331b20a6e7

4. Wait for the Interaction Receipt

Now that we have submitted the Interaction, we wait for the Interaction Receipt confirming that the Interaction has been finalized. This usually occurs within 0.2 seconds, but if the load on the network is very high, it can take a little longer. We can poll for the status on receipt with the following code:

// Poll the network for the Interaction Receipt and print it
const receipt = await ix.wait()
console.log("Interaction Receipt: ", receipt)
// Console Output
Interaction Receipt: {
ix_hash: '0xedf953a3769450911755765f090b75aedad14ee42a20bab485927b331b20a6e7',
status: 0,
fuel_used: '0x64',
ix_operations: [ { tx_type: '0x1', status: 0, data: null } ],
from: '0x45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08',
ts_hash: '0x9e6baa663b1dbffa76c13af3696868294901f38f7d31ff62668ebbf7c0114e89',
participants: [
{
address: '0x45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08',
height: '0x4',
transitive_link: '0xdbf675623c615c6339255fa4328a1084fe5584cab5c1c9a5f893984824ff3251',
prev_context: '0x637104288d0eb688bc7e8332f06db6e68a27b6427ed4458a6649ac2410f891ec',
latest_context: '0x637104288d0eb688bc7e8332f06db6e68a27b6427ed4458a6649ac2410f891ec',
context_delta: null,
state_hash: '0x694bfae20b75ca2f17cdb931445011425fa8b8abd80b0758648fcdc578197ff7'
},
{
address: '0x916f62f7bdf311ba46d9df465283aa4e97a5dcf36e4c0761bc7d87d32557f8cc',
height: '0x0',
transitive_link: '0x0000000000000000000000000000000000000000000000000000000000000000',
prev_context: '0x0000000000000000000000000000000000000000000000000000000000000000',
latest_context: '0x4d20097c4c7eb7d1a8e0cc3833e7d1bb805e9a463ffdeec8b71b6ef9a31d4737',
context_delta: {
behavioural_nodes: [
'3WwXxVcGVAmRUh3P3Vr8ofZuCQwm1WhnWQdZVr7BMARMRXu6JiFR.16Uiu2HAmBpqJgCzkAJDeNMGuUZKLzvawnAvohnSsDWv7XhgL9NtC'
],
random_nodes: [
'3WvobsLdcc3mi8dvtBdeTjEfgGZmJj1B65Mh9aW9EUJAb8xRuboD.16Uiu2HAmS15QRQdSB1iujqe6ufE3Y3B6Rv6gsPj65H1p2rhS8zjP'
],
replaced_nodes: null
},
state_hash: '0x8a1a1f29878d3939854f0ea3de0a10a698ac29ba5a2598e42fccdde96fbac7e7'
},
{
address: '0xa6ba9853f131679d00da0f033516a2efe9cd53c3d54e1f9a6e60e9077e9f9384',
height: '0x5',
transitive_link: '0xdbf675623c615c6339255fa4328a1084fe5584cab5c1c9a5f893984824ff3251',
prev_context: '0x348561859bb43cb835672e29f9b0e5d8ec79c1c08fc5b9553f9c9fa73c0ff900',
latest_context: '0x348561859bb43cb835672e29f9b0e5d8ec79c1c08fc5b9553f9c9fa73c0ff900',
context_delta: null,
state_hash: '0xb5a41c121097dd33e0279219c2c97ed9d8e081d87e62365f4f7f115ba9972f45'
}
]
}

Steps to Submit an Interaction with Multiple Operations

In this guide, we will walk through submitting an interaction containing multiple operations on the MOI network. We will transfer tokens to different receivers and perform various operations like asset minting, deployment, etc., within a single interaction.

Note: The MOI protocol currently accepts a maximum of three operations per interaction.

1. Constructing an Interaction

To submit an interaction with multiple operations, we create a payload that includes multiple operation objects, each specifying its action type and payload. These operations are serialized and signed together within a single interaction.

Here's an example of constructing an interaction containing two ix_operations: asset create and asset mint:

// Interaction with multiple ix_operations: Asset create and mint
const interaction = {
fuel_price: 1,
fuel_limit: 200,
nonce: 1,
ix_operations: [
{
type: OpType.ASSET_CREATE,
payload: {
standard: AssetStandard.MAS1,
symbol: "CANDYLAND001",
supply: 1 // The supply for MAS1 Assets must always be 1
}
},
{
type: OpType.ASSET_MINT,
payload: {
asset_id: "0x00000000b9a9d618867bec092db71c06c368a6d7f78dc01cf36f86a35991fee11303c3d9",
amount: 100
}
}
]
}

2. Serializing & Signing the Interaction

Like before, this interaction must be serialized and signed with the sender’s private key to generate the required payload for the moi.SendInteraction RPC call. This can be done with the following code:

// Serialize the Interaction and sign it with the wallet

const wallet = await loadWallet(provider, mnemonic);

const payload = wallet.signInteraction(interaction);
console.log("Payload:", payload);
Payload: {
"ix_args": "0ebf0206860483089308a308be08ae12ae21802b802b45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f0800000000000000000000000000000000000000000000000000000000000000000101c81f0e3f068309303030303030303062396139643631383836376265633039326462373163303663333638613664376637386463303163663336663836613335393931666565313133303363336439643f0eae042f0316030edf0106c301d301e301e101e101e00143414e44594c414e4430303101012f0316060e3f068309303030303030303062396139643631383836376265633039326462373163303663333638613664376637386463303163663336663836613335393931666565313133303363336439643f0ede043f06830445b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08013f068304b9a9d618867bec092db71c06c368a6d7f78dc01cf36f86a35991fee11303c3d901",
"signature": "0146304402206e5d21f04a6b49a3d5d994358432056b247d4ae8f3295e8a785cefbc02442d930220434deed0d9597ed33349d689056f8ef4db65fa5f4fcb8e001a65c38bb526cd6a03"
}
info

This process of preparing the final payload is handled automatically by the JS-MOI-SDK and does not need to be performed by the developer. We can now proceed to submit the Interaction.

3. Submitting the Interaction

After preparing the signed payload, submit the interaction containing the multiple operations to the network using the sendInteraction method. The system automatically appends necessary fields like nonce and sender.

// Submit the Interaction to the network
const ix = await wallet.sendInteraction(interaction)

// Obtain the Interaction Hash from the response and print it
console.log("Interaction Hash: ", ix.hash)
// Console Output
Interaction Hash: 0x2524b124430758a3dbdf02a45b3bf1c81911e627c1bf35b90d68afe4366c8f94

4. Wait for the Interaction Receipt

Once submitted, poll for the interaction receipt to verify that all operations within the interaction have been processed. The receipt will include the result of each operation.

// Poll the network for the Interaction Receipt and print it
const receipt = await ix.wait()
console.log("Interaction Receipt: ", receipt)
// Console Output
Interaction Receipt: {
ix_hash: '0x2524b124430758a3dbdf02a45b3bf1c81911e627c1bf35b90d68afe4366c8f94',
status: 0,
fuel_used: '0xc8',
ix_operations: [
{
tx_type: '0x3',
status: 0,
data: {
asset_id: '0x0000000196c93a80bc13e4864b485937d5aca52a2e61135b03e4918c58cc2bcc1b9e7a6b',
address: '0x96c93a80bc13e4864b485937d5aca52a2e61135b03e4918c58cc2bcc1b9e7a6b'
}
},
{
tx_type: '0x6',
status: 0,
data: {
total_supply: '0x5f5e164'
}
}
],
from: '0x45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08',
ts_hash: '0xb094ba2c37db84b9945f82b6d5042ffd81287f55965fd314ba10b7d7a189bfe1',
participants: [
{
address: '0x45b9906e65c9bdf4703918aa2c78fe139ba8e32c5e0dcda585dac4c584651f08',
height: '0x2',
transitive_link: '0x55f3b142ac595923018562b0cccdcabc3ffadfb7ca5f0089552af6f2f3ffc65e',
prev_context: '0x637104288d0eb688bc7e8332f06db6e68a27b6427ed4458a6649ac2410f891ec',
latest_context: '0x637104288d0eb688bc7e8332f06db6e68a27b6427ed4458a6649ac2410f891ec',
context_delta: null,
state_hash: '0xd503aea03cfbd8bda19dea6fa0fd91fba18e9a78328434b33b9615c5d1fc7d53'
},
{
address: '0x96c93a80bc13e4864b485937d5aca52a2e61135b03e4918c58cc2bcc1b9e7a6b',
height: '0x0',
transitive_link: '0x0000000000000000000000000000000000000000000000000000000000000000',
prev_context: '0x0000000000000000000000000000000000000000000000000000000000000000',
latest_context: '0x4c48e0914649d092c815edc253592b2286758a471522d9f26e365291e1bd0bc6',
context_delta: {
behavioural_nodes: [
'3WzTAsf9L2jib7QrFGEULCPkdV9BLmg4QQg2xbcynH7174ar1g1M.16Uiu2HAm1cfqwj2YKmVk3H955aKb4E4nSTtFSC1wfoJ2YF9zV2UJ'
],
random_nodes: [
'3WyrLrR4BaJFsp6AyijjE65E3xoQWTw6n3wHLDFpauLWk8dGQk3y.16Uiu2HAm8XxGdU9D3dCiaNcnJysjv6orv3UH8ybxw4GL2L3wp58e'
],
replaced_nodes: null
},
state_hash: '0xc8bb74903d19c72a6212dbb050819a4973291a3da5908b62b3098c4227d639f5'
},
{
address: '0xa6ba9853f131679d00da0f033516a2efe9cd53c3d54e1f9a6e60e9077e9f9384',
height: '0x2',
transitive_link: '0x55f3b142ac595923018562b0cccdcabc3ffadfb7ca5f0089552af6f2f3ffc65e',
prev_context: '0x348561859bb43cb835672e29f9b0e5d8ec79c1c08fc5b9553f9c9fa73c0ff900',
latest_context: '0x348561859bb43cb835672e29f9b0e5d8ec79c1c08fc5b9553f9c9fa73c0ff900',
context_delta: null,
state_hash: '0x20c59fd640016e8403fd591027400ae3b3dba05b5caadf2244f62021a05b960f'
},
{
address: '0xb9a9d618867bec092db71c06c368a6d7f78dc01cf36f86a35991fee11303c3d9',
height: '0x1',
transitive_link: '0x55f3b142ac595923018562b0cccdcabc3ffadfb7ca5f0089552af6f2f3ffc65e',
prev_context: '0x6cc73bd750af73cb9d24682fc8013dc30e0274b642921e88ba5f566d28bc9024',
latest_context: '0x6cc73bd750af73cb9d24682fc8013dc30e0274b642921e88ba5f566d28bc9024',
context_delta: null,
state_hash: '0xf4509af6f2af668c748acdf896755fec70812a77780b8d0bfb6b82cfee264d14'
}
]
}

Further Reading

Now that we have successfully submitted a simple AssetTransfer Interaction, we can attempt to build simple applications that leverage the capabilities of MOI using the Asset and Logic capabilities of the protocol. Happy Hacking!