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"
}
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"
}
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!