In this tutorial, we'll walk through a simple decentralized application, the NFT Auction dAPP; where one user can create an nft, and have other users bid for it. The highest bidder will gain ownership of the NFT and be able to resell it again.
This tutorial aims to give the required knowledge to build, test, and implement custom blockchain logic easily.
Reach is designed to work on POSIX systems with make, Docker, and Docker Compose installed. The best way to install Docker on Mac and Windows is with Docker Desktop.
You probably already have make installed. For example, OS X and many other POSIX systems come with make, but some versions of Linux do not include it by default and will require you to install it. If you’re on Ubuntu, you can run sudo apt install make to get it.
You’ll know that you have everything installed if you can run the following three commands without errors
$ make --version
$ docker --version
$ docker-compose --version
If you’re using Windows, consult the guide to using Reach on Windows.
Once you’ve confirmed that they are installed, choose a directory for this project. We recommend
$ mkdir -p ~/reach/nftroyalties && cd ~/reach/nftroyalties
Next, install Reach by downloading it from GitHub by running
$ curl https://raw.githubusercontent.com/reach-sh/reach-lang/master/reach -o reach ; chmod +x reach
You’ll know that the download worked if you can run
$ ./reach version
Since Reach is Dockerized, when you first use it, you’ll need to download the images it uses. This will happen automatically when you first use it, but you can do it manually now by running
$ ./reach update
You’ll know that everything is in order if you can run
$ ./reach compile --help
Get language support for Reach in your editor by visiting IDE/Text Editor Support. Now that your Reach installation is in order, you should open a text editor and get ready to write your Reach application!
In this tutorial, we’ll be building a version of NFT Auction! There will be a creator who owns Nft and starts the auction, and two people who bid on the auction. We’ll start simple and slowly make the application more fully-featured.
You should follow along by copying each part of the program and seeing how things go. If you’re like us, you may find it beneficial to type each line out, rather than copying & pasting so you can start building your muscle memory and begin to get a sense for each part of a Reach program
Below are the steps to help the reader re-create the same application application. We assume that you already know the basics of reach. If not, checkout the Rock Paper Scissors Tutorial
By the end of this tutorial you will be able to create a D-App where one user can create an nft, and have other users bid for it. The highest bidder will gain ownership of the NFT and be able to resell it again; in a buy sell cycle.
Rather than jumping into the Reach program, we're first going to write a test scenario corresponding to the bidding process. We'll demonstrate how to use Reach's testing tools to write a convenient testing framework customized for your application. We'll show the tests, then the framework, then the Reach code, and show how the Reach code connects to the framework.
After setting up your project with ./reach init
. Clear index.mjs and index.rsh
In index.mjs,
Lines 103 to 112 in d77732e
In this sample, we use test.one
to define a single test scenario. We use the function makeNft, which we will define later, to create a JavaScript object for the NFT abstraction. When it is created, it has the details of the event in it.
Lines 114 to 118 in d77732e
Next, we define objects for each of the people involved in the scenario. This code uses NFT.makeBidders, a function which we will define later, to turn a list of labels into Bidder abstractions.
Lines 120 to 146 in 9f74443
From then on we perform the bidding cycle. test.chkErr
Is to confirm various checks that might through error as will be seen in the reach code. await NFT.waitUntilDeadline();
will be used to make sure the bidding sesssion terminates.
Lines 148 to 154 in 9f74443
Finally, we print out the balances of everyone and see that they match our expectations. The function test.run
instructs Reach to run all of the tests and not print out extra debugging information.
The framework is needed to facilitate the testing. It needs to provide:
makeNft
=> A function which accepts the details of an nft and returns an NFT abstraction.NFT.Creator
=> An abstraction of the Host.NFT.makeBidders
=> A function that produces an array of Bidder abstractions, which are subclasses of Person abstractions.NFT.waitUntilDeadline()
=> A function that waits until the deadline has passed.Person.startAuction()
=> A function for a person to start an auctionBidder.placeBid()
=> A function for one bidder to place a bidPerson.getBalance
=> A function to read one person's balance.
Lines 1 to 5 in 9f74443
First, we have the basic header that imports and initializes the Reach standard library.
Lines 7 to 11 in 9f74443
We define the makeRSVP function and create an initial test account for the host and set its label for debugging.
Lines 13 to 29 in 9f74443
Next, we define the function stdPerson
which takes an obj with an acc field and adds a Person.getBalance function that returns the account's current balance as a nice formatted string. We use this function to define the NFT.Creator
value
Lines 29 to 35 in 9f74443
Next, we define the deadline, based on the current time, and the NFT.waitUntilDeadline
function for waiting until that time has passed.
Lines 37 to 66 in 9f74443
Now, we can define the details object that will be consumed by Reach then pass the object interact object for the creator contract. There after we have the creator observe event so that we can be notified on various actions.
Lines 68 to 83 in 9f74443
Next, we define the NFT.makeBidder
function, which starts by creating a new test account and setting its label. There after we define the bidder functions.
Lines 84 to 99 in 9f74443
We close the definition of the Bidder abstraction by calling stdPerson
to add the Person.getBalance
function. Then, we define NFT.makeBidders
, which produces a single promise out of the array of promises of Bidder abstractions. Also we define Creator.startAuction
. These values are all wrapped together into a final object, which is the result of makeNFT
.
First, we'll review the changes to the Reach application code.
Lines 5 to 33 in 5e92bd0
We add definitions for the View and Event objects.
Let's look at the View
first. The first argument is a label for it, like how we give labels to APIs and participants. Next, we provide an object where the keys are the names of the view components and the fields are their types. This object is just like an interact object, except that the values are provided from Reach, rather than to Reach. In this case, like APIs, these values can be accessed on- and off-chain. On-chain, they can be accessed using the normal ABI of the consensus network, just like APIs. For example, the details
are provided via a function named Info_details
that takes no arguments and returns a Details
structure. Off-chain, they can be accessed via a frontend function like ctc.views.Info.details()
. The off-chain function returns the value or an indication that it was not available.
Next, let's look at the Events
definition. It can also be provided with a label, but we've chosen not to include one. We don't have to provide labels for APIs or Views either, but we think it is a good idea in those cases. The object provided to Events is not an interface, where the keys are types, but instead has tuples of types as the values. These are the values that will be emitted together. For example, the seeOutcome
event will contain an address and an integer. Like APIs and Views, they are available on- and off-chain. On-chain, they are available using the standard ABI for the platform. (Although, note, that some chains, like Ethereum, don't provide any on-chain mechanism for consuming events.) Off-chain, they are available via a frontend function like ctc.events.register
. The off-chain function has sub-methods for reading the next instance of the event or monitoring every event, as well as other options.
In both cases, we have not actually defined the values or meaning of these Views and Events. We've merely indicated that our application contains them. This is similar to how we define a Participant and then later indicate what actions it performs. Let's look at the view definitions next.
Lines 34 to 43 in 5e92bd0
A View can have a different value at each point in the program, including not having any value at all. You define the value by calling <View name>.<field name>.set
and providing a value that satisfies the type. For example, here (on line 43) we indicate that the details field is always the same as the nftInfo
variable. This definition applies to all dominated occurrences of the commit()
keyword. Views are not mutable references: instead, they are ways of naming, for external consumption, portions of the consensus state.
Lines 52 to 55 in 5e92bd0
We similarly expose the contents of the Guests mapping, as well as the owner
variable. We use the .define
feature of parllelReduce
to introduce a statement that dominates the commit()
s implicit in the parallelReduce. This context is the only context that has access to the owner
variable, which is why we must place it there.
Next, let's look at the code that emits instances of the Events
we defined.
Lines 58 to 66 in 5e92bd0
We can emit an event by calling <Events name>.<kind name>(args)
in a consensus step. We do so inside of the .api_
for the Owner.isAuctionOn
API call on line 62. Note that in each of the ._api
calls, various checks are inplace to ensure that they can be called from the frontend only under specific cercirmastances.
Lines 79 to 87 in 5e92bd0
There are many other instances where Events are emmit in the contract. As seen above in lines 81-84 , we wrap it inside an if statement to ensure that the event is emmitted only when the NFT ownership has changed.
In conslusion, from the perspective of a frontend or even a conventionaly application, the View serve the purpose of providing general and primary information for setting up the applications while Events are there to ensure the application of updated live with actions taken from other users that might affect the current user.
This sections assume you are well firmiliar with React framework but will be explained ensure that it can be replicated on other frameworks.
We use the MainAppContext.js to store general application State and its modifier methods. The Bidder.js and Creator.js which extend Participant.js are provided as interact object that enable the user to interact with the frontend.
NFT-Auction/src/components/participants/Participant.js
Lines 47 to 86 in 0b52877
As seen above, for all users, this.contract.e.<event name>.monitor
is used to keep the frontend update with events on-chain.
NFT-Auction/src/components/participants/Participant.js
Lines 88 to 94 in 0b52877
The this.contract.a.<Api name>.<Method name>()
is used to make calls to the API methods to place bids by the users.
NFT-Auction/src/components/participants/Creator.js
Lines 25 to 27 in 0b52877
The createNft
method serves it corresponding function for the Creator Particiapant.
Other than those, the rest in Bidder.js and Creator.js are concerned with statemanagement, navigation and deployment of the code which are out of the scope of thise tutorial.