Skip to main content
Version: IOTA

Examples in Node.js

In this section, you will go over several examples that use the node.js binding of the wallet.rs library. You can also find examples of the code in the /bindings/nodejs/examples folder of the official GitHub repository.

ll the examples in this section expect you to set your custom password in the .env file:

SH_PASSWORD="here is your super secure password"

Account Manager and Individual Accounts

You can initialize (open) secure storage for individual accounts. This storage is backed up by Stronghold by default, using an AccountManager instance.

The following example creates a new database and account:

/**
* 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',
});

try {
manager.setStrongholdPassword(process.env.SH_PASSWORD);
let account;
try {
account = manager.getAccount('Alice');
} catch (e) {
console.log("Couldn't get account, creating a new one");
}

// Create account only if it does not already exist
if (!account) {
manager.storeMnemonic(SignerType.Stronghold);
account = manager.createAccount({
clientOptions: {
node: { url: 'https://api.lb-0.h.chrysalis-devnet.iota.cafe' },
localPow: true,
},
alias: 'Alice',
});
console.log('Account created:', account.id());
}

const synced = await account.sync();
console.log('Synced account', synced);
} catch (error) {
console.log('Error: ' + error);
}
}

run();
  • Storage is initialized under the given path (./alice-database).
  • The password is set based on your password in .env file (manager.setStrongholdPassword(process.env.SH_PASSWORD) ).
  • When you initialize the new database, a Stronghold mnemonic (seed) is automatically generated and stored by default (manager.storeMnemonic(SignerType.Stronghold) ).
  • You will only need to set the seed when you initialize the database for the first time. You can open an already initialized database using your password.

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

note

We recommended that you store your Stronghold password encrypted on rest and separated from Stronghold snapshots.

Deal with the password with utmost care.

Storage is made of two things:

  • A single file called wallet.stronghold contains the seed. Stronghold will secure the seed and encrypt it at rest. The generated seed (mnemonic) serves as a cryptographic key, which is used to generate all accounts and related addresses.
  • Other data used by the library that is stored under db sub-directory. The includes account information, generated addresses, fetched messages, etc. This data is used to speed up some operations, such as account creation, address generation, etc.

One of the key principles behind Stronghold based storage is that no one can extract a seed from the storage. You manage your accounts via an AccountManager instance and all the complexities are hidden under the hood and are dealt with securely.

If you also want to store a seed somewhere else, you can use the AccountManager.generateMnemonic() method. Using this method generates a random seed. You can also use it before the actual account initialization.

You can find detailed information about seed generation in the Developer Guide to Chrysalis.

Accounts

The wallet.rs library uses a model of individual accounts to separate individual users or clients from each other. You can generate multiple addresses for each account deterministically. You can find more information about account management in the Developer Guide to Chrysalis.

Once you have created the backend storage, you can create individual accounts for individual users can be created by running the manager.createAccount() method:

    let account = await manager.createAccount({
alias: 'Alice', // an unique id from your existing user
clientOptions: { node: 'https://api.lb-0.h.chrysalis-devnet.iota.cafe', localPow: false }
})

Each account is related to a specific IOTA network (mainnet and devnet), which is referenced by node properties such as node url. In this example, it is on the Chrysalis devnet balancer.

You can refer to the Wallet Node.js API Reference for more information about clientOptions .

Alias

An Alias should be unique, and it can be any string that you see fit. The alias is used to identify the account later on. Each account is also represented by an index which is incremented by 1 every time new account is created. Any account can be then referred to by its index , alias , or one of its generated addresses .

Several API calls can be performed via an account instance.

note

It is good practice to sync accounts with the Tangle every time you work with an account instance. This way, you can ensure that you rely on the latest available information.

You can do this using account.sync().account.sync() is performed automatically on send, retry,reattach and promote API calls.

Once an account has been created, you can retrieve an instance using the following methods:

The most common methods of account instance are:

account.alias() : Returns an alias of the given account. account.listAddresses() : Returns list of addresses related to the account. account.getUnusedAddress() : Returns a first unused address. account.generateAddress() : 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 have multiple addresses. Addresses are generated deterministically based on the account and address index. This means that the combination of account and index uniquely identifies the given address.

There are two types of addresses, internal and public (external), and each set of addresses is independent of each other and has an independent index id.

  • You can create Public addresses using the account.generateAddress() function. Public addresses are flagged as internal=false (public).
  • Internal addresses are also called change addresses. Internal addresses are used to store the excess funds and are indicated as internal=false.

This approach is also known as a BIP32 Hierarchical Deterministic wallet (HD Wallet).

note

The IOTA 1.5 (Chrysalis) network supports reusing addresses multiple times.

You can use the following example to generate a new address:

/**
* This example generates 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
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.chrysalis-devnet.iota.cafe/',
);

const addresses = account.listAddresses();
console.log('Addresses:', addresses);
}

run();

Checking Balance

Before continuing, you should visit the IOTA devnet faucet service and send some tokens to your devnet addresses.

IOTA Faucet Service

You can use the following example to generate a new database and account:

/**
* 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
await account.sync();
console.log('Syncing...');

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

run();

IOTA is based on Unspent Transaction Output model. You can find a detailed explanation in the Developer Guide to Chrysalis.

Sending Tokens

You can use the following example to send tokens using an Account instance to any desired address:

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

require('dotenv').config();

async function run() {
const {
AccountManager,
RemainderValueStrategy,
} = 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...');
await account.sync();
console.log('Available balance', account.balance().available);

//TODO: Replace with the address of your choice!
const address =
'atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r';
const amount = 1000000;

const response = await account.send(address, amount, {
remainderValueStrategy: RemainderValueStrategy.reuseAddress(),
});

console.log(
`Check your message on https://explorer.iota.org/devnet/message/${response.id}`,
);
}

run();

The full function signature is Account.send(address, amount, [options]). You can use the default options. However, you can provide additional options, such as remainderValueStrategy , which has the following strategies:

  • changeAddress(): Send the remainder value to an internal address.
  • reuseAddress(): Send the remainder value back to its original address.

The Account.send() function returns a wallet message that fully describes the given transaction. You can use the messageId to check confirmation statuses. You can also retrieve individual messages related to any given account using the Account.listMessages() function.

Dust Protection

The network uses a dust protection protocol to prevent malicious actors from spamming the network while also keeping track of the unspent amount ( UTXO ).

info

Micro-transactions below 1Mi of IOTA tokens can be sent to another address if there is already at least 1Mi on that address. That is why we sent 1Mi in the last example, to comply with the dust protection.

Dust protection also means you cannot leave less than 1Mi on a spent address (leave a dust behind).

Backing Up a Database

Due to security practices that are incorporated in the Stronghold's DNA, there is no way to retrieve a seed, as it is encrypted at rest. So, if you are using the default options, you should make sure that you back up your seed regularly.

The following example will guide you in backing up your data in secure files. You can move this file to another app or device, and restore it.

/**
* 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);

const path = await manager.backup('./backup', process.env.SH_PASSWORD);

console.log('Backup path:', path);
}

run();

Alternatively, you can create a copy of the wallet.stronghold file and use it as seed backup. This can be achieved by a daily cronjob, rsync, or scp with a datetime suffix for example.

Restore a Database

To restore a database via wallet.rs, you will need to create a new empty database with a password (without the mnemonic seed). After you have created the empty database, you will need to import all accounts from the file that has been backed up earlier.

The following example restores a secured backup file:

/**
* 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
const path = 'input your backup file';

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

run();

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

Listen to Events

The wallet.rs library can listen to several supported events. As soon as the event occurs, a provided callback will be triggered.

You can use the following example to fetch an existing Account and listen to transaction events related to that Account :

/**
* This example shows how to use events.
*/

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
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.chrysalis-devnet.iota.cafe/',
);

const callback = function (err, data) {
if (err) {
console.error(err);
} else {
console.log('Data:', data);
}
};

manager.listen('BalanceChange', callback);

// Event listeners would be removed after 30 seconds.
setTimeout(() => {
manager.removeEventListeners('BalanceChange');
console.log('Event listeners removed');

// Exit the process
process.exit(0);
}, 30000);

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

run();

Example output:

data: {
accountId: 'wallet-account://1666fc60fc95534090728a345cc5a861301428f68a237bea2b5ba0c844988566',
address: {
address: 'atoi1q9c6r2ek5w2yz54en78m8dxwl4qmwd7gmh9u0krm45p8txxyhtfry6apvwj',
balance: 20000000,
keyIndex: 0,
internal: false,
outputs: [ [Object], [Object] ]
},
balance: 20000000
}

You can then use the accountId to identify the account via AccountManager.getAccount(accountId).

Read more about Events in the API reference.