Welcome

This is the documentation for the official IOTA Client Library. It can be used to easily interact with IOTA network (Tangle) via IOTA node software. Official IOTA libraries serve as one-source-code-of-truth to IOTA users and providing binding to other programming languages. You can read more about core principles behind IOTA client libraries in the following blog post.

Example of tasks that iota.rs is able to help with:

  • Create messages and transactions
  • Sign transactions
  • Generate addresses
  • Interact with an IOTA node

Please note: there is also available wallet.rs library that contains all the logic to safely build wallets or integrations that require value-based IOTA transfers. We strongly recommend to leverage wallet.rs library in case one is more focused on IOTA value-based transfers since it integrates the best security practices including our stronghold enclave.

Changes from IOTA 1.0 to 1.5

For an overview of all significant changes in the protocol, please see Chrysalis documentation.

Testnet

To join the Chrysalis public testnet checkout this link. More information about Chrysalis components is available at documentation portal.

Joining the discussion

If you want to get involved in discussions about this library, or you're looking for support, go to the #clients-discussion channel on Discord.

What you will find here

This documentation has five paths.

  1. The Overview, an detailed overview of the client library.
  2. Libraries bindings, all avaiable programming languages and their resources.
  3. The Specification, detailed explaination requirements and functionality.
  4. Contribute, how you can work on the client software.
  5. Get in touch, join the community and become part of the X-Team!

Overview

The IOTA client library is a stateless library that provides a high level abstractions on top of IOTA node software to help interact with IOTA network in user friendly way and allows you to do the following, for instance:

  • Create messages and transactions
  • Sign transactions
  • Generate addresses
  • Interact with an IOTA node

IOTA client library in a nutshell

The library provides two types of API calls under a common interface:

  • Full node API calls: those calls are basically translated to native node rest api calls. For more information, please see node rest API reference
  • General high level API calls: those are convenience functions with some typical default parameters in order to use them in a straightforward manner. They typically implement several recommended steps for the given task. Implementation details are part of the full specification

See the full specification here.

High level layered overview:

iota layers overview

IOTA Client libraries

There are currently available the following official bindings to iota.rs:

Getting Started

It is a recommended approach to start your interactions with IOTA on a testnet network. API load balancer: https://api.lb-0.testnet.chrysalis2.com:443

Network explorer is available at IOTA Tangle Explorer.

In order to properly test value-based transactions on testnet network, you are going to need some tokens! You can get some testnet tokens using the faucet. However, we strongly recommend to leverage official wallet.rs library in case of value-based IOTA transfers.

IOTA Client Rust Library

status

Joining the discussion

If you want to get involved in discussions about this library, or you're looking for support, go to the #clients-discussion channel on Discord.

Overview

Getting Started with Rust

Requirements

To use the library, we recommend you update Rust to latest stable version $ rustup update stable. Nightly should be fine but some changes might not be compatible.

no_std is not currently supported, but we are working on it in bee, and will provide it as feature once the new implementation is ready.

Using the library

Using the library is easy, just add it as dependency in your Cargo.toml:

[dependencies]
iota-client = { git = "https://github.com/iotaledger/iota.rs", branch = "dev" }

And then you can use the library in your code with use iota_client;.

Initialisation

This example fetches node information

use iota_client::Client;

#[tokio::main]
async fn main() {
    let iota = Client::builder() // Crate a client instance builder
        .with_node("https://api.lb-0.testnet.chrysalis2.com")
        .unwrap()
        .finish()
        .await
        .unwrap();

    let info = iota.get_info().await.unwrap();
    println!("Nodeinfo: {:?}", info);
}

Examples

It's possible to send transactions with iota.rs, but we strongly recommend to use official wallet.rs library together with stronghold.rs enclave for value-based transfers. This combination incorporates the best security practices while dealing with seeds, related addresses and UTXO. See more information on wallet docs.

git clone https://github.com/iotaledger/iota.rs
cd iota.rs

Rename the .env.example file to .env.

Run the examples like:

cargo run --example 01_get_info --release
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! cargo run --example 01_get_info --release

use iota_client::Client;

/// In this example we will get information about the node

#[tokio::main]
async fn main() {
    // Create a client instance
    let iota = Client::builder()
        .with_node("https://api.lb-0.testnet.chrysalis2.com") // Insert your node URL here
        // .with_node_auth("https://somechrysalisiotanode.com", "name", "password") // Optional authentication
        .unwrap()
        .finish()
        .await
        .unwrap();

    let info = iota.get_info().await.unwrap();
    println!("Node Info: {:?}", info);
}
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! cargo run --example 02_generate_seed --release

use iota_client::crypto::signatures::ed25519::SecretKey;

/// In this example we will generate a seed

#[tokio::main]
async fn main() {
    let secret_key = SecretKey::generate().unwrap();
    println!("{}", hex::encode(&secret_key.to_le_bytes()));
}
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! cargo run --example 03_generate_addresses --release

use iota_client::{api::GetAddressesBuilder, Client, Seed};
extern crate dotenv;
use dotenv::dotenv;
use std::env;

/// In this example we will create addresses from a seed defined in .env

#[tokio::main]
async fn main() {
    // Create a client instance
    let iota = Client::builder()
        .with_node("https://api.lb-0.testnet.chrysalis2.com") // Insert your node URL here
        .unwrap()
        .finish()
        .await
        .unwrap();

    // This example uses dotenv, which is not safe for use in production
    dotenv().ok();

    let seed = Seed::from_bytes(&hex::decode(env::var("NONSECURE_USE_OF_DEVELOPMENT_SEED_1").unwrap()).unwrap());

    // Generate addresses with default account index and range
    let addresses = iota.get_addresses(&seed).finish().await.unwrap();
    println!("List of generated public addresses:\n{:?}\n", addresses);

    // Generate addresses with custom account index and range
    let addresses = iota
        .get_addresses(&seed)
        .with_account_index(0)
        .with_range(0..4)
        .finish()
        .await
        .unwrap();

    println!("List of generated public addresses:\n{:?}\n", addresses);

    // Generate public (false) & internal (true) addresses
    let addresses = iota.get_addresses(&seed).with_range(0..4).get_all().await.unwrap();
    println!("List of generated public and internal addresses:\n{:?}\n", addresses);

    // Generate public addresses offline with the bech32_hrp defined
    let addresses = GetAddressesBuilder::new(&seed)
        .with_bech32_hrp("atoi".into())
        .with_account_index(0)
        .with_range(0..4)
        .finish()
        .await
        .unwrap();

    println!("List of offline generated public addresses:\n{:?}\n", addresses);
}
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! cargo run --example 04_get_balance --release

use iota_client::{Client, Seed};
extern crate dotenv;
use dotenv::dotenv;
use std::env;

/// In this example we will get the account balance of a known seed and the balance and outputs of a known address

#[tokio::main]
async fn main() {
    // Create a client instance
    let iota = Client::builder()
        .with_node("https://api.lb-0.testnet.chrysalis2.com") // Insert your node URL here
        .unwrap()
        .with_node_sync_disabled()
        .finish()
        .await
        .unwrap();

    // This example uses dotenv, which is not safe for use in production
    dotenv().ok();

    let seed = Seed::from_bytes(&hex::decode(env::var("NONSECURE_USE_OF_DEVELOPMENT_SEED_1").unwrap()).unwrap());

    let seed_balance = iota.get_balance(&seed).finish().await.unwrap();
    println!("Account balance: {:?}i\n", seed_balance);

    let address = "atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r";

    let response = iota.get_address().balance(&address).await.unwrap();
    println!("The balance of {:?} is {:?}i\n", address, response.balance);

    let outputs = iota.get_address().outputs(&address, Default::default()).await.unwrap();

    println!("The outputs of address {:?} are: {:?}", address, outputs);
}
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! cargo run --example 05_get_address_outputs --release

use iota_client::{Client, Result};

/// In this example we will get the outputs of a known address

#[tokio::main]
async fn main() -> Result<()> {
    let iota = Client::builder()
        .with_node("https://api.lb-0.testnet.chrysalis2.com")?
        .finish()
        .await?;

    let address = "atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r";

    let outputs = iota.get_address().outputs(&address, Default::default()).await.unwrap();

    println!("The outputs of address {:?} are: {:?}", address, outputs);
    Ok(())
}
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! cargo run --example 06_simple_message --release

use iota_client::{Client, Result};

/// In this example we will send a message without a payload

#[tokio::main]
async fn main() -> Result<()> {
    let iota = Client::builder()
        .with_node("https://api.lb-0.testnet.chrysalis2.com")?
        .finish()
        .await?;

    let message = iota.message().finish().await?;

    println!(
        "Empty message sent: https://explorer.iota.org/testnet/message/{}",
        message.id().0
    );
    Ok(())
}
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! cargo run --example 07_get_message_metadata --release

use iota_client::{Client, Result};

/// In this example we will send a message and get the metadata for it

#[tokio::main]
async fn main() -> Result<()> {
    let iota = Client::builder()
        .with_node("https://api.lb-0.testnet.chrysalis2.com")?
        .finish()
        .await?;

    let message = iota.message().finish().await?;

    let metadata = iota.get_message().metadata(&message.id().0).await?;
    println!("Message metadata: {:?}", metadata);
    Ok(())
}
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! cargo run --example 08_data_message --release

use iota_client::{Client, Result};

/// In this example we will send a message without a payload

#[tokio::main]
async fn main() -> Result<()> {
    let iota = Client::builder()
        .with_node("https://api.lb-0.testnet.chrysalis2.com")?
        .finish()
        .await?;

    let message = iota
        .message()
        .with_index("Hello")
        .with_data("Tangle".as_bytes().to_vec())
        .finish()
        .await?;

    println!(
        "Message sent https://explorer.iota.org/testnet/message/{}\n",
        message.id().0
    );

    let fetched_message_ids = iota.get_message().index("Hello").await.unwrap();
    println!("Messages with Hello index: {:?}", fetched_message_ids);
    Ok(())
}
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! cargo run --example 09_transaction --release

use iota_client::{Client, Result, Seed};
extern crate dotenv;
use dotenv::dotenv;
use std::env;

/// In this example we will send a transaction

#[tokio::main]
async fn main() -> Result<()> {
    let iota = Client::builder()
        .with_node("https://api.lb-0.testnet.chrysalis2.com")?
        .finish()
        .await?;

    // This example uses dotenv, which is not safe for use in production
    // Configure your own seed in ".env". Since the output amount cannot be zero, the seed must contain non-zero balance
    dotenv().ok();
    let seed_1 = Seed::from_bytes(&hex::decode(env::var("NONSECURE_USE_OF_DEVELOPMENT_SEED_1").unwrap())?);

    let message = iota
        .message()
        .with_seed(&seed_1)
        // Insert the output address and amount to spent. The amount cannot be zero.
        .with_output(
            // We generate an address from our seed so that we send the funds to ourselves
            &iota.get_addresses(&seed_1).with_range(0..1).finish().await?[0],
            1_000_000,
        )?
        .finish()
        .await?;

    println!(
        "Transaction sent: https://explorer.iota.org/testnet/message/{}",
        message.id().0
    );
    Ok(())
}
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! cargo run --example 10_mqtt --release

use iota_client::{bee_message::Message, Client, MqttEvent, Result, Topic};
use std::sync::{mpsc::channel, Arc, Mutex};

// Connecting to a MQTT broker using raw ip doesn't work. This is a limitation of rustls.
#[tokio::main]
async fn main() -> Result<()> {
    // Create a client instance
    let mut iota = Client::builder()
        .with_node("https://api.hornet-0.testnet.chrysalis2.com")?
        .finish()
        .await?;

    let (tx, rx) = channel();
    let tx = Arc::new(Mutex::new(tx));

    let mut event_rx = iota.mqtt_event_receiver();
    tokio::spawn(async move {
        while event_rx.changed().await.is_ok() {
            let event = event_rx.borrow();
            if *event == MqttEvent::Disconnected {
                println!("mqtt disconnected");
                std::process::exit(1);
            }
        }
    });

    iota.subscriber()
        .with_topics(vec![
            Topic::new("milestones/latest").unwrap(),
            Topic::new("messages").unwrap(),
        ])
        .subscribe(move |event| {
            match event.topic.as_str() {
                "messages" => {
                    let message: Message = serde_json::from_str(&event.payload).unwrap();
                    println!("{:?}", event);
                    println!("{:?}", message);
                }
                _ => println!("{:?}", event),
            }
            tx.lock().unwrap().send(()).unwrap();
        })
        .await
        .unwrap();

    for _ in 0..10 {
        rx.recv().unwrap();
    }

    iota.subscriber().disconnect().await.unwrap();
    // alternatively
    // iota.subscriber().unsubscribe().unwrap();
    Ok(())
}

You can find more advanced examples in the examples folder.

API Reference

API reference

You can read the API reference here, or generate it yourself.

If you'd like to explore the implementation in more depth, the following command generates docs for the whole crate, including private modules:

cargo doc --document-private-items --no-deps --open

Troubleshooting

StackExchange

https://iota.stackexchange.com

The IOTA StackExchange a a nice tool for developers to find answers for a problem. Just search your problem and find your answer! If there is no one, submit your question and share it in the discussion channel below.

Joining the discussion

If you want to get involved in discussions about this library, or you're looking for support, go to the #clients-discussion channel on Discord.

IOTA Client Node.js Library

status

Joining the discussion

If you want to get involved in discussions about this library, or you're looking for support, go to the #clients-discussion channel on Discord.

Changes from IOTA 1.0 to 1.5

For an overview of all significant changes in the protocol, please see Chrysalis documentation.

Overview

Getting Started with Node.js

Installation

  • Using NPM:
$ npm i @iota/client
  • Using yarn:
$ yarn add @iota/client

Example

const { ClientBuilder } = require('@iota/client')
const client = new ClientBuilder()
    .node('https://api.lb-0.testnet.chrysalis2.com')
    .build()
client.getInfo().then(console.log).catch(console.error)

Examples

Please note: It is not recommended to store passwords/seeds on host's environment variables or in the source code in a production setup! Please make sure you follow our backup and security recommendations for production use!

Connecting to node(s)

All features of iota.rs library are accessible via an instance of Client class that provides high-level abstraction to all interactions over IOTA network (Tangle). This class has to be instantiated before starting any interactions with the library, or more precisely with IOTA nodes that power IOTA network.

In nodejs binding, the Client instance is instantiated and optionally configured via chaining calls of ClientBuilder helper class.

The library is designed to automatically choose a starting IOTA node based on the network type one would like to participate in: testnet or mainnet. So very simplistic example how to connect to IOTA testnet is the following one:


function run() {
    const { ClientBuilder } = require('@iota/client');

    // client will connect to testnet by default
    const client = new ClientBuilder()
        .localPow(true)
        .build();

    client.getInfo().then(console.log).catch(console.error);
}

run()

Output example of getInfo() function of the ClientBuilder instance:

{
   "nodeinfo":{
      "name":"HORNET",
      "version":"0.6.0-alpha",
      "isHealthy":true,
      "networkId":"migration",
      "bech32HRP":"atoi",
      "minPoWScore":100,
      "messagesPerSecond":4.2,
      "referencedMessagesPerSecond":4.1,
      "referencedRate":97.61904761904762,
      "latestMilestoneTimestamp":1618139001,
      "latestMilestoneIndex":7092,
      "confirmedMilestoneIndex":7092,
      "pruningIndex":0,
      "features":[
         "PoW"
      ]
   },
   "url":"https://api.lb-0.testnet.chrysalis2.com"
}

The most important properties:

  • isHealthy: indicates whether the given node is in sync with the network and so it is safe to use it. Even if a node is up and running it may not be fully prepared to process your API calls properly. The node should be "synced", meaning should be aware of all TXs in the Tangle. It is better to avoid not fully synced nodes
  • bech32HRP: indicates whether the given node is a part of testnet (atoi) or mainnet (iota). See more info regarding IOTA address format

Please note, when using node load balancers then mentioned health check may be quite useless since follow-up API calls may be served by different node behind the load balancer that may have not been actually checked. One should be aware of this fact and trust the given load balancer participates only with nodes that are in healthy state. iota.rs library additionally supports a management of internal node pool and so load-balancer-like behavior can be mimicked using this feature locally.

Needless to say, the ClientBuilder helper class provides several chaining calls via which the process can be closely managed.

