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 Public Devnet
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 Beta Network
const provider = new VoyageProvider("devnet");
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 Public Devnet. 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;
};
const sender = async () => ({
id: (await wallet.getIdentifier()).toHex(),
key_id: await wallet.getKeyId(),
sequence: await wallet.getNonce(),
});
const operator = await sender();
Steps to Submit an Interaction
In this guide, we will fire a simple StorageDeposit Interaction, which converts KMOI into a prepaid storage
allowance. It reserves 1000 KMOI worth of storage on the logic account
0x200000009f6b3348f43e93fdfa30cd4898653115f569dda495f7392700000000 and credits the allowance to the sender's
account 0x00000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f2700000000.
Refer to the Operation Reference for the payload format of every other operation type.
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:
// StorageDeposit Interaction buying a storage allowance on the logic
// account 0x2000..3927, credited to the sender's account 0x0000..3f27.
const interaction = {
sender: operator,
fuel_price: 1,
fuel_limit: 10000,
ix_operations: [
{
type: OpType.STORAGE_DEPOSIT,
payload: {
target_account:
"0x200000009f6b3348f43e93fdfa30cd4898653115f569dda495f7392700000000",
deposit_for:
"0x00000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f2700000000",
amount: 1000,
},
},
],
};
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 = await wallet.signInteraction(interaction, SigType.EOA);
console.log("Payload:", payload);
Payload:
{
ix_args: '0e9f020ef604f3088309a009ae09be13b01db01d5f068304930400000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f27000000003c00000000000000000000000000000000000000000000000000000000000000000127101f0e2f0316100e7f0686048308a308200000009f6b3348f43e93fdfa30cd4898653115f569dda495f739270000000000000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f270000000003e83f0eee045f068304810400000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f27000000005f0683048104200000009f6b3348f43e93fdfa30cd4898653115f569dda495f7392700000000',
signatures: '0e1f0e5f068304860400000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f27000000000146304402203b349652fbe7f705c97de85a61282ba9bc9aaf6f745d78440e6033fb108811a90220049b6832d4c06e2fb234cde6852502a30a78558290325b8a1195f91e197181ba03'
}
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 sender field and its sequence number are filled in
automatically, so the explicit sender shown above can be omitted.
// Submit the Interaction to the network
const ix = await wallet.sendInteractions(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": "0xf5a825828761d6fdf8f785c5df27608fd4b7b1c80456e779452c8abb99ef3894",
"status": 0,
"fuel_used": "0x8d9",
"ix_operations": [
{
"tx_type": "0x10",
"status": 0,
"data": null
}
],
"from": "0x00000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f2700000000",
"ts_hash": "0xeb9f206d58df4be19eaf6d7eed7eba63a33b1703461e813e37567bd0c623a04f",
"participants":
[
{
"id": "0x00000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f2700000000",
"height": "0x9",
"transitive_link": "0xe316b956a43e944faf5e194d7b22dc76316f20a1787fcaca509c227153f1f66f",
"locked_context": "0x08d829a5911b07af4741fb77cd7d33d0cd6b81f1bf335314de9281ec8d30c4f6",
"context_delta": null,
"state_hash": "0x829ff831050723b2cb472587a627ff3ad1f50f351a6622e8ee015930e85ce750"
},
{
"id": "0x200000009f6b3348f43e93fdfa30cd4898653115f569dda495f7392700000000",
"height": "0x0",
"transitive_link": "0x0000000000000000000000000000000000000000000000000000000000000000",
"locked_context": "0x0000000000000000000000000000000000000000000000000000000000000000",
"context_delta": {
"consensus_nodes": [
"1116Uiu2HAmBtsBV1wCtFdTjGHerv8iJHVJkwqhuCoajF28Gmib6Vam",
"1116Uiu2HAkvTvEmGE8FKXkP1XZqsVk6ijyD5ssswwqBegQr2VY35HN",
"1116Uiu2HAmNtefGB429mGfvYZdkCpn4MDZCQwy5ah3SXTiRXkY8iVD",
"1116Uiu2HAkyuYKntwfNdh9wGvJT99TNM71EZTqAvmYKEzg82rJR7sG",
"1116Uiu2HAmB934A8YeWu6mi7gyz6u7sAKHYzg8zjw2ahFMzYsTFR5Q"
],
"replaced_nodes": null
},
"state_hash": "0x5cd54829b9b1358e0ee386919a6012e1663295b61bbf19248c1fbe6cb115b970"
},
{
"id": "0x20800000a6ba9853f131679d00da0f033516a2efe9cd53c3d54e1f9a00000000",
"height": "0x3",
"transitive_link": "0x4b0a1c6d8732e21f4a8f1fcd867bc7c7e5a79e642a99dd2a1f37fe1c11889400",
"locked_context": "0xe4cd45abf9de20991d1a73e77b9d7313aacbfc49a276cea89abc5ca8300e8479",
"context_delta": null,
"state_hash": "0x246cafe87be88df541f1741cfd57ebd37016dd8c22a81f4d5422e2f9aff64787"
}
]
}
Steps to Submit an Interaction with Multiple Operations
A single Interaction can carry several operations, which execute atomically - they all succeed, or the whole Interaction reverts. Here we buy a storage allowance on a logic account and, in the same Interaction, publish the Access Policy that governs who may write to that storage.
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 interaction containing two ix_operations, a StorageDeposit followed by an AccessCreate:
// Interaction with multiple ix_operations: Storage-deposit and Access-create
const interaction = {
sender: operator,
fuel_price: 1,
fuel_limit: 10000,
ix_operations: [
{
type: OpType.STORAGE_DEPOSIT,
payload: {
target_account:
"0x200000009f6b3348f43e93fdfa30cd4898653115f569dda495f7392700000000",
deposit_for:
"0x00000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f2700000000",
amount: 1000,
},
},
{
type: OpType.ACCESS_CREATE,
payload: {
target_account: operator.id,
access_policy: {
resource: ResourceType.STORAGE,
resource_id:
"0x200000009f6b3348f43e93fdfa30cd4898653115f569dda495f7392700000000",
actions: AccessAction.STORAGE_MUTATE,
caller: {
kind: CallerKind.SET,
set: [
"0x00000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f2700000000",
],
},
origin: {
kind: CallerKind.ANY,
set: [],
},
},
},
},
],
};
Three identifiers appear here:
counterLogic- the Logic ID of the deployed logic. The allowance is reserved on it, and it is the resource the policy governs.operator- the sender. The allowance is credited to it, and the policy is written onto it;target_accounton an access operation must always be the sender."0x00000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f2700000000"- the only caller permitted to drive the logic's writes into the operator's storage. Theoriginconstraint is left asANY, so any account may send the interaction.
AccessCreate writes a brand-new policy and fails if one already exists for the same (resource, resource_id) pair - use AccessUpdate to replace that one. See Manage Access Policies for the full lifecycle.
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: '0e9f020ef604f3088309a009ae09ce28c032c0325f068304930400000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f27000000003d00000000000000000000000000000000000000000000000000000000000000000127103f0efe092f0316100e7f0686048308a308200000009f6b3348f43e93fdfa30cd4898653115f569dda495f739270000000000000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f270000000003e82f0316130e3f068e0400000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f2700000000af0103169304ae04ee04ee0d01200000009f6b3348f43e93fdfa30cd4898653115f569dda495f7392700000000012f0e100f2f031e013f06860400000000f61441f746fa33fe52d4295176305b89bcb9122df86c0db100000000108000004cd973c4eb83cdb8870c0de209736270491b7acc99873da1000000002f030e0f3f0eee045f068304810400000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f27000000005f0683048104200000009f6b3348f43e93fdfa30cd4898653115f569dda495f7392700000000',
signatures: '0e1f0e5f068304860400000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f27000000000146304402205105cddfbdb816b59bb683827868c2c9d08b68bf8496a0d4b705968df50942ab02204ea51bc7bd60d4373d8b253f4f530c61125019df25d70c1e0ecb27ff2e2ea61703'
}
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. As before, the sender field is filled in automatically.
// 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: '0x10',
status: 0,
data: null
},
{
tx_type: '0x12',
status: 0,
data: null
}
],
from: '0x00000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f2700000000',
ts_hash: '0xb094ba2c37db84b9945f82b6d5042ffd81287f55965fd314ba10b7d7a189bfe1',
participants:
[
{
id: '0x00000000a71b83d59925c1305855cf19efaa8ab886b2e4ea93443f2700000000',
height: '0x9',
transitive_link: '0xe316b956a43e944faf5e194d7b22dc76316f20a1787fcaca509c227153f1f66f',
locked_context: '0x08d829a5911b07af4741fb77cd7d33d0cd6b81f1bf335314de9281ec8d30c4f6',
context_delta: null,
state_hash: '0x829ff831050723b2cb472587a627ff3ad1f50f351a6622e8ee015930e85ce750'
},
{
id: '0x200000009f6b3348f43e93fdfa30cd4898653115f569dda495f7392700000000',
height: '0x0',
transitive_link: '0x0000000000000000000000000000000000000000000000000000000000000000',
locked_context: '0x0000000000000000000000000000000000000000000000000000000000000000',
context_delta: {
consensus_nodes: [
'1116Uiu2HAmBtsBV1wCtFdTjGHerv8iJHVJkwqhuCoajF28Gmib6Vam',
'1116Uiu2HAkvTvEmGE8FKXkP1XZqsVk6ijyD5ssswwqBegQr2VY35HN',
'1116Uiu2HAmNtefGB429mGfvYZdkCpn4MDZCQwy5ah3SXTiRXkY8iVD',
'1116Uiu2HAkyuYKntwfNdh9wGvJT99TNM71EZTqAvmYKEzg82rJR7sG',
'1116Uiu2HAmB934A8YeWu6mi7gyz6u7sAKHYzg8zjw2ahFMzYsTFR5Q'
],
replaced_nodes: null
},
state_hash: '0x5cd54829b9b1358e0ee386919a6012e1663295b61bbf19248c1fbe6cb115b970'
},
{
id: '0x20800000a6ba9853f131679d00da0f033516a2efe9cd53c3d54e1f9a00000000',
height: '0x3',
transitive_link: '0x4b0a1c6d8732e21f4a8f1fcd867bc7c7e5a79e642a99dd2a1f37fe1c11889400',
locked_context: '0xe4cd45abf9de20991d1a73e77b9d7313aacbfc49a276cea89abc5ca8300e8479',
context_delta: null,
state_hash: '0x246cafe87be88df541f1741cfd57ebd37016dd8c22a81f4d5422e2f9aff64787'
}
]
}
Further Reading
Now that we have submitted both a single-operation and a multi-operation Interaction, we can build applications that leverage the Asset, Logic and Access capabilities of the protocol. Happy Hacking!
- JSON-RPC Documentation
- Create & Manage Assets in MOI
- Deploy & Invoke Logics in MOI
- Manage Access Policies in MOI