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.
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 wallet.rs
The IOTA SDK is a brand new project which is a combination and improvement of both iota.rs and wallet.rs. If you were already supporting Shimmer with wallet.rs, we encourage you to switch to the SDK as wallet.rs 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:
- Rust
- Typescript (Node.js)
- Python
- The crate changes from
iota-wallet
toiota-sdk
. AccountManager
becomesWallet
.manager.generateMnemonic()
becomesClient::generateMnemonic()
.generateAddress
becomesgenerateEd25519Addresses
.sendAmount
becomessend
.- The callbacks to
listen
now provide a properly typedEvent
- The npm package changes from
@iota/wallet
to@iota/sdk
. AccountManager
becomesWallet
.manager.generateMnemonic()
becomesUtils.generateMnemonic()
.AccountManager.generateAddress
becomesWallet.generateEd25519Addresses
.Account.sendAmount
becomessend
.- The callbacks to
listen
now provide a properly typedEvent
.
- The pip package changes from
iota-wallet
toiota-sdk
. wallet.generate_mnemonic()
becomesutils.generate_mnemonic()
.wallet.generate_address()
becomeswallet.generate_ed25519_addresses()
.account.send_amount
becomesaccount.send
.- The callbacks to
listen
now provide a properly typedevent
.
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.
- Rust
- Typescript (Node.js)
- Python
Check out our Getting Started with Rust guide for more detailed instructions.
- Cargo add
- Cargo.toml
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
To start using the IOTA SDK in your Rust project, you can include the following line under
dependencies
in your Cargo.toml
:
[dependencies]
iota-sdk = "1.0.0"
Check out our Getting Started with Node.js guide for more detailed instructions.
- npm
- Yarn
- pnpm
npm i @iota/sdk dotenv
yarn add @iota/sdk dotenv
pnpm add @iota/sdk dotenv
Check out our Getting Started with Python guide for more detailed instructions.
pip install 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:
WALLET_DB_PATH="./wallet-database"
NODE_URL="https://api.testnet.shimmer.network"
STRONGHOLD_SNAPSHOT_PATH="./wallet.stronghold"
STRONGHOLD_PASSWORD="*K6%79#yeFn7AMQhMQ"
EXPLORER_URL="https://explorer.shimmer.network/testnet"
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.
It is best practice to keep the stronghold
password and the stronghold
database on separate devices.
- Rust
- Typescript (Node.js)
- Python
loading...
loading...
loading...
3. Create an Account for Each User
You can import the IOTA SDK and create a wallet using the following example:
- Rust
- Typescript (Node.js)
- Python
loading...
loading...
loading...
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
.
- Rust
- Typescript (Node.js)
- Python
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:
account.addresses()
: Returns a list of addresses related to the account.account.generate_ed25519_addresses()
: Generates one or more new address(es) 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.
You get an instance of any created account using
Wallet.getAccount(accountId|alias)
or get all accounts with
Wallet.getAccounts()
.
Common methods of an Account
instance include:
account.addresses()
: Returns a list of addresses related to the account.account.generateEd25519Addresses()
: Generates one or more new address(es) for the address index incremented by 1.account.getBalance()
: Returns the balance for the given account.account.sync()
: Sync the account information with the Tangle.
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:
account.addresses()
: Returns a list of addresses related to the account.account.generate_ed25519_addresses()
: Generates one or more new address(es) for the address index incremented by 1.account.get_balance()
: Returns the balance for the given account.account.sync()
: Sync the account information with the Tangle.
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.
- Rust
- Typescript (Node.js)
- Python
loading...
loading...
loading...
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:
- Rust
- Typescript (Node.js)
- Python
for address in account.addresses().await? {
println!("{}", address.address());
}
const addresses = await account.addresses();
for (const address of addresses) console.log(address.address);
addresses = account.addresses()
for address in addresses:
print(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 asinternal=false
(public). - Internal addresses (
internal=true
) are calledchange
addresses and are used to send the excess funds to them.
5. Check the Account Balance
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:
- Rust
- Typescript (Node.js)
- Python
loading...
loading...
loading...
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:
- Rust
- Typescript (Node.js)
- Python
loading...
loading...
loading...
Example output:
NewOutput: {
output: {
outputId: '0x2df0120a5e0ff2b941ec72dff3464a5b2c3ad8a0c96fe4c87243e4425b9a3fe30000',
metadata: [Object],
output: [Object],
isSpent: false,
address: [Object],
networkId: '1862946857608115868',
remainder: false,
chain: [Array]
},
transaction: null,
transactionInputs: null
}
- Rust
- Typescript (Node.js)
- Python
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.
Alternatively you can use account.outputs()
to get all the outputs
that are stored in the account, or account.unspentOutputs()
,
to get the unspent outputs only.
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.
When sending tokens, you should consider a dust protection mechanism.
- Rust
- Typescript (Node.js)
- Python
loading...
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.
loading...
The full function signature is
Account.send(amount, address,
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.sendWithParams()
to send to multiple addresses in a single transaction.
loading...
The full function signature is
Account.send(send(amount: str, address: str, options: Optional[TransactionOptions] =
None)
.
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.