Welcome

This is the documentation for the official IOTA Wallet Library Software. It can be used to easily integrate an IOTA Wallet into your applications. You can read more about core principles behind IOTA client libraries in the following blog post.

Wallet.rs is a general wallet library written in Rust. It is being utilized by our wallet software Firefly and other software components across IOTA ecosystem. Wallet.rs contains all the logic to safely build wallets or integrations that require value-based transfers (such as exchanges, pay-as-you-go systems, etc.). It includes account state management and backup, account creation, transferring tokens and much more. Needless to say, it is also based on our official one-source-code-of-truth IOTA Rust library and can be integrated with the Stronghold enclave to achieve a maximum level of security.

Using stronghold is a recommended approach to store account data using wallet.rs. The best security practices are integrated for free

With the wallet.rs library, developers do not need to use a self-generated seed anymore. By default, the seed is created and stored in Stronghold encrypted at rest. It is not possible to extract the seed from Stronghold for security purposes. Stronghold uses encrypted snapshots that can easily be backed up and securely shared between devices. These snapshots are further secured with a password.

IOTA 1.5 (Chrysalis) in a nutshell

All main concepts behind the IOTA Chrysalis are explained in detail at Developer guide to Chrysalis.

Please, see a summary of changes in comparison to IOTA 1.0 at 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 wallet library.
  2. Libraries, all available programming languages and their resources.
  3. The Specification, detailed explanation requirements and functionality.
  4. Contribute, how you can work on the wallet software.
  5. Get in touch, join the community and become part of the X-Team!

Overview

The wallet library is a stateful package with a standardized interface for developers to build applications involving IOTA value transactions. It provides abstractions to handle IOTA payments and can optionally interact with IOTA Stronghold enclave for seed handling, seed storage and state backup.

See the full specification here.

High level layered overview:

iota layers overview

IOTA Wallet Libraries

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

Getting Started

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

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.

The library in a nutshell

In the wallet.rs, we use an account model, so you can create an account for each of your users. The other approach would be to use one account and generate many addresses, which you can link to your users in your database. The library allows users to assign a meaningful alias to each account. It also leaves the choice to users if they want to segregate their funds across multiple accounts or multiple addresses. So basically it is up to a developer to decide whether single-account approach or multi-account approach is chosen. The library provides a support to any of the scenarios.

The library is based on a derivation for multiple accounts from a single seed. An account is simply a deterministic identifier from which multiple addresses can be further derived. The following illustrates the relationships between seed, accounts and addresses:

accounts

IOTA Wallet 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

Prerequisites

Rust and Cargo are required. Install them here.

We recommend you update Rust to the latest stable version rustup update stable. Nightly should be fine but there's a chance some changes are not compatible.

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

Dependencies

cmake and openssl are required. In order to run the build process succesfully using Cargo you might need install additional build tools on your system.

Windows

cmake can be downloaded on the official website and openssl can be installed with vcpkg or chocolatey.

  • Installing openssl with vcpkg:
$ ./vcpkg.exe install openssl:x64-windows
$ ./vcpkg.exe integrate install
# you may want to add this to the system environment variables since you'll need it to compile the crate
$ set VCPKGRS_DYNAMIC=1
  • Installing openssl with chocolatey:
$ choco install openssl
# you may need to set the OPENSSL_ROOT_DIR environment variable
$ set OPENSSL_ROOT_DIR="C:\Program Files\OpenSSL-Win64"

macOS

cmake and openssl can be installed with Homebrew:

$ brew install cmake
$ brew install openssl@1.1
# you may want to add this to your .zshrc or .bashrc since you'll need it to compile the crate
$ OPENSSL_ROOT_DIR=$(brew --prefix openssl@1.1)

Linux

Install cmake and openssl with your distro's package manager or download from their websites. On Debian and Ubuntu you will also need build-essential.

Usage

To use the library, add this to your Cargo.toml:

[dependencies]
iota-wallet = { git = "https://github.com/iotaledger/wallet.rs" }

Initialisation

In order to use the library you first need to create an AccountManager:

use iota_wallet::{
    account_manager::AccountManager, client::ClientOptionsBuilder, signing::SignerType,
};
use std::path::PathBuf;

#[tokio::main]
async fn main() -> iota_wallet::Result<()> {
    let storage_folder: PathBuf = "./my-db".into();
    let manager =
        AccountManager::builder()
            .with_storage(&storage_folder, None)
            .finish()
            .await?;
    let client_options = ClientOptionsBuilder::new().with_node("http://api.lb-0.testnet.chrysalis2.com")?.build();
    let account = manager
        .create_account(client_options)
        .signer_type(SignerType::EnvMnemonic)
        .initialise()
        .await?;
    Ok(())
}

Examples

You can see the examples in the examples directory and try them with:

cargo run --example # lists the available examples
cargo run --example transfer # execute the `transfer` example

Backup and restore example

Create an account manager and set an password:


#![allow(unused)]
fn main() {
let mut manager = AccountManager::builder().finish().await.unwrap();

manager.set_stronghold_password("password").await.unwrap();
manager.store_mnemonic(SignerType::Stronghold, None).await.unwrap();

}

Create your account:


#![allow(unused)]
fn main() {
let client_options = ClientOptionsBuilder::new()
    .with_node("https://api.lb-0.testnet.chrysalis2.com")?
    .build()
    .unwrap();
let account_handle = manager
    .create_account(client_options)?
    .alias("alias")
    .initialise()
    .await?;
let id = account_handle.id().await;

}

Now you can secure your account in a backup file:


#![allow(unused)]
fn main() {
// backup the stored accounts to ./backup/${backup_name}
let backup_path = manager.backup("./backup").await?;

}

You can import the backup later or by another application like here:


#![allow(unused)]
fn main() {
manager.import_accounts(backup_path, "password").await?;

let imported_account_handle = manager.get_account(&id).await?;

let account = account_handle.read().await;
let imported_account = imported_account_handle.read().await;

}

That's it! Now you know, how do backup and restore your account!

See the full example here

API Reference

Link to hosted api documentation: TODO

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

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 Wallet 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.

Overview

Getting Started with Node.js

The IOTA Wallet Node.js binding is published on npmjs.org.

Installation

Currently the package isn't published so you'd need to link it to your project using npm or yarn.

  • Using NPM:
$ npm install @iota/wallet
  • Using yarn:
$ yarn install @iota/wallet

Usage

/**
 * This example creates a new database and account
 */

require('dotenv').config()

async function run() {
    const { AccountManager, SignerType } = require('@iota/wallet')
    const manager = new AccountManager({
        storagePath: './alice-database',
    })
    manager.setStrongholdPassword(process.env.SH_PASSWORD)
    manager.storeMnemonic(SignerType.Stronghold)

    const account = await manager.createAccount({
        clientOptions: { node: "https://api.lb-0.testnet.chrysalis2.com", localPow: true },
        alias: 'Alice',
    })

    console.log('Account created:', account.alias())
      
}

run()

Examples

There are several examples to show the usage of the library.

All examples can be found in /bindings/nodejs/examples

Setup

First, setup your environment as follows:

git clone https://github.com/iotaledger/wallet.rs
cd bindings/node/examples
npm install # or `yarn`
cp .env.example .env

Add your custom password to the .env file.

1. Example: Create an Account

Run:

node 1-create-account.js

Code:

/**
 * This example creates a new database and account
 */

require('dotenv').config()

async function run() {
    const { AccountManager, SignerType } = require('@iota/wallet')
    const manager = new AccountManager({
        storagePath: './alice-database',
    })
    manager.setStrongholdPassword(process.env.SH_PASSWORD)
    manager.storeMnemonic(SignerType.Stronghold)

    const account = await manager.createAccount({
        clientOptions: { node: "https://api.lb-0.testnet.chrysalis2.com", localPow: true },
        alias: 'Alice',
    })

    console.log('Account created:', account.alias())
      
}

run()

2. Generate Address

Run:

node 2-generate-address.js

Code:

/**
 * This example genrates a new address.
 */

require('dotenv').config()

async function run() {
	const { AccountManager } = require('@iota/wallet')
    const manager = new AccountManager({
        storagePath: './alice-database'
    })

    manager.setStrongholdPassword(process.env.SH_PASSWORD)

    const account = manager.getAccount('Alice')
    console.log('Account:', account.alias())

    // Always sync before doing anything with the account
    const synced = await account.sync()
    console.log('Syncing...')

    const { address } = account.generateAddress()
    console.log('New address:', address)

    // You can also get the latest unused address:
    // const addressObject = account.latestAddress()
    // console.log("Address:", addressObject.address)

    // Use the Chrysalis Faucet to send testnet tokens to your address:
    console.log("Fill your address with the Faucet: https://faucet.testnet.chrysalis2.com/")
}

run()

3. Example: Check Balance

Run:

node 3-check_balance

Code:

/**
 * This example creates a new database and account
 */

require('dotenv').config()

async function run() {
	const { AccountManager } = require('@iota/wallet')
    const manager = new AccountManager({
        storagePath: './alice-database'
    })

    manager.setStrongholdPassword(process.env.SH_PASSWORD)

    const account = manager.getAccount('Alice')
    
    console.log('Account:', account.alias())
    
    // Always sync before doing anything with the account
    const synced = await account.sync()
    console.log('Syncing...')

    console.log('Available balance', account.balance().available)
}

run()

4. Example: Check Balance

Now you can send the test tokens to an address!

Run

node 4-send.js

Code:

/**
 * This example sends IOTA Toens to an address.
 */

 require('dotenv').config();

async function run() {
	const { AccountManager } = require('@iota/wallet')
    const manager = new AccountManager({
        storagePath: './alice-database'
    })

    manager.setStrongholdPassword(process.env.SH_PASSWORD)

    const account = manager.getAccount('Alice')
    
    console.log('alias', account.alias())
    console.log('syncing...')
    const synced = await account.sync()
    console.log('available balance', account.balance().available)
    
    //TODO: Replace with the address of your choice!
	const addr = 'atoi1qykf7rrdjzhgynfkw6z7360avhaaywf5a4vtyvvk6a06gcv5y7sksu7n5cs'
	const amount = 10000000

	const node_response = await synced.send(
		addr,
		amount
    ) 

    console.log(`Check your message on https://explorer.iota.org/chrysalis/message/${node_response.id}`)
}

run()

5. Backup

Run

node 5-backup.js

Code:

/**
 * This example backups your data in a secure file. 
 * You can move this file to another app or device and restore it.
 */

require('dotenv').config();

async function run() {

    const { AccountManager } = require('@iota/wallet')
    const manager = new AccountManager({
        storagePath: './alice-database'
    })

    manager.setStrongholdPassword(process.env.SH_PASSWORD)

    let backup_path = await manager.backup("./backup", process.env.SH_PASSWORD)
    
    console.log('Backup path:', backup_path)
}

run()

6. Restore

Run

node 6-restore.js

Code:

/**
 * This example restores a secured backup file. 
 */

require('dotenv').config();

async function run() {

    const { AccountManager } = require('@iota/wallet')
    const manager = new AccountManager({
        storagePath: './alice-database'
    })

    // Add the path to the file from example 5-backup.js
    // for example: ./backup/2021-02-12T01-23-11-iota-wallet-backup-wallet.stronghold
    let backup_path = "input your backup file"

    await manager.importAccounts(backup_path, process.env.SH_PASSWORD)
    const account = manager.getAccount('Alice')
    console.log('Account:', account.alias())
}

run()

7. Events

Run

node 7-events.js

Code:

/**
 * This example shows some events.
 */

require('dotenv').config()

async function run() {
    const { AccountManager, addEventListener } = require('@iota/wallet')
    const manager = new AccountManager({
        storagePath: './alice-database'
    })

    manager.setStrongholdPassword(process.env.SH_PASSWORD)

    const account = manager.getAccount('Alice')
    console.log('Account:', account.alias())

    // Always sync before doing anything with the account
    const synced = await account.sync()
    console.log('Syncing...')
    // let address = account.generateAddress()

    // get latest address
    let addressObject = account.latestAddress()

    console.log("Address:", addressObject.address)

    // Use the Chrysalis Faucet to send testnet tokens to your address:
    console.log("Fill your address with the Faucet: https://faucet.testnet.chrysalis2.com/")


    const callback = function (err, data) {
        console.log("data:", data)
    }

    addEventListener("BalanceChange", callback)

    // Possible Event Types:
    //
    // ErrorThrown
    // BalanceChange
    // NewTransaction
    // ConfirmationStateChange
    // Reattachment
    // Broadcast
}

run()

8. Migration

Run

node 8-migration.js

Code:

/**
 * This example creates a new database and account,
 * and migrate funds from the legacy network to the chrysalis network
 */

require('dotenv').config()

const ADDRESS_SECURITY_LEVEL = 2
// Minimum balance that is required for a migration bundle, because of the dust protection in the new network
const MINIMUM_MIGRATION_BALANCE = 1000000
// This value shouldn't be too high, because then the PoW could take to long to get it confirmed
const MAX_INPUTS_PER_BUNDLE = 10


async function run() {
  try {
    const { AccountManager, SignerType, addEventListener } = require('@iota/wallet')

    // We store all bundle hashes here and check later if the bundles got confirmed
    let migrationBundleHashes = [];
    // Log migration events
    const callback = function (err, data) {
      // After a successful broadcast of this bundle, the library will automatically reattach bundle to 
      // speed up the confirmation process. An event with type "TransactionConfirmed" (with corresponding bundle hash) 
      // will be emitted as soon as the bundle is confirmed.
      if (data.event.type === 'TransactionConfirmed') {
        console.log("MigrationProgress:", data)
        migrationBundleHashes = migrationBundleHashes.filter(hash => hash !== data.event.data.bundleHash)
        if (migrationBundleHashes.length == 0) {
          process.exit()
        }
        console.log("Still unconfirmed bundles: ", migrationBundleHashes);
      }
    }
    addEventListener("MigrationProgress", callback)

    const manager = new AccountManager({
      storagePath: './migration-database',
    })
    manager.setStrongholdPassword(process.env.SH_PASSWORD)
    // Save this mnemonic securely. If you lose it, you potentially lose everything.
    const mnemonic = manager.generateMnemonic()
    console.log("Save this mnemonic securely. If you lose it, you potentially lose everything:", mnemonic);
    manager.storeMnemonic(SignerType.Stronghold, mnemonic)

    const account = await manager.createAccount({
      // Node url for the new network
      clientOptions: { node: "https://chrysalis-nodes.iota.cafe", localPow: true, network: "chrysalis-mainnet" },
      alias: 'Migration',
    })

    console.log('Account created:', account.alias())
    // Nodes for the legacy network
    const nodes = ['https://nodes.iota.org']
    const seed = process.env.MIGRATION_SEED
    const migrationData = await manager.getMigrationData(
      nodes,
      seed,
      {
        // permanode for the legacy network
        permanode: 'https://chronicle.iota.org/api',
        securityLevel: ADDRESS_SECURITY_LEVEL,
        // this is the default and from there it will check addresses for balance until 30 in a row have 0 balance
        // if not all balance got detected because a higher address index was used it needs to be increased here
        initialAddressIndex: 0
      }
    )
    console.log(migrationData)

    let input_batches = getMigrationBundles(migrationData.inputs)
    // create bundles with the inputs
    for (batch of input_batches) {
      try {
        const bundle = await manager.createMigrationBundle(seed, batch.inputs.map(input => input.index), {
          logFileName: 'iota-migration.log',
          // if the input is a spent address we do a bundle mining process which takes 10 minutes to reduce the amount 
          // of the parts of the private key which get revealed
          mine: batch.inputs[0].spent
        })
        migrationBundleHashes.push(bundle.bundleHash)
      } catch (e) {
        console.error(e);
      }
    }

    // Send all bundles to the Tangle and reattach them until they are confirmed
    for (bundleHash of migrationBundleHashes) {
      try {
        await manager.sendMigrationBundle(nodes, bundleHash)
      } catch (e) { console.error(e) }
    }

  } catch (e) {
    console.error(e);
  }
}

run()

const getMigrationBundles = (inputs) => {
  // Categorise spent vs unspent inputs
  const { spent, unspent } = inputs.reduce((acc, input) => {
    if (input.spent) {
      acc.spent.push(input)
    } else {
      acc.unspent.push(input)
    }
    return acc;
  }, { spent: [], unspent: [] })
  const unspentInputChunks = selectInputsForUnspentAddresses(unspent)
  const spentInputs = spent.filter((input) => input.balance >= MINIMUM_MIGRATION_BALANCE)
  return [
    ...spentInputs.map((input) => ({
      // Make sure for spent addresses, we only have one input per bundle    
      inputs: [input]
    })),
    ...unspentInputChunks.map((inputs) => ({ inputs }))
  ]
};
/**
 * Prepares inputs (as bundles) for unspent addresses.
 * Steps:
 *   - Categorises inputs in two groups 1) inputs with balance >= MINIMUM_MIGRATION_BALANCE 2) inputs with balance < MINIMUM_MIGRATION_BALANCE
 *   - Creates chunks of category 1 input addresses such that length of each chunk should not exceed MAX_INPUTS_PER_BUNDLE
 *   - For category 2: 
 *         - Sort the inputs in descending order based on balance;
 *         - Pick first N inputs (where N = MAX_INPUTS_PER_BUNDLE) and see if their accumulative balance >= MINIMUM_MIGRATION_BALANCE
 *         - If yes, then repeat the process for next N inputs. Otherwise, iterate on the remaining inputs and add it to a chunk that has space for more inputs
 *         - If there's no chunk with space left, then ignore these funds. NOTE THAT THESE FUNDS WILL ESSENTIALLY BE LOST!
 * 
 * NOTE: If the total sum of provided inputs are less than MINIMUM_MIGRATION_BALANCE, then this method will just return and empty array as those funds can't be migrated.
 * 
 * This method gives precedence to max inputs over funds. It ensures, a maximum a bundle could have is 30 inputs and their accumulative balance >= MINIMUM_MIGRATION_BALANCE
 * 
 * @method selectInputsForUnspentAddresses
 * 
 * @params {Input[]} inputs
 * 
 * @returns {Input[][]}
 */
const selectInputsForUnspentAddresses = (inputs) => {
  const totalInputsBalance = inputs.reduce((acc, input) => acc + input.balance, 0);

  // If the total sum of unspent addresses is less than MINIMUM MIGRATION BALANCE, just return an empty array as these funds cannot be migrated
  if (totalInputsBalance < MINIMUM_MIGRATION_BALANCE) {
    return [];
  }

  const { inputsWithEnoughBalance, inputsWithLowBalance } = inputs.reduce((acc, input) => {
    if (input.balance >= MINIMUM_MIGRATION_BALANCE) {
      acc.inputsWithEnoughBalance.push(input);
    } else {
      acc.inputsWithLowBalance.push(input);
    }

    return acc;
  }, { inputsWithEnoughBalance: [], inputsWithLowBalance: [] })

  let chunks = inputsWithEnoughBalance.reduce((acc, input, index) => {
    const chunkIndex = Math.floor(index / MAX_INPUTS_PER_BUNDLE)

    if (!acc[chunkIndex]) {
      acc[chunkIndex] = [] // start a new chunk
    }

    acc[chunkIndex].push(input)

    return acc
  }, [])

  const fill = (_inputs) => {
    _inputs.every((input) => {
      const chunkIndexWithSpaceForInput = chunks.findIndex((chunk) => chunk.length < MAX_INPUTS_PER_BUNDLE);

      if (chunkIndexWithSpaceForInput > -1) {
        chunks = chunks.map((chunk, idx) => {
          if (idx === chunkIndexWithSpaceForInput) {
            return [...chunk, input]
          }

          return chunk
        })

        return true;
      }

      // If there is no space, then exit
      return false;
    })
  }

  const totalBalanceOnInputsWithLowBalance = inputsWithLowBalance.reduce((acc, input) => acc + input.balance, 0)

  // If all the remaining input addresses have accumulative balance less than the minimum migration balance,
  // Then sort the inputs in descending order and try to pair the
  if (totalBalanceOnInputsWithLowBalance < MINIMUM_MIGRATION_BALANCE) {
    const sorted = inputsWithLowBalance.slice().sort((a, b) => b.balance - a.balance)

    fill(sorted)
  } else {
    let startIndex = 0

    const sorted = inputsWithLowBalance.slice().sort((a, b) => b.balance - a.balance)
    const max = Math.ceil(sorted.length / MAX_INPUTS_PER_BUNDLE);

    while (startIndex < max) {
      const inputsSubset = sorted.slice(startIndex * MAX_INPUTS_PER_BUNDLE, (startIndex + 1) * MAX_INPUTS_PER_BUNDLE)
      const balanceOnInputsSubset = inputsSubset.reduce((acc, input) => acc + input.balance, 0);

      if (balanceOnInputsSubset >= MINIMUM_MIGRATION_BALANCE) {
        chunks = [...chunks, inputsSubset]
      } else {
        fill(inputsSubset)
      }

      startIndex++;
    }
  }

  return chunks;
};

API Reference

initLogger(config: LogOptions)

Initializes the logging system.

LogOptions

ParamTypeDefaultDescription
color_enabledbooleanundefinedWhether to enable colored output or not
outputsLogOutput[]undefinedThe log outputs

LogOutput

ParamTypeDefaultDescription
namestringundefined'stdout' or a path to a file
level_filterstring'info'The maximum log level that this output accepts
target_filtersstring[][]Filters on the log target (library and module names)

addEventListener(event, cb)

Adds a new event listener with a callback in the form of (err, data) => {}. Supported event names:

  • ErrorThrown
  • BalanceChange
  • NewTransaction
  • ConfirmationStateChange
  • Reattachment
  • Broadcast
  • TransferProgress
  • MigrationProgress

AccountManager

constructor([options])

Creates a new instance of the AccountManager.

ParamTypeDefaultDescription
[options]objectundefinedThe options to configure the account manager
[storagePath]stringundefinedThe path where the database file will be saved
[storagePassword]stringundefinedThe storage password
[outputConsolidationThreshold]number100The number of outputs an address must have to trigger the automatic consolidation process
[automaticOutputConsolidation]booleantrueDisables the automatic output consolidation if false
[syncSpentOutputs]booleanfalseEnables fetching spent output history on account sync
[persistEvents]booleanfalseEnables event persistence
[allowCreateMultipleEmptyAccounts]booleancodeEnables creating accounts with latest account being empty

setStrongholdPassword(password): void

Sets the stronghold password and initialises it.

ParamTypeDefaultDescription
passwordstringundefinedThe stronghold snapshot password

changeStrongholdPassword(currentPassword, newPassword): void

Changes the stronghold password.

ParamTypeDefaultDescription
currentPasswordstringundefinedThe current stronghold password
newPasswordstringundefinedThe new stronghold password

createAccount(account): Account

Creates a new account.

ParamTypeDefaultDescription
accountobject{}The account to be created
account.clientOptionsClientOptionsundefinedThe node configuration
[account.mnemonic]stringundefinedThe account BIP39 mnemonic
[account.alias]stringAccount ${index + 1}The account alias
[account.createdAt]stringthe current date and timeThe ISO 8601 date string of the account creation
[account.signerType]number1 = StrongholdThe account signer type. 1 = Stronghold, 2 = EnvMnemonic

getAccount(accountId)

Gets the account with the given identifier or index.

ParamTypeDefaultDescription
accountIdstring | numbernullThe account identifier or account index

Returns the associated Account instance or undefined if the account wasn't found.

getAccountByAlias(alias)

Gets the account with the given alias (case insensitive).

ParamTypeDefaultDescription
aliasstringnullThe account alias

Returns the associated Account instance or undefined if the account wasn't found.

getAccounts()

Gets all stored accounts.

Returns an array of Account objects.

removeAccount(accountId)

Removes the account with the given identifier or index.

ParamTypeDefaultDescription
accountIdstring | numbernullThe account identifier or account index

syncAccounts([options])

Synchronize all stored accounts with the Tangle.

ParamTypeDefaultDescription
[options]object{}The sync options
[options.addressIndex]numberlatest address indexThe index of the first account address to sync
[options.gapLimit]number10The number of addresses to check on each account sync

Returns A promise resolving to an array of SyncedAccount.

internalTransfer(fromAccount, toAccount, amount)

Transfers an amount from one subaccount to another.

ParamTypeDefaultDescription
fromAccountAccountnullThe source account
toAccountAccountnullThe destination account
amountnumberundefinedThe transfer amount

Returns A promise resolving to the transfer's Message.

backup(destination, password)

Backups the database.

ParamTypeDefaultDescription
destinationstringundefinedThe path to the backup file
passwordstringundefinedThe backup stronghold password

Returns The full path to the backup file.

importAccounts(source)

Imports a database file.

ParamTypeDefaultDescription
sourcestringundefinedThe path to the backup file
passwordstringundefinedThe backup stronghold password

isLatestAddressUnused()

Determines whether all accounts has unused latest address after syncing with the Tangle.

Returns A promise resolving to the boolean value.

setClientOptions(options)

Updates the client options for all accounts.

ParamTypeDefaultDescription
optionsClientOptionsnullThe new account client options

generateMigrationAddress(address)

Convert a Ed25519 to a Tryte migration address with checksum (last 9 Trytes)

ParamTypeDefaultDescription
addressstringnullBech32 encoded Ed25519 address

getBalanceChangeEvents([count, skip, fromTimestamp])

Gets the persisted balance change events.

ParamTypeDefaultDescription
[count]number0The number of events to return (0 to return all)
[skip]number0The number of events to skip
[fromTimestamp]numbernullFilter events that were stored after the given UTC timestamp

Event object: { indexationId: string, accountId: string, address: string, message?: string, balanceChange: { spent: number, received: number } }

getBalanceChangeEventCount([fromTimestamp])

Gets the number of persisted balance change events.

ParamTypeDefaultDescription
[fromTimestamp]numbernullFilter events that were stored after the given UTC timestamp

getTransactionConfirmationEvents([count, skip, fromTimestamp])

Gets the persisted transaction confirmation change events.

ParamTypeDefaultDescription
[count]number0The number of events to return (0 to return all)
[skip]number0The number of events to skip
[fromTimestamp]numbernullFilter events that were stored after the given UTC timestamp

Event object: { indexationId: string, accountId: string, message: Message, confirmed: boolean }

getTransactionConfirmationEventCount([fromTimestamp])

Gets the number of persisted transaction confirmation change events.

ParamTypeDefaultDescription
[fromTimestamp]numbernullFilter events that were stored after the given UTC timestamp

getNewTransactionEvents([count, skip, fromTimestamp])

Gets the persisted new transaction events.

ParamTypeDefaultDescription
[count]number0The number of events to return (0 to return all)
[skip]number0The number of events to skip
[fromTimestamp]numbernullFilter events that were stored after the given UTC timestamp

Event object: { indexationId: string, accountId: string, message: Message }

getNewTransactionEventCount([fromTimestamp])

Gets the number of persisted new transaction events.

ParamTypeDefaultDescription
[fromTimestamp]numbernullFilter events that were stored after the given UTC timestamp

getReattachmentEvents([count, skip, fromTimestamp])

Gets the persisted transaction reattachment events.

ParamTypeDefaultDescription
[count]number0The number of events to return (0 to return all)
[skip]number0The number of events to skip
[fromTimestamp]numbernullFilter events that were stored after the given UTC timestamp

Event object: { indexationId: string, accountId: string, message: Message }

getReattachmentEventCount([fromTimestamp])

Gets the number of persisted transaction reattachment events.

ParamTypeDefaultDescription
[fromTimestamp]numbernullFilter events that were stored after the given UTC timestamp

getBroadcastEvents([count, skip, fromTimestamp])

Gets the persisted transaction broadcast events.

ParamTypeDefaultDescription
[count]number0The number of events to return (0 to return all)
[skip]number0The number of events to skip
[fromTimestamp]numbernullFilter events that were stored after the given UTC timestamp

Event object: { indexationId: string, accountId: string, message: Message }

getBroadcastEventCount([fromTimestamp])

Gets the number of persisted transaction broadcast events.

ParamTypeDefaultDescription
[fromTimestamp]numbernullFilter events that were stored after the given UTC timestamp

SyncedAccount

The result of a sync operation on an Account.

Account

id()

Returns the account's identifier.

index()

Returns the account's index.

alias()

Returns the account's alias.

balance(): AccountBalance

Returns the account's balance information object.

Balance object: { total: number, available: number, incoming: number, outgoing: number }

messageCount([type])

Returns the number of messages associated with the account.

ParamTypeDefaultDescription
[type]numbernullThe message type filter (Received = 1, Sent = 2, Failed = 3, Unconfirmed = 4, Value = 5)

listMessages([count, from, type])

Returns the account's messages.

ParamTypeDefaultDescription
[count]number0The number of messages to return (0 to return all)
[skip]number0The number of messages to skip
[type]numbernullThe message type filter (Received = 1, Sent = 2, Failed = 3, Unconfirmed = 4, Value = 5)

Message object: { confirmed: boolean, broadcasted: boolean, incoming: boolean, value: number }

listAddresses([unspent])

Returns the account's addresses.

ParamTypeDefaultDescription
[unspent]booleannullThe unspent status filter

Address object: { address: string, keyIndex: number }

sync([options])

Synchronizes the account with the Tangle.

ParamTypeDefaultDescription
[options]object{}The sync options
[options.addressIndex]numberlatest address indexThe index of the first address to sync
[options.gapLimit]number10The number of addresses to check

Returns a SyncedAccount instance.

send(address, amount[, options])

Send funds to the given address.

ParamTypeDefaultDescription
addressstringnullThe bech32 string of the transfer address
amountnumberundefinedThe transfer amount
optionsTransferOptionsundefinedThe transfer options
TransferOptions
ParamTypeDefaultDescription
remainderValueStrategyRemainderValueStrategynullThe strategy to use for the remainder value if any
indexation{ index: string, data?: Uint8Array }nullMessage indexation
RemainderValueStrategy
changeAddress()

Send the remainder value to an internal address.

reuseAddress()

Send the remainder value to its original address.

accountAddress(address: string)

Send the remainder value to a specific address that must belong to the account.

retry(messageId)

Retries (promotes or reattaches) the given message.

ParamTypeDefaultDescription
messageIdstringnullThe message's identifier

reattach(messageId)

Reattach the given message.

ParamTypeDefaultDescription
messageIdstringnullThe message's identifier

promote(messageId)

Promote the given message.

ParamTypeDefaultDescription
messageIdstringnullThe message's identifier

consolidateOutputs()

Consolidate the outputs on all account addresses.

isLatestAddressUnused()

Determines whether the account has unused latest address after syncing with the Tangle.

Returns A promise resolving to the boolean value.

setAlias(alias)

Updates the account alias.

ParamTypeDefaultDescription
aliasstringnullThe new account alias

setClientOptions(options)

Updates the account client options.

ParamTypeDefaultDescription
optionsClientOptionsnullThe new account client options

getMessage(messageId)

Gets the message associated with the given identifier.

ParamTypeDefaultDescription
messageIdstringnullThe message's identifier

getAddress(addressBech32)

Gets the address object by its bech32 representation.

ParamTypeDefaultDescription
addressBech32stringnullThe address bech32 representation

generateAddress()

Generates a new unused address and returns it.

latestAddress()

Returns the latest address (the one with the biggest keyIndex).

getUnusedAddress()

Synchronizes the account addresses with the Tangle and returns the latest address in the account, which is an address without balance.

ClientOptions

FieldTypeDefaultDescription
[network]numberundefinedThe tangle network to connect to (Mainnet = 1, Devnet = 1, Comnet = 3)
[node]stringundefinedA node URL to connect to
[nodes]string[]undefinedA list node URL to connect to
[quorumSize]numberundefinedIf multiple nodes are provided, quorum size determines the number of nodes to query to check for quorum.
[quorumThreshold]numberundefinedMinimum number of nodes from the quorum pool that need to agree to consider a result true.
[localPow]booleantrueWhether to use local or remote PoW.

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 Wallet 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.

Overview

Getting Started with IOTA Wallet 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-wallet-python-binding 0.1.0

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

Usage

import iota_wallet
print(iota_wallet.__doc__)

Examples

Account manager and individual accounts

First of all, let's initialize (open) a secure storage for individual accounts (backed up by Stronghold by default) using AccountManager instance:

# Copyright 2020 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0


import iota_wallet as iw
import os
from dotenv import load_dotenv


# Load the env variables
load_dotenv()

# Get the stronghold password
STRONGHOLD_PASSWORD = os.getenv('STRONGHOLD_PASSWORD')

account_manager = iw.AccountManager(
    storage_path='./alice-database'
)  # note: `storage` and `storage_path` have to be declared together

account_manager.set_stronghold_password(STRONGHOLD_PASSWORD)

# mnemonic (seed) should be set only for new storage
# once the storage has been initialized earlier then you should omit this step
account_manager.store_mnemonic("Stronghold")
  • Storage is initialized under the given path (./alice-database)
  • Password is set (password)
  • Only during the initialization new database: stronghold mnemonic (seed) is automatically generated and stored by default

The storage is encrypted at rest and so you need a strong password and location where to put your storage.

Please note: deal with the password with utmost care

Technically speaking, the storage means a single file called wallet.stronghold. It is also needed to generate a seed (mnemonic) that serves as a cryptographic key from which all accounts and related addresses are generated.

One of the key principle behind the stronghold-based storage is that no one can get a seed from the storage. You deal with all accounts purely via Account_Manager instance and all complexities are hidden under the hood and are dealt with in a secure way.

In case one would like to store a seed also somewhere else, there is a method AccountManager.generate_mnemonic() that generates random seed and it can be leveraged before the actual account initialization.

Please note, it is highly recommended to store stronghold password and stronghold database on separate devices.

More detailed information about seed generation is available at Developer guide to Chrysalis.

Accounts

The library uses a model of individual accounts to separate individual users/clients from each other. It is possible to generate multiple addresses for each account deterministically based on seed mnemonic.

Please note, it is important to declare on which IOTA network should be the given account created (argument node).

# ... continue from prev example 1a

# general Tangle specific options
client_options = {
    "nodes": [
        {
            "url": "https://api.hornet-0.testnet.chrysalis2.com",
            "auth": None,
            "disabled": False
        }
    ],
    "local_pow": True
}

# an account is generated with the given alias via `account_initialiser`
account_initialiser = account_manager.create_account(client_options)
account_initialiser.alias('Alice')

# initialise account based via `account_initialiser`
# store it to db and sync with Tangle
account = account_initialiser.initialise()
print(f'Account created: {account.alias()}')

Once an account has been created you get an instance of it using the following methods: get_account(account_id: str) or get_accounts(). An account can be then referred to via index, alias or one of its generated addresses. The network against which the account is active can be checked via account.bech32_hrp().

Overview of all accounts:

for acc in account_manager.get_accounts():
  print(f"Account alias: {acc.alias()}; network: {acc.bech32_hrp()}")

Get the instance of a specific account:

account = account_manager.get_account("Alice")

Several api calls can be performed via account instance. Note: it is a good practice to sync the given account with the Tangle every time you work with account instance to rely on the latest information available: account.sync().execute().

The most common methods:

  • account.alias(): returns an alias of the given account
  • account.addresses(): returns list of addresses related to the account
  • account.get_unused_address(): returns a first unused address
  • account.is_latest_address_unused(): it queries the Tangle and returns bool whether latest address was already used
  • account.generate_address(): generate a new address for the address index incremented by 1
  • account.balance(): returns the balance for the given account
  • account.sync(): sync the account information with the tangle

Generating address(es)

Each account can posses multiple addresses. Addresses are generated deterministically based on the account and address index. It means that the combination of account and index uniquely identifies the given address.

Addresses are of two types: internal and public (external):

  • each set of addresses is independent from each other and has independent index id
  • addresses that are created by account.generate_address() are indicated as internal=false (public)
  • internal addresses (internal=true) are so called change addresses and are used to send the excess funds to
  • the approach is also known as a BIP32 Hierarchical Deterministic wallet (HD Wallet).

Addresses are generated via instance of account that is gotten from the account_manager instance:

# Copyright 2020 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0


import iota_wallet as iw
import os
from dotenv import load_dotenv

# Load the env variables
load_dotenv()

# Get the stronghold password
STRONGHOLD_PASSWORD = os.getenv('STRONGHOLD_PASSWORD')

# This example generates a new address.
account_manager = iw.AccountManager(
    storage_path='./alice-database'
)

account_manager.set_stronghold_password(STRONGHOLD_PASSWORD)

# get a specific instance of some account
account = account_manager.get_account('Alice')
print(f'Account: {account.alias()}')

# Always sync before doing anything with the account
print('Syncing...')
synced = account.sync().execute()

# generate new address
address = account.generate_address()
print(f'New address: {address}')

# print all addresses generated so far
print("List of addresses:")
print(account.addresses())

# You can also get the latest unused address
last_address_obj = account.latest_address()
print(f"Last address: {last_address_obj['address']}")

Output example:

[{'address': {'inner': 'atoi1qzy79ew8x4hn4dsr0t3j8ce8hdwdrh8xzx85x2gkse6k0fx2jkyaqdgd2rn'},
  'balance': 0,
  'key_index': 0,
  'internal': False,
  'outputs': []},
 {'address': {'inner': 'atoi1qzht4m2jt0q50lhlqa786pcx6vardm4xj8za72fezde6tj39acatq5zh2cg'},
  'balance': 0,
  'key_index': 1,
  'internal': False,
  'outputs': []}]