The most common ones:

  • .network(str): can be testnet or mainnet. It instructs the library whether to automatically select testnet nodes or mainnet nodes
  • .node(url): specify address of actual running IOTA node that should be used to communicate with (in format https://node:port), for ex: https://api.lb-0.testnet.chrysalis2.com:443
  • .nodePoolUrls(urls): library also supports a management of pool of nodes. You can provide a list of nodes and library manages access to them automatically (selecting them based on their sync status)
  • .localPow(bool): .localPow (True) (by default) means a Proof-of-work is done locally and not remotely
  • .disableNodeSync(): when called, it means library also uses nodes that are not in sync with network. This parameter is usually useful if one would like to interact with local test node that is not fully synced. This parameter should not be used in production

If .nodePoolUrls(urls) is provided then the library periodically checks in some interval (call .nodeSyncInterval(interval)) whether node is in sync or not.

Example of use of additional initialization chaining calls, such as leveraging a custom node:


function run() {
    const { ClientBuilder } = require('@iota/client');

    // client will connect to testnet by default
    const client = new ClientBuilder()
        .node('https://api.lb-0.testnet.chrysalis2.com:443')    // custom node
        .localPow(true)                                         // pow is done locally
        .disableNodeSync()                                      // even non-synced node is fine - do not use in production
        .build();

    client.getInfo().then(console.log).catch(console.error);
}

run()

Generating seed and addresses

Since the IOTA network is permission-less type of network, anybody is able to use it and interact with it. No central authority is required at any stage. So anybody is able to generate own seed and then deterministically generate respective private keys/addresses.

Please note, it is highly recommended to NOT use online seed generators at all. The seed is the only key to the given addresses. Anyone who owns the seed owns also all funds related to respective IOTA addresses (all of them).

We strongly recommend to use official wallet.rs library together with stronghold.rs enclave for value-based transfers. This combination incorporates the best security practices while dealing with seeds, related addresses and UTXO. See more information on Chrysalis docs.

IOTA uses Ed25519 signature scheme and address is usually represented by Bech32 (checksummed base32) format string of 64 characters.

A root of Ed25519 signature scheme is basically a 32-byte (256-bit) uniformly randomly generated seed based on which all private keys and corresponding addresses are generated. In the examples below, the seed is represented by a string of 64 characters using [0-9a-f] alphabet (32 bytes encoded in hexadecimal).

Seed can be for example generated using SHA256 algorithm on some random input generated by cryptographically secure pseudo-random generator, such as crypto.randomBytes():

function run() {
    const crypto = require('crypto');
    const seed = crypto.createHash('sha256').update(crypto.randomBytes(256)).digest('hex');
    console.log(seed);

    const { ClientBuilder } = require('@iota/client');
    const client = new ClientBuilder().build();

    const mnemonic = client.generateMnemonic();
    console.log(mnemonic);

    const hexEncodedSeed = client.mnemonicToHexSeed(mnemonic);
    console.log(hexEncodedSeed);
}

run()

Output example:

39bccf7b88a8017e6a96e6f31e34f138829c574dc6061523e84c5f2e53f5ca36
pass phrase weapon yellow diary scissors gift drive strategy antique scheme make surround aerobic mystery coral hope lock walnut become exclude only glove syrup
eff5c97c96ddab55d6fe78f914508750152eaab1b9692236bc79268895ecfd168e91eedd2489ed6c51fc44156b9a2e6c967e4edcfb649ff33d41581be4627347

In modern wallet implementations, such as our wallet.rs library and firefly wallet, the seed is usually generated from a seed mnemonic (seed phrase), using BIP39 standard, to be better memorized/stored by humans. It is based on randomly generated list of english words and later used to generate the seed. Either way, the seed is a root for all generated private keys and addresses

Address/key space

Before an actual address generation process, let's quickly focus on BIP32 standard that describes an approach to Hierarchical Deterministic Wallets. The standard was improved by BIP44 lately.

These standards define a tree structure as a base for address and key space generation which is represented by a derivation path:

m / purpose / coin_type / account / change / address_index
  • m: a master node (seed)
  • purpose: constant which is {44}
  • coin_type: a constant set for each crypto currency. IOTA = 4218, for instance.
  • account: account index. Zero-based increasing int. This level splits the address/key space into independent branches (ex. user identities) which each has own set of addresses/keys
  • change: change index which is {0, 1}, also known as wallet chain.
    There are two independent chain of addresses/keys. 0 is reserved for public addresses (for coin receival) and 1 is reserved for internal (also known as change) addresses to which transaction change is returned. IOTA is totally fine with address reuse, and so it is, technically speaking, totally valid to return transaction change to the same originating address. So it is up to developers whether to leverage it or not. iota.rs library and its sibling wallet.rs help with either scenario
  • address_index: address index. Zero-based increasing int that indicates an address index

As outlined, there is a quite large address/key space that is secured by a single unique seed.

And there are few additional interesting notes:

  • Each level defines a completely different subtree (subspace) of addresses/keys and those are never mixed up
  • The hierarchy is ready to "absorb" addresses/keys for many different coins at the same time (coin_type), and all those coins are secured by the same seed.
    (So basically any BIP32/44-compliant wallet is potentially able to manage any BIP32/44-compliant coin(s))
  • There may be also other purposes in the future however let's consider a single purpose as of now. The constant 44 stands for BIP44
  • The standard was agreed upon different crypto communities, although not all derivation path components are always in active use. For example, account is not always actively leveraged across crypto space (if this is a case then there is usually account=0 used)
  • Using different accounts may be useful to split addresses/key into some independent spaces and it is up to developers to implement.
    Please note, it may have a negative impact on a performance while account discovery phase. So if you are after using many multiple accounts then you may be interested in our stateful library wallet.rs that incorporates all business logic needed to efficiently manage independent accounts. Also our exchange guide provides some useful tips how different accounts may be leveraged

address_generation

So in case of IOTA, the derivation path of address/key space is [seed]/44/4218/{int}/{0,1}/{int}. The levels purpose and coin_type are given, the rest levels are up to developers to integrate.

Generating address(es)

IOTA addresses are generated via AddressGetter helper class by calling Client.getAddresses() function and respective chaining calls that returns a list of tuples with generated addresses. Considering the previous chapter about individual address/key spaces, it becomes quite clear what all used input function arguments are for.

Please note: for the examples outlined below, an example seed b3d7092195c36d47133ff786d4b0a1ef2ee6a0052f6e87b6dc337935c70c531e was used via environment variable called IOTA_SEED_SECRET. This seed serves for training purposes only.

The whole process is deterministic which means the output is the same as long as the seed is the same:

async function run() {
  const { ClientBuilder } = require('@iota/client');

  // Get the seed from environment variable
  const IOTA_SEED_SECRET = process.env.IOTA_SEED_SECRET;

  // client will connect to testnet by default
  const client = new ClientBuilder().build();

  const addresses = await client.getAddresses(IOTA_SEED_SECRET)
    .accountIndex(0)
    .range(0, 5)
    .get();

  console.log(addresses);
}

run()

Output example:

['atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86',
 'atoi1qzfvkkp398v7hhvu89fu88hxctf7snwc9sf3a3nd7msfv77jk7qk2ah07s3',
 'atoi1qq4t98j5y8wxkaujue99mjwqcp6jvvmsd5lv0755sz7dtjdz3p2lydv76sy',
 'atoi1qrhzhjxc4z8vpwjt3hafs5xpdng5katqe890p0h95mc0l273j8yzxn7r4hc',
 'atoi1qputu0yvfvxd7g39wf4rc67e0f0dyhl6enxu9jxnsrjqmemh067tw7qelyc',
 'atoi1qptg5w2x47qwjf3gpqt3h7d2ey5x7xf8v7qtt29gkxt4mjfjfc28sutvd8a',
 'atoi1qprvelq9paakh72fgm6j2kf8kexadw3t5xljer9dpsep5c7wx5mjwdxch6z',
 'atoi1qrwk37tz47ddng9kpxfflkpz5tplcq7ll56v4acam04307xk70l7uf6wg8j',
 'atoi1qper3zr5xe9x0wqs35ytwh622870g44frkyygdhs0ds8yejle3xujhq7dx3',
 'atoi1qq6lkr9hucfylqjaqphu0stvk8pcmsx98r7ukuq40asszwmqytlnc058thk',
 'atoi1qzpn7se3ryhscmqg404pycxzvfpt8v4xn8aul0tqdh00xsncgnxu7na7zjj',
 'atoi1qz4qqakty9qytw8fk9shelt9lwlvv83s5ggt3wjag9fkgcc74z78w4l86y5',
 'atoi1qp20uddchglqry0l5qnjg5aln8d5rk2v5l45hwrxv9z0daxs7u6xcsh4077',
 'atoi1qrlqm2u5txxxnjx22fxq0jfjzk6l4nwnue6ht5pepk65m2f4xmxqynmxu2m',
 'atoi1qqydc70mpjdvl8l2wyseaseqwzhmedzzxrn4l9g2c8wdcsmhldz0ulwjxpz',
 'atoi1qrkjennxyl2xcqem6x69ya65sasma33z0ux872k846lqft0s3qf7k6lqpft',
 'atoi1qr4yuekp30ff7mnnnjwy9tdhynxmlmkpuxf70qurtwudp2zpf3jeyw4uh37',
 'atoi1qp6m5sz5ayjtccfxapdk5lp4qkheyfg0emzntmulyxzftps730vcul8dmqr',
 'atoi1qzrwhkzhu67fqltfffwljejawdcghedukpgu9x6tzevwlnq89gmfjtayhgz',
 'atoi1qpehxcp24z947dgupjqc9ktkn5ylmdxqqnx83m7xlajnf8005756u4n7z77']

IOTA address is represented by a checksumed base 32 string (Bech32) and you can see a detailed explanation on Chrysalis docs. Just a recap:

  • If an address starts with atoi then it means it is related to testnet. iota stands for mainnet
  • Number 1 at 5th position is just a separator
  • The last 6 characters are reserved for a checksum

Address can be also represented in a hex format and luckily iota.rs provides some convenience functions to convert addresses respectively: Client.bech32ToHex(bech32) and Client.hexToBech32(hex, bech32_hrp (optional)).

To quickly validate any IOTA address, there is a convenience function Client.isAddressValid() that returns bool value. Needless to say, performing a sanity check of an address before its use is an advisable practice.

Checking a balance

In Chrysalis testnet, there is a faucet service that provides test tokens to any testnet address: https://faucet.testnet.chrysalis2.com/

There are three common api calls that can be leveraged:

  • Client.getAddressBalance(str): it expects a single address in Bech32 format and returns dict with a balance for the address
  • Client.getAddressBalances([]): a convenience function that expects list of addresses in Bech32 format and returns list of dict with balances for all given addresses
  • Client.getBalance(seed): a convenience helper BalanceGetter class that combines Client.getAddresses() and Client.getAddressBalance() api calls. It returns a combined balance for the provided seed and optional chaining calls .accountIndex(index), .initialAddressIndex(index) and .gapLimit(amount)
async function run() {
    const { ClientBuilder } = require('@iota/client');

    // Get the seed from environment variable
    const IOTA_SEED_SECRET = process.env.IOTA_SEED_SECRET;

    // client will connect to testnet by default
    const client = new ClientBuilder().build();

    console.log(
        await client.getAddressBalance("atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86")
    );

    const balance = await client.getBalance(IOTA_SEED_SECRET)
        .accountIndex(0)
        .initialAddressIndex(0)
        .get();

    console.log(balance);
}

run()

Example of output:

{
   "address_type":0,
   "address":"atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86",
   "balance":10000000,
   "dustAllowed":false
}
  • address_type indicates type of address. Value 0 denotes a Ed25519 address (currently the default for IOTA 1.5 network)
  • dustAllowed indicates whether the given address is allowed to accepts a dust due to dust protection mechanism

Client.getBalance(seed) performs a several tasks under the hood. It starts generating addresses for the provided seed and .accountIndex from .initialAddressIndex(index), and checks for a balance of each of the generated addresses. Since it does not know how many addresses are used in fact, there is a condition set by .gapLimit(amount) argument when to stop searching. If .gapLimit amount of addresses in a row have no balance the function returns result and searching does not continue.

Messages, payload and transactions

Before we continue, let's introduce some additional terms that describe an unit that is actually broadcasted in IOTA network. IOTA is based on a concept of messages and payloads.

Message is a data structure that is actually being broadcasted in IOTA network and represent a node (vertex) in the Tangle graph. It can refer to up to 8 previous messages and once a message was attached to the Tangle and approved by a milestone, the Tangle structure ensures the content of the message is unaltered. Every message is referenced by message_id which is based on a hash algorithm of binary content of the message. Message is an atomic unit that is confirmed by network as a whole.

IOTA is no longer based on ternary. IOTA 1.5 (Chrysalis) uses binary to encode and broadcast all underlying data entities

Message is broadcasted using a binary format, is arbitrary size (up to 35 kB) and it can hold a variable sets of information so called payloads. Number of payloads a single message can encapsulate is not given (even a message without any payload at all is completely valid).

Payload represents a layer of concern. Some payloads may change a state of the ledger (ex. transactions) and some may provide extra features to some specific applications and business use cases (ex. indexed data).

There are already implemented core payloads, such as SignedTransaction, MilestonePayload and IndexationPayload but the message and payload definition is generic enough to incorporate any future payload(s) the community agrees upon.

Needless to say, IOTA network ensures the outer structure of message itself is valid and definitely aligned with a network consensus protocol, however the inner structure is very flexible, future-proof, and offer an unmatched network extensibility.

messages_in_tangle

The current IOTA network incorporates the following core payloads:

  • SignedTransaction: payload that describes UTXO transactions that are cornerstone of value-based transfers in IOTA network. Via this payload, message can be also cryptographically signed
  • MilestonePayload: payload that is emitted by Coordinator
  • IndexationPayload: payload that enables addition of an index to the encapsulating message, as well as some arbitrary data. The given index can be later used to search the message(s)

Unspent Transaction Output (UTXO)

IOTA uses unspent transaction output model, so called UTXO. It is based on an idea to track unspent amount of tokens via data structure called output.

Simplified analogy:

  • There is 100 tokens recorded in the ledger as Output A and this output belongs to Alice. So initial state of ledger: Output A = 100 tokens
  • Alice sends 20 tokens to Paul, 30 tokens to Linda and keeps 50 tokens at her disposal
  • Her 100 tokens are recorded as Output A and so she has to divide (spent) tokens and create three new outputs:
    Output B with 20 tokens that goes to Paul, Output C with 30 tokens that goes to Linda and finally Output D with the rest 50 tokens that she keep for herself
  • Original Output A was completely spent and can't be used any more. It has been spent and so becomes irrelevant to ledger state
  • New state of ledger: Output B = 20 tokens, Output C = 30 tokens and Output D = 50 tokens
  • Total supply remains the same. Just number of outputs differs and some outputs were replaced by other outputs in the process

utxo

The key takeaway of the outlined process is the fact that each unique output can be spent only once. Once the given output is spent, can't be used any more and is irrelevant in regards to the ledger state.

So even if Alice still wants to keep remaining tokens at her fingertips, those tokens have to be moved to completely new output that can be for instance still tight to the same Alice's iota address as before.

Every output stores also information about an IOTA address to which it is coupled with. So addresses and tokens are indirectly coupled via outputs. So basically sum of outputs and their amounts under the given address is a balance of the given address, ie. the number of tokens the given address can spend. And sum of all unspent outputs and theirs amounts is equal to the total supply.

Before the chapter is wrapped up, one thing was left unexplained: "how outputs are being sent and broadcasted to network?" Outputs are being sent encapsulated in a message as a part of SignedTransaction payload.

Outputs

There are three functions to get UTXO outputs (related to the given address):

  • Client.getAddressOutputs(str): it expects address in Bech32 format and returns str[] of output_ids
  • Client.getOutput(str): it expects output_id and returns the UTXO output metadata associated with it
  • Client.findOutputs(output_ids (optional), addresses (optional)): it is a bit more general and it searches for UTXO outputs associated with the given output_ids and/or addresses
async function run() {
    const { ClientBuilder } = require('@iota/client');

    // client will connect to testnet by default
    const client = new ClientBuilder().build();

    const outputs = await client.getAddressOutputs('atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86');
    console.log(outputs);
}

run()

Output example: ```json` [ '0f2d5d2651f8061a9f5417d0658009f32b2e3f77f9706b0be3b4b3f466171f360000', '7614ba900a90b130707766a660a454942ac7cc4adea3fb9ad0cdca90114417c20000', '768c20c15a290e02a43b83263a98501b9d7eb0b57da40a9247289c672de63ea60000' ] ``

Then the function Client.getOutput(str) can be used to get metadata about the given output_id:

async function run() {
    const { ClientBuilder } = require('@iota/client');

    // client will connect to testnet by default
    const client = new ClientBuilder().build();

    const output = await client.getOutput('a22cba0667c922cbb1f8bdcaf970b2a881ccd6e88e2fcce50374de2aac7c37720000');
    console.log(output);
}

run()

Output example:

{
  "messageId": "f303bc90a5ed3ef15af5fc6aa81a739978c59458a71e68ce8e380f1f534da1e6",
  "transactionId": "0f2d5d2651f8061a9f5417d0658009f32b2e3f77f9706b0be3b4b3f466171f36",
  "outputIndex": 0,
  "isSpent": false,
  "address": "atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r",
  "amount": 1000000
}

A function Client.findOutputs() is a convenient shortcut combining both mentioned methods in a single call:

async function run() {
    const { ClientBuilder } = require('@iota/client');

    // client will connect to testnet by default
    const client = new ClientBuilder().build();

    const outputs = await client.findOutputs(outputIds = [], addresses = ["atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86"]);
    console.log(outputs);
}

run()
  • it supports two arguments, a list of output_ids or a list of addresses

Output example:

[
  {
    "messageId": "f303bc90a5ed3ef15af5fc6aa81a739978c59458a71e68ce8e380f1f534da1e6",
    "transactionId": "0f2d5d2651f8061a9f5417d0658009f32b2e3f77f9706b0be3b4b3f466171f36",
    "outputIndex": 0,
    "isSpent": false,
    "address": "atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r",
    "amount": 1000000
  },
  {
    "messageId": "825266a79c0ffb6001ed263eb150357863b7d0052627c5766e8ef5acd6fed533",
    "transactionId": "768c20c15a290e02a43b83263a98501b9d7eb0b57da40a9247289c672de63ea6",
    "outputIndex": 0,
    "isSpent": false,
    "address": "atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r",
    "amount": 1000000
  }
]
  • message_id: refer to the encapsulating message in which the transaction was sent
  • transaction_id, output_index: refer to the given output within the SignedTransaction payload. There may be several different outputs involved in a single transaction and so just transaction_id is not enough
  • output: this section provides details about the iota address to which the given unspent transaction output is coupled with
  • amount: state an amount of tokens related to the output
  • is_spent: of course, very important one indicating whether the given output is a part of the actual ledger state or not. As mentioned above, if an output was already spent, it is not part of ledger state any more and was replaced by some other output(s) in the process

So this is quite interesting part, notice the output_id that was used in a function call to get output details is the same as a combination of transaction_id and output index.

This way a transaction is tightly coupled with outputs since SignedTransaction payload is a main vehicle how outputs are being created and spent, and altogether everything is encapsulated in a message.

Messages

As mentioned above, the message is encapsulating data structure that is being actually broadcasted across network. It is an atomic unit that is accepted/rejected as a whole.

There is a function Client.postMessage(message) that accepts message instance and sends it over a network. Alternatively, there is also convenience MessageSender helper class with respective chaining calls that prepares a message instance and broadcasts it over network.

The simplest message that can be broadcasted is a message without any particular payload:

async function run() {
    const { ClientBuilder } = require('@iota/client');

    // client will connect to testnet by default
    const client = new ClientBuilder().build();

    const messageId = await client.message().submit();
    console.log(messageId);
}

run()

Output example:

e2daa4c6b012b615becd6c12189b2c9e701ba0d53b31a15425b21af5105fc086
  • message_id is an unique id that refers to the given message in network

Once a message is broadcasted, there is MessageFinder helper class instantiated via Client.getMessage() function that provides helper functions related to the given message, such as Client.getMessage().data(str) and Client.getMessage().metadata(str):

async function run() {
    const { ClientBuilder } = require('@iota/client');

    // client will connect to testnet by default
    const client = new ClientBuilder().build();

    const tips = await client.getTips();
    const message_data = await client.getMessage().data(tips[0]);
    const message_metadata = await client.getMessage().metadata(tips[0]);
    console.log(message_metadata);
    console.log(message_data);
}

run()

Output example:

Message meta data:
{
  "messageId": "656f753d781a4c4f545ac6a69e391ec400b7f33ac8bed98add70f7310de910b6",
  "parentMessageIds": [
    "28359f7010ec08b1ed67465f495424f9046ec3890164dddc0240a275f20cecdb",
    "93b142fdf1bdfe1c5fa68d807287480aa14f2347f601fb349a3f89127b7a9e53",
    "cf192d94f2af091b1094e7b6513ff6752fcf477dd919b16c8ab823944a78aee7",
    "dbca8d2119b00fd5ef130bdb900592cafde56142e81c279b80cf18dcaae86f44"
  ],
  "isSolid": true,
  "referencedByMilestoneIndex": 224175,
  "ledgerInclusionState": "noTransaction"
}

Message data:
{
  "message": {
    "networkId": "14379272398717628000",
    "parentMessageIds": [
      "28359f7010ec08b1ed67465f495424f9046ec3890164dddc0240a275f20cecdb",
      "93b142fdf1bdfe1c5fa68d807287480aa14f2347f601fb349a3f89127b7a9e53",
      "cf192d94f2af091b1094e7b6513ff6752fcf477dd919b16c8ab823944a78aee7",
      "dbca8d2119b00fd5ef130bdb900592cafde56142e81c279b80cf18dcaae86f44"
    ],
    "payload": {
      "type": 2,
      "index": "494f54412e52532042494e44494e47202d204e4f44452e4a53",
      "data": "736f6d65207574662062617365642064617461"
    },
    "nonce": 9223372036855090000
  },
  "messageId": "656f753d781a4c4f545ac6a69e391ec400b7f33ac8bed98add70f7310de910b6"
}
  • Client.getMessage().metadata() provides information how the given message fits to network structures such as ledger_inclusion_state, etc.
  • Client.getMessage().data() provides all data that relates to the given message and its payload(s)

IndexationPayload

IndexationPayload is a payload type that can be used to attach an arbitrary data and key index to a message. At least index should be provided in order to send the given payload. Data part (as bytes[]) is optional one:

async function run() {
    const { ClientBuilder } = require('@iota/client');

    // client will connect to testnet by default
    const client = new ClientBuilder().build();

    const message = await client.message()
        .index('IOTA.RS BINDING - NODE.JS')
        .data('some utf based data')
        .submit();

    console.log(message);
}

run()

Output example:

8d4fa37be3c00691131c2c3e03e7b8b956c9118a2ce4be3a8597d51d82ed2de9
  • Feel free to check the given message using its message_id via Tangle explorer
  • There are three payloads prepared (transaction, milestone and indexation) however only indexation payload is leveraged this time
  • data contains an arbitrary data encoded in bytes
  • Please note there is no IOTA address involved while sending data messages. Such messages are referenced using message_id or key index
  • IOTA addresses are part of UTXO data structure that is sent using SignedTransaction payload explained below

SignedTransaction

SignedTransaction is a payload type that is used to transfer value-based messages as UTXO (Unspent Transaction Output).

As mentioned above, this core payload changes the ledger state as old outputs are being spent (replaced) and new outputs are being created:

async function run(){
    const { ClientBuilder } = require('@iota/client');

    // client will connect to testnet by default
    const client = new ClientBuilder().build();

    const message_data = await client.getMessage().data("e2daa4c6b012b615becd6c12189b2c9e701ba0d53b31a15425b21af5105fc086");
    console.log(message_data);
}

run()

Example of a message with SignedTransaction payload:

{
   "message":{
      "networkId":"14379272398717627559",
      "parentMessageIds":[
         "108e58d210b3b918d75fe2c4d7a5248878e29454cfb071688393ec9d1f9ad81b",
         "90d8d2722bc661aa893a7fa7bb044f7dcdd8503da7e10f5c907649897f110c44",
         "ac4a389ee6985b9238dbc5882a0a27e3a8b6cf5960d176e64c6f832bdadbe7c6",
         "c20dc0da13802f9c1e34c269fe19fa96a92c184e5776ed211bfa74ebe33d82a8"
      ],
      "payload":{
         "type":0,
         "essence":{
            "type":0,
            "inputs":[
               {
                  "type":0,
                  "transactionId":"7614ba900a90b130707766a660a454942ac7cc4adea3fb9ad0cdca90114417c2",
                  "transactionOutputIndex":0
               }
            ],
            "outputs":[
               {
                  "type":0,
                  "address":{
                     "type":0,
                     "address":"08dc79fb0c9acf9fea71219ec32070afbcb44230e75f950ac1dcdc4377fb44fe"
                  },
                  "amount":1000000
               }
            ],
            "payload":null
         },
         "unlockBlocks":[
            {
               "type":0,
               "signature":{
                  "type":0,
                  "publicKey":"2baaf3bca8ace9f862e60184bd3e79df25ff230f7eaaa4c7f03daa9833ba854a",
                  "signature":"a0354502ef69dbbfdadb4248791a271ac59c2707b55f3c758de9f5762278a2800553cfec278a4b270c501bbfd5cfcdf8613eda5879d9088835d328228f789d08"
               }
            }
         ]
      },
      "nonce":"17293822569102755792"
   },
   "messageId":"0cd0a0362217a3fec8c03c22fa7135cef96e808f0e2f4a40d3be67c639b17b85"
}

Each transaction includes the following set of information:

  • inputs: list of valid outputs that should be used to fund the given message. Those outputs will be spent and once the message is confirmed, those outputs are not valid anymore. Outputs are uniquely referenced via transaction_id and inner index. At least one output has to be given with enough balance to source all outputs of the given message
  • outputs: list of IOTA address(es) and related amount(s) the input outputs should be split among. Based on this information, new UTXO entities (outputs) are being created
  • unlock_blocks: it includes a transaction signature(s) (currently based on Ed25519 scheme) that proofs token ownership based on a valid seed. Needless to say, only valid seed owner is able to correctly sign the given transaction and proofs the ownership of tokens under the given output(s). Each input output has to have a corresponding unblockBlocks entry in case more outputs are used to fund the operation either using the given signature or as a reference to existing signature
  • payload: each SignedTransaction(payload type 0) can include additional payload(s) such as IndexationPayload (payload type 1), etc. Meaning, any value-based messages can also contain arbitrary data and its key index. It is also an example how individual payloads can be encapsulated on different levels of concern

Sending value-based messages is also very straightforward process via MessageSender helper class.

As a minimum, it needs a valid seed, output addresses and amount. The method finds valid output(s) that can be used to fund the given amount(s) and the unspent amount is sent to the same address:

async function run() {
    const {
        ClientBuilder
    } = require('@iota/client');

    // Get the seed from environment variable
    const IOTA_SEED_SECRET = process.env.IOTA_SEED_SECRET;

    // client will connect to testnet by default
    const client = new ClientBuilder().build();

    const message = await client.message()
        .seed(IOTA_SEED_SECRET)
        .output('atoi1qqydc70mpjdvl8l2wyseaseqwzhmedzzxrn4l9g2c8wdcsmhldz0ulwjxpz', 1000000)
        .submit();

    console.log(message);
}

run()

We recommend to use official wallet.rs library together with stronghold.rs enclave for value-based transfers. This combination incorporates the best security practices while dealing with seeds, related addresses and UTXO. See more information on Chrysalis docs.

Dust protection

Please note, there is also implemented a dust protection mechanism in the network protocol to avoid malicious actors to spam network in order to decrease node performance while keeping track of unspent amount (UTXO):

"... microtransaction below 1Mi of IOTA tokens [can be sent] to another address if there is already at least 1Mi on that address" That's why we did send 1Mi in the given example to comply with the protection."

Listening to MQTT

IOTA node(s) provides Message Queuing Telemetry Transport (MQTT) layer, if enabled, which is a lightweight publish-subscribe network protocol that provides information about events that is being triggered by IOTA network.

iota.rs client library supports asynchronous event listeners that can be listened to, and continuously receive MQTT events based on a topic, which can be:

  • milestones/latest
  • milestones/confirmed
  • messages
  • messages/referenced
  • messages/indexation/{index}
  • messages/{messageId}/metadata
  • transactions/{transactionId}/included-message
  • outputs/{outputId}
  • addresses/{address}/outputs
  • addresses/ed25519/{address}/outputs

The listener is reachable via instance of Client.TopicSubscriber object that is returned from Client.subscriber() function.

It offers several chaining calls:

  • .topic(str) / .topics(str[]): a topic or list of topics that should trigger a provided callback
  • .subscribe(cb): it subscribes the listener to a callback function that is being triggered every time the given topic(s) is noticed
  • .unsubscribe(cb): it unsubscribes the listener from the given topics. Once unsubscribed, then the given callback function is executed in a form (err, message) => {}
async function run() {
    const {
        ClientBuilder
    } = require('@iota/client');

    // client connects to a node that has MQTT enabled
    const client = new ClientBuilder()
        .node('https://api.hornet-0.testnet.chrysalis2.com')
        .build();

    client.subscriber().topics(['milestones/confirmed', 'messages']).subscribe((err, data) => {
        console.log(data);
    })
}

run()

Please note: IOTA node has to have enabled MQTT layer. There is a set of test nodes available that have MQTT enabled. See testnet chapter for more information

API Reference

Connecting to a MQTT broker using raw ip doesn't work. This is a limitation of rustls.

API Reference

ClientBuilder

node(url): ClientBuilder

Adds an IOTA node to the client pool.

ParamTypeDescription
urlstringA node URL

Returns the client builder instance for chained calls.

nodeAuth(url, name, password): ClientBuilder

Adds an IOTA node with basic authentication to the client pool.

ParamTypeDescription
urlstringA node URL
namestringA name
passwordstringA password

Returns the client builder instance for chained calls.

primaryNode(url, [, name, password]): ClientBuilder

Add a node to always connect first to with optional name and password for basic authentication.

ParamTypeDescription
urlstringA node URL
namestringA name
passwordstringA password

Returns the client builder instance for chained calls.

primaryPowNode(url, [, name, password]): ClientBuilder

Add a node to always connect first to when using remote PoW with optional name and password for basic authentication. Will overwrite the primary node for this case.

ParamTypeDescription
urlstringA node URL
namestringA name
passwordstringA password

Returns the client builder instance for chained calls.

nodes(urls): ClientBuilder

Adds a list of IOTA nodes to the client pool.

ParamTypeDescription
urlstring[]An array of node URLs

Returns the client builder instance for chained calls.

nodePoolUrls(urls): ClientBuilder

Adds a list of IOTA nodes from node pool URLs to the client pool.

ParamTypeDescription
urlstring[]An array of node pool URLs

Returns the client builder instance for chained calls.

network(networkName): ClientBuilder

Set a network to get default nodes for it. Can be "testnet" or "mainnet". Nodes that don't belong to this network are ignored.

ParamTypeDescription
networkNamestringThe network

Returns the client builder instance for chained calls.

quorum(enabled): ClientBuilder

Defines how many of nodes will be queried at the same time to check for quorum.

ParamTypeDescription
enabledbooleanDefine if quourm should be used or not

Returns the client builder instance for chained calls.

quorumSize(size): ClientBuilder

Defines how many of nodes will be queried at the same time to check for quorum.

ParamTypeDescription
sizenumberThe number of nodes that will be queried

Returns the client builder instance for chained calls.

quorumThreshold(threshold): ClientBuilder

Defines the minimum amount of nodes from the quorum pool that need to agree if we want to consider the result true.

ParamTypeDescription
thresholdnumberMinimum amount of nodes

Returns the client builder instance for chained calls.

brokerOptions(options): ClientBuilder

Sets the options for the MQTT connection with the node.

ParamTypeDescription
optionsBrokerOptionsThe MQTT broker options

Returns the client builder instance for chained calls.

nodeSyncInterval(interval): ClientBuilder

Sets the node syncing interval.

ParamTypeDescription
intervalnumberThe interval for the node syncing process

Returns the client builder instance for chained calls.

disableNodeSync(): ClientBuilder

Disables the node syncing process. Every node will be considered healthy and ready to use.

Returns the client builder instance for chained calls.

requestTimeout(timeoutMs): ClientBuilder

Sets the default HTTP request timeout.

ParamTypeDescription
timeoutnumberThe timeout in milliseconds

Returns the client builder instance for chained calls.

apiTimeout(api, timeoutMs): ClientBuilder

Sets the HTTP request timeout for the specified API.

ParamTypeDescription
api'GetHealth' | 'GetInfo' | 'GetTips' | 'PostMessage' | 'GetOutput' | 'GetMilestone'The API to set the request timeout
timeoutnumberThe timeout in milliseconds

Returns the client builder instance for chained calls.

localPow(local): ClientBuilder

Sets the PoW type.

ParamTypeDescription
localbooleanFlag determining if PoW should be done locally or remotely

Returns the client builder instance for chained calls.

build(): Client

Builds the client instance.

Returns a Client instance.

Client

networkInfo(): NetworkInfo

Gets the cached network info.

Returns a NetworkInfo instance.

subscriber(): TopicSubscriber

Gets a handle to the MQTT topic subscriber.

Returns a TopicSubscriber instance.

message(): MessageSender

Initiates the builder to send messages.

Returns a MessageSender instance.

getUnspentAddress(seed): UnspentAddressGetter

Get a valid unspent address.

ParamTypeDescription
seedstringThe hex-encoded seed to search

Returns a UnspentAddressGetter instance.

getAddresses(seed): AddressGetter

Find addresses from the seed regardless of their validity.

ParamTypeDescription
seedstringThe hex-encoded seed to search

Returns a AddressGetter instance.

findMessages(indexationKeys, messageIds): Promise<Message[]>

Finds all messages associated with the given indexation keys and message ids.

ParamTypeDescription
indexationKeysstring[]The list of indexations keys too search
messageIdsstring[]The list of message ids to search

Returns a promise resolving to the list of the found messages.

getBalance(seed: string): BalanceGetter

Get balance on a given seed and its wallet account index.

ParamTypeDescription
seedstringThe hex-encoded seed to search

Returns a BalanceGetter instance.

getAddressBalances(addresses): Promise<AddressBalance[]>

Get the balance in iotas for the given addresses.

ParamTypeDescription
addressesstring[]The list of addresses to search

Returns A promise resolving to the list of { address, balance } pairs.

generateMnemonic()

Returns a random generated Bip39 mnemonic with the English word list.

Returns A String

mnemonicToHexSeed(mnemonic)

Returns the seed hex encoded.

ParamTypeDescription
mnemonicstringBip39 mnemonic with words from the English word list.

Returns A String

bech32ToHex(bech32)

Returns a parsed hex String from bech32.

ParamTypeDescription
bech32stringThe address Bech32 string

Returns A String

hexToBech32(hex, bech32_hrp (optional))

Returns a parsed bech32 String from hex.

ParamTypeDescription
bech32stringThe address Bech32 string
bech32_hrpstringThe Bech32 hrp string

Returns A String

isAddressValid(address: string): boolean

Checks if a given address is valid.

ParamTypeDescription
addressstringThe address Bech32 string

Returns A boolean.

retry(messageId: string): Promise

Retries (promotes or reattaches) the message associated with the given id.

ParamTypeDescription
messageIdstringThe id of the message to retry

Returns A promise resolving to the new Message instance.

retryUntilIncluded(messageId: string[, interval: int, maxAttempts: int]): Promise

Retries (promotes or reattaches) the message associated with the given id until it's included in the Tangle. Default interval is 5 seconds and max_attempts is 10.

ParamTypeDescription
messageIdstringThe id of the message to retry
[options.interval]intThe interval in seconds in which we retry the message.
[options.maxAttempts]intThe maximum of attempts we retry the message.

Returns the message ids and Message of reattached messages.

consolidateFunds(seed: string, accountIndex: int, startIndex: int, endIndex: int): Promise

Function to consolidate all funds from a range of addresses to the address with the lowest index in that range

ParamTypeDescription
seedstringThe seed
accountIndexintThe account index.
startIndexintThe lowest address index, funds will be consolidated to this address.
endIndexintThe address index until which funds will be consolidated

Returns the address to which the funds got consolidated, if any were available.

getInfo(): Promise

Gets information about the node.

Returns a promise resolving to the NodeInfoWrapper object.

getTips(): Promise<[string, string]>

Gets two non-lazy tips.

Returns a promise resolving to an array of length 2 containing the message ids of the tips.

postMessage(message): Promise

Submits a message.

ParamTypeDescription
messageMessageDtoThe message to submit

Returns the message identifier.

getMessage(): MessageFinder

Gets a message from its identifier.

Returns an instance of the MessageFinder for choices of response.

getOutput(outputId): Promise

Gets the UTXO outputs associated with the given output id.

ParamTypeDescription
outputIdstringThe id of the output to search

Returns a promise resolving to the associated OutputMetadata.

findOutputs(outputIds, addresses): Promise<OutputMetadata[]>

Gets the UTXO outputs associated with the given output ids and addresses.

ParamTypeDescription
addressesstring[]The list of addresses to search
outputIdsstring[]The list of output ids to search

Returns a promise resolving to a list of OutputMetadata.

getAddressOutputs(address[, options]): Promise<string[]>

Gets the UTXO outputs associated with the given address.

ParamTypeDescription
addressstringThe address Bech32 string
[options.includeSpent]booleanWhether the query should include spent outputs or not
[options.outputType]'SignatureLockedSingle' | 'SignatureLockedDustAllowance'The output type filter

Returns a promise resolving to a list of output ids.

getAddressBalance(address): Promise

Gets the balance of the given address.

ParamTypeDescription
addressstringThe address Bech32 string

getMilestone(index): Promise

Gets the milestone by the given index.

ParamTypeDescription
indexnumberThe index of the milestone

Returns a promise resolving to the MilestoneMetadata.

getMilestoneUtxoChanges(index): Promise

Gets the utxo changes by the given milestone index.

ParamTypeDescription
indexnumberThe index of the milestone

Returns a promise resolving to the MilestoneUTXOChanges.

getReceipts(): Promise<Receipts[]>

Get all receipts.

Returns a promise resolving to the Receipts.

getReceiptsMigratedAt(index): Promise<Receipts[]>

Get all receipts for a given milestone index

ParamTypeDescription
indexnumberThe index of the milestone

Returns a promise resolving to the Receipts.

getTreasury(): Promise

Get the treasury amount.

Returns a promise resolving to the Treasury.

getIncludedMessage(): Promise

Get the included message of a transaction.

ParamTypeDescription
indexstringThe id of the transaction

Returns A promise resolving to the new Message instance.

reattach(messageId): Promise

Reattaches the message associated with the given id.

ParamTypeDescription
messageIdstringThe id of the message to reattach

Returns A promise resolving to the new Message instance.

promote(messageId): Promise

Promotes the message associated with the given id.

ParamTypeDescription
messageIdstringThe id of the message to promote

Returns A promise resolving to the new Message instance.

NetworkInfo

FieldTypeDescription
networkstringThe network
networkIdnumberThe network hashed
bech32HRPstringBech32 HRP for this network
minPoWScorenumberThe network's minimum score for PoW
localPowbooleanWhether we are using local PoW or not

TopicSubscriber

topic(topic): TopicSubscriber

Adds a topic to this manager instance.

ParamTypeDescription
topicstringA MQTT topic

Returns the topic subscriber instance for chained calls.

topics(topic): TopicSubscriber

Adds a list of topics to this manager instance.

ParamTypeDescription
topicsstring[]An array of MQTT topics

Returns the topic subscriber instance for chained calls.

subscribe(cb): TopicSubscriber

Subscribe to the provided topics.

ParamTypeDescription
cbfunctionThe topic handler callback in the form of (err, message) => {}

Returns the topic subscriber instance for chained calls.

unsubscribe(cb: Callback): TopicSubscriber

Unsubscribes from the provided topics.

ParamTypeDescription
cbfunctionA callback executed when the unsubscribe is finished in the form of (err, message) => {}

Returns the topic subscriber instance for chained calls.

MessageSender

Builder to create and submit messages to the Tangle.

index(index): MessageSender

Sets the message indexation. This field is required for indexation payloads.

ParamTypeDescription
indexstringnumber[]

Returns the message submit instance for chained calls.

data(data): MessageSender

Sets the indexation data.

ParamTypeDescription
datastringUint8Array

Returns the message submit instance for chained calls.

seed(seed): MessageSender

Sets the transaction account seed. This field is required for transaction payloads.

ParamTypeDescription
seedstringThe hex-encoded seed of the account to spend

Returns the message submit instance for chained calls.

parents(messageId): MessageSender

Sets 1-8 custom parent message ids.

ParamTypeDescription
messageIdstring[]The parents message ids

Returns the message submit instance for chained calls.

accountIndex(index): MessageSender

Sets the account index. This field is required for transactions.

ParamTypeDescription
indexnumberThe account index

Returns the message submit instance for chained calls.

input(transactionId, index): MessageSender

Adds an output to the transaction.

ParamTypeDescription
transactionIdstringThe transaction id
indexnumberThe input index

Returns the message submit instance for chained calls.

inputRange(start, end): MessageSender

Defines the range in which to search for addresses fro custom inputs.

ParamTypeDescription
startnumberThe start index
endnumberThe end index

Returns the message submit instance for chained calls.

output(address, amount): MessageSender

Adds an output to the transaction.

ParamTypeDescription
addressstringThe output address
amountnumberThe output amount

Returns the message submit instance for chained calls.

dustAllowanceOutput(address, amount): MessageSender

Adds a dust allowance output to the transaction.

ParamTypeDescription
addressstringThe output address
amountnumberThe output amount

Returns the message submit instance for chained calls.

initialAddressIndex(index): MessageSender

Sets the initial address index to search for balance. Defaults to 0 if the function isn't called.

ParamTypeDescription
indexnumberThe initial address index

Returns the message submit instance for chained calls.

submit(): Promise

Submits the message.

Returns a promise resolving to the message identifier.

UnspentAddressGetter

Gets a valid unspent address associated with the seed.

accountIndex(index): UnspentAddressGetter

Sets the account index. This field is required.

ParamTypeDescription
indexnumberThe account index

Returns the address getter instance for chained calls.

initialAddressIndex(index): UnspentAddressGetter

Sets the initial address index. Defaults to 0 if the function isn't called.

ParamTypeDescription
indexnumberThe initial address index

Returns the address getter instance for chained calls.

get(): Promise<[Address, number]>

Performs the operation.

Returns a promise resolving to the Address instance and its index.

AddressGetter

Generates addresses with a given seed.

accountIndex(index): AddressGetter

Sets the account index. This field is required.

ParamTypeDescription
indexnumberThe account index

Returns the address finder instance for chained calls.

range(start, end): AddressGetter

Defines the range of addresses to get. Defaults to 0..20 if the function isn't called.

ParamTypeDescription
startnumberThe first address index
endnumberThe last address index

Returns the address finder instance for chained calls.

includeInternal(): AddressGetter

Defines that public and internal address will be returned instead of only public addresses.

Returns the address finder instance for chained calls.

get(): Address[] | [Address, bool][]

Performs the operation.

Returns an array of public Address instances or an array of arrays with an Address and a bool, where the bool defines whether it's an internal address or not.

BalanceGetter

Gets balance on a given seed.

accountIndex(index): BalanceGetter

Sets the account index. This field is required.

ParamTypeDescription
indexnumberThe account index

Returns the balance getter instance for chained calls.

initialAddressIndex(index): BalanceGetter

Sets the initial address index. Defaults to 0 if the function isn't called.

ParamTypeDescription
indexnumberThe initial address index

Returns the balance getter instance for chained calls.

gapLimit(amount): BalanceGetter

Sets the gapLimit to specify how many addresses will be checked each round. If gapLimit amount of addresses in a row have no balance the BalanceGetter will return. Defaults to 20 if the function isn't called.

ParamTypeDescription
gapLimitnumberThe initial address index

Returns the balance getter instance for chained calls.

get(): Promise

Performs the operation.

Returns a promise resolving to the account balance.

MessageFinder

Gets a message by indexation key or identifier.

initialAddressIndex(index): Promise<string[]>

ParamTypeDescription
indexstringThe indexation key

Gets a list of message identifiers associated with the given indexation key.

Returns a promise resolving to the list of associated ids.

data(id): Promise

Gets the message object associated with the given identifier.

ParamTypeDescription
idstringThe message identifier

Returns a Message object.

raw(id): Promise

Gets the message raw data.

ParamTypeDescription
idstringThe message identifier

Returns the message raw data as string.

children(id): Promise<string[]>

Gets the children of the given message.

ParamTypeDescription
idstringThe message identifier

Returns the list of message ids of the message children.

metadata(id): Promise

Gets the metadata of the given message.

ParamTypeDescription
idstringThe message identifier

Returns a MessageMetadata object.

BrokerOptions

FieldTypeDescription
automaticDisconnectnumberWhether the MQTT broker should be automatically disconnected when all topics are unsubscribed or not.
timeoutnumberMQTT connection timeout in secods

Address

FieldTypeDescription
datastringAddress as a Bech32 string

Message

FieldTypeDescription
networkIdnumberNetwork identifier
parentsstring[]Message ids of the message references
payloadPayloadMessage payload
noncenumberMessage nonce

MessageWrapper

FieldTypeDescription
messageMessageMessage
messageIdstringThe message id

Payload

FieldTypeDescription
data{ type: 'Transaction', data: TransactionPayload } | { type: 'Indexation', data: IndexationPayload } | { type: 'Milestone', data: MilestonePayload}Payload data
TransactionPayload
FieldTypeDescription
essenceTransactionPayloadEssenceTransaction essence
unlock_blocksUnlockBlock[]Unlock blocks
  • TransactionPayloadEssence
FieldTypeDescription
inputsInput[]Inputs
outputsOutput[]Outputs
payloadPayload | undefinedPayload for chaining
  • Input
FieldTypeDescription
type'UTXO'Input type identifier
datastringThe associated output id
  • Output
FieldTypeDescription
type'SignatureLockedSingle'Output type identifier
data{ address: string, amount: number }The output definition
  • UnlockBlock
FieldTypeDescription
type'Signature' | 'Reference'Unlock block type identifier
dataEd25519SignatureUnlockBlock | numberUnlock block data (signature type or reference index)
  • Ed25519SignatureUnlockBlock
FieldTypeDescription
public_keynumber[]Ed25519 public key
signaturenumber[]Ed25519 signature
IndexationPayload
FieldTypeDescription
indexstringIndexation key
datanumber[]Indexation data as byte array
MilestonePayload
FieldTypeDescription
essenceMilestoneEssenceMilestone essence
signaturesnumber[][]Milestone signatures
  • MilestoneEssence
FieldTypeDescription
indexnumberMilestone index
timestampnumberTimestamp
parentsstring[]Message ids of the messages the milestone references
merkleProofnumber[]Merkle proof
nextPoWScorenumberNext PoW score
nextPoWScoreMilestoneIndexnumberMilestone index at which the nextPoWScore will be used
publicKeysnumber[][]public keys

MessageDto

FieldTypeDescription
parentsstring[] | undefinedMessage ids of the messages it references. getTips is used by default
payloadPayloadDtoMessage payload

PayloadDto

FieldTypeDescription
dataTransactionPayloadDto | IndexationPayloadDtoPayload data
TransactionPayloadDto
FieldTypeDescription
essenceTransactionPayloadEssenceDtoTransaction essence
unlockBlocksUnlockBlockDto[]Unlock blocks
  • TransactionPayloadEssenceDto
FieldTypeDescription
inputsstring[]Inputs
outputsOutput[]Outputs
payloadPayloadDto | undefinedPayload for chaining
  • OutputDto
FieldTypeDescription
addressstringOutput address
amountamountOutput amount
  • UnlockBlockDto
FieldTypeDescription
dataEd25519SignatureUnlockBlockDto | numberUnlock block data (signature type or reference index)
  • Ed25519SignatureUnlockBlockDto
FieldTypeDescription
publicKeynumber[]Ed25519 public key
signaturenumber[]Ed25519 signature
IndexationPayloadDto
FieldTypeDescription
indexstringIndexation key
dataUint8ArrayIndexation data
AddressBalance
FieldTypeDescription
addressstringBech32 encoded address
balancenumberAddress balance
dustAllowedbooleanDust allowed

MessageMetadata

FieldTypeDescription
messageIdstringMessage identifier
parentsstring[]Message id of the messages it references
isSolidbooleanMessage solid state
shouldPromoteboolean | undefinedIndicates whether the message should be promoted or not
shouldReattachboolean | undefinedIndicates whether the message should be reattached or not
referencedByMilestoneIndexnumber | undefinedIndex of the milestone that references this message
ledgerInclusionStatestring | undefinedLedger inclusion state

NodeInfoWrapper

FieldTypeDescription
urlstringNode url
nodeinfoNodeInfoNodeInfo

NodeInfo

FieldTypeDescription
namestringNode name
versionstringNode version
isHealthybooleanNode health status
networkIdstringNode network identifier
bech32HRPstringBech32 HRP for this network
minPoWScorenumberMin PoW score
messagesPerSecondnumberNetwork stats: Messages per second in the network
referencedMessagesPerSecondnumberNetwork stats: Referenced messages per second
referencedRatenumberNetwork stats: referenced rate
latestMilestoneTimestampnumberTimestamp of the latest milestone
latestMilestoneIndexnumberIndex of the latest milestone
confirmedMilestoneIndexnumberIndex of the confirmed milestone
pruningIndexnumberPruning index
featuresstring[]List of node features

OutputMetadata

FieldTypeDescription
messageIdstringId of the message associated with the output
transactionIdstringId of the transaction associated with the output
outputIndexnumberOutput index
isSpentbooleanOutput spent state
addressstringOutput address
amountnumberOutput amount

MilestoneMetadata

FieldTypeDescription
milestoneIndexnumberMilestone index
messageIdstringId of the message associated with the milestone
timestampnumberMilestone timestamp

MilestoneUTXOChanges

FieldTypeDescription
indexnumberMilestone index
createdOutputsstring[]OutputIds from new created outputs
consumedOutputsstring[]OutputIds from consumed outputs

Receipts

FieldTypeDescription
receiptreceiptReceipt
milestoneIndexnumberMilestone index

Treasury

FieldTypeDescription
milestoneIdstringMilestone id
amountnumberAmount

Troubleshooting

StackExchange

https://iota.stackexchange.com

The IOTA StackExchange a a nice tool for developers to find answers for a problem. Just search your problem and find your answer! If there is no one, submit your question and share it in the discussion channel below.

Joining the discussion

If you want to get involved in discussions about this library, or you're looking for support, go to the #clients-discussion channel on Discord.

IOTA Client Python Library

status

Joining the discussion

If you want to get involved in discussions about this library, or you're looking for support, go to the #clients-discussion channel on Discord.

Changes from IOTA 1.0 to 1.5

For an overview of all significant changes in the protocol, please see Chrysalis documentation.

Overview

Getting Started with IOTA Client Python binding

Installation

Easiest way how to get python binding up and running is to leverage pre-built python libraries for linux/macos/windows that can be installed to your python environment (3.6+) via pip. The binding is automagically generated using github actions.

The latest artifacts for major python versions can be also grabbed using nighly.link service. Download zip file for the given os and pyversion, unpack wheel file (.whl) and install it via pip:

pip install <wheel_file>

Once it has been properly installed you can double check it using pip:

pip list

You should see the similar output:

Package                    Version
-------------------------- -------
iota-client-python           0.2.0a3

Once installed in the given python environment you are all set and can start hacking using python binding!

Usage

import iota_client
print(iota_client.__doc__)
print(dir(iota_client))

Examples

Please note: It is not recommended to store passwords/seeds on host's environment variables or in the source code in a production setup! Please make sure you follow our backup and security recommendations for production use!

Connecting to node(s)

All features of iota.rs library are accessible via an instance of Client class that provides high-level abstraction to all interactions over IOTA network (Tangle). This class has to be instantiated before starting any interactions with the library, or more precisely with IOTA nodes that power IOTA network.

The library is designed to automatically choose a starting IOTA node based on the network type one would like to participate in: testnet or mainnet. So very simplistic example how to connect to IOTA testnet is the following one:

import iota_client

# client will connect to testnet by default
client = iota_client.Client()
print(client.get_info())

Output example of get_info() function of the Client instance:

{
   "nodeinfo":{
      "name":"HORNET",
      "version":"0.6.0-alpha",
      "is_healthy":true,
      "network_id":"testnet7",
      "bech32_hrp":"atoi",
      "min_pow_score":4000.0,
      "messages_per_second":27.3,
      "referenced_messages_per_second":34.5,
      "referenced_rate":126.37362637362637,
      "latest_milestone_timestamp":1618133322,
      "latest_milestone_index":33602,
      "confirmed_milestone_index":33602,
      "pruning_index":16086,
      "features":[
         "PoW"
      ]
   },
   "url":"https: //api.hornet-0.testnet.chrysalis2.com"
}

The most important properties:

  • is_healthy: indicates whether the given node is in sync with the network and so it is safe to use it. Even if a node is up and running it may not be fully prepared to process your API calls properly. The node should be "synced", meaning should be aware of all TXs in the Tangle. It is better to avoid not fully synced nodes. A node healthiness can be alternatively obtained also with a method Client.get_health()
  • bech32_hrp: it indicates whether the given node is a part of testnet (atoi) or mainnet (iota). See more info regarding IOTA address format

Please note, when using node load balancers then mentioned health check may be quite useless since follow-up API calls may be served by different node behind the load balancer that may have not been actually checked. One should be aware of this fact and trust the given load balancer participates only with nodes that are in healthy state. iota.rs library additionally supports a management of internal node pool and so load-balancer-like behavior can be mimicked using this feature locally.

Needless to say, the Client class constructor provides several parameters via which the process can be closely managed.

The most common ones:

  • network: can be Testnet or Mainnet. It instructs the library whether to automatically select testnet nodes or mainnet nodes
  • node: specify address of actual running IOTA node that should be used to communicate with (in format https://node:port), for ex: https://api.lb-0.testnet.chrysalis2.com:443
  • node_pool_urls: library also supports a management of pool of nodes. You can provide a list of nodes and library manages access to them automatically (selecting them based on their sync status)
  • local_pow: local_pow==True (by default) means a Proof-of-work is done locally and not remotely
  • node_sync_disabled: node_sync_disabled==False (by default) means that library checks for sync status of node(s) periodically before its use. node_sync_disabled==True means library also uses nodes that are not in sync with network. This parameter is usually useful if one would like to interact with local test node that is not fully synced. This parameter should not be used in production

If node_pool_urls is provided then the library periodically checks in some interval (argument node_sync_interval) whether node is in sync or not.

Generating seed and addresses

Since the IOTA network is permission-less type of network, anybody is able to use it and interact with it. No central authority is required at any stage. So anybody is able to generate own seed and then deterministically generate respective private keys/addresses.

Please note, it is highly recommended to NOT use online seed generators at all. The seed is the only key to the given addresses. Anyone who owns the seed owns also all funds related to respective IOTA addresses (all of them).

We strongly recommend to use official wallet.rs library together with stronghold.rs enclave for value-based transfers. This combination incorporates the best security practices while dealing with seeds, related addresses and UTXO. See more information on Chrysalis docs.

IOTA uses Ed25519 signature scheme and address is usually represented by Bech32 (checksummed base32) format string of 64 characters.

A root of Ed25519 signature scheme is basically a 32-byte (256-bit) uniformly randomly generated seed based on which all private keys and corresponding addresses are generated. In the examples below, the seed is represented by a string of 64 characters using [0-9a-f] alphabet (32 bytes encoded in hexadecimal).

Seed can be for example generated using SHA256 algorithm on some random input generated by cryptographically secure pseudo-random generator, such as os.urandom():

import os
import hashlib

rnd_seed = hashlib.sha256(os.urandom(256)).hexdigest()
print(rnd_seed)

Seed examples (a single seed per line):

4892e2265c45734d07f220294b1697244a8ab5beb38ba9a7d57aeebf36b6e84a
37c4aab22a5883595dbc77907c1626c1be39d104df39c5d5708423c0286aea89
e94346bce41402155ef120e2525fad2d0bf30b10a89e4b93fd8471df1e6a0981
...

In modern wallet implementations, such as our wallet.rs library and firefly wallet, the seed is usually generated from a seed mnemonic (seed phrase), using BIP39 standard, to be better memorized/stored by humans. It is based on randomly generated list of english words and later used to generate the seed. Either way, the seed is a root for all generated private keys and addresses

Address/key space

Before an actual address generation process, let's quickly focus on BIP32 standard that describes an approach to Hierarchical Deterministic Wallets. The standard was improved by BIP44 lately.

These standards define a tree structure as a base for address and key space generation which is represented by a derivation path:

m / purpose / coin_type / account / change / address_index
  • m: a master node (seed)
  • purpose: constant which is {44}
  • coin_type: a constant set for each crypto currency. IOTA = 4218, for instance.
  • account: account index. Zero-based increasing int. This level splits the address/key space into independent branches (ex. user identities) which each has own set of addresses/keys
  • change: change index which is {0, 1}, also known as wallet chain.
    There are two independent chain of addresses/keys. 0 is reserved for public addresses (for coin receival) and 1 is reserved for internal (also known as change) addresses to which transaction change is returned. IOTA is totally fine with address reuse, and so it is, technically speaking, totally valid to return transaction change to the same originating address. So it is up to developers whether to leverage it or not. iota.rs library and its sibling wallet.rs help with either scenario
  • address_index: address index. Zero-based increasing int that indicates an address index

As outlined, there is a quite large address/key space that is secured by a single unique seed.

And there are few additional interesting notes:

  • Each level defines a completely different subtree (subspace) of addresses/keys and those are never mixed up
  • The hierarchy is ready to "absorb" addresses/keys for many different coins at the same time (coin_type), and all those coins are secured by the same seed.
    (So basically any BIP32/44-compliant wallet is potentially able to manage any BIP32/44-compliant coin(s))
  • There may be also other purposes in the future however let's consider a single purpose as of now. The constant 44 stands for BIP44
  • The standard was agreed upon different crypto communities, although not all derivation path components are always in active use. For example, account is not always actively leveraged across crypto space (if this is a case then there is usually account=0 used)
  • Using different accounts may be useful to split addresses/key into some independent spaces and it is up to developers to implement.
    Please note, it may have a negative impact on a performance while account discovery phase. So if you are after using many multiple accounts then you may be interested in our stateful library wallet.rs that incorporates all business logic needed to efficiently manage independent accounts. Also our exchange guide provides some useful tips how different accounts may be leveraged

address_generation

So in case of IOTA, the derivation path of address/key space is [seed]/44/4218/{int}/{0,1}/{int}. The levels purpose and coin_type are given, the rest levels are up to developers to integrate.

Generating address(es)

IOTA addresses are generated via Client.get_addresses() function that returns a list of tuples with generated addresses. Considering the previous chapter about individual address/key spaces, it becomes quite clear what all used input function arguments are for.

Please note: for the examples outlined below, an example seed b3d7092195c36d47133ff786d4b0a1ef2ee6a0052f6e87b6dc337935c70c531e was used via environment variable called IOTA_SEED_SECRET. This seed serves for training purposes only.

The whole process is deterministic which means the output is the same as long as the seed is the same:

import os
import iota_client

# Get the seed from environment variable
IOTA_SEED_SECRET = os.getenv('IOTA_SEED_SECRET')
if not IOTA_SEED_SECRET:
    raise Exception("Please define environment variable called `IOTA_SEED_SECRET`")

client = iota_client.Client()

address_changed_list = client.get_addresses(
    seed=IOTA_SEED_SECRET,
    account_index=0,
    input_range_begin=0,
    input_range_end=10,
    get_all=True
)
print(address_changed_list)

Output example:

[('atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86', False),
 ('atoi1qzfvkkp398v7hhvu89fu88hxctf7snwc9sf3a3nd7msfv77jk7qk2ah07s3', True),
 ('atoi1qq4t98j5y8wxkaujue99mjwqcp6jvvmsd5lv0755sz7dtjdz3p2lydv76sy', False),
 ('atoi1qrhzhjxc4z8vpwjt3hafs5xpdng5katqe890p0h95mc0l273j8yzxn7r4hc', True),
 ('atoi1qputu0yvfvxd7g39wf4rc67e0f0dyhl6enxu9jxnsrjqmemh067tw7qelyc', False),
 ('atoi1qptg5w2x47qwjf3gpqt3h7d2ey5x7xf8v7qtt29gkxt4mjfjfc28sutvd8a', True),
 ('atoi1qprvelq9paakh72fgm6j2kf8kexadw3t5xljer9dpsep5c7wx5mjwdxch6z', False),
 ('atoi1qrwk37tz47ddng9kpxfflkpz5tplcq7ll56v4acam04307xk70l7uf6wg8j', True),
 ('atoi1qper3zr5xe9x0wqs35ytwh622870g44frkyygdhs0ds8yejle3xujhq7dx3', False),
 ('atoi1qq6lkr9hucfylqjaqphu0stvk8pcmsx98r7ukuq40asszwmqytlnc058thk', True),
 ('atoi1qzpn7se3ryhscmqg404pycxzvfpt8v4xn8aul0tqdh00xsncgnxu7na7zjj', False),
 ('atoi1qz4qqakty9qytw8fk9shelt9lwlvv83s5ggt3wjag9fkgcc74z78w4l86y5', True),
 ('atoi1qp20uddchglqry0l5qnjg5aln8d5rk2v5l45hwrxv9z0daxs7u6xcsh4077', False),
 ('atoi1qrlqm2u5txxxnjx22fxq0jfjzk6l4nwnue6ht5pepk65m2f4xmxqynmxu2m', True),
 ('atoi1qqydc70mpjdvl8l2wyseaseqwzhmedzzxrn4l9g2c8wdcsmhldz0ulwjxpz', False),
 ('atoi1qrkjennxyl2xcqem6x69ya65sasma33z0ux872k846lqft0s3qf7k6lqpft', True),
 ('atoi1qr4yuekp30ff7mnnnjwy9tdhynxmlmkpuxf70qurtwudp2zpf3jeyw4uh37', False),
 ('atoi1qp6m5sz5ayjtccfxapdk5lp4qkheyfg0emzntmulyxzftps730vcul8dmqr', True),
 ('atoi1qzrwhkzhu67fqltfffwljejawdcghedukpgu9x6tzevwlnq89gmfjtayhgz', False),
 ('atoi1qpehxcp24z947dgupjqc9ktkn5ylmdxqqnx83m7xlajnf8005756u4n7z77', True)]
  • Each tuple contains address and bool value indicating the given address is a change address or not.
    True means the given address is a change address (internal). So basically we've got two independent sets of addresses (10 items per each)
  • This behavior is controlled via get_all argument. get_all=False (default) means to generate only public addresses

IOTA address is represented by a checksumed base 32 string (Bech32) and you can see a detailed explanation on Chrysalis docs. Just a recap:

  • If an address starts with atoi then it means it is related to testnet. iota stands for mainnet
  • Number 1 at 5th position is just a separator
  • The last 6 characters are reserved for a checksum

Address can be also represented in a hex format and luckily iota.rs provides some convenience functions to convert addresses respectively: Client.bech32_to_hex() and Client.hex_to_bech32().

To quickly validate any IOTA address, there is a convenience function Client.is_address_valid() that returns bool value. Needless to say, performing a sanity check of an address before its use is an advisable practice.

Checking a balance

In IOTA testnet, there is a faucet service that provides test tokens to any testnet address: https://faucet.testnet.chrysalis2.com/

There are three common api calls that can be leveraged:

  • Client.get_address_balance(address: str): it expects a single address in Bech32 format and returns dict with a balance for the address
  • Client.get_address_balances(list[str]): a convenience function that expects list of addresses in Bech32 format and returns list of dict with balances for all given addresses
  • Client.get_balance(seed, account_index (optional), initial_address_index(optional), gap_limit(optional)): a convenience function that combines Client.get_addresses() and Client.get_address_balances() api calls. It returns a combined balance for the provided seed and its wallet account index
import os
import iota_client

# Get the seed from environment variable
IOTA_SEED_SECRET = os.getenv('IOTA_SEED_SECRET')
if not IOTA_SEED_SECRET:
    raise Exception("Please define environment variable called `IOTA_SEED_SECRET`")

client = iota_client.Client()

print("Return a balance for a single address:")
print(
    client.get_address_balance("atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86")
)

print("Return a balance for the given seed and account_index:")
print(
    client.get_balance(
        seed=IOTA_SEED_SECRET,
        account_index=0,
        initial_address_index=0
    )
)

Example of output:

Return balance for a single address:
{
   "address_type": 0,
   "address":"atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86",
   "balance":9000000,
   "dust_allowed":false
}

Return balance for the given seed and account_index:
21000000
  • address_type indicates type of address. Value 0 denotes a Ed25519 address (currently the default for IOTA)
  • dust_allowed indicates whether the given address is allowed to accepts a dust due to dust protection mechanism

Client.get_balance() performs a several tasks under the hood. It starts generating addresses for the provided seed and account_index from initial_address_index, and checks for a balance of each of the generated addresses. Since it does not know how many addresses are used in fact, there is a condition set by gap_limit argument when to stop searching. If gap_limit amount of addresses in a row have no balance the function returns result and searching does not continue.

Messages, payload and transactions

Before we continue, let's introduce some additional terms that describe an unit that is actually broadcasted in IOTA network. IOTA is based on a concept of messages and payloads.

Message is a data structure that is actually being broadcasted in IOTA network and represent a node (vertex) in the Tangle graph. It can refer to up to 8 previous messages and once a message was attached to the Tangle and approved by a milestone, the Tangle structure ensures the content of the message is unaltered. Every message is referenced by message_id which is based on a hash algorithm of binary content of the message. Message is an atomic unit that is confirmed by network as a whole.

Message is broadcasted using a binary format, is arbitrary size (up to 35 kB) and it can hold a variable sets of information so called payloads. Number of payloads a single message can encapsulate is not given (even a message without any payload at all is completely valid).

Payload represents a layer of concern. Some payloads may change a state of the ledger (ex. transactions) and some may provide extra features to some specific applications and business use cases (ex. indexed data).

There are already implemented core payloads, such as SignedTransaction, MilestonePayload and IndexationPayload but the message and payload definition is generic enough to incorporate any future payload(s) the community agrees upon.

Needless to say, IOTA network ensures the outer structure of message itself is valid and definitely aligned with a network consensus protocol, however the inner structure is very flexible, future-proof, and offer an unmatched network extensibility.

messages_in_tangle

The current IOTA network incorporates the following core payloads:

  • SignedTransaction: payload that describes UTXO transactions that are cornerstone of value-based transfers in IOTA network. Via this payload, message can be also cryptographically signed
  • MilestonePayload: payload that is emitted by Coordinator
  • IndexationPayload: payload that enables addition of an index to the encapsulating message, as well as some arbitrary data. The given index can be later used to search the message(s)

Unspent Transaction Output (UTXO)

IOTA uses unspent transaction output model, so called UTXO. It is based on an idea to track unspent amount of tokens via data structure called output.

Simplified analogy:

  • There is 100 tokens recorded in the ledger as Output A and this output belongs to Alice. So initial state of ledger: Output A = 100 tokens
  • Alice sends 20 tokens to Paul, 30 tokens to Linda and keeps 50 tokens at her disposal
  • Her 100 tokens are recorded as Output A and so she has to divide (spent) tokens and create three new outputs:
    Output B with 20 tokens that goes to Paul, Output C with 30 tokens that goes to Linda and finally Output D with the rest 50 tokens that she keep for herself
  • Original Output A was completely spent and can't be used any more. It has been spent and so becomes irrelevant to ledger state
  • New state of ledger: Output B = 20 tokens, Output C = 30 tokens and Output D = 50 tokens
  • Total supply remains the same. Just number of outputs differs and some outputs were replaced by other outputs in the process

utxo

The key takeaway of the outlined process is the fact that each unique output can be spent only once. Once the given output is spent, can't be used any more and is irrelevant in regards to the ledger state.

So even if Alice still wants to keep remaining tokens at her fingertips, those tokens have to be moved to completely new output that can be for instance still tight to the same Alice's iota address as before.

Every output stores also information about an IOTA address to which it is coupled with. So addresses and tokens are indirectly coupled via outputs. So basically sum of outputs and their amounts under the given address is a balance of the given address, ie. the number of tokens the given address can spend. And sum of all unspent outputs and theirs amounts is equal to the total supply.

Before the chapter is wrapped up, one thing was left unexplained: "how outputs are being sent and broadcasted to network?" Outputs are being sent encapsulated in a message as a part of SignedTransaction payload.

Outputs

There are three functions to get UTXO outputs (related to the given address):

  • Client.get_address_outputs(str): it expects address in Bech32 format and returns list[dict] of transaction_ids and respective indexes
  • Client.get_output(str): it expects output_id and returns the UTXO output associated with it
  • Client.find_outputs(output_ids (optional), addresses (optional)): it is a bit more general and it searches for UTXO outputs associated with the given output_ids and/or addresses

Client.get_address_outputs(str) returns transaction_ids and indexes in a raw form (in bytes) defined on protocol level and so usually some quick conversion is needed:

import iota_client
client = iota_client.Client()

outputs = client.get_address_outputs("atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86")
for output in outputs:
    print(f"Output index: {output['index']}; raw transaction id: {output['transaction_id']}")
    encoded_hex = "".join(f"{i:0>2x}" for i in output["transaction_id"] + list(int(output["index"]).to_bytes(2, 'little')))
    print(f"`output_id` encoded in hex: {encoded_hex}")

Output example:

Output index: 0; raw transaction id: [162, 44, 186, 6, 103, 201, 34, 203, 177, 248, 189, 202, 249, 112, 178, 168, 129, 204, 214, 232, 142, 47, 204, 229, 3, 116, 222, 42, 172, 124, 55, 114]
`output_id` encoded in hex: a22cba0667c922cbb1f8bdcaf970b2a881ccd6e88e2fcce50374de2aac7c37720000
  • as a result, UTXO output is represented by output index and transaction_id. transaction_id is basically a list of 32 bytes. index is 2-bytes (16bits) uint
  • index and transaction_id is usually combined into single hex string of 68 characters = 32 * 2 chars (transaction_id; 32 bytes in hex) + 4 chars (index; 2 bytes in hex).
    The resulting output_id is the unique id of the given output

Then the function Client.get_output(str) can be used to get details about the given output_id:

import iota_client
client = iota_client.Client()

print(
    client.get_output("a22cba0667c922cbb1f8bdcaf970b2a881ccd6e88e2fcce50374de2aac7c37720000")
)

Output example:

{
   "message_id":"f51fb2839e0a24d5b4a97f1f5721fdac0f1eeafd77645968927f7c2f4b46565b",
   "transaction_id":"a22cba0667c922cbb1f8bdcaf970b2a881ccd6e88e2fcce50374de2aac7c3772",
   "output_index":0,
   "is_spent":false,
   "output":{
      "treasury":"None",
      "signature_locked_single":{
         "kind":0,
         "address":{
            "ed25519":{
               "kind":0,
               "address":"atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86"
            }
         },
         "amount":10000000
      },
      "signature_locked_dust_allowance":"None"
   }
}

A function Client.find_outputs() is a convenient shortcut combining both mentioned methods in a single call:

import iota_client
client = iota_client.Client()

client.find_outputs(addresses=["atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86"])
  • it supports two arguments, a list of output_ids or a list of addresses

Output example:

[
   {
      "message_id":"7c47db1c4555348c260d91e90cc10fd66c2e73a84ec24bf9533e440f6d945d42",
      "transaction_id":"c3f416b4394dd6e49354444d53e3c33f1169f25889ad7bc1c2ea83e35e291d5c",
      "output_index":1,
      "is_spent":false,
      "output":{
         "treasury":"None",
         "signature_locked_single":{
            "kind":0,
            "address":{
               "ed25519":{
                  "kind":0,
                  "address":"atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86"
               }
            },
            "amount":9000000
         },
         "signature_locked_dust_allowance":"None"
      }
   }
]
  • message_id: refer to the encapsulating message in which the transaction was sent
  • transaction_id, output_index: refer to the given output within the SignedTransaction payload. There may be several different outputs involved in a single transaction and so just transaction_id is not enough
  • output: this section provides details about the iota address to which the given unspent transaction output is coupled with
  • amount: state an amount of tokens related to the output
  • is_spent: of course, very important one indicating whether the given output is a part of the actual ledger state or not. As mentioned above, if an output was already spent, it is not part of ledger state any more and was replaced by some other output(s) in the process

So this is quite interesting part, notice the output_id that was used in a function call to get output details is the same as a combination of transaction_id and output index.

This way a transaction is tightly coupled with outputs since SignedTransaction payload is a main vehicle how outputs are being created and spent, and altogether everything is encapsulated in a message.

Messages

As mentioned above, the message is encapsulating data structure that is being actually broadcasted across network. It is an atomic unit that is accepted/rejected as a whole.

There is a convenient function Client.message() that prepares a message instance and sends it over a network. It accepts wide range of input parameters and can help with any kind of message type to be broadcasted.

The simplest message that can be broadcasted is a message without any particular payload:

import iota_client
client = iota_client.Client()

message = client.message()
print(message)

Output example:

{
   "message_id":"e2daa4c6b012b615becd6c12189b2c9e701ba0d53b31a15425b21af5105fc086",
   "network_id":7712883261355838377,
   "parents":[
      "0e2705ce50fec88f896663d4b7d562e74cbcfdd951ac482b1f03cfa5f27396d7",
      "0f5a0b2041766127c3f3bff2dd653b450b72e364765fcc805a40423c59ed01f9",
      "20635b30aee437575d7e6abdf6629eec80543bee30848b0abdda2200fc11a977",
      "da97cd6cfcbb854b8fd3f064c8459c5c9eae80dbd5ef594a3e1a26dcb8fc078c"
   ],
   "payload":"None",
   "nonce":2305843009213869242
}
  • message_id is an unique id that refers to the given message in network
  • as mentioned above, every message in the Tangle should refer to up to 8 other messages, those are indicated in the section parents
  • no actual payload was given in this example message (payload=None)
  • nonce refer to a result of proof-of-work

Once a message is broadcasted, there are two main functions that can be used to read all information about the given message from the Tangle (Client.get_message_data() and Client.get_message_metadata()):

import iota_client
client = iota_client.Client()

message = client.get_message_data("e2daa4c6b012b615becd6c12189b2c9e701ba0d53b31a15425b21af5105fc086")
message_meta = client.get_message_metadata("e2daa4c6b012b615becd6c12189b2c9e701ba0d53b31a15425b21af5105fc086")

print("Message meta data:")
print(message_meta)
print("Message data:")
print(message)

Output example:

Message meta data:
{
   "message_id":"e2daa4c6b012b615becd6c12189b2c9e701ba0d53b31a15425b21af5105fc086",
   "parent_message_ids":[
      "0e2705ce50fec88f896663d4b7d562e74cbcfdd951ac482b1f03cfa5f27396d7",
      "0f5a0b2041766127c3f3bff2dd653b450b72e364765fcc805a40423c59ed01f9",
      "20635b30aee437575d7e6abdf6629eec80543bee30848b0abdda2200fc11a977",
      "da97cd6cfcbb854b8fd3f064c8459c5c9eae80dbd5ef594a3e1a26dcb8fc078c"
   ],
   "is_solid":true,
   "referenced_by_milestone_index":284866,
   "milestone_index":"None",
   "ledger_inclusion_state":{
      "state":"NoTransaction"
   },
   "conflict_reason":"None",
   "should_promote":"None",
   "should_reattach":"None"
}

Message data:
{
   "message_id":"e2daa4c6b012b615becd6c12189b2c9e701ba0d53b31a15425b21af5105fc086",
   "network_id":7712883261355838377,
   "parents":[
      "0e2705ce50fec88f896663d4b7d562e74cbcfdd951ac482b1f03cfa5f27396d7",
      "0f5a0b2041766127c3f3bff2dd653b450b72e364765fcc805a40423c59ed01f9",
      "20635b30aee437575d7e6abdf6629eec80543bee30848b0abdda2200fc11a977",
      "da97cd6cfcbb854b8fd3f064c8459c5c9eae80dbd5ef594a3e1a26dcb8fc078c"
   ],
   "payload":"None",
   "nonce":2305843009213869242
}
  • Client.get_message_metadata provides information how the given message fits to network structures such as ledger_inclusion_state, etc.
  • Client.get_message_data provides all data that relates to the given message and its payload(s)

IndexationPayload

IndexationPayload is a payload type that can be used to attach an arbitrary data and key index to a message. At least index should be provided in order to send the given payload. Data part (as list[bytes]) is optional one:

import iota_client
client = iota_client.Client()

# encoding utf string into list of bytes
some_utf_data = "some utf based data".encode("utf8")

message = client.message(
    index="some_data_index", data=some_utf_data
)
print(message)

Output example:

{
   "message_id":"8d4fa37be3c00691131c2c3e03e7b8b956c9118a2ce4be3a8597d51d82ed2de9",
   "network_id":7712883261355838377,
   "parents":[
      "3719d308ae14b7ef1ed5a3a1604228e97587b9da487db10bc6e4a4f800083da0",
      "4431e2f776db888488728e0aa34c94975e65d6fa74893aa675172af6b9f37257",
      "8f9fa84954c58bcfc9acc33ca827b4ea35c2caae88db736399a031120e85eebf",
      "f63d416de97e6a9fd1314fbbbbb263f30dff260f3075f9a65e7dfe1f2cc56ce3"
   ],
   "payload":{
      "transaction":"None",
      "milestone":"None",
      "indexation":[
         {
            "index":"736f6d655f646174615f696e646578",
            "data":[
               115,
               111,
               109,
               101,
               32,
               117,
               116,
               102,
               32,
               98,
               97,
               115,
               101,
               100,
               32,
               100,
               97,
               116,
               97
            ]
         }
      ],
      "receipt":"None",
      "treasury_transaction":"None"
   },
   "nonce":6917529027641573188
}
  • Feel free to check the given message using its message_id via Tangle explorer
  • In comparison to an empty message sent in the previous chapter, the payload section looks more interesting
  • There are three payloads prepared (transaction, milestone and indexation) however only indexation payload is leveraged this time
  • index was simply encoded to list[bytes] in hex (no hash algorithm) and the resulting string can be leveraged as an additional way how to search for a set of indexed messages with the same key index via Tangle explorer or Client.find_messages() API call
  • data contains an arbitrary data encoded in bytes
  • Please note there is no IOTA address involved while sending data messages. Such messages are referenced using message_id or key index
  • IOTA addresses are part of UTXO data structure that is sent using SignedTransaction payload explained below

SignedTransaction

SignedTransaction is a payload type that is used to transfer value-based messages as UTXO (Unspent Transaction Output).

As mentioned above, this core payload changes the ledger state as old outputs are being spent (replaced) and new outputs are being created:

import iota_client
client = iota_client.Client()

client.get_message_data("f51fb2839e0a24d5b4a97f1f5721fdac0f1eeafd77645968927f7c2f4b46565b")

Example of a message with SignedTransaction payload:

{
   "message_id":"f51fb2839e0a24d5b4a97f1f5721fdac0f1eeafd77645968927f7c2f4b46565b",
   "network_id":7712883261355838377,
   "parents":[
      "4a84bf1d345a441cfdefd0e71d6efe820c1077e5dda9122a09cbf026132d208c",
      "6e9153884fd1983be4c27c3ccdc69760b4775484eea498ec0707c2ff8901995e",
      "7ac1407c88007a54d603400b558d5110f2bbf93a68100fb34f0b40cece9d0868",
      "9ac0fd457998a1b3ddab9c0014f41344475358ad36c64a4b763de3b51f47c09a"
   ],
   "payload":{
      "transaction":[
         {
            "essence":{
               "inputs":[
                  {
                     "transaction_id":"4a34274992474d91cf45366425ad1d4df6042cba64f3b6c07d297a2e6b7154a9",
                     "index":0
                  }
               ],
               "outputs":[
                  {
                     "address":"atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86",
                     "amount":10000000
                  },
                  {
                     "address":"atoi1qzdnav0zdgd4grn25cnwcuudtahvlhgh0r349ur749y9l03vadrfurhkxwj",
                     "amount":100016136757200
                  }
               ],
               "payload":{
                  "transaction":"None",
                  "milestone":"None",
                  "indexation":[
                     {
                        "index":"54414e474c454b495420464155434554",
                        "data":[
                        ]
                     }
                  ],
                  "receipt":"None",
                  "treasury_transaction":"None"
               }
            },
            "unlock_blocks":[
               {
                  "signature":{
                     "public_key":[
                        ...
                     ],
                     "signature":[
                        ...
                     ]
                  },
                  "reference":"None"
               }
            ]
         }
      ],
      "milestone":"None",
      "indexation":"None",
      "receipt":"None",
      "treasury_transaction":"None"
   },
   "nonce":1146102
}

Each transaction includes the following set of information:

  • inputs: list of valid outputs that should be used to fund the given message. Those outputs will be spent and once the message is confirmed, those outputs are not valid anymore. Outputs are uniquely referenced via transaction_id and inner index. At least one output has to be given with enough balance to source all outputs of the given message
  • outputs: list of IOTA address(es) and related amount(s) the input outputs should be split among. Based on this information, new UTXO entities (outputs) are being created
  • unlock_blocks: it includes a transaction signature(s) (currently based on Ed25519 scheme) that proofs token ownership based on a valid seed. Needless to say, only valid seed owner is able to correctly sign the given transaction and proofs the ownership of tokens under the given output(s). Each input output has to have a corresponding unblock_block entry in case more outputs are used to fund the operation either using the given signature or as a reference to existing signature
  • payload: each SignedTransaction can include additional payload(s) such as IndexationPayload, etc. Meaning, any value-based messages can also contain arbitrary data and its key index. It is also an example how individual payloads can be encapsulated on different levels of concern

Sending value-based messages is also very straightforward process.

As a minimum, it needs a valid seed, output addresses and amount. The method finds valid output(s) that can be used to fund the given amount(s) and the unspent amount is sent to the same address:

import os
import iota_client

# Get the seed from environment variable
IOTA_SEED_SECRET = os.getenv('IOTA_SEED_SECRET')
if not IOTA_SEED_SECRET:
    raise Exception("Please define environment variable called `IOTA_SEED_SECRET`")

client = iota_client.Client()

message = client.message(
    seed=IOTA_SEED_SECRET,
    outputs=[
        {
            'address': 'atoi1qqydc70mpjdvl8l2wyseaseqwzhmedzzxrn4l9g2c8wdcsmhldz0ulwjxpz',
            'amount': 1_000_000
        }
    ]
)
print(message)

Output example:

{
   "message_id":"7c47db1c4555348c260d91e90cc10fd66c2e73a84ec24bf9533e440f6d945d42",
   "network_id":7712883261355838377,
   "parents":[
      "0ec0cd3c0303845980981bf7cc72371a8cd6e38c15924a2950fb15c5ecf4a53b",
      "4011f7724f96b6e39cdf9987ee650c0552d4fc63c09dd72b9be30a3cc7b53806",
      "5730d5bd607c6125130df30204c995db5edcbd16c4ab150946dffac37ace26f9",
      "8c1982682dbfa0abdd8772e38d044dbfcbea5ebb99bbe7174c07d81adda62419"
   ],
   "payload":{
      "transaction":[
         {
            "essence":{
               "inputs":[
                  {
                     "transaction_id":"a22cba0667c922cbb1f8bdcaf970b2a881ccd6e88e2fcce50374de2aac7c3772",
                     "index":0
                  }
               ],
               "outputs":[
                  {
                     "address":"atoi1qqydc70mpjdvl8l2wyseaseqwzhmedzzxrn4l9g2c8wdcsmhldz0ulwjxpz",
                     "amount":1000000
                  },
                  {
                     "address":"atoi1qp9427varyc05py79ajku89xarfgkj74tpel5egr9y7xu3wpfc4lkpx0l86",
                     "amount":9000000
                  }
               ],
               "payload":"None"
            },
            "unlock_blocks":[
               {
                  "signature":{
                     "public_key":[
                        "..."
                     ],
                     "signature":[
                        "..."
                     ]
                  },
                  "reference":"None"
               }
            ]
         }
      ],
      "milestone":"None",
      "indexation":"None",
      "receipt":"None",
      "treasury_transaction":"None"
   },
   "nonce":9223372036854802939
}

We recommend to use official wallet.rs library together with stronghold.rs enclave for value-based transfers. This combination incorporates the best security practices while dealing with seeds, related addresses and UTXO. See more information on Chrysalis docs.

Dust protection

Please note, there is also implemented a dust protection mechanism in the network protocol to avoid malicious actors to spam network in order to decrease node performance while keeping track of unspent amount (UTXO):

"... microtransaction below 1Mi of IOTA tokens [can be sent] to another address if there is already at least 1Mi on that address" That's why we did send 1Mi in the given example to comply with the protection."

API Reference

Note that in the following APIs, the corresponding exception will be returned if an error occurs. Also for all the optional values, the default values are the same as the ones in the Rust version.

Client

constructor(network (optional), storage (optional), password (optional), polling_interval (optional)): AccountManager

Creates a new instance of the Client.

ParamTypeDefaultDescription
[network]strundefinedThe network
[node]strundefinedA node URL
[name]strundefinedThe name for basic authentication
[password]strundefinedThe password for basic authentication
[nodes]list[str]undefinedAn array of node URLs
[node_sync_interval]intundefinedThe interval for the node syncing process
[node_sync_disabled]boolundefinedDisables the node syncing process. Every node will be considered healthy and ready to use
[node_pool_urls]strundefinedAn array of node pool URLs
[request_timeout]intundefinedSets the default HTTP request timeout
[api_timeout]dictundefinedThe API to set the request timeout. Key: 'GetHealth', 'GetInfo', 'GetPeers', 'GetTips', 'PostMessage', 'GetOutput', 'GetMilestone' Value: timeout in milliseconds
[local_pow]boolundefinedFlag determining if PoW should be done locally or remotely
[tips_interval]intundefinedTime between requests for new tips during PoW
[mqtt_broker_options]BrokerOptionsundefinedSets the options for the MQTT connection with the node

Returns The constructed Client.

Full Node APIs

get_health(): bool

Gets the node health status.

Returns whether the node is healthy.

get_info(): NodeInfoWrapper

Gets information about the node.

Returns the NodeInfoWrapper.

get_peers(): list[PeerDto]

Gets peers of the node.

Returns the list of PeerDto.

get_tips(): list[str]

Gets non-lazy tips.

Returns two non-lazy tips' message ids in list.

post_message(msg): str

Submits a message.

ParamTypeDefaultDescription
[msg]MessageundefinedThe message to submit

Returns the message id of the submitted message.

get_output(output_id): OutputResponse

Gets the UTXO outputs associated with the given output id.

ParamTypeDefaultDescription
[output_id]strundefinedThe id of the output to search

Returns the OutputResponse[#outputresponse].

get_address_balance(address): BalanceAddressResponse

Gets the balance in the address.

ParamTypeDefaultDescription
[address]list[str]undefinedThe address Bech32 string

Returns the BalanceAddressResponse.

get_address_outputs(address, options (optional)): list[UtxoInput]

Gets the UTXO outputs associated with the given address.

ParamTypeDefaultDescription
[address]strundefinedThe address Bech32 string
[options][AddressOutputsOptions]undefinedThe query filters

Returns the list of UtxoInput.

find_outputs(output_ids (optional), addresses (optional)): list[OutputResponse]

Gets the UTXO outputs associated with the given output ids and addresses.

ParamTypeDefaultDescription
[output_ids]list[str]undefinedThe list of addresses to search
[addresses]list[str]undefinedThe list of output ids to search

Returns the list of OutputResponse.

get_milestone(index): MilestoneDto

Gets the milestone by the given index.

ParamTypeDefaultDescription
[index]intundefinedThe index of the milestone

Returns the MilestoneDto.

get_milestone_utxo_changes(index): MilestoneUTXOChanges

Gets the utxo changes by the given milestone index.

ParamTypeDefaultDescription
[index]intundefinedThe index of the milestone

Returns the MilestoneUTXOChanges.

get_receipts(): Vec

Get all receipts.

Returns the ReceiptDto.

get_receipts_migrated_at(index): Vec

Get all receipts for a given milestone index.

ParamTypeDefaultDescription
[index]intundefinedThe index of the milestone

Returns the ReceiptDto.

get_treasury(): TreasuryResponse

Get the treasury amount.

Returns the TreasuryResponse.

get_included_message(): Message

Get the included message of a transaction.

ParamTypeDescription
[index]stringThe id of the transaction

Returns the new Message.

High-Level APIs

message(seed (optional), account_index (optional), initial_address_index (optional), inputs (optional), input_range_begin (optional), input_range_end (optional), outputs (optional), dust_allowance_outputs (optional), index (optional), index_raw (optional), data (optional), data_str (optional), parents (optional)): Message

Build a message.

ParamTypeDefaultDescription
[seed]strundefinedThe hex-encoded seed of the account to spend
[account_index]intundefinedThe account index
[initial_address_index]intundefinedThe initial address index
[inputs]list[Input]undefinedInputs
[input_range_begin]intundefinedThe begin index of the input
[input_range_end]intundefinedThe end index of the input
[outputs]list[Output]undefinedOutputs
[dust_allowance_outputs]list[Output]undefinedDust allowance output to the transaction
[index]strundefinedThe indexation string
[index_raw]list[int]undefinedThe indexation byte array
[data]list[int]undefinedThe data in bytes
[data_str]strundefinedThe data string
[parents]list[str]undefinedThe message ids of the parents

Returns the built Message.

get_message_metadata(message_id): MessageMetadataResponse

ParamTypeDefaultDescription
[message_id]strundefinedThe message id

Returns the MessageMetadataResponse.

get_message_data(message_id): Message

Gets the message data from the message id.

ParamTypeDefaultDescription
[message_id]strundefinedThe message id

Returns the Message.

get_message_raw(message_id): str

Gets the raw message string from the message id.

ParamTypeDefaultDescription
[message_id]strundefinedThe message id

Returns the raw message string.

get_message_children(message_id): list[str]

Gets the children of the given message.

ParamTypeDefaultDescription
[message_id]strundefinedThe message id

Returns the list of children strings.

get_message_index(index): list[str]

Gets the list of message indices from the message_id.

ParamTypeDefaultDescription
[index]strundefinedThe identifier of message

Returns the list of message ids.

find_messages(indexation_keys (optional), message_ids (optional)): list[Message]

Finds all messages associated with the given indexation keys and message ids.

ParamTypeDefaultDescription
[indexation_keys]list[str]undefinedThe list of indexations keys too search
[message_ids]list[str]undefinedThe list of message ids to search

Returns the list of the found messages.

get_unspent_address(seed, account_index (optional), initial_address_index(optional)): (str, int)

Gets a valid unspent address.

ParamTypeDefaultDescription
[seed]strundefinedThe hex-encoded seed to search
[account_index]intundefinedThe account index
[initial_address_index]intundefinedThe initial address index

Returns a tuple with type of (str, int) as the address and corresponding index in the account.

get_addresses(seed, account_index (optional), input_range_begin (optional), input_range_end (optional) get_all (optional)): list[(str, bool (optional))]

Finds addresses from the seed regardless of their validity.

ParamTypeDefaultDescription
[seed]strundefinedThe hex-encoded seed to search
[account_index]intundefinedThe account index
[input_range_begin]intundefinedThe begin of the address range
[input_range_end]intundefinedThe end of the address range
[get_all]boolundefinedGet all addresses

Returns a list of tuples with type of (str, int) as the address and corresponding index in the account.

get_balance(seed, account_index (optional), initial_address_index(optional), gap_limit(optional)): int

Get balance on a given seed and its wallet account index.

ParamTypeDefaultDescription
[seed]strundefinedThe hex-encoded seed to search
[account_index]intundefinedThe account index
[initial_address_index]intundefinedThe initial address index
[gap_limit]intundefinedThe gap limit

Returns the amount of balance.

get_address_balances(addresses): list[AddressBalancePair]

Get the balance in iotas for the given addresses.

ParamTypeDefaultDescription
[addresses]list[str]undefinedThe list of addresses to search

Returns the list of AddressBalancePair.

generate_mnemonic()

Returns a random generated Bip39 mnemonic with the English word list.

Returns A String

mnemonic_to_hex_seed(mnemonic)

Returns the seed hex encoded.

ParamTypeDefaultDescription
mnemonicstringundefinedBip39 mnemonic with words from the English word list.

Returns A String

bech32_to_hex(bech32)

Returns a parsed hex String from bech32.

ParamTypeDefaultDescription
bech32stringundefinedThe address Bech32 string

Returns A String

hex_to_bech32(hex, bech32_hrp (optional))

Returns a parsed bech32 String from hex.

ParamTypeDefaultDescription
bech32stringundefinedThe address Bech32 string
bech32_hrpstringundefinedThe Bech32 hrp string

Returns A String

is_address_valid(address): bool

Checks if a given address is valid.

ParamTypeDefaultDescription
addressstringundefinedThe address Bech32 string

Returns A boolean.

retry(message_id): (str, Message)

Retries (promotes or reattaches) the message associated with the given id.

ParamTypeDefaultDescription
[message_id]strundefinedThe message id

Returns the message id and the retried Message.

retry_until_included(message_id, interval (optional), max_attempts (optional)): list[(str, Message)]

Retries (promotes or reattaches) the message associated with the given id.

ParamTypeDefaultDescription
[message_id]strundefinedThe message id
intervalint5The interval in seconds in which we retry the message.
max_attemptsint10The maximum of attempts we retry the message.

Returns the message ids and Message of reattached messages.

consolidate_funds(seed, account_index, start_index, end_index): str

Function to consolidate all funds from a range of addresses to the address with the lowest index in that range

ParamTypeDescription
[seed]stringThe seed
[account_index]intThe account index.
[start_index]intThe lowest address index, funds will be consolidated to this address.
[end_index]intThe address index until which funds will be consolidated

Returns the address to which the funds got consolidated, if any were available.

reattach(message_id): (str, Message)

Reattaches the message associated with the given id.

ParamTypeDefaultDescription
[message_id]strundefinedThe message id

Returns the message id and the reattached Message.

promote(message_id): (str, Message)

Promotes the message associated with the given id.

ParamTypeDefaultDescription
[message_id]strundefinedThe message id

Returns the message id and the promoted Message.

MQTT APIs

subscribe_topic(topic, callback): void

Subscribe a topic and assign the associated callback.

ParamTypeDefaultDescription
[topic]strundefinedThe MQTT topic
[callback]functionundefinedThe callback function

subscribe_topics(topics, callback): void

Subscribe topics and assign the associated callbacks, respectively.

ParamTypeDefaultDescription
[topics]list[str]undefinedThe MQTT topics
[callback]functionundefinedThe callback functions

unsubscribe(): void

Unsubscribe all topics.

disconnect(): void

Disconnect the mqtt broker.

WalletAddress

A dict with the following key/value pairs.

message_metadata_response = {
    'message_id': str,
    'parent_message_ids': list[str],
    'is_solid': bool,
    'referenced_by_milestone_index': int, # (optional)
    'milestone_index': int,  # (optional)
    'ledger_inclusion_state': LedgerInclusionStateDto,  # (optional)
    'conflict_reason': int,  # (optional)
    'should_promote:' bool  # (optional)
    'should_reattach': bool  # (optional)
}

Please refer to LedgerInclusionStateDto for the details of this type.

BalanceAddressResponse

A dict with the following key/value pairs.

balance_for_address_response = {
    'address_type': int,
    'address': str,
    'balance': int
}

AddressBalancePair

A dict with the following key/value pairs.

address_balance_pair = {
    'address': str,
    'balance': int
    'dust_allowed': bool
}

MilestoneDto

A dict with the following key/value pairs.

milestoned_to = {
    'index': int,
    'timestamp': int,
    'message_id':  str
}

MilestoneUTXOChanges

A dict with the following key/value pairs.

milestone_utxo_changes = {
    'index': int,
    'created_outputs': list[str],
    'consumed_outputs': list[str]
}

ReceiptDto

A dict with the following key/value pairs.

receiptDto = {
    'receipt': Receipt,
    'milestone_index': int,
}

TreasuryResponse

A dict with the following key/value pairs.

treasuryResponse = {
    'milestone_id': str,
    'amount': int,
}

UtxoInput

A dict with the following key/value pairs.

utxo_input = {
    'transaction_id': list[int],
    'index': int
}

OutputResponse

A dict with the following key/value pairs.

output_response = {
    'message_id': str,
    'transaction_id': str,
    'output_index': int,
    'is_spent': bool,
    'output': OutputDto
}

Please refer to OutputDto for the details of this type.

OutputDto

A dict with the following key/value pairs.

output_dto = {
    'treasury': TreasuryOutputDto, # (opitonal)
    'signature_locked_single': SignatureLockedSingleOutputDto, # (opitonal)
    'signature_locked_dust_allowance': SignatureLockedDustAllowanceOutputDto # (opitonal)
}

Please refer to TreasuryOutputDto, SignatureLockedSingleOutputDto, and SignatureLockedDustAllowanceOutputDto for the details of these types.

SignatureLockedSingleOutputDto

A dict with the following key/value pairs.

signature_locked_single_output_dto = {
    'kind': int,
    'address': AddressDto,
    'amount': int
}

Please refer to AddressDto for the details of this type.

SignatureLockedDustAllowanceOutputDto

A dict with the following key/value pairs.

signature_locked_dust_allowance_output_dto = {
    'kind': int,
    'address': AddressDto,
    'amount': int
}

Please refer to AddressDto for the details of this type.

pub struct TreasuryOutputDto {

A dict with the following key/value pairs.

treasury_output_dto = {
    'kind': int,
    'amount':int
}

AddressDto

A dict with the following key/value pairs.

address_dto = {
    'ed25519': Ed25519AddressDto
}

Please refer to Ed25519AddressDto for the details of this type.

Ed25519AddressDto

A dict with the following key/value pairs.

ed25519_address_dto = {
    'kind': int,
    'address': str
}

Message

A dict with the following key/value pairs.

message = {
    'message_id': str,
    'network_id': int,
    'parents': list[str],
    'payload': Payload, # (optional)
    'nonce': int
}

Please refer to Payload for the details of this type.

Payload

A dict with the following key/value pairs.

payload = {
    'transaction': list[Transaction], # (optional)
    'milestone': list[Milestone], # (optional)
    'indexation': list[Indexation], # (optional)
}

Please refer to Transaction, Milestone, and Indexation for the details of these types.

Transaction

A dict with the following key/value pairs.

transaction = {
    'essence': RegularEssence,
    'unlock_blocks': list[UnlockBlock]
}

Please refer to RegularEssence, and UnlockBlock for the details of these types.

Milestone

A dict with the following key/value pairs.

milestone = {
    'essence': MilestonePayloadEssence,
    'signatures': list[list[int]]
}

Please refer to MilestonePayloadEssence for the details of this type.

MilestonePayloadEssence

A dict with the following key/value pairs.

milestone_payload_essence = {
    'index': int,
    'timestamp': int,
    'parents': list[str],
    'merkle_proof': list[int],
    'next_pow_score': int,
    'next_pow_score_milestone_index': int,
    'public_keys': list[list[int]]
}

Indexation

A dict with the following key/value pairs.

indexation = {
    'index': str,
    'data': list[int]
}

RegularEssence

A dict with the following key/value pairs.

regular_essence = {
    'inputs': list[Input],
    'outputs': list[Output],
    'payload': list[Payload]
}

Please refer to Input, Output, and Payload for the details of these types.

Output

A dict with the following key/value pairs.

output = {
    'address': str,
    'amount': int
}

Input

A dict with the following key/value pairs.

input = {
    'transaction_id': str,
    'index': int
}

UnlockBlock

A dict with the following key/value pairs.

unlock_block = {
    'signature': Ed25519Signature, # (optional)
    'reference': int # (optional)
}

Please refer to Ed25519Signature for the details of this type.

Ed25519Signature

A dict with the following key/value pairs.

ed25519_signature = {
    'public_key': list[int],
    'signature': list[int]
}

BrokerOptions

A dict with the following key/value pairs.

broker_options = {
    'automatic_disconnect': bool,
    'timeout': int,
    'max_reconnection_attempts': int,
}

LedgerInclusionStateDto

A dict with the following key/value pairs.

ledger_inclusion_state_dto = {
    'state': str
}

NodeInfoWrapper

A dict with the following key/value pairs.

nodeinfo_wrapper{
    url: str,
    nodeinfo: info_response,
}
info_response = {
    'name': str,
    'version': str,
    'is_healthy': bool,
    'network_id': str,
    'bech32_hrp': str,
    'min_pow_score': float,
    'messages_per_second': float,
    'referenced_messages_per_second': float,
    'referenced_rate': float,
    'latest_milestone_timestamp': u64,
    'latest_milestone_index': int,
    'confirmed_milestone_index': int,
    'pruning_index': int,
    'features': list[str],
    'min_pow_score': float,
}

NetworkInfo

A dict with the following key/value pairs.

network_info = {
    'network': str,
    'network_id': int,
    'bech32_hrp': str,
    'min_pow_score': float,
    'local_pow': bool,
    'tips_interval': int,
}

PeerDto

A dict with the following key/value pairs.

peer_dto = {
    'id': str,
    'multi_addresses': list[str],
    'alias': str, # (optional)
    'relation': RelationDto,
    'connected': bool,
    'gossip': GossipDto, # (optional)
}

Please refer to RelationDto and GossipDto for the details of these types.

RelationDto

A dict with the following key/value pairs.

relation_dto = {
    'relation': str
}

GossipDto

A dict with the following key/value pairs.

gossip_dto = {
    'heartbeat': HeartbeatDto,
    'metrics': MetricsDto
}

Please refer to HeartbeatDto and MetricsDto for the details of these types.

HeartbeatDto

A dict with the following key/value pairs.

heart_beat_dto = {
    'solid_milestone_index': int,
    'pruned_milestone_index': int,
    'latest_milestone_index': int,
    'connected_neighbors': int,
    'synced_neighbors': int
}

MetricsDto

A dict with the following key/value pairs.

metrics_dto = {
    'received_messages': int,
    'known_messages': int,
    'received_message_requests': int,
    'received_milestone_requests': int,
    'received_heartbeats': int,
    'sent_messages': int,
    'sent_message_requests': int,
    'sent_milestone_requests': int,
    'sent_heartbeats': int,
    'dropped_packets': int,
}

AddressOutputsOptions

A dict with the following key/value pairs.

options = {
    'include_spent': bool,
    'output_type': string
}

Troubleshooting

StackExchange

https://iota.stackexchange.com

The IOTA StackExchange a a nice tool for developers to find answers for a problem. Just search your problem and find your answer! If there is no one, submit your question and share it in the discussion channel below.

Joining the discussion

If you want to get involved in discussions about this library, or you're looking for support, go to the #clients-discussion channel on Discord.

High Level API Specification

Table of Contents

Introduction

This document specifies a user friendly API to be used in the client libraries. The main implementation will be in Rust which will receive automatically compiled client libraries in other languages via C or Webassembly bindings. There are also many crates to support developers creating foreign function interfaces with native bindings.

Builder

The data structure to initialize the instance of the Higher level client library. This is always called first when starting a new interaction with the library. Note: This is the common approach to do initialization in Rust. Different languages might use different methods such as just calling an initialization function directly.

Parameters

ParameterRequiredDefault ValueTypeDefinition
networkTestnet&strOptional, the network type can be "testnet" or "mainnet". If no node url is provided, some default nodes are used for the specified network. Nodes that aren't in this network will be ignored.
nodeNone&strThe URL of a node to connect to; format: https://node:port
primary_nodeNone&str, auth_name_passw: Option<(&str, &str)>The URL of a node to always connect first to with optional name and password for basic authentication, if multiple nodes are available; format: https://node:port, Some("name", "password")
primary_pow_nodeNone&str, auth_name_passw: Option<(&str, &str)>The URL of a node to always connect first to when submitting a message with remote PoW, if multiple nodes are available. Will override primary_node in that case. With optional name and password for basic authentication; format: https://node:port, Some("name", "password")
node_authNone&str, &str, &strThe URL of a node to connect to with name and password for basic authentication; format: https://node:port, name, password
nodesNone&[&str]A list of nodes to connect to; nodes are added with the https://node:port format. The amount of nodes specified in quorum_size are randomly selected from this node list to check for quorum based on the quorum threshold. If quorum_size is not given the full list of nodes is checked.
node_sync_intervalDuration::from_secs(60)std::time::DurationThe interval in milliseconds to check for node health and sync
node_sync_disabledfalseboolIf disabled also unhealty nodes will be used
node_pool_urlsNone&[String]A list of node_pool_urls from which nodes are added. The amount of nodes specified in quorum_size are randomly selected from this node list to check for quorum based on the quorum threshold. If quorum_size is not given the full list of nodes is checked.
quorumfalseboolDefine if quorum should be used for the requests
quorum_size3usizeDefine how many nodes should be used for quorum
quorum_threshold66usizeDefine the % of nodes that need to return the same response to accept it
request_timeoutDuration::from_secs(30)std::time::DurationThe amount of seconds a request can be outstanding to a node before it's considered timed out
api_timeoutApi::GetInfo: Duration::from_secs(2)),
Api::GetHealth: Duration::from_secs(2),
Api::GetPeers: Duration::from_secs(2),
Api::GetMilestone: Duration::from_secs(2),
Api::GetTips: Duration::from_secs(2),
Api::PostMessage: Duration::from_secs(2),
Api::PostMessageWithRemotePow: Duration::from_secs(30),
Api::GetOutput: Duration::from_secs(2)
HashMap<Api,
std::time::Duration>
The amount of milliseconds a request to a specific Api endpoint can be outstanding to a node before it's considered timed out.
local_powTrueboolIf not defined it defaults to local PoW to offload node load times
tips_interval15u64Time interval during PoW when new tips get requested.
mqtt_broker_optionsTrue,
Duration::from_secs(30),
True
BrokerOptionsIf not defined the default values will be used
  • Note that there must be at least one node to build the instance successfully.

Return

Finalize the builder with finish() will run the instance in the background. Users don’t need to worry about the return object handling.

On initialization

On initialisation, call getNodeInfo API. Check the health of each node in the node list, and put healty nodes, matching the PoW settings and network in a synced nodelist.

Node metadataDescription
networkIf this parameter does not match the global builder parameter, don't add it to the synced nodelist.
powIf the global local_pow parameter is set to false, then put only nodes with the PoW feature in the synced nodelist.

Sync Process

When a Client instance (The instance which is used for calling the client APIs) is built, the status of each node listed is checked. If the returned status of the node information is healthy, which means the node is synced, then this node will be pushed into a synced_nodes list. The rust-like pseudo code of synced_nodes construction process follows. The process of syncing a node is repeated every 60 seconds or at the interval specified in the node_sync_interval argument of the initializer if set.


#![allow(unused)]
fn main() {
synced_nodes = Vec::new()
for node in node_pool_urls{
   status = Client.get_info(node).await?;
   if status == healthy{
      synced_nodes.push(node)
   }
}
}

General high level API

Here is the high level abstraction API collection with sensible default values for users easy to use.

message()

A generic send function for easily sending a message.

Parameters

ParameterRequiredDefaultTypeDefinition
seedNoneSeedThe seed of the account we are going to spend, only needed for transactions
account_index0usizeThe account index, responsible for the value in the Bip32Path m/44'/4218'/✘'/0'/0'.
initial_address_index0usizeThe index from where to start looking for balance. Responsible for the value in the Bip32Path m/44'/4218'/0'/0'/✘'.
inputNoneUtxoInputUsers can manually select their UtxoInputs instead of having automatically selected inputs.
input_range0..100RangeCustom range to search for the input addresses if custom inputs are provided.
outputNoneaddress: &[String],
amount: u64
Address to send to and amount to send. Address needs to be Bech32 encoded.
output_hexNoneaddress: &str,
amount: u64
Address to send to and amount to send. Address needs to be hex encoded.
indexNone&[u8] / &strAn optional indexation key for an indexation payload. 1-64 bytes long.
dataNoneVecOptional data for the indexation payload.
parentsNoneMessageId1-8 optional parents MessageId to be used.

Depending on the provided values this function will create a message with:

  • no payload
  • an indexation payload
  • a transaction payload
  • a transaction payload containing an indexation payload

Return

The Message object we build.

Implementation Details

  • Validate inputs, such as address and seed to check if they are correct.
  • Check if account balance is bigger or equal to the value using method similar to get_balance();
  • Build and validate the message with signed transaction payload accordingly;
  • Get tips using get_tips();
  • Perform proof-of-work locally (if not set to remote);
  • Send the message using post_messages();

get_message()

(GET /api/v1/messages)

Endpoint collection all about GET messages.

Parameters

ParameterRequiredTypeDefinition
message_idMessageIdThe identifier of message.
index&[u8] / &strAn indexation key.

Returns

Depend on the final calling method, users could get different results they need:

  • metadata(&MessageId): Return MessageMetadata of the message.
  • data(&MessageId): Return a Message object.
  • raw(&MessageId): Return the raw data of given message.
  • children(&MessageId): Return the list of MessageIds that reference a message by its identifier.
  • index(&[u8] | &str) : Return the list of MessageIds that have this str as indexation key

find_messages()

Find all messages by provided message IDs.

Parameters

ParameterRequiredTypeDefinition
indexation_keys[&[u8] / &str]The index key of the indexation payload.
message_ids[MessageId]The identifier of message.

Returns

A vector of Message Object.

get_unspent_address()

Return a valid unspent public Bech32 encoded address.

Parameters

ParameterRequiredDefaultTypeDefinition
seed-SeedThe seed we want to use.
account_index0usizeThe account index, responsible for the value in the Bip32Path m/44'/4218'/✘'/0'/0'.
initial_address_index0usizeStart index of the addresses to search. Responsible for the value in the Bip32Path m/44'/4218'/0'/0'/✘'.

Return

Return a tuple with type of (String, usize) as the address and corresponding index in the account.

Implementation Details

Following are the steps for implementing this method:

  • Start generating addresses with given account index and starting index. We will have a default gap limit of 20 at a time;
  • Check for balances on the generated addresses using find_outputs() and keep track of the positive balances;
  • Repeat the above step till there's an unspent address found;
  • Return the address with corresponding index on the wallet chain;

get_addresses()

Return a list of addresses from the seed regardless of their validity.

Parameters

ParameterRequiredDefaultTypeDefinition
seedNoneSeedThe seed we want to search for.
account_index0usizeThe account index, responsible for the value in the Bip32Path m/44'/4218'/✘'/0'/0'.
rangeNonestd::ops::RangeRange indices of the addresses we want to search for. Default is (0..20)
get_allGet public and change addresses. Will return Vec<([String], bool)>, where the bool is indicating whether it's a change address

Return

Vec<[String]>, with the public addresses

get_balance()

Return the balance for a provided seed and its wallet account index.

Parameters

ParameterRequiredDefaultTypeDefinition
seed-SeedThe seed we want to search for.
account_index0usizeThe account index, responsible for the value in the Bip32Path m/44'/4218'/✘'/0'/0'.
initial_address_index0usizeStart index from which to generate addresses. Default is 0. Responsible for the value in the Bip32Path m/44'/4218'/0'/0'/✘'.
gap_limit20usizeThe gap limit specifies how many addresses will be checked each round. If gap_limit amount of addresses in a row have no balance the function will return.

Return

Total account balance.

Implementation Details

Following are the steps for implementing this method:

  • Start generating addresses with given wallet account index and starting index. We will have a default gap limit of 20 at a time;
  • Check for balances on the generated addresses using find_outputs() and keep track of the positive balances;
  • Repeat the above step till an address of zero balance is found;
  • Accumulate the positive balances and return the result.

get_address_balances()

Return the balance in iota for the given addresses; No seed or security level needed to do this since we are only checking and already know the addresses.

Parameters

ParameterRequiredTypeDefinition
addresses[[String]]List of Bech32 encoded addresses.

Return

A list of tuples with value of AddressBalancePair. The usize is the balance of the address accordingly.

Implementation details:

Following are the steps for implementing this method:

  • Validate address semantics;
  • Get latest balance for the provided address using find_outputs() with addresses as parameter;
  • Return the list of Output which contains corresponding pairs of address and balance.

generate_mnemonic()

Returns a random generated Bip39 mnemonic with the English word list.

Return

Parsed [String].

mnemonic_to_hex_seed(mnemonic)

Returns the seed hex encoded.

Parameters

ParameterRequiredTypeDefinition
mnemonic[String]Bip39 mnemonic with words from the English word list.

Return

Parsed [String].

bech32_to_hex()

Returns a parsed hex String from bech32.

Parameters

ParameterRequiredTypeDefinition
bech32[String]Bech32 encoded address.

Return

Parsed [String].

hex_to_bech32()

Returns a parsed bech32 String from hex.

Parameters

ParameterRequiredTypeDefinition
hex[String]Hex encoded address.
bech32_hrp[Option]Optional bech32 hrp.

Return

Parsed [String].

parse_bech32_address()

Returns a valid Address parsed from a String.

Parameters

ParameterRequiredTypeDefinition
address[String]Bech32 encoded address.

Return

Parsed Address.

is_address_valid()

Parameters

ParameterRequiredTypeDefinition
address[String]Bech32 encoded address.

Return

A boolean showing if the address is valid.

subscriber()

Subscribe to a node event Topic (MQTT)

Required: one of

  • topic(): Add a new Topic to the list.

  • topics(): Add a vector of Topic to the list.

  • subscribe(): Subscribe to the given topics with the callback, which will be called every time when the topic is detected.

  • unsubscribe(): Unsubscribes from all subscriptions.

  • disconnect(): Disconnects the broker. This will clear the stored topic handlers and close the MQTT connection.

Returns

Nothing apart from a Ok(()) result if successful

retry()

Retries (promotes or reattaches) a message for provided MessageId if the node suggests it. The need to use this function should be low, because the confirmation throughput of the node is expected to be quite high.

Parameters

ParameterRequiredTypeDefinition
message_idMessageIdThe identifier of message.

Returns:

A tuple with the newly promoted or reattached (MessageId, Message).

Implementation Details

Following are the steps for implementing this method:

  • Only unconfirmed messages should be allowed to retry. The method should validate the confirmation state of the provided messages. If a message id of a confirmed message is provided, the method should error out;
  • The method should also validate if a retry is necessary. This can be done by leveraging the /messages/{messageId}/metadata endpoint (already available through get_message). See this implementation for reference;
  • Use reattach or promote accordingly.

retry_until_included()

Retries (promotes or reattaches) a message for provided MessageId until it's included (referenced by a milestone). Default interval is 5 seconds and max attempts is 10. The need to use this function should be low, because the confirmation throughput of the node is expected to be quite high.

Parameters

ParameterRequiredTypeDefinition
message_id[&MessageId]The identifier of message.
intervalOptionThe interval in which we retry the message.
max_attemptsOptionThe maximum of attempts we retry the message.

Returns:

An array of tuples with the newly reattached (MessageId, Message).

consolidate_funds()

Function to consolidate all funds from a range of addresses to the address with the lowest index in that range

Parameters

| Parameter | Type | Definition | | - | - | - | - | | seed | Seed | The seed we want to search for. | | account_index | usize | The account index, responsible for the value in the Bip32Path m/44'/4218'/✘'/0'/0'. | | address_range | Range | Range from which to generate public and internal addresses from which to consolidate the funds. Responsible for the value in the Bip32Path m/44'/4218'/0'/0'/✘'. |

Returns:

The address to which the funds got consolidated.

reattach()

Depends on find_messages, get_message and post_message.

Reattaches a message. The method should validate if a reattachment is necessary through get_message. If not, the method should error out and should not allow unnecessary reattachments.

Parameters

ParameterRequiredTypeDefinition
message_idMessageIdThe identifier of message.

Returns

A tuple with the newly reattached (MessageId, Message).

promote()

Depends on find_messages, get_message and post_message.

Promotes a message. The method should validate if a promotion is necessary through get_message. If not, the method should error out and should not allow unnecessary promotions.

Parameters

ParameterRequiredTypeDefinition
message_idMessageIdThe identifier of message.

Returns

A tuple with the newly promoted (MessageId, Message).

Full node API

Full node API of Bee and HORNET will still be public. Users who know these relative low level Restful API can still call them directly if they are confident and think it’s good for them. Note that both Bee and HORNET haven't finalized their APIs either. Following items and signatures might change later.

get_health()

(GET /health)

Returns the health of the node, which can be used for load-balancing or uptime monitoring.

Parameters

None

Returns

Boolean to indicate if node is healthy.

get_peers()

(GET /peers)

Get information about the peers of the node.

Parameters

None

Returns

pub struct PeerDto {
    pub id: String,
    #[serde(rename = "multiAddresses")]
    pub multi_addresses: Vec<String>,
    pub alias: Option<String>,
    pub relation: RelationDto,
    pub connected: bool,
    pub gossip: Option<GossipDto>,
}

get_info()

(GET /api/v1/info)

Returns information about the node.

Parameters

None

Returns

A Response Object similar to this:


#![allow(unused)]
fn main() {
pub struct NodeInfoWrapper {
    pub nodeinfo: NodeInfo,
    pub url: String,
}
pub struct NodeInfo {
    pub name: String,
    pub version: String,
    pub is_healthy: bool,
    pub network_id: String,
    pub latest_milestone_index: usize,
    pub min_pow_score: f64,
    pub messages_per_second: f64,
    pub referenced_messages_per_second: f64,
    pub referenced_rate: f64,
    pub latest_milestone_timestamp: u64,
    pub confirmed_milestone_index: usize,
    pub pruning_index: usize,
    pub features: Vec<String>,
}
}

get_tips()

(GET /tips)

Returns two non-lazy tips. In case the node can only provide one tip, tip1 and tip2 are identical.

Parameters

None

Returns

A tuple with two MessageId:


#![allow(unused)]
fn main() {
(MessageId, MessageId)
}

post_message()

(POST /message)

Submit a message. The node takes care of missing fields and tries to build the message. On success, the message will be stored in the Tangle. This endpoint will return the identifier of the message.

Parameters

ParameterRequiredTypeDefinition
messageMessageThe message object.

Returns

The MessageId of the message object.

get_output()

(GET /outputs)

Get the producer of the output, the corresponding address, amount and spend status of an output. This information can only be retrieved for outputs which are part of a confirmed transaction.

Parameters

ParameterRequiredTypeDefinition
output_idUtxoInputIdentifier of the output.

Returns

An OutputMetadata that contains various information about the output.

get_address()

(GET /addresses)

Parameters

ParameterRequiredTypeDefinition
address[String]The address to search for.

Returns

Depend on the final calling method, users could get different outputs they need:

  • balance(): Return confirmed balance of the address.
  • outputs([options]): Return UtxoInput array (transaction IDs with corresponding output index).

find_outputs()

Find all outputs based on the requests criteria.

Parameters

ParameterRequiredTypeDefinition
output_id[UtxoInput]The identifier of output.
addresses[[String]]The Bech32 encoded address.

Returns

A vector of OutputMetadata.

get_milestone()

(GET /milestones)

Get the milestone by the given index.

Parameters

ParameterRequiredTypeDefinition
indexu32Index of the milestone.

Returns

An Milestone object.

get_milestone_utxo_changes()

(GET /milestones/{}/utxo-changes)

Get all UTXO changes of a given milestone.

Parameters

ParameterRequiredTypeDefinition
indexu32Index of the milestone.

Returns

MilestoneUTXOChanges {
    index: 1,
    created_outputs: [],
    consumed_outputs: [],
}

get_receipts()

(GET /receipts)

Get all receipts.

Returns

Vec<ReceiptDto>

get_receipts_migrated_at()

(GET /receipts/{migratedAt})

Get all receipts for a given milestone index.

Returns

Vec<ReceiptDto>

get_treasury()

(GET /treasury)

Get the treasury amount.

Returns

pub struct TreasuryResponse {
    #[serde(rename = "milestoneId")]
    milestone_id: String,
    amount: u64,
}

get_included_message()

(GET /transactions/{transactionId}/included-message)

Get the included message of the transaction.

Parameters

ParameterRequiredTypeDefinition
transaction_id[TransactionId]The id of the transaction.

Returns

struct Message {
    parents: Vec<MessageId>,
    payload: Option<Payload>,
    nonce: u64,
}

Objects

Here are the objects used in the API above. They aim to provide a secure way to handle certain data structures specified in the Iota stack.

MessageId

MessageId is a 32 bytes array which can represent as hex string.


#![allow(unused)]
fn main() {
struct MessageId([u8; MESSAGE_ID_LENGTH]);
}

Seed

pub enum Seed {
    /// Ed25519 variant
    Ed25519(Ed25519Seed)
}

An IOTA seed that inner structure is omitted. Users can create this type by passing a String. It will verify and return an error if it’s not valid. |

Message

The message object returned by various functions; based on the RFC for the Message object. Here's the brief overview of each components in Message type would look like:


#![allow(unused)]
fn main() {
struct Message {
    parents: Vec<MessageId>,
    payload: Option<Payload>,
    nonce: u64,
}

enum Payload {
    Transaction(Box<Transaction>),
    Milestone(Box<Milestone>),
    Indexation(Box<Indexation>),
}

struct Transaction {
    pub essence: TransactionPayloadEssence,
    pub unlock_blocks: Vec<UnlockBlock>,
}

struct Milestone {
    essence: MilestoneEssence,
    signatures: Vec<Box<[u8]>>,
}

struct Indexation {
    index: String,
    data: Box<[u8]>,
}

struct TransactionPayloadEssence {
    pub(crate) inputs: Box<[Input]>,
    pub(crate) outputs: Box<[Output]>,
    pub(crate) payload: Option<Payload>,
}

enum Input {
    UTXO(UtxoInput(OutputId)),
}

struct OutputId {
    transaction_id: TransactionId,
    index: u16,
}

enum Output {
    SignatureLockedSingle(SignatureLockedSingleOutput),
}

struct SignatureLockedSingleOutput {
    address: Address,
    amount: u64,
}

enum UnlockBlock {
    Signature(SignatureUnlock),
    Reference(ReferenceUnlock),
}

enum SignatureUnlock {
    Ed25519(Ed25519Signature),
}

struct Ed25519Signature {
    public_key: [u8; 32],
    signature: Box<[u8]>,
}

struct ReferenceUnlock(u16);
}

MessageMetadata


#![allow(unused)]
fn main() {
pub struct MessageMetadata {
    /// Message ID
    pub message_id: String,
    /// Message IDs of parents
    pub parents: Vec<String>,
    /// Solid status
    pub is_solid: bool,
    /// Should promote
    pub should_promote: Option<bool>,
    /// Should reattach
    pub should_reattach: Option<bool>,
    /// Referenced by milestone index
    pub referenced_by_milestone_index: Option<u32>,
    /// Ledger inclusion state
    pub ledger_inclusion_state: Option<String>,
}
}

OutputMetadata

The metadata of an output:


#![allow(unused)]
fn main() {
pub struct OutputMetadata {
    /// Message ID of the output
    pub message_id: Vec<u8>,
    /// Transaction ID of the output
    pub transaction_id: Vec<u8>,
    /// Output index.
    pub output_index: u16,
    /// Spend status of the output
    pub is_spent: bool,
    /// Corresponding address
    pub address: Address,
    /// Balance amount
    pub amount: u64,
}
}

Address

An Ed25519 address can be encoded in Bech32 or Hex, with Bech32 being preferred and also used in most functions.

pub enum Address {
    Ed25519(Ed25519Address),
}

AddressBalancePair

pub struct AddressBalancePair {
    /// Address, bech32 encoded
    pub address: String,
    /// Balance in the address
    pub balance: u64,
    /// If dust is allowed on the address
    pub dust_allowed: bool,
}

Milestone

A milestone metadata.


#![allow(unused)]
fn main() {
pub struct MilestoneMetadata {
    /// Milestone index
    pub milestone_index: u32,
    /// Milestone ID
    pub message_id: String,
    /// Timestamp
    pub timestamp: u64,
}
}

Api

pub enum Api {
    /// `get_health` API
    GetHealth,
    /// `get_info`API
    GetInfo,
    /// `get_tips` API
    GetTips,
    /// `post_message` API
    PostMessage,
    /// `post_message` API with remote pow
    PostMessageWithRemotePow,
    /// `get_output` API
    GetOutput,
    /// `get_milestone` API
    GetMilestone,
}

BrokerOptions

pub struct BrokerOptions {
    #[serde(default = "default_broker_automatic_disconnect", rename = "automaticDisconnect")]
    pub(crate) automatic_disconnect: bool,
    #[serde(default = "default_broker_timeout")]
    pub(crate) timeout: std::time::Duration,
    #[serde(rename = "maxReconnectionAttempts", default)]
    pub(crate) max_reconnection_attempts: usize,
}

Topic

A string with the exact MQTT topic to monitor, can have one of the following variations:

milestones/latest
milestones/confirmed

messages
messages/referenced
messages/indexation/{index}
messages/{messageId}/metadata
transactions/{transactionId}/included-message

outputs/{outputId}

addresses/{address}/outputs
addresses/ed25519/{address}/outputs

Contribute to the project

Thanks for thinking about contributing to the project! We have the following ways that you can contribute.

Join the IOTA Libraries Initiative

The IOTA Libraries Initiative is a collaborative effort to help improve the developer experience.

  • Quality assurance and review
  • Documentation
  • Code samples

If you'd like to get involved, join the #experience channel on Discord.

Contribute to the project's GitHub repository

All the code is open source and hosted on GitHub where you can do the following:

  • Report a bug
  • Suggest a new feature
  • Contribute to the documentation

Contribute to the documentation

This documentation is also open source and hosted on GitHub.

If you want to contribute new documentation or fix an error, see the contribution guidelines.

Share your knowledge

Helping others is an important part of any open source ecosystem.

By sharing your knowledge with others, you can provide a lot of value to the community and maybe inspire someone else to learn and contribute.

Take a look at what discussions are going on in the #clients-discussion channel on Discord.

Thanks :heart: