Edgars Nemse

Introducing the Radix JavaScript Library

Radix
October 11, 2018

This post will guide you through the architecture of the Radix JavaScript library, introducing you to all of the main parts and finally pulling it all together with an example. By the end of this you will be ready to start building your first simple dApp on Radix.

Overview

The Radix JavasScript library is built to run both in a browser as well as serverside on Node.js - you can build a large number of different dApps that only need the front-end component, using the Radix DLT as a decentralised backend database relying on the constraints enforced by the ledger for your application logic, however for some applications you might want to run your own backend servers connected to the ledger.

The library is fully built in Typescript, allowing you to build more robust applications leveraging type checking. It also follows the reactive programming paradigm and relies on the RxJS library in many places - I talk about it in my previous blog post https://www.radixdlt.com/post/reactive-programming-and-rxjs

The architecture

The library consists of 4 logically separate parts:

  • The Universe
  • Account
  • Identity
  • Transaction Builder

Using a combination of these parts, you can access the full power of the ledger. Furthermore, each part is built to be easily extensible both by us in the future and you in your applications. I will now walk you through them.

The Universe

The Universe represents the Radix network. It maintains connections to nodes, and you can ask it to give you a connection to a node that serves a specific shard. Because Radix is built to be sharded from the ground up, it is not enough to have a single connection to the network - depending on what addresses you’re trying to work with, you might need a number of connections.

Before you can start doing anything, you need to choose which Universe you want to connect to.

At the time of writing this, we have 3 public testing universes - Alphanet, Sunstone and Highgarden. Alphanet is our main testing universe, the other ones are used for testing unstable features. You should probably be connecting to the Alphanet universe unless you know exactly what you are doing.

The Universe also affects the address format - a Radix address is generated from a public key plus some information from the Universe configuration. The library will throw an error if you accidentally try to use an address from a different universe.

To initialize a universe, you need to import the radixUniverse singleton from the library, and call the bootstrap function with the appropriate universe config.

import {radixUniverse, RadixUniverse} from 'radixdlt-js'

radixUniverse.bootstrap(RadixUniverse.ALPHANET)

Accounts

An account represents all the data stored in an address on the ledger. This includes tokens, but also arbitrary data, as well as more advanced types of transactions in the future such as multi-sig and Scrypto smart contracts.Typically you would create an account from a Radix address string (you can also create it from a public key)

const account = RadixAccount.fromAddress('9i9hgAyBQuKvkw7Tg5FEbML59gDmtiwbJwAjBgq5mAU4iaA1ykM')

After creating the account, you need to connect it to the network:

account.openNodeConnection()

This will get a node connection from the Universe which serves the shard the account lives on, and ask the node for all the atoms in the address. It will maintain a connection to the network until you destroy the account. If a node connection dies, a new one will automatically be found.

Once the account is connected to the network, it will take any incoming atoms and pass them through the account systems. An account comes with a few systems out of the box, but you can also create your own if you want access to the raw atoms. The default systems are:

  • Transfer System, which keeps a list of transactions involving this account as well as the account balance for all the different tokens in the account
  • Radix Messaging System, which manages the different Radix messaging chats this account is involved in
  • Data System used for custom data stored on the ledger  

You can access the data in these systems either as up-to-date lists/objects/ES6 maps, or you can subscribe to RxJS update subjects, which will emit an update whenever a new event happens on the ledger.

account.transferSystem.balance // This is the account balance for each token
account.transferSystem.transactions // This is a list of transactions

// Subscribe for any new incoming transactions
account.transferSystem.transactionSubject.subscribe(transactionUpdate => {
  console.log(transactionUpdate)
})

// Subscribe for all previous transactions as well as new ones
account.transferSystem.getAllTransactions().subscribe(transactionUpdate => {
  console.log(transactionUpdate)
})

However, as this account only has access to the public key, you cannot decrypt any of the encrypted data in the account, such as Radix messages. So if you try to read data from a public account, you won't be able to read their encrypted messages.

Identity

This brings us to Identities. An identity represents a private key which can sign atoms and decrypt data. This private key can be stored in the application, or in the future, it might live elsewhere such as the users wallet application or hardware wallet.

The only type of identity available currently is a Simple Identity. It has a private key stored in memory. You can create a new random identity using the Identity Manager.

const identityManager = new RadixIdentityManager()

const identity = identityManager.generateSimpleIdentity()

Each identity automatically comes with a corresponding account.

const account = identity.account

account.openNodeConnection()

Once you have opened a node connection for this account, you will now be able to access not just the public data, but also the encrypted messages and application payloads.

// A list of Radix chat messages in the order of receivng them
account.messagingSystem.messages

// Radix chat messages grouped by the other address
account.messagingSystem.chats

// Subscribe for incoming messages
account.messagingSystem.messageSubject.subscribe(...)

// Subscribe for all previous messages as well as new ones
account.messagingSystem.getAllMessages().subscribe(...)

// Custom application data
account.dataSystem.applicationData.get('my-test-application')

// Subscribe for all incoming application data
account.dataSystem.applicationDataSubject.subscribe(...)

// Subscribe for all previous messages as well as new ones
account.dataSystem.getApplicationData('my-test-application').subscribe(...)

We are planning to release more types of identities shortly, the first one of which will be a Wallet Identity, which will let your application connect to the users Radix Wallet software and use their existing account to sign atoms, instead of your dApp requiring the user to create a new identity, or importing their full private keys.

The key point is that all these new types of identities will match the same interface as Simple Identity, so you can start developing your application using the Simple Identity and seamlessly swap it out for Wallet Identity whenever it becomes available.

Transaction Builder

Once you have an identity and the identity is connected to the network and has loaded all of the latest atoms, you are ready to start submitting data to the ledger.

This is where the RadixTransactionBuilder comes into the picture. It handles creating and submitting to the network any kind of atoms that the Radix ledger can accept. Right now this means token transfer atoms, data payload atoms and Radix messaging atoms (which are just a special case of the data payload atoms). In the future the atom model will be a lot more powerful.

To send a transaction, you first need an identity that can sign the atom.

const myIdentity = identityManager.generateSimpleIdentity()

Furthermore, you also need to initialise the account of this identity by loading all of the latest atoms from the ledger - you need an up to date information about available funds in the sending account to construct a valid transaction.

const myAccount = myIdentity.account

console.log('My address: ', myAccount.getAddress())

myAccount.openNodeConnection()

Typically you would do this as soon as your application starts up - that way the account is always up to date and ready to transact with the ledger.

Once you have all of that ready, you can construct a new transaction atom and submit it to the ledger.

// No need to load data from the ledger for the recipient account

const toAccount = RadixAccount.fromAddress('9i9hgAyBQuKvkw7Tg5FEbML59gDmtiwbJwAjBgq5mAU4iaA1ykM', true)

const token = 'TEST' // The Radix TEST token
const amount = 123.12

const transactionStatus = RadixTransactionBuilder
  .createTransferAtom(myAccount, toAccount, token, amount)
  .signAndSubmit(myIdentity)

Calling .signAndSubmit() will manage the full process of getting your transaction submitted to the ledger. It returns an RxJS observable that you can subscribe to to get updates about the state of your transaction as it makes its way on to the ledger.

transactionStatus.subscribe({
  next: status => {
    console.log(status)
    // For a valid transaction, this will print, 'FINDING_NODE', 'GENERATING_POW', 'SIGNING', 'STORE', 'STORED'
  },
  complete: () => {console.log('Transaction has been stored on the ledger')},
  error: error => {console.error('Error submitting transaction', error)}
})

Similarly, you can also send messages

const transactionStatus = RadixTransactionBuilder
  .createRadixMessageAtom(myAccount, toAccount, message)
  .signAndSubmit(myIdentity)

And store arbitrary data on the ledger.

const applicationId = 'my-test-app'

const payload = JSON.stringify({
  message: 'Hello World!',
  otherData: 123
})

const transactionStatus = RadixTransactionBuilder
  .createPayloadAtom([myAccount, toAccount], applicationId, payload)
  .signAndSubmit(myIdentity)

Application id is just a string you choose yourself for your application. There's no guarantee that someone else won't select the same application id, so it's up to you to validate the data you read from the ledger.

Bringing it all together

Now that you’re familiar with the main components of the Radix JS library, you’re ready to start building your dApps. To get you started, let me give you a quick example of how all these steps fit together.

This example will create a new Simple Identity and send a message to the Radix faucet service (the faucet service sends you free test money if you send a message to it).

Afterwards it will subscribe to balance updates for the account to detect when you receive the money, and once we have enough money, it will send some to a different address (try inputing your wallet address).


radixUniverse.bootstrap(RadixUniverse.ALPHANET)

const identityManager = new RadixIdentityManager()


const myIdentity = identityManager.generateSimpleIdentity()
const myAccount = myIdentity.account
console.log('My address: ', myAccount.getAddress())

myAccount.openNodeConnection()

const faucetAccount = RadixAccount.fromAddress('9ey8A461d9hLUVXh7CgbYhfmqFzjzSBKHvPC8SMjccRDbkTs2aM', true)
const message = 'Dear Faucet, may I please have some moeny? (◕ᴥ◕)'


RadixTransactionBuilder
  .createRadixMessageAtom(myAccount, faucetAccount, message)
  .signAndSubmit(myIdentity)

const radixToken = radixTokenManager.getTokenByISO('TEST')  
myAccount.transferSystem.balanceSubject.subscribe(balance => {
  // Convert balance from subunits to decimal point value
  const floatingPointBalance = radixToken.toTokenUnits(balance[radixToken.id])

  if (floatingPointBalance > 5) {
    // Put your own address here
   const toAccount = RadixAccount.fromAddress('9i9hgAyBQuKvkw7Tg5FEbML59gDmtiwbJwAjBgq5mAU4iaA1ykM', true)

    // Send 5 tokens to the address
    RadixTransactionBuilder
      .createTransferAtom(myAccount, toAccount, radixToken, 5)
      .signAndSubmit(myIdentity)
    }
})

Roadmap

This is just first public release of the library, we will keep working on adding new features as they become available in the Radix core, as well as making the library as easy to use as possible. Below is a rough list of things we plan to do next:

  • Adding a WalletAppIdentity, so that your applications can leverage user's existing wallets
  • Support for hardware wallets
  • In-depth documentation
  • Improved testing
  • Revamped atom model (you will hear about this a lot more in the coming months)
  • Integration with the Radix decentralised exchange (DEX)
  • Support for creating your own fungible and non-fungible tokens
  • Native multi-sig

If you have feedback on this list - maybe there are particular things you would like to see that would make your life easier - let us know on our Discord server, or on the Github issues page.

Conclusion

Now you're fully prepared to dive into the library. Head over to the GitHub page to get started. New to Javascript/short on time, and don't want to mess around setting up a build system? Grab this browser skeleton or Node.js server skeleton and start hacking right away.

Become a Radix Insider

subscribe

Related articles