Take a closer look at the output above and check the beginning of both addresses. As mentioned in Developer guide to Chrysalis there are two human-readable prefixes in IOTA 1.5 network: iota (mainnet) and atoi (testnet).

More detailed information about generating addresses is available at Developer guide to Chrysalis.

Checking the balance

Before we continue further, go to IOTA testnet faucet service and send to your testnet addresses some tokens: faucet screenshot

# Copyright 2020 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0


import iota_wallet as iw
import os
from dotenv import load_dotenv

# Load the env variables
load_dotenv()

# Get the stronghold password
STRONGHOLD_PASSWORD = os.getenv('STRONGHOLD_PASSWORD')

# This example checks the account balance.
account_manager = iw.AccountManager(
    storage_path='./alice-database'
)

account_manager.set_stronghold_password(STRONGHOLD_PASSWORD)

# get a specific instance of some account
account = account_manager.get_account('Alice')
print(f'Account: {account.alias()}')

# Always sync before doing anything with the account
print('Syncing...')
synced = account.sync().execute()

# get total balance for the account
print("Total balance:")
print(account.balance())

print("Balance per individual addresses:")
print(account.addresses())

Output:

Total balance:
{'total': 10000000, 'available': 10000000, 'incoming': 10000000, 'outgoing': 0}

Balance per individual addresses:
[{'address': {'inner': 'atoi1qzy79ew8x4hn4dsr0t3j8ce8hdwdrh8xzx85x2gkse6k0fx2jkyaqdgd2rn'},
  'balance': 0,
  'key_index': 0,
  'internal': False,
  'outputs': []},
 {'address': {'inner': 'atoi1qzht4m2jt0q50lhlqa786pcx6vardm4xj8za72fezde6tj39acatq5zh2cg'},
  'balance': 10000000,
  'key_index': 1,
  'internal': False,
  'outputs': [{'transaction_id': '1c88c91fe0a8eed074b5ccdfdad52403d7908d157b231ae1ef28b0e20ba14e8e',
    'message_id': 'f1575f984f7fda6e9b3e23e96ef3304fcd0ba4ce323af3920856a427fabe1abe',
    'index': 0,
    'amount': 10000000,
    'is_spent': False,
    'address': {'inner': 'atoi1qzht4m2jt0q50lhlqa786pcx6vardm4xj8za72fezde6tj39acatq5zh2cg'}}]},
 {'address': {'inner': 'atoi1qpvnsgygzal4vkxhlc0ew7c6c6csnjr72x5rgn3txqswrsa2xfrec8v04f7'},
  'balance': 0,
  'key_index': 2,
  'internal': False,
  'outputs': []}]

In the detailed view per individual addresses, there is also outputs section that indicates transaction(s) (also known as wallet message(s)) "responsible" for the current amount. The amount can be also double checked using Tangle Explorer.

IOTA is based on Unspent Transaction Output model which is explained at Developer guide to Chrysalis.

Sending tokens

The process of sending tokens via wallet.rs can be described as follows:

  • Create instance of iota_wallet.Transfer() class with the following mandatory arguments: amount, address and remainder_value_strategy
  • remainder_value_strategy can be: ReuseAddress or ChangeAddress. You may be familiar with a concept changing address with every spent in IOTA 1.0. It is not an issue in IOTA 1.5 world but it may still become handy depending on your use case
  • once instance of iota_wallet.Transfer() is created, it can be sent via transfer() function of the Account instance
  • needless to repeat, always sync the account information with the Tangle before do anything with the account
# Copyright 2020 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0


import iota_wallet as iw
import os
from dotenv import load_dotenv

# Load the env variables
load_dotenv()

# Get the stronghold password
STRONGHOLD_PASSWORD = os.getenv('STRONGHOLD_PASSWORD')

# This example sends IOTA toens to an address.
account_manager = iw.AccountManager(
    storage_path='./alice-database'
)
account_manager.set_stronghold_password(STRONGHOLD_PASSWORD)

print("Selecting a specific account")
account = account_manager.get_account('Alice')
print(f'Account: {account.alias()} selected')

# Always sync before doing anything with the account
print('Syncing...')
synced = account.sync().execute()

print(f"Available balance {account.balance()['available']}")

# TODO: Replace with the address of your choice!
transfer = iw.Transfer(
    amount=1_000_000,
    address='atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r',
    remainder_value_strategy='ReuseAddress'
)

# Propogate the Transfer to Tangle
# and get a response from the Tangle
node_response = account.transfer(transfer)
print(
    node_response
)

You receive an output similar to the following one:

{'id': '9d3c401d59b0a87f6fbaa58582bb71e1858d63336421ccbae834821d9be113d3',
 'version': 1,
 'parents': ['66009ff08637c3e74340fb9e09e30e3c4453728c857fd425df2d2e0587af6426',
  '6da392ac35f73594bf5509fb5c3304e972b36313ce98f2cc63def7cde2054b53',
  '9157b29cbffcd5c9669cf22004fbc557354e5ade7268f5bfe25fbc75ab29e3b1',
  'bfe860e09350cd3b8db90611e78e03fdda654139a4b34e68e4b1bb07528b2bef'],
 'payload_length': 233,
 'payload': {'transaction': [{'essence': {'regular': {'inputs': [{'transaction_id': '692d6660084dd3b6341ef4f761bc8b8bb27ac35bb0b352bfb030f2c80753815b',
        'index': 0,
        'metadata': {'transaction_id': '692d6660084dd3b6341ef4f761bc8b8bb27ac35bb0b352bfb030f2c80753815b',
         'message_id': 'c6284e0cc2a6383474782d4e6b6cfaf16c1831c8875cca262982782758a248c0',
         'index': 0,
         'amount': 10000000,
         'is_spent': False,
         'address': {'inner': 'atoi1qq24vlx53qdskyfw6940xa2vg55ma5egzyqv6glq23udx3e0zkmmg97cwze'}}}],
      'outputs': [{'address': 'atoi1qq24vlx53qdskyfw6940xa2vg55ma5egzyqv6glq23udx3e0zkmmg97cwze',
        'amount': 9000000},
       {'address': 'atoi1qpvnsgygzal4vkxhlc0ew7c6c6csnjr72x5rgn3txqswrsa2xfrec8v04f7',
        'amount': 1000000}],
      'payload': None}},
    'unlock_blocks': [{'signature': {'public_key': [15...<TRIMMED>...],
       'signature': [210...<TRIMMED>...]},
      'reference': None}]}],
  'milestone': None,
  'indexation': None},
 'timestamp': 1615132552,
 'nonce': 274654,
 'confirmed': None,
 'broadcasted': True,
 'incoming': False,
 'value': 1000000,
 'remainder_value': 9000000}

It is a wallet message that fully describes the given transaction.

Please, kindly get yourself familiar with a concept of UTXO to understand all aspects of messages.

The given message can be double checked via Tangle Explorer using node_response['id'] field (Tangle Explorer).

Needless to say, if remainder_value_strategy == ChangeAddress is used, the given message transfer tokens to target address as well as new internal address within the given account (internal=True).

Messages and payloads are described at Developer guide to Chrysalis.

Reattachments

If message reattachment is needed then account_id and message_id is passed to iota_wallet.promote(account_id, message_id) or iota_wallet.reattach(account_id, message_id).

List of messages (transactions)

List of all particular messages (transactions) related to the given account get be obtained via: account.list_messages() and related account.message_count().

Those can be used also to check whether the given message was confirmed/broadcasted, etc. Needless to say, sync the account with the Tangle before checking confirmation status:

# Copyright 2020 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0


import iota_wallet as iw
import os
from dotenv import load_dotenv

# Load the env variables
load_dotenv()

# Get the stronghold password
STRONGHOLD_PASSWORD = os.getenv('STRONGHOLD_PASSWORD')

# This example sends IOTA toens to an address.
account_manager = iw.AccountManager(
    storage_path='./alice-database'
)
account_manager.set_stronghold_password(STRONGHOLD_PASSWORD)

account = account_manager.get_account('Alice')
print(f'Account: {account.alias()} selected')

# Always sync before doing anything with the account
print('Syncing...')
synced = account.sync().execute()

for ac in account.list_messages():
    print(f"message {ac['id']}; confirmation status = {ac['confirmed']}'")

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."

Dust protection also means you can't leave (return back) less than 1Mi on a spent address (leave a dust behind).

Backup database

Underlying database (provided by Stronghold by default) is encrypted at rest and there is no way how to get a seed from it due to security practices that are incorporated in the Stronghold's DNA. It means you are dealing with the database as an atomic unit that includes all wallet information.

So backing up the database is very important task from this respect.

# Copyright 2020 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0


import iota_wallet as iw
import os
from dotenv import load_dotenv

# Load the env variables
load_dotenv()

# Get the stronghold password
STRONGHOLD_PASSWORD = os.getenv('STRONGHOLD_PASSWORD')

# This example backups your data in a secure file.
# You can move this file to another app or device and restore it.
account_manager = iw.AccountManager(
    storage_path='./alice-database'
)
account_manager.set_stronghold_password(STRONGHOLD_PASSWORD)

backup_dir_path = './backup'
if not os.path.exists(backup_dir_path):
    os.makedirs(backup_dir_path)
backup_file_path = account_manager.backup(backup_dir_path, STRONGHOLD_PASSWORD)

print(f'Backup path: {backup_file_path}')

Output:

Backup path: ./backup/2021-03-07T18-24-06-iota-wallet-backup-wallet.stronghold

Restore database

The process of restoring underlying database via wallet.rs can be described as follows:

  • create new empty database with a password (without mnemonic [seed])
  • import all accounts from the file that has been backed up earlier
# Copyright 2020 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0


import iota_wallet as iw
import os
from dotenv import load_dotenv

# Load the env variables
load_dotenv()

# Get the stronghold password
STRONGHOLD_PASSWORD = os.getenv('STRONGHOLD_PASSWORD')

# This example restores a secured backup file.
account_manager = iw.AccountManager(
    storage_path='./alice-database-restored'
)

# NOTE: In real use cases you need to set the password in a safer way, like getting it from env variables
account_manager.set_stronghold_password(STRONGHOLD_PASSWORD)

#  Add the path to the file from example 5-backup.js
#  for example: ./backup/2021-03-04T15-31-04-iota-wallet-backup-wallet.stronghold
backup_file_path = r'./backup/2021-03-31T14-45-23-iota-wallet-backup-wallet.stronghold'

# NOTE: In real use cases you need to set the password in a safer way, like getting it from env variables
account_manager.import_accounts(backup_file_path, STRONGHOLD_PASSWORD)
account = account_manager.get_account('Alice')
print(f'Account: {account.alias()}')

Since the backup file is just a copy of the original database it can be alternatively also renamed to wallet.stronghold and opened in a standard way:

account_manager = iw.AccountManager(
    storage_path='./alice-database'
)
account_manager.set_stronghold_password("password")

Listening to events

Wallet.rs also supports asynchronous event listeners to be listened to. In the python binding it is currently implemented in a way the provided callback is executed as soon as the event is triggered; event details are passed as an argument to the callback method. No coroutines (async/await) are implemented in the python binding.

There are following event listeners supported:

  • on_balance_change(callback): id
  • on_new_transaction(callback): id
  • on_confirmation_state_change(callback): id
  • on_reattachment(callback): id
  • on_broadcast(callback): id
  • on_error(callback): id
  • on_stronghold_status_change(callback): id

When the event listener is registered by calling function above, it returns id of the listener as a list[Bytes]. This id can be then leveraged to deregister the given listener:

  • remove_balance_change_listener(id)
  • remove_new_transaction_listener(id)
  • remove_confirmation_state_change_listener(id)
  • remove_reattachment_listener(id)
  • remove_broadcast_listener(id)
  • remove_error_listener(id)
  • remove_stronghold_status_change_listener(id)

Example of listening to on_balance_change via simple event-based pattern in python:

# Copyright 2020 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

import threading
import time
import iota_wallet as iw
import os
from dotenv import load_dotenv

# Load the env variables
load_dotenv()

# Get the stronghold password
STRONGHOLD_PASSWORD = os.getenv('STRONGHOLD_PASSWORD')

result_available = threading.Event()


def balance_changed_event_processing(event):
    print(f'On balanced changed: {event}')
    result_available.set()


# This example shows some events.
account_manager = iw.AccountManager(
    storage_path='./alice-database'
)

# NOTE: In real use cases you need to set the password in a safer way, like getting it from env variables
account_manager.set_stronghold_password(STRONGHOLD_PASSWORD)

account = account_manager.get_account('Alice')
print(f'Account: {account.alias()}')

# Always sync before doing anything with the account
print('Syncing...')
synced = account.sync().execute()

# Get the latest unused address
last_address_obj = account.latest_address()
print(f"Address: {last_address_obj['address']}")

# Use the Chrysalis Faucet to send testnet tokens to your address:
print('Fill your address with the Faucet: https://faucet.tanglekit.de/')

iw.on_balance_change(balance_changed_event_processing)
print("Waiting for external event (on_balance_changed)...")

# wait for results to be available before continue
# will not wait longer than 360 seconds
result_available.wait(timeout=360)

print("Done.")

Output:

Account: Alice
Syncing...
Address: {'inner': 'atoi1qquszp0hzfsrgx4vx58dfg4v6eh20d2k3ddfgg9dt5778c2egc9uyw7g457'}
Fill your address with the Faucet: https://faucet.testnet.chrysalis2.com/
Waiting for external event (on_balance_changed)...
On balanced changed: {"indexationId":"c3a7a1ab8ba78460954223a704693d088ddd0388681ac6cc1dd964a388d1a619","accountId":"wallet-account://e51a6285ea2d8cbdf5b6da2b85a8344f619d798d869ef4fb88c5fac0e653d6cc","address":"atoi1qquszp0hzfsrgx4vx58dfg4v6eh20d2k3ddfgg9dt5778c2egc9uyw7g457","balanceChange":{"spent":0,"received":10000000}}
Done.

Alternatively it can be consumed via queue-base pattern:

# Copyright 2020 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0


import iota_wallet
import threading
import queue
import time
import os
from dotenv import load_dotenv

# Load the env variables
load_dotenv()

# Get the stronghold password
STRONGHOLD_PASSWORD = os.getenv('STRONGHOLD_PASSWORD')

# This example shows how to listen to on_balance_change event.

# The queue to store received events
q = queue.Queue()


def worker():
    """The worker to process the queued events.
    """
    while True:
        item = q.get(True)
        print(f'Get event: {item}')
        q.task_done()


def balance_changed_event_processing(event):
    """Processing function when event is received.
    """
    print(f'On balanced changed: {event}')
    q.put(event)


# Get the acount manager
manager = iota_wallet.AccountManager(
    storage_path='./alice-database')

# NOTE: In real use cases you need to set the password in a safer way, like getting it from env variables
manager.set_stronghold_password(STRONGHOLD_PASSWORD)

# Get the account
account = manager.get_account('Alice')
print(f'Account: {account.alias()}')

# Always sync before doing anything with the account
print('Syncing...')
synced = account.sync().execute()

# Get the latest unused address
last_address_obj = account.latest_address()
print(f"Address: {last_address_obj['address']}")

# turn-on the worker thread
threading.Thread(target=worker, daemon=True).start()

# listen to the on_balance_change event
iota_wallet.on_balance_change(balance_changed_event_processing)

# Use the Chrysalis Faucet to send testnet tokens to your address:
print(
    f"Fill your Address ({last_address_obj['address']['inner']}) with the Faucet: https://faucet.tanglekit.de/")
print("To see how the on_balance_change is called, please send tokens to the address in 1 min")
time.sleep(60)

# block until all tasks are done
q.join()
print('All work completed')

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.

AccountManager

constructor(storage_path (optional), password (optional), polling_interval (optional), automatic_output_consolidation(optional), output_consolidation_threshold(optional), sync_spent_outputs(optional), persist_events(optional)): AccountManager

Creates a new instance of the AccountManager.

ParamTypeDefaultDescription
[storage_path]str./storageThe path where the database file will be saved
[storage_password]strundefinedThe storage password to encrypt/decrypt accounts
[polling_interval]int30000The polling interval in milliseconds
[automatic_output_consolidation]booltrueDisables the automatic output consolidation process
[output_consolidation_threshold]int100Sets the number of outputs an address must have to trigger the automatic consolidation process
[sync_spent_outputs]booleanfalseEnables fetching spent output history on account sync
[persist_events]booleanfalseEnables event persistence
[allow_create_multiple_empty_accounts]booleancodeEnables creating accounts with latest account being empty

Note: if the storage_path is set, then the storage needs to be set too. An exception will be thrown when errors happened.

Returns The constructed AccountManager.

stop_background_sync(): void

Stops the background polling and MQTT monitoring.

set_storage_password(password): void

Sets the password used for encrypting the storage.

ParamTypeDefaultDescription
passwordstrundefinedThe storage password

set_stronghold_password(password): void

Sets the stronghold password.

ParamTypeDefaultDescription
passwordstrundefinedThe storage password

is_latest_address_unused(): bool

Determines whether all accounts have the latest address unused.

Returns true if the latest address is unused.

store_mnemonic(signer_type, mnemonic (optional)): bool

Stores a mnemonic for the given signer type. If the mnemonic is not provided, we'll generate one.

ParamTypeDefaultDescription
signer_typestrundefinedShould be Stronghold, LedgerNano, or LedgerNanoSimulator
mnemonicstrrandomly generatedThe provided mnemonic or the randomly generated one

generate_mnemonic(): str

Generates a new mnemonic.

Returns The generated mnemonic string.

verify_mnemonic(mnemonic): void

Checks is the mnemonic is valid. If a mnemonic was generated with generate_mnemonic(), the mnemonic here should match the generated.

ParamTypeDefaultDescription
mnemonicstrundefinedThe provided mnemonic

create_account(client_options): AccountInitialiser

Creat a new account.

ParamTypeDefaultDescription
client_optionsClientOptionsundefinedThe client options

Returns A constructed AccountInitialiser.

remove_account(account_id): void

Deletes an account.

ParamTypeDefaultDescription
account_idstrundefinedThe account with this id to be deleted

sync_accounts(): AccountsSynchronizer

Returns the AccountsSynchronizer to setup the process to synchronize the accounts with the Tangle.

internal_transfer(from_account_id, to_account_id, amount): WalletMessage

Transfers an amount from an account to another.

ParamTypeDefaultDescription
from_account_idstrundefinedThe source of account id in the transfering
to_account_idstrundefinedThe destination of account id in the transfering
amountintundefinedThe transfer amount

Returns The transfer's WalletMessage.

backup(destination, stronghold_password): str

Backups the storage to the given destination.

ParamTypeDefaultDescription
destinationstrundefinedThe path to the backup file
stronghold_passwordstrundefinedThe backup stronghold password

Returns The full path to the backup file.

import_accounts(source, stronghold_password): void

Imports a database file.

ParamTypeDefaultDescription
sourcestrundefinedThe path to the backup file
stronghold_passwordstrundefinedThe backup stronghold password

get_account(account_id): AccountHandle

Gets the account with the given identifier or index.

ParamTypeDefaultDescription
account_idstrundefinedThe account id, alias, index or one of its addresses

Returns the associated AccountHandle object or undefined if the account wasn't found.

get_accounts(): list[AccountHandle]

Gets all stored accounts.

Returns an list of AccountHandle.

retry(account_id, message_id): WalletMessage

Retries (promotes or reattaches) the given message.

ParamTypeDefaultDescription
account_idstrundefinedThe account id, alias, index or one of its addresses
message_idstrundefinedThe message's identifier

Returns the retried WalletMessage.

reattach(account_id, message_id): WalletMessage

Reattach the given message.

ParamTypeDefaultDescription
account_idstrundefinedThe account id, alias, index or one of its addresses
message_idstrundefinedThe message's identifier

Returns the reattached WalletMessage.

promote(account_id, message_id): WalletMessage

Promote the given message.

ParamTypeDefaultDescription
account_idstrundefinedThe account id, alias, index or one of its addresses
message_idstrundefinedThe message's identifier

Returns the promoted WalletMessage.

get_balance_change_events(count (optional), skip (optional), from_timestamp (optional))

Gets the persisted balance change events.

ParamTypeDefaultDescription
countnumber0The number of events to return (0 to return all)
skipnumber0The number of events to skip
from_timestampnumbernullFilter events that were stored after the given UTC timestamp

Event object: { accountId: string, address: string, balanceChange: { spent: number, received: number } }

get_balance_change_event_count(from_timestamp (optional))

Gets the number of persisted balance change events.

ParamTypeDefaultDescription
from_timestampnumbernullFilter events that were stored after the given UTC timestamp

get_transaction_confirmation_events(count (optional), skip (optional), from_timestamp (optional))

Gets the persisted transaction confirmation change events.

ParamTypeDefaultDescription
countnumber0The number of events to return (0 to return all)
skipnumber0The number of events to skip
from_timestampnumbernullFilter events that were stored after the given UTC timestamp

Event object: { accountId: string, message: Message, confirmed: boolean }

get_transaction_confirmation_event_count(from_timestamp (optional))

Gets the number of persisted transaction confirmation change events.

ParamTypeDefaultDescription
from_timestampnumbernullFilter events that were stored after the given UTC timestamp

get_new_transaction_events(count (optional), skip (optional), from_timestamp (optional))

Gets the persisted new transaction events.

ParamTypeDefaultDescription
countnumber0The number of events to return (0 to return all)
skipnumber0The number of events to skip
from_timestampnumbernullFilter events that were stored after the given UTC timestamp

Event object: { accountId: string, message: Message }

get_new_transaction_event_count(from_timestamp (optional))

Gets the number of persisted new transaction events.

ParamTypeDefaultDescription
from_timestampnumbernullFilter events that were stored after the given UTC timestamp

get_reattachment_events(count (optional), skip (optional), from_timestamp (optional))

Gets the persisted transaction reattachment events.

ParamTypeDefaultDescription
countnumber0The number of events to return (0 to return all)
skipnumber0The number of events to skip
from_timestampnumbernullFilter events that were stored after the given UTC timestamp

Event object: { accountId: string, message: Message }

get_reattachment_event_count(from_timestamp (optional))

Gets the number of persisted transaction reattachment events.

ParamTypeDefaultDescription
from_timestampnumbernullFilter events that were stored after the given UTC timestamp

get_broadcast_events(count (optional), skip (optional), from_timestamp (optional))

Gets the persisted transaction broadcast events.

ParamTypeDefaultDescription
countnumber0The number of events to return (0 to return all)
skipnumber0The number of events to skip
from_timestampnumbernullFilter events that were stored after the given UTC timestamp

Event object: { accountId: string, message: Message }

get_broadcast_event_count(from_timestamp (optional))

Gets the number of persisted transaction broadcast events.

ParamTypeDefaultDescription
from_timestampnumbernullFilter events that were stored after the given UTC timestamp

AccountSynchronizer

gap_limit(limit): void

Set the number of address indexes that are generated.

ParamTypeDefaultDescription
limitintundefinedThe number of address indexes that are generated

skip_persistence(): void

Skip saving new messages and addresses on the account object. The found SyncedAccount is returned on the execute call but won't be persisted on the database.

address_index(address_index): void

Set the initial address index to start syncing.

ParamTypeDefaultDescription
address_indexintundefinedThe initial address index to start syncing

execute(): SyncedAccount

Syncs account with the tangle. The account syncing process ensures that the latest metadata (balance, transactions) associated with an account is fetched from the tangle and is stored locally.

AccountsSynchronizer

gap_limit(limit): void

Set the number of address indexes that are generated on each account.

ParamTypeDefaultDescription
limitintundefinedThe number of address indexes that are generated on each account

address_index(address_index): void

Set the initial address index to start syncing on each account.

ParamTypeDefaultDescription
address_indexintundefinedThe initial address index to start syncing on each account

execute(): listSyncedAccount

Syncs the accounts with the tangle.

Transfer

constructor(amount, address, indexation (optional), remainder_value_strategy: str): Transfer

The Transfer object used in SyncedAccount

ParamTypeDefaultDescription
amountintundefinedThe amount to transfer
addressstrundefinedThe addree to send
indexationIndexationundefinedThe indexation payload
remainder_value_strategystrundefinedShould be ReuseAddress or ChangeAddress

SyncedAccount

The result of a sync operation on an Account.

account_handle(): AccountHandle

Get the AccountHandle of this account

Returns the AccountHandle.

deposit_address(): Address

Get the deposit_address of this account.

Returns the Address.

messages(): WalletMessage

Get the messages of this account.

Returns the WalletMessage.

addresses()

Get the addresses of this account.

Returns the list of WalletMessage.

AccountHandle

id(): str

Returns the account ID.

signer_type(): str

Returns the singer type of this account.

index(): int

Returns the account index.

alias(): str

Returns the account alias.

created_at(): int

Returns the created UNIX timestamp.

last_synced_at(): int or None (it did not be synced before)

Returns the last synced UNIX timestamp.

client_options(): ClientOptions

Returns the client options of this account.

bech32_hrp(): str

Returns the Bech32 HRP string.

sync(): AccountSynchronizer

Returns the AccountSynchronizer to setup the process to synchronize this account with the Tangle.

transfer(transfer_obj): WalletMessage

Transfer tokens.

ParamTypeDefaultDescription
transfer_objTransferundefinedThe transfer we want to make

Returns the WalletMessage which makes the transfering.

retry(message_id): WalletMessage

Retries (promotes or reattaches) the given message.

ParamTypeDefaultDescription
message_idstrundefinedThe message's identifier

Returns the retried WalletMessage.

reattach(message_id): WalletMessage

Reattach the given message.

ParamTypeDefaultDescription
message_idstrundefinedThe message's identifier

Returns the reattached WalletMessage.

promote(message_id): WalletMessage

Promote the given message.

ParamTypeDefaultDescription
message_idstrundefinedThe message's identifier

Returns the promoted WalletMessage.

consolidate_outputs(): listWalletMessage

Consolidates the account addresses outputs.

Returns the list of generated WalletMessage.

generate_address(): Address

Returns a new unused address and links it to this account.

get_unused_address(): Address

Synchronizes the account addresses with the Tangle and returns the latest address in the account, which is an address without balance.

Returns the latest address in the account.

is_latest_address_unused(): bool

Syncs the latest address with the Tangle and determines whether it's unused or not. An unused address is an address without balance and associated message history. Note that such address might have been used in the past, because the message history might have been pruned by the node.

Returns true if the latest address in the account is unused.

latest_address(): Address

Returns the most recent address of the account.

addresses(): list[Address]

Returns a list of Address in the account.

balance(): AccountBalance

Gets the account balance information.

Returns the AccountBalance in this account.

get_node_info(url (optional)): NodeInfoWrapper

Gets information about the node.

ParamTypeDefaultDescription
urlstrundefinedThe node url

Returns the NodeInfoWrapper

set_alias(alias): void

Updates the account alias.

ParamTypeDefaultDescription
aliasstrundefinedThe account alias to set

set_client_options(options): void

Updates the account's client options.

ParamTypeDefaultDescription
optionsClientOptionsundefinedThe client options to set

message_count(message_type (optional)): int

Returns the number of messages associated with the account.

ParamTypeDefaultDescription
message_typestrundefinedShould be Received, Sent, Failed, Unconfirmed, or Value

list_messages(count, from, message_type (optional)): list(WalletMessage)

Get the list of messages of this account.

ParamTypeDefaultDescription
countintundefinedThe count of the messages to get
fromintundefinedThe iniital address index
message_typestrundefinedShould be Received, Sent, Failed, Unconfirmed, or Value

list_spent_addresses(): list[Address]

Returns the list of spent Address in the account.

get_message(message_id): WalletMessage](#walletmessage) (optional)

Get the WalletMessage by the message identifier in the account if it exists.

AccountInitialiser

signer_type(signer_type): void

Sets the account type.

ParamTypeDefaultDescription
signer_typestrsigner_typeShould be Stronghold, LedgerNano, or LedgerNanoSimulator

alias(alias): void

Defines the account alias. If not defined, we'll generate one.

ParamTypeDefaultDescription
aliasstrundefinedThe account alias

created_at(created_at): void

Time of account creation.

ParamTypeDefaultDescription
created_atu64undefinedThe account creation time

messages(messages): void

Messages associated with the seed. The account can be initialised with locally stored messages.

ParamTypeDefaultDescription
messageslist(WalletMessage)undefinedThe locally stored messages

addresses(addresses): list(WalletAddress)

Address history associated with the seed. The account can be initialised with locally stored address history.

ParamTypeDefaultDescription
addresseslist(WalletAddress)undefinedThe historical addresses

skip_persistence(): void

Skips storing the account to the database.

initialise(): AccountHandle

Initialises the account.

Returns the initilized AccountHandle

Event Listeners

on_balance_change(callback): list[int]

Listen to balance changes.

ParamTypeDefaultDescription
[callback]functionundefinedThe callback function

Returns the event id as list[int].

remove_balance_change_listener(list[int]): void

Removes the balance change listener associated with the given identifier.

ParamTypeDefaultDescription
[id]list[int]undefinedThe event id

on_new_transaction(callback): list[int]

Listen to new messages.

ParamTypeDefaultDescription
[callback]functionundefinedThe callback function

Returns the event id as list[int].

remove_new_transaction_listener(list[int]): void

Removes the new transaction listener associated with the given identifier.

ParamTypeDefaultDescription
[id]list[int]undefinedThe event id

on_confirmation_state_change(callback): list[int]

Listen to transaction confirmation state change.

ParamTypeDefaultDescription
[callback]functionundefinedThe callback function

Returns the event id as list[int].

remove_confirmation_state_change_listener(list[int]): void

Removes the new transaction listener associated with the given identifier.

ParamTypeDefaultDescription
[id]list[int]undefinedThe event id

on_reattachment(callback): list[int]

Listen to transaction reattachment.

ParamTypeDefaultDescription
[callback]functionundefinedThe callback function

Returns the event id as list[int].

remove_reattachment_listener(list[int]): void

Removes the reattachment listener associated with the given identifier.

ParamTypeDefaultDescription
[id]list[int]undefinedThe event id

on_broadcast(callback): list[int]

Listen to transaction broadcast.

ParamTypeDefaultDescription
[callback]functionundefinedThe callback function

Returns the event id as list[int].

remove_broadcast_listener(list[int]): void

Removes the broadcast listener associated with the given identifier.

ParamTypeDefaultDescription
[id]list[int]undefinedThe event id

on_error(callback): list[int]

Listen to errors.

ParamTypeDefaultDescription
[callback]functionundefinedThe callback function

Returns the event id as list[int].

remove_error_listener(list[int]): void

Removes the error listener associated with the given identifier.

ParamTypeDefaultDescription
[id]list[int]undefinedThe event id

on_stronghold_status_change(callback): list[int]

Listen to stronghold status change events.

ParamTypeDefaultDescription
[callback]functionundefinedThe callback function

Returns the event id as list[int].

remove_stronghold_status_change_listener(list[int]): void

Removes the stronghold status change listener associated with the given identifier.

ParamTypeDefaultDescription
[id]list[int]undefinedThe event id

on_transfer_progress(callback): list[int]

Listen to transfer progress events.

ParamTypeDefaultDescription
[callback]functionundefinedThe callback function

Returns the event id as list[int].

remove_transfer_progress_listener(list[int]): void

Removes the transfer progress listener associated with the given identifier.

ParamTypeDefaultDescription
[id]list[int]undefinedThe event id

on_migration_progress(callback): list[int]

Listen to migration progress events.

ParamTypeDefaultDescription
[callback]functionundefinedThe callback function

Returns the event id as list[int].

remove_migration_progress_listener(list[int]): void

Removes the migration progress listener associated with the given identifier.

ParamTypeDefaultDescription
[id]list[int]undefinedThe event id

WalletAddress

A dict with the following key/value pairs.

wallet_address = {
    'address': str,
    'balance': int,
    'key_index': int,
    'internal': bool,
    'outputs': dict[(string, WalletAddressOutput)],
}

Please refer to WalletAddressOutput for the details of this type.

WalletAddressOutput

A dict with the following key/value pairs.

wallet_address_output = {
    'transaction_id': str,
    'message_id': str,
    'index': int,
    'amount': int,
    'is_spent': bool,
    'address': str,
    'kind': str,
}
}

Address

A dict with the following key/value pairs.

address = {
    'address': AddressWrapper,
    'balance': int,
    'key_index': int,
    'internal': bool,
    'outputs': list[AddressOutput],
}

Please refer to AddressWrapper and AddressOutput for the details of this type.

AddressWrapper

A dict with the following key/value pairs.

address_wrapper = {
    'inner': str
}

AddressOutput

A dict with the following key/value pairs.

address_output = {
    'transaction_id': str,
    'message_id': str,
    'index': int,
    'amount': int,
    'is_spent': bool,
    'address': AddressWrapper,
}

Please refer to AddressWrapper for the details of this type.

AccountBalance

A dict with the following key/value pairs.

account_balance = {
    'total': int,
    'available': int,
    'incoming': int,
    'outgoing': int,
}

ClientOptions

A dict with the following key/value pairs.

client_options = {
    'nodes': list[[Node](#node)] (optional),
    'node_pool_urls': list[str] (optional),
    'network': str (optional),
    'mqtt_broker_options': [BrokerOptions](#brokeroptions) (optional),
    'local_pow': bool (optional),
    'node_sync_interval': int (optional), # in milliseconds
    'node_sync_enabled': bool (optional),
    'request_timeout': int (optional), # in milliseconds
    'api_timeout': {
        'GetTips': int (optional) # in milliseconds
        'PostMessage': int (optional) # in milliseconds
        'GetOutput': int (optional) # in milliseconds
    } (optional)
}

Note that this message object in wallet.rs is not the same as the message object in iota.rs.

Node

A dict with the following key/value pairs.

node = {
    'url': string,
    'auth': [NodeAuth](#nodeauth) (optional),
}

NodeAuth

A dict with the following key/value pairs.

node = {
    'username': string,
    'password': string,
}

BrokerOptions

A dict with the following key/value pairs.

broker_options = {
    'automatic_disconnect': bool (optional),
    'timeout': int (optional)
}

WalletMessage

A dict with the following key/value pairs.

wallet_message = {
    'id': str,
    'version': u64,
    'parents': list[str],
    'payload_length': int,
    'payload': Payload,
    'timestamp': int,
    'nonce': int,
    'confirmed': bool (optional),
    'broadcasted': bool
}

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': {
        regular: 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[bytes],
}

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': bytes,
    'public_keys': bytes
}

Indexation

A dict with the following key/value pairs.

indexation = {
    'index': bytes,
    'data': bytes
}

RegularEssenceEssence

A dict with the following key/value pairs.

transaction_regular_essence = {
    'inputs': list[Input],
    'outputs': list[Output],
    'payload': Payload (optional),
    'internal': bool,
    'incoming': bool,
    'value': int,
    'remainder_value': int,
}

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': bytes,
    'public_key': bytes
}

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.

Wallet Library Spec

Table of Contents

Introduction

The wallet library is a stateful package with a standardised interface to build applications with IOTA value transactions. The package will be compatible with different platforms such as web, desktop and mobile.

The package introduces the concept of an account. An account is a reference or a label to a seed. An account has certain properties such as addresses and messages. An account has various possible behaviours, including moving funds, looking for new messages, and making copies of message histories. An account should also be able to provide a degree of financial privacy and this should not incur any overhead.

A similar package was previously developed but this becomes obsolete with the introduction of Ed25519 signatures. The previous account package was limited to a single account. As an improvement, the (new) package will be able to manage multiple accounts.

To summarize, the main motivation behind this package is to offer a simplified (stateful) approach to handle IOTA payments.

Considerations

  • Seeds should be stored and managed separately in a secure enclave and should never leave the secure environment. Secure enclaves include software enclaves such as IOTA’s Rust-based Stronghold library or hardware enclaves such as a Ledger Nano;
  • The secure enclave should have the ability to generate addresses and sign messages upon receipt of a message, and return the output in a message. If the secure enclave is initialised with a pre-generated seed, the sender process should immediately remove the seed traces from memory.

Naming Conventions

The primary language is Rust. Therefore, standard Rust naming conventions are followed. All interfaces (types) use CamelCase while all function and variable names use snake_case.

Interfaces

AccountConfiguration

Account configuration or initialization object. It should support parameters accepted by high level client libraries.

Property Required Type Description
seed string BIP-39 mnemonic. When importing an account from Stronghold backup, the seed will not be required.
id string SHA-256 hash of the first address on the seed (m/44'/0'/0'/0'/0'). Required for referencing a seed in Stronghold. The id should be provided by Stronghold.
index number Account index in BIP-44 derivation path.
alias string Account name. If not provided, Account + ${index} should be used. When importing an account from Stronghold backup, the alias will be required from Stronghold.
pow ‘local’ | ‘remote’ Proof of work settings. Defaults to ‘local’.
  • ‘local’: Should be performed on device;
  • ‘remote’: Should be performed on the node.
nodes node[] A list of nodes to connect to.
quorum_size number If multiple nodes are provided, quorum size determines the number of nodes to query to check for quorum.
quorum_threshold number Minimum number of nodes from the quorum pool that need to agree to consider a result true.
network ‘mainnet’ | ‘devnet’ | ‘comnet’ IOTA public network.
type ‘default’ | ‘ledger’ Account type. Required for differentiating ledger vs non-ledger accounts.
provider string Node URL.
created_at Date Time of account creation.
messages Message[] Messages associated with account. Accounts can be initialised with locally stored messages.
addresses Address[] Address history associated with the account. Accounts can be initialised with locally stored address history.

AccountObject

Property Required Type Description
id string SHA-256 hash of the first address on the seed (m/44'/0'/0'/0/0). Required for referencing a seed in Stronghold.
alias string Account name.
created_at number Account creation time.
last_synced_at string Time the account was last synced with the Tangle.
sync() function Syncs account with the Tangle.
reattach() function Reattaches unconfirmed transaction to the Tangle.
total_balance() function Gets total account balance.
available_balance() function Gets available account balance.
set_alias() function Updates account name.
list_messages() function Gets messages.
list_received_messages() function Gets all received messages.
list_sent_messages() function Gets all sent messages.
list_failed_messages() function Gets all failed messages.
list_unconfirmed_messages() function Gets all unconfirmed messages.
get_message() function Gets message for provided id.
list_addresses() function Gets all addresses.
list_unspent_addresses() function Gets all unspent input addresses.
generate_address() function Gets the latest unused address.

SyncedAccountObject

Property Required Type Description
deposit_address Address Deposit address. Only exposed on successful completion of account syncing process.
send() function Send transaction method. Only exposed on successful completion of account syncing process.
retry() function Rebroadcasts failed transaction. Only exposed on successful completion of account syncing process.

AccountManagerObject

Property Required Type Description
accounts Account[] Account objects.
add_account() function Adds a new account.
remove_account() function Removes an account.
sync_accounts() function Syncs all stored accounts with the Tangle.
move() function Inititates an internal transaction between accounts.
backup() function Creates a backup to a provided destination.
import_accounts() function Imports backed up accounts.
get_account() function Returns the account associated with the provided address.
reattach() function Reattaches an unconfirmed transaction.

Address

Useful reference for address management in Hierarchical Deterministic (HD) wallets.

Property Required Type Description
address string Address (Bech32) string.
balance number Address balance.
index number Address index.
internal boolean Determines if an address is a public or an internal (change) address. See the concept of chain node for more details.
checksum string Address checksum.

Node

Property Required Type Description
url string Node URL.
pow boolean Determines if the node accepts proof of work.
username string Node username. Only required if node requires authorisation.
password string Node password. Only required if node requires authorisation.
network ‘mainnet’ | ‘devnet’ | ‘comnet’ IOTA public network name.

Timestamp

Property Required Type Description
format(type: string):string function Transaction timestamp in various formats. For example: MM-DD-YYYY, DD MM YYYY hh:mm:ss.

Transfer

Transfer object required for creating a transaction. It allows end-users to specify the transaction amount and recipient address.

Note: Currently, it is not possible to send multiple payloads as part of the message. That is why tag property is omitted from this interface. See this for details.

Property Required Type Description
amount number Transfer amount.
address string Transfer address.
indexation_key Indexation Payload (Optional) Indexation payload.

Value

Property Required Type Description
with_denomination():string function Transaction amount with unit.
without_denomination():number function Transaction amount without unit.

Input

Property Required Type Description
type number Input type. Defaults to 0.
id string BLAKE2b-256 hash of the transaction.
output_index number Index of the output on the referenced transaction.

OutputAddress

Property Required Type Description
type number Set to value 0 to denote an Ed25519 address.
address string If type is set to 0, it should contain an Ed25519 address.

Output

Property Required Type Description
type number Output type. Defaults to 0.
address OutputAddress Output address.
amount number Amount of tokens to deposit.

UnsignedDataPayload

Property Required Type Description
type number Set to 2 to denote a unsigned data payload.
data string Data of unsigned payload.

SignedDataPayload

Property Required Type Description
type number Set to 3 to denote a signed data payload.
data string Data of signed data payload.
public_key string Ed25519 public key used to verify the signature.
signature string Signature of signing data.

IndexationPayload

Property Required Type Description
index string Indexation key.
data string Indexation data.

UnsignedTransaction

Property Required Type Description
type number Transaction type. Defaults to 0.
inputs_count number Amount of inputs proceeding.
inputs Input[] Transaction inputs.
outputs_count number Amount of outputs proceeding.
outputs Output[] Output address.
payload_length number Length of optional payload.
payload UnsignedDataPayload | SignedDataPayload | IndexationPayload Payload containing data. As multiple payloads are not yet supported, only unsigned data payload should be used.

Ed25519Signature

Property Required Type Description
type number Set to value 1 to denote an Ed25519 signature.
public_key number Public key of the Ed25519 keypair which is used to verify the signature.
signature string Signature signing the serialized unsigned transaction.

SignatureUnblockBlock

Property Required Type Description
type number Set to value 0 to denote a signature unlock block.
signature Ed25519Signature An unlock block containing signature(s) unlocking input(s).

ReferenceUnblockBlock

Property Required Type Description
type number Set to value 1 to denote a reference unlock block.
reference number Index of a previous unlock block.

SignedTransactionPayload

Property Required Type Description
type number Payload type. Defaults to `0`.
transaction UnsignedTransaction Essence data making up a transaction by defining its inputs and outputs and an optional payload.
unblock_blocks_count number Number of inputs specifed.
unblock_blocks SignatureUnblockBlock | ReferenceUnblockBlock Holds the unlock blocks unlocking inputs within an Unsigned Transaction

Message

Property Required Type Description
version number Message version. Defaults to `1`.
parents string[] Message ids this message references.
payload_length number Length of the payload.
payload SignedTransactionPayload | UnsignedDataPayload | SignedDataPayload Transaction amount (exposed as a custom type with additional methods).
timestamp Timestamp Transaction timestamp (exposed as a custom type with additional methods).
nonce string Transaction nonce.
confirmed boolean Determines if the transaction is confirmed.
broadcasted boolean Determines if the transaction was broadcasted to the network. Will be true in the following scenarios:
  • If the transaction was fetched from the network;
  • If the transaction was successfully broadcasted from the client itself.
Note: This property may only be required for clients with persistent storage.
incoming boolean Determines if the message is an incoming transaction or not.
value number Message transfer value.

StorageAdapter

Property Required Type Description
get(key: string):Account function Gets the account object for provided account name or id.
getAll(): Account[] function Gets all account objects from storage.
set(key: string, payload: string):void function Stores account in storage.
remove(key: string): void function Removes account from storage.

Storage

Note: Using Stronghold for storage is currently under research/development.

Multiple storage options should be considered for managing data that requires persistence. For wallet basic metadata, such as user settings or theming options, a simple key-value storage could be leveraged. For transactions and address data management a relational database such as SQLite can be used. What follows is an Entity Relationship (ERD) diagram that shows the logical representation of the data. An account is the basic entity in this database design. It has a one-to-many relationship with addresses i.e. an account could have multiple addresses but also an address can only belong to a single account. An account has a many-to-many relationship with transactions i.e. an account could have multiple transactions but it’s possible that a transaction belongs to multiple accounts. To accommodate for that, an additional table is added that stores account ids against transaction ids (hashes).

A storage adapter is required by the Rust layer because the storage operations (read/write) will be (mostly) done from that layer. A generic storage adapter is defined here.

Entity Relationship Diagram

Storage Adapter

The package should have a default opinionated storage mechanism but should also provide the ability for users to override the storage by specifying an adapter. As a default option, a relational database such as SQLite can be used.

See storage adapter for adapter interface.

Account

API

Initialisation

Initialises account There are several scenarios in which an account can be initialised:

  • Seed generated outside the Stronghold: In this case, the account should be initialised with a seed. It should communicate with the Stronghold using its “importAccount” method and should expect an “id” as a response;
  • Seed generated inside the Stronghold: In this case, the account should be initialised without a seed. It should communicate with the Stronghold using its “createAccount” method and should expect an “id” in response;
  • Importing accounts from Stronghold backup: In this case, the account should receive all initialisation properties from the Stronghold. Note that during backup, these configuration settings should be passed to the Stronghold. See import_accounts().

The following should be considered when initialising an account:

  • An account should never be initialised directly. The only way an account can be initialized is through the add_account() method;
  • An account should always be initialised after a successful response from the Stronghold. If the Stronghold fails to create an account, the account initialisation should error out. If the Stronghold successfully creates an account, the account should be stored in the persistent storage. Upon a successful store operation, the user should be returned an account object;
  • If a provider property is not passed, a random node should be selected from the nodes property;
  • If a type property is not passed, "default” should be used as an account type;
  • quorum_size and quorum_threshold should be validated. For example, quorum_size should not be greater than the number of nodes provided by the user.
  • The nodes property should validate and remove duplicate node URLs;
  • All the properties of the returned account object should be read-only. It should not be possible to manipulate them directly.
Parameters
Name Required Type Description
config AccountConfig Initialisation method receives a configuration object.
Returns
Name Type Description
account Account Account instance.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

sync_addresses()

Syncs addresses with the Tangle. The method should ensure that the wallet's local state contains all used addresses and an unused address.

The following should be considered when implementing this method:

  • The updated address history should not be written down in the database/persistent storage. Instead the method should only return the updated address history (with transaction hashes). This will ensure that there are no partial writes to the database;
  • To sync addresses for an account from scratch, index = 0 and gap_limit = 10 should be provided;
  • To sync addresses from the latest address, index = latest address index and gap_limit = 1 should be provided.
Parameters
Name Required Type Description
index number Address index. By default the length of addresses stored for this account should be used as an index.
gap_limit number Number of address indexes that are generated.
Returns
Name Type Description
addresses Address[] Address history upto latest unused address.
ids string[] Message ids associated with the addresses.
Additional Information
Name Description
Access modifiers Private
Errors List of error messages [TBD]
Required client library methods

sync_messages()

Syncs messages with the Tangle. The method should ensure that the wallet's local state has all messages associated with the address history.

The following should be considered when implementing this method:

  • The updated message history should not be written down in the database/persistent storage. Instead the method should only return the updated message history (with message ids);
  • This method should check if there are any local messages (with “broadcasted: false”) matching the messages fetched from the network. If there are such messages, their “broadcasted” property should be set to true;
  • For newly-confirmed messages, the method should ensure that it updates the “confirmed” property of all its reattachments.
Parameters
Name Required Type Description
ids string[] Message ids. New message ids should be calculated by running a difference of local message ids with latest message ids on the Tangle.
Returns
Name Type Description
messages Message[] Message history
Additional Information
Name Description
Access modifiers Private
Errors List of error messages [TBD]
Required client library methods

select_inputs()

Selects input addresses for funds transfer.

Note: This method should only be used internally by send(). Also, the input selection method should ensure that the recipient address doesn’t match any of the selected inputs or the remainder address.

See Input Selection Process for implementation details.

Parameters
Name Required Type Description
threshold number Amount user wants to spend.
address string Recipient address.
Returns
Name Type Description
inputs Address[] Selected Inputs
remainder Address Remainder address object. Empty or null if there’s no need for a remainder
Additional Information
Name Description
Access modifiers Private
Errors List of error messages [TBD]
Required client library methods None

send()

Sends a message to the Tangle.

Note: This method should only be exposed as a successful response from sync().

Currently, it is not possible to send multiple payloads.

The process for sending a value transaction:

  • Ensure amount is not set to zero;
  • Ensure amount does not exceed the total balance;
  • Ensure recipient address has correct checksum;
  • Validate data property semantics and size;
  • Select inputs by using select_inputs();
  • Pass the seralized unsigned transaction to the Stronghold for signing with its “signTransaction” method;
  • Perform proof-of-work. pow property in the account object should determine if the proof of work should be offloaded;
  • Once proof-of-work is successfully performed, the message should be validated and stored in the persistent storage;
  • After persisting the transaction, it should be broadcasted to the network;
  • In the event of a broadcast error, there should be (three) attempts for automatic rebroadcasting. If all attempts fail, the send process should terminate and it should be left to the user to retry the failed message. For failed messages, the “broadcasted” property in the transaction objects should be set to false.
Parameters
Name Required Type Description
transfer Transfer Transfer object.
Returns
Name Type Description
message Message Newly made message.
Additional Information
Name Description
Access modifiers Private
Errors List of error messages [TBD]
Required client library methods

retry()

Rebroadcasts failed message.

Note: This method should only be exposed as a successful response from sync().

The process for retrying a failed message:

  • Get message by using get_message();
  • Rebroadcast message;
  • Update account in persistent storage.
Parameters
Name Required Type Description
id string Message id
Returns
Name Type Description
message Message Newly made message.
Additional Information
Name Description
Access modifiers Private
Errors List of error messages [TBD]
Required client library methods

sync()

Syncs account with the Tangle. The account syncing process should ensure that the latest metadata (balance, messages) associated with an account is fetched from the Tangle and stored locally.
Note that it is a proposed design decision to enforce account syncing before every send. An alternative way would be to have the send method always exposed and internally ensuring that the account is synced before every message.

The process for account syncing:_

  • Sync addresses using sync_addresses();
  • Sync messages using sync_messages();
  • Store updated addresses and messages information in persistent storage (if not explicitly set otherwise by the user).
Parameters
Name Required Type Description
index number Address index. By default the number of addresses stored for this account should be used as an index.
gap_limit number Number of address indexes that are generated.
skip_persistence boolean Skips write to the database. This will be useful if a user wants to scan the Tangle for further addresses to find balance. See the snapshot transition feature provided by Trinity.
Returns
Name Type Description
account SyncedAccount Synced account object.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods

reattach()

Reattaches unconfirmed message to the Tangle. The following should be considered when implementing this method:

  • Only an unconfirmed message can be reattached. The method should validate the confirmation state of the provided transaction. If a confirmed message id is provided, the method should error out;
  • The method should also validate if reattachment is necessary, by checking if the message falls below max depth. The criteria for whether the message has fallen below max depth is determined through its timestamp. If 11 minutes have passed since the timestamp of the most recent (reattachment), the message can be be reattached. See this implementation for reference;
  • Once reattached, the message should be stored in the persistent storage;
  • If the message was reattached via polling, a reattachment event should be emitted to notify all subscribers.
Parameters
Name Required Type Description
id string Message id.
Returns
Name Type Description
message Message Newly reattached message.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods

total_balance()

Gets total account balance.

Total balance should be read directly from the local storage. To read the latest account balance from the network, sync() should be used first.

Returns
Type Description
Value Account total balance.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

available_balance()

Gets available account balance. Available account balance is the amount users are allowed to spend. It should subtract the pending balance from the total balance.

For example, if a user with 50i total account balance has made a transaction spending 30i, the available balance should be 20i (i.e. 50i - 30i).

Available balance should be read directly from the local storage. To read the latest account balance from the network, sync() should be used first.

Returns
Type Description
Value Account available balance.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

set_alias()

Updates account name

Parameters
Name Required Type Description
alias string New account name.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

list_messages()

Gets messages. Messages should be read directly from the local storage. To ensure the local database is updated with the latest messages, sync() should be used first.

Parameters
Name Required Type Description
count number Number of (most recent) messages.
from number Subset of messages. For example: count = 10, from = 5, it should return ten messages skipping the most recent five messages.
Returns
Name Type Description
messages Message[] All messages.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

list_received_messages()

Gets all received messages.

Messages should be read directly from the local storage. To ensure the local database is updated with the latest messages, sync() should be used first.

Parameters
Name Required Type Description
count number Number of (most recent) received messages.
from number Subset of received messages.
Returns
Name Type Description
messages Message[] All received messages.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

list_sent_messages()

Gets all sent messages.

Messages should be directly read from the local storage. To ensure the local database is updated with the latest messages, sync() should be used first.

Parameters
Name Required Type Description
count number Number of (most recent) sent messages.
from number Subset of sent messages.
Returns
Name Type Description
messages Message[] All sent messages.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

list_failed_messages()

Gets all failed (broadcasted = false) messages. Messages should be read directly from the local storage.

Returns
Name Type Description
Name Required Type Description
count number Number of (most recent) failed messages.
from number Subset of failed messages.
Returns
messages Message[] All failed messages.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

list_unconfirmed_messages()

Gets all unconfirmed (confirmed = false) messages. Messages should be read directly from the local storage.

Returns
Name Required Type Description
count number Number of (most recent) unconfirmed messages.
from number Subset of unconfirmed messages.
Returns
Name Type Description
messages Message[] All unconfirmed messages.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

get_message()

Gets message for provided id.

Messages should be read directly from the local storage. To ensure the local database is updated with the latest messages, sync() should be used first.

Parameters
Name Required Type Description
id string Message id.
Returns
Name Type Description
message Message Message object.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

list_addresses()

Gets all addresses.

Returns
Name Type Description
addresses Address[] All addresses.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

list_unspent_addresses()

Gets all unspent input addresses

Returns
Name Type Description
addresses Address[] All unspent input addresses.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

generate_address()

Gets the latest unused address.

Returns
Name Type Description
address Address A new address object.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

Account Manager

An account manager class should be publicly available for users. With the account manager, the user can create, update, delete or manage multiple accounts. The implementation details of a specific account should be abstracted away using this account manager wrapper.

API

Initialisation

Initialises the account manager. Account manager initialisation should validate the adapter object semantics and should return an instance of the account manager.

Parameters
Name Required Type Description
adapter Adapter Initialisation method receives an optional storage adapter.
Returns
Name Type Description
manager AccountManager Account manager instance.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

add_account()

Adds new account

See account initialisation for detailed implementation guidelines.

Parameters
Name Required Type Description
config AccountConfig Account configuration object.
Returns
Name Type Description
accounts Account Newly created account.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

remove_account()

Removes an account.

The following should be considered when removing an account:

  • An account should first be removed from the Stronghold using its “removeAccount” method;
  • Once the account references have been removed from the Stronghold, the account should be deleted from the persistent storage.
Parameters
Name Required Type Description
identifier { address: <string> } | { alias: <string> } |

{ id: <number> } |

{ index: <number }

Identifier. Could be one of address, alias, id or index.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

sync_accounts()

Syncs all stored accounts with the Tangle. Syncing should get the latest balance for all accounts and should find any new messages associated with the stored account.

See Accounts Syncing Process.

Returns
Name Type Description
account SyncedAccount[] Synced accounts.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods

move()

Moves funds from one account to another. This method should leverage the send() method from the sender account and should initiate a message to the receiver account.

Parameters
Name Required Type Description
from { address: <string> } | \ { alias: <string> } |

{ id: <number> } |

{ index: <number }

Identifier. Could be one of address, alias, id or index.
to { address: <string> } | \ { alias: <string> } |

{ id: <number> } |

{ index: <number }

Identifier. Could be one of address, alias, id or index.
amount number Transaction amount.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

backup()

Safely creates a backup of the accounts to a destination. The file could simply be JSON containing the address & transaction histories for accounts.

This method should provide the Stronghold instance with the metadata of all accounts.

Parameters
Name Required Type Description
destination string Path where the backup should be stored.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

import_accounts

Import (backed up) accounts.

Implementation details are not finalised.

Parameters
Name Required Type Description
accounts Account[] Account object.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

get_account()

Returns the account associated with the provided identifier.

Parameters
Name Required Type Description
identifier { address: <string> } | \ { alias: <string> } |

{ id: <number> } |

{ index: <number }

Identifier. Could be one of address, alias, id or index.
Returns
Name Type Description
account Account Account associated with identifier.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

reattach()

Reattaches an unconfirmed message.

See reattach() method for implementation details. This method is a wrapper method provided for convenience. A user could directly access the reattach() method on an account object.

Parameters
Name Required Type Description
identifier { address: <string> } | \ { alias: <string> } |

{ id: <number> } |

{ index: <number }

Identifier. Could be one of address, alias, id or index.
id string Message id.
Returns
Name Type Description
message Message Newly reattached message.
Additional Information
Name Description
Access modifiers Public
Errors List of error messages [TBD]
Required client library methods None

Events

Events are categorised as the following:

  1. Reactive messages emitted from the node software whenever the state on the node changes. For example, emitting new messages received by the node. Clients (Wallet) can subscribe to these events to get notified if any relevant change occurs on the node. See example.

  2. Messages emitted from the wallet library whenever there are any important state changes. Note that in cases where a user triggered action leads to a state change, the messages would not be emitted. For example, if a user explicitly triggers a sync() action leading to a state change, an explicit event is not necessary.

Category 1 events

On every update sent from the node software via an event, the wallet library should update internal (persistent) storage and should also emit events via category 2.

Monitor address for balance changes

Event Returned Data
<Address : Balance>
  • Index 1: Address
  • Index 2: New balance on the address

Monitor address for new messages

Event Returned Data
<Address : Message>
  • Index 1: Address
  • Index 2: Id of the new message

Monitor message for confirmation state

Event Returned Data
<MessageId>
  • Index 1: Message Id
  • Index 2: Confirmation state

Category 2 events

They could be triggered via events from category 1 or through polling.

Monitor for balance changes

Event Returned Data
balances [{ accountId, address, balance }]

Monitor for new messages

Event Returned Data
messages [{ accountId, messages }]

Monitor for confirmation state

Event Returned Data
confirmations [{ accountId, messages }]

Monitor for reattachments

Event Returned Data
reattachments [{ accountId, messages }]

Monitor for broadcasts

Event Returned Data
broadcasts [{ accountId, messages }]

Monitor for errors

Event Returned Data
error { type, error }

Privacy

To maintain the financial privacy of wallet users, the application/wallet should enforce strategies that will guarantee a certain level of anonymity. These strategies should be followed:

  • The wallet should only use a single address per message i.e. if an address is already used in a message, it should not be used as a deposit address and instead a new address should be generated;
  • The input selection strategy should expose as little information as possible. See input selection for details.

Some other privacy enhancing techniques can be found here.

Input Selection

The goal of input selection is to avoid remainder addresses. The remainder output leaves a clue to the user's future spends. There should be a standardised input selection strategy used by the wallet. The steps for input selection are as follows:

  1. Try to select an input with an exact match. For example, if a user intends to spend X iotas, the wallet should try to find an address that has X iotas as available balance;
  2. If the previous step fails, try to select a combination of inputs that satisfy the amount leaving no change. For example, consider a scenario where the wallet with account name Foo has three addresses A, B and C with 10, 20 and 50 IOTA respectively. If a user intends to spend X = 30 IOTA, the application should search for an exact match (step no. 1). Clearly, in this case, no address balance matches X, therefore, the wallet should search for a subset of addresses with an accumulated balance of X. In this scenario, A and B;
  3. If both the previous steps fail, the wallet should select a combination of inputs that produce the minimum remainder.

A reference implementation of different input selection algorithms for Bitcoin can be found here.

Also, the implementation of step no. 2 is quite similar to the subset sum problem. Given a total and a set of non-negative numbers (inputs), we need to determine if there is a subset which adds up to the total.

Account Syncing Process

The account syncing process should detect all (used) accounts on a seed with their corresponding address and message history. Once, all accounts and histories are detected, the wallet should accumulate total balance. The syncing process should work as follows:

  1. Start with the account at index 0, generate gap limit number of addresses, default to 20;
  2. Check for messages and balances on the generated addresses;
  3. If there are no messages and balances of 0 on all addresses, the process for generating addresses and finding messages and balances should be stopped;
  4. If any address has balance or associated messages, generate gap limit number of addresses from the index of the last address with messages or balance;
  5. Steps (1-4) should also be peformed for account at index 1. The general idea is that n + 1 accounts should be checked if account n has any messages or balance.

Treat accounts like addresses. Only allow 1 latest unused account.

Scenario 1: Wallet message and address history stored in Stronghold backup

  • Start syncing from the latest address index stored in the Stronghold backup
  • Also provide a “Full sync” function to resync from index 0 across all accounts
  • Also provide “Find more history” function to sync a further 50 addresses

Scenario 2: User has no backup file

  • Start syncing from account 0 address 0

Polling

A background process that automatically performs several tasks periodically should be part of the wallet library. The goal of the background process is to perform the following tasks:

  • Sync accounts: The background process should sync all accounts with the network. This should be done using the sync_accounts() method. If new messages are detected, a messages event should be used to notify all subscribers. If new balances are detected, a balances event should be used to notify all subscribers. If new confirmations are detected, a confirmations event should be used to notify all subscribers;

Note that if there are multiple failed messages, priority should be given to the old ones.

  • Reattach: The background process should check if there are any (unconfirmed) messages that require reattachments. The detailed implementation flow for reattachment can be found here.

The following should be considered for implementation:

  • Invoking a task explicitly that is already being performed through polling should lead to an error. For example, if the polling process is already syncing accounts and a user explicitly calls sync(), it should throw an error;
  • Errors during the polling process should be communicated to subscribers via error events.

The background process should have a recurring checker that sequentially performs all the above tasks. The implementation should ensure that future tasks can easily be added to the background process. For reference, see Trinity’s implementation of the poll component.

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: