Skip to main content

Stardust Exchange Integration Guide

This guide explains how to integrate the IOTA SDK into your exchange, custody solution, or product.

Features of the IOTA SDK:

  • Secure seed management.
  • Account management with multiple accounts and multiple addresses.
  • Confirmation monitoring.
  • Deposit address monitoring.
  • Backup and restore functionality.
Stardust Update

The Stardust update is a set of protocol changes that were first deployed to the Shimmer network. See What is Stardust? for an overview of its features or TIPs flagged Stardust for a deeper understanding of the protocol changes.

How Does It Work?

The IOTA SDK is a stateful library which provides an API for developers to build value transaction applications. It offers abstractions to handle payments, and can optionally interact with Stronghold for seed handling, seed storage, and state backup.

You can use the following examples as a guide to implement the multi-account model.

If you were already supporting Shimmer with

The IOTA SDK is a brand new project which is a combination and improvement of both and If you were already supporting Shimmer with, we encourage you to switch to the SDK as will be deprecated and not receive any updates anymore.

Most of the APIs are the same and the changes are fairly minimal, here are some of the major changes:

1. Set Up the IOTA SDK

Install the IOTA SDK

First, you should install the components needed to use the IOTA SDK and the binding of your choice; it may vary a bit from language to language.

You can read more about backup and security in this guide.

Check out our Getting Started with Rust guide for more detailed instructions.

To start using the IOTA SDK in your Rust project, you can include it as a dependencies in your project's Cargo.toml by running:

cargo add iota-sdk

Set Up you .env File


This guide uses dotenv to share user-defined constants, like database paths, node URLs, and wallet credentials across the code examples. This is for convenience only and shouldn't be done in production.

You can create a .env by running the following command:

touch .env

The following examples will need these variables to exist, here are some default values you can use but also change:


2. Generate a Mnemonic

After you have created the .env file, you can initialize the Wallet instance with a secret manager (Stronghold by default) and client options.

By default, the Stronghold file will be called wallet.stronghold. It will store the seed (derived from the mnemonic) that serves as a cryptographic key from which all accounts and related addresses are generated.

One of the key principles behind Stronghold is that is impossible to extract a mnemonic from it ever again, that is, recover it from the .stronghold file. That's why you should always back up your 24-word mnemonic and have it stored in a safe place. You deal with accounts using the Wallet instance exclusively, which abstracts away all internal complexities and makes transacting in a network very easy and convenient.

Back up and Security

It is best practice to keep the stronghold password and the stronghold database on separate devices.


3. Create an Account for Each User

You can import the IOTA SDK and create a wallet using the following example:


The Alias must be unique and can be whatever fits your use case. The Alias is typically used to identify an account later on. Each account is also represented by an index, which is incremented by one every time a new account is created. You can refer to any account via its index or its alias.

You get an instance of any created account using Wallet.get_account(accountId|alias) or get all accounts with Wallet.get_accounts().

Common methods of an Account instance include:

4. Generate a User Address to Deposit Funds

The wallet module of the IOTA SDK is a stateful library. This means it caches all relevant information in storage to provide performance benefits while dealing with, potentially, many accounts and addresses.


Every account can have multiple addresses. Addresses are represented by an index which is incremented by one every time a new address is created. You can access the addresses using the account.addresses() method:

for address in account.addresses().await? {
println!("{}", address.address());

You can use the Faucet to request test tokens and test your account.

There are two types of addresses, internal and public (external). This approach is known as a BIP32 Hierarchical Deterministic wallet (HD Wallet).

  • Each set of addresses is independent of each other and has an independent index id.
  • Addresses that are created by account.generateEd25519Addresses() are indicated as internal=false (public).
  • Internal addresses (internal=true) are called change addresses and are used to send the excess funds to them.

5. Check the Account Balance

Unlock Conditions

Outputs may have multiple UnlockConditions, which may require returning some or all of the transferred amount. The outputs could also expire if not claimed in time, or may not be unlockable for a predefined period.

To get outputs with only the AddressUnlockCondition, you should synchronize with the option syncOnlyMostBasicOutputs: true.

If you are synchronizing outputs with other unlock conditions, you should check the unlock conditions carefully before crediting users any balance.

You can find an example illustrating how to check if an output has only the address unlock condition, where the address belongs to the account in the Check Unlock Conditions how-to guide.

You can get the available account balance across all addresses of the given account using the following example:


6. Listen to Events

The IOTA SDK supports several events for listening. A provided callback is triggered as soon as an event occurs (which usually happens during syncingA process when a node downloads and verifies the entire history of the Tangle corresponding to a slot commitment chain. This allows to ensure that it has an up-to-date and accurate copy of the ledger.).

You can use the following example to listen to new output events:


Example output:

NewOutput: {
output: {
outputId: '0x2df0120a5e0ff2b941ec72dff3464a5b2c3ad8a0c96fe4c87243e4425b9a3fe30000',
metadata: [Object],
output: [Object],
isSpent: false,
address: [Object],
networkId: '1862946857608115868',
remainder: false,
chain: [Array]
transaction: null,
transactionInputs: null

Alternatively you can use account.outputs() to get all the outputs that are stored in the account, or account.unspent_outputs() , to get the unspent outputs only.

7. Enable Withdrawals

You can use the following example to send tokens to an address.

Dust Protection

When sending tokens, you should consider a dust protection mechanism.


The full function signature is Account.send(&self, amount: u64, address: impl ConvertTo<Bech32Address>,options: impl Into<Option <TransactionOptions>>).

The default TransactionOptions are fine and successful. However, you can provide additional options, such as RemainderValueStrategy, which can have the following values:

  • ChangeAddress: Send the remainder value to an internal address.
  • ReuseAddress: Send the remainder value back to its original address.
  • CustomAddress: Send the remainder value back to a provided account address.

The account.send() function returns a Transaction with its id. You can use the blockId to check the confirmation status. You can obtain individual transactions related to the given account using the account.transactions() function.

You can use the account.send_with_params() to send to multiple addresses in a single transaction.