During this tutorial, we will build a small distributed App (dApp). The techniques you’ll learn in this guide are fundamental to building any dApp on Radix, and mastering it will give you a better understanding of Radix distributed ledger.
This guide is divided into several sections:
​Basic Setup will give you a starting point to follow the tutorial.
​Overview will teach you the fundamentals of Radix's architecture.
​Getting some Radix tokens will show you how to build your first basic dApp.
​Beyond the basics will give you additional examples to get a deeper insight into the strengths of Radix JS library.
Our example dApp for this guide will get some free test money from Radix, and once it has enough money, our dApp will send some to a different address. With our small dApp you'll learn how to interact with accounts, send transactions across the network and handle test tokens in an easy way.
Don't worry if you're new to Radix's concepts, as we will review the basic building blocks along the way.
Our next step is to set you up so that you can start building your first dApp with Radix.
Before we can begin, make sure you have a recent version of Node.js installed in your system. We'll also need a minimalistic Node.js boilerplate project so we can properly build and run our dApp. If you're experienced building your own Node.js applications, feel free to skip our next step and continue with the library installation.
To prepare the environment, let's now set up a minimalistic Node.js project, by cloning an open source Webpack boilerplate:
git clone https://github.com/taniarascia/webpack-boilerplate.git
Once we have cloned the boilerplate project, we can go ahead and install the required libraries for it:
cd webpack-boilerplate/npm install
As an optional step, you can start the server using npm start
and open http://localhost:8080 on your browser to see if everything was set up correctly.
You can install the radixdlt
library in your Node.js project using your preferred package manager:
npm install radixdlt --save
yarn add radixdlt
Note: the library provides full TypeScript support
For this guide, we will assume that you have some familiarity with JavaScript, but you should be able to follow along even if you’re coming from a different programming language.
If you need to review JavaScript, we recommend reading this guide.
We’ll also assume that you’re familiar with programming concepts like functions, objects, arrays, and classes. Additionally, having an understanding of Reactive programming, RxJS and observable patterns is suggested for a better insight.
If you need a quick review on Reactive programming, we recommend reading our blog post.
Now that you’re set up, let’s dig a bit on the concepts that make Radix a unique distributed ledger technology, so we can share a common language.
A 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.
A Shard is simply a segment of a Universe. A public Radix network (Universe) is segmented into a very large shard space (currently 2^64 shards). The shard number of an address is deterministically calculated, so it's trivial for anyone to correctly calculate the shard a public key lives on.
A Node provides general computing and networking resources to the network. Nodes are responsible for validating events and transactions, relaying messages, resolving conflicts and executing scripts on the network. They also maintain a subset of the shard space and get fees in proportion to their work.
An Atom is the fundamental unit of storage on Radix's distributed ledger. Its structure defines one or more actions which update the ledger's state as an atomic transaction, that is, all-or-nothing.
An Account represents all the data stored for a user 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.
An Address lives in a Shard and is the start and end point for any Atom in the Radix Universe. It's also a reference to an Account and allows a user to receive tokens and/or data from other users. A Radix address is generated from a public key and a Universe checksum.
Keep in mind, the defined Universe affects the generated Address.
When an Account is connected to the network, it will take any incoming Atoms and pass them through the account systems. 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
Token Definition System used for managing user-defined tokens
You can create your own custom Account Systems if you want access to the raw Atoms.
An Identity represents a private key which can sign Atoms and read encrypted data. This private key can be stored in the application, or in the future, it might live elsewhere such as the user's wallet application or hardware wallet.
The most basic type of an Identity is a SimpleIdentity
, which stores the private key in memory.
The Transaction Builder 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 particular case of the data payload atoms). In the future, the atom model will be a lot more powerful.
The Faucet service is a simple development service running on the Radix network that sends free test tokens back to any account that sends a message to it.
In the BETANET Universe, the Faucet service address is JH1P8f3znbyrDj8F4RWpix7hRkgxqHjdW2fNnKpR3v6ufXnknor
Now that we have done a brief overview of the concepts behind Radix and we share a common language, we are ready to begin building our example dApp and get some Radix test tokens along the way.
The first step, before we can interact with the ledger, is to choose which Universe we want to connect to. We will use the LOCALHOST_SINGLENODE universe configuration since it's our main testing environment, and the other development universes are used for testing unstable features.
Now, to initialize the universe we have to import the radixUniverse
singleton from the library, and call the bootstrap function with the LOCALHOST_SINGLENODE
universe configuration:
import {radixUniverse, RadixUniverse} from 'radixdlt'​radixUniverse.bootstrap(RadixUniverse.LOCALHOST_SINGLENODE)
Note: the LOCALHOST_SINGLENODE
configuration requires a locally hosted Betanet Emulator running. Review this article to set it up on your computer.
As we want to interact with the ledger and be able to sign and decrypt atoms, we'll need to have our own Identity. To create a new random identity, we use the RadixIdentityManager
:
const identityManager = new RadixIdentityManager()
Now we create a new random identity using the Identity Manager's generateSimpleIdentity() method:
const myIdentity = identityManager.generateSimpleIdentity()
With it, we can easily get our own Account using the account reference:
const myAccount = myIdentity.account​console.log('My account address: ', myAccount.getAddress())
Now that we have our account, the next step is connect it to the network. We do it by calling the openNodeConnection() method:
myAccount.openNodeConnection()
This call opens a connection to a Node from the BETANET
universe which serves the shard where our account lives on, and asks the node for all the atoms in the address. It will also maintain a connection to the network until we destroy the account.
Tip: if a connection to a node dies, a new one will be found automatically.
To get the Faucet's account, we resolve the address using the fromAddress(...)
method:
const faucetAddress = 'JH1P8f3znbyrDj8F4RWpix7hRkgxqHjdW2fNnKpR3v6ufXnknor'​const faucetAccount = RadixAccount.fromAddress(faucetAddress, true)
Regarding the second boolean
parameter, we set it to true, so the method doesn't create the default Account systems along the way.
For accounts that won't be connected to the network, since you won't need any accounting system, set the parameter to true.
Note: the library will throw an error if you accidentally try to use an address from a different Universe.
Now that we have the Faucet's account and our own account connected to the network, we are ready to send a message and request some free tokens to the Faucet service. We send the message using RadixTransactionBuilder's createRadixMessageAtom(...) method, and signing the result Atom with our Identity:
const message = 'Dear Faucet, may I please have some money?'​RadixTransactionBuilder.createRadixMessageAtom(myAccount, faucetAccount, message).signAndSubmit(myIdentity)
After we send the message, we have to subscribe to the Balance subject from the Transfer system to know when we receive the free test tokens sent by the Faucet service:
myAccount.transferSystem.getTokenUnitsBalanceUpdates().subscribe(balance => {// there's a balance updateconsole.log(balance);// ...})
Note: the balance
includes the type of token, and the amount of tokens in token units, which are stored in a Decimal.js
object.
As the balance can have different types of tokens, let's see how to find the token we are interested in. Since we are working with the native platform tokens, we can get a reference from the radixUniverse
object:
const radixToken = radixUniverse.nativeToken
Then, we can find the balance of the token in the balance
object returned in the balance update.:
const nativeTokenBalance = balance[radixToken.toString()]
Our last step is to send some of the free test tokens that we've got from the Faucet, to another address on the network. To do it, first we get the account from the destination address, just as we did before for the Faucet service:
// Put your friends' address hereconst toAddress = 'JHn1iZFKf3GPwk7dMcsYRN9gG8BCSdsvQa8CBENuiwV69Y9pPCB'const toAccount = RadixAccount.fromAddress(toAddress, true)
We now have the destination account and our own account connected to the network, and we are ready to send a few test tokens to our friend's account. We send the tokens using RadixTransactionBuilder's createTransferAtom(...) method, and signing the result Atom with our Identity:
// Send 5 tokens to the addressRadixTransactionBuilder.createTransferAtom(myAccount, toAccount, radixToken, 5).signAndSubmit(myIdentity)
At this point, we have all the basic building blocks for our simple "Get Radix test tokens" dApp. Now, to have a real complete and functional dApp, we need to put the pieces together:
import {radixUniverse,RadixUniverse,RadixIdentityManager,RadixAccount,RadixTransactionBuilder} from 'radixdlt'​radixUniverse.bootstrap(RadixUniverse.LOCALHOST_SINGLENODE)​const identityManager = new RadixIdentityManager()const myIdentity = identityManager.generateSimpleIdentity()const myAccount = myIdentity.account​myAccount.openNodeConnection()​const faucetAddress = 'JH1P8f3znbyrDj8F4RWpix7hRkgxqHjdW2fNnKpR3v6ufXnknor'const faucetAccount = RadixAccount.fromAddress(faucetAddress, true)const message = 'Dear Faucet, may I please have some money?'​RadixTransactionBuilder.createRadixMessageAtom(myAccount, faucetAccount, message).signAndSubmit(myIdentity)​​const radixToken = radixUniverse.nativeTokenmyAccount.transferSystem.getTokenUnitsBalanceUpdates().subscribe(balance => {// Get the balance for the token we are interested inconst nativeTokenBalance = balance[radixToken.toString()]// do we have at least 5 tokens?if (nativeTokenBalance.greaterThan(5)) {​// Put your friends' address hereconst toAddress = 'JHn1iZFKf3GPwk7dMcsYRN9gG8BCSdsvQa8CBENuiwV69Y9pPCB'const toAccount = RadixAccount.fromAddress(toAddress, true)​// Send 5 tokens to the addressRadixTransactionBuilder.createTransferAtom(myAccount, toAccount, radixToken, 5).signAndSubmit(myIdentity)}})
Finally, to see this code running, we have to include it in our Node.js project. If you're using the minimalistic boilerplate project that we set up previously, you only have to copy the dApp code shown above to the ./src/index.js
file. You can also add the following line at the end of the file to get your Radix address in the web browser window:
document.getElementById('root').innerHTML = 'My address: '+ myAccount.getAddress();
To run the dApp, use npm start
, and point your browser to http://127.0.0.1:8080. You should see your address on the main window, and if you open the developer console you will see the messages and atoms flowing through the network:
Note: if you are running the local Betanet Emulator, be sure to edit the /config/webpack.dev.js
file and change the port
value to avoid issues.
As we reach the end of our dApp example, we want to share some extra code snippets for those who want to go beyond the basics and showcase a few additional things that you can do with our library.
​Account management​
​Atom management​
​Identity management​
​Transaction management​
​Private key management​
​Telegram for general chat
​Discord for developer chat
​Reddit for general discussion
​Twitter for announcements
​Email newsletter for weekly updates