This tutorial will guide developers to create DApp based on a real business scenario using Ultrain's Robin Framework. In this process, you can get a deep understanding of Ultrain's technical features in DApp development, and quickly learn how to write DApp. You will find it is very easy to write DApp based on the Robin Framework.

The source code can be found at https://github.com/ultrain-os/UltrainDappDemo

This tutorial is an intermediate tutorial. Through this tutorial, you can learn the basic concepts of DApp development, the usage of Ultrain Database and triggers;

Precondition:

You have already learned the "Quickly writing XXXXX" tutorial, and through the guidelines of this tutorial, you have installed the Ultrain Robin development environment, the local test environment LongCrow;

System Requirements

  1. Mac operating system (Ultrain now only supports in Mac environment development, based on Linux and Window integrated development environment developing)
  2. IDE:WebStorm
  3. Docker

Business scenario introduction

NewEnergy company’s new energy equipment can significantly reduce carbon emissions through the use of new clean energy sources. NewEnergy wants to convert its reduced carbon emissions into carbon coins accordingly. On the one side, the companies using NewEnergy’s equipment can obtain additional income through the carbon coin; on the other hand, companies with more carbon emissions can purchase carbon coins, which can provide financial support for emission reduction, and enhance the public image of the company in terms of carbon emissions; therefore, the scenario has the following Character:

NewEnergy: the operator of the carbon coin, creating and issuing carbon coins, the Ultrain’s account used by NewEnergy is ben;

Company A: Using new energy equipment A produced by NewEnergy, in the process of using the equipment, the calorie value consumed will be recorded on the blockchain every 30 seconds, and the value will be converted into carbon coins and distributed to Company A's Ultrain’s account, bob;

Company B: Using new energy equipment B produced by NewEnergy, in the process of using the equipment, the calorie value consumed will be recorded on the blockchain every 30 seconds, and the value will be converted into carbon coins to be distributed to Company B's Ultrain’s account, tom;

Company C: Airline, a major consumer of fossil energy, which can enhance the public image by purchasing and consuming carbon coins; the Ultrain account used by Company C is tony

Company D: Automobile manufacturing company, a large-scale fossil energy consumer, which can enhance the corporate public image by purchasing and consuming carbon coins; the Ultrain account used by Company D is jerry

In the Ultrain account jack, when exchanging the charity points, CarbonCoin burns the entered address, and the CarbonCoin which enters the address will no longer be used;

0_1543571484931_fd7d46b3-9172-48fe-9597-5cb81a8b8b2b-image.png
As shown in the pictures above, the system consists of four programs:

  • Energy Smart Contract: A smart contract is developed based on TypeScript, running on the Ultrain chain;

  • MiningDApp: The terminal DApp is developed based on the U3 framework collecting saving energy data,;

  • UDApp: Exchanging Charity Points DApp is developed based on U3 framework

  • EnergyServer: The server program which monitors and executes the redemption is developed based on the NodeJS framework;

Step1: Energy Smart Contract Development

  1. Create a project directory and initialize the project environment
mkdir CarbonProject
cd CarbonProject
robin init
  1. Install the dependent needed for Ultrain development via npm, and fix project errors via the robin command
npm install
robin lint

0_1543571650872_9d47d91e-bd34-4cbb-a6bb-54eb54745e33-image.png

  1. Develop IDE WebStorm using Javascript, and open the project CarbonProject

Briefly introduce the functions of each directory in the project

contract: The project's smart contract catalog, where the project's main smart contract is placed, and only one smart contract in each project is compiled, which is the smart contract under the catalog; at the same time, Ultrain now supports only one smart contract per account.

migrations: Configuration file for smart contract deployment parameters

node_modules: Project dependent JS class library

template: Smart contract code for the example

test: Test code directory, test code is javascript code

config.js: The default parameters that need to be configured when the U3 framework accesses the Ultrain chain; these parameters can also be dynamically assigned when the U3 framework is created;

  1. Writing Energy Smart Contracts

a. Delete the MyContract.ts file in the contract directory, copy the template/token/token.ts file to the contract directory, and modify the file name to energy.ts; modify the class name to energy;

b. Write code that calls energy.ts and releases CarbonCoin

In the test directory, create a new javascript file named issueCarbonCoin.js

build code framework

const U3Utils = require('u3-utils/dist/es5');
const { createU3, format } = require('u3.js/src');
const config = require('../config');

const chai = require('chai');
require('chai')
    .use(require('chai-as-promised'))
    .should();

const should = chai.should();
const expect = chai.expect;
const assert = chai.assert;

describe('Contract\'s test cases', function() {

});

Write and release CarbonCoin core logic code in describe

it('create and issue a token', async () => {
    let SYMBOL = 'CARB';
    const u3 = createU3(config);
    let account = 'ben';
    const tx = await u3.transaction(account, token => {
        token.create(account, '10000000.0000 ' + SYMBOL);
        token.issue(account, '10000000.0000 ' + SYMBOL, 'issue');
    });
});

To easily analysis the above code, we set the code for the issued Token to CARB, the issued account to ben, issued 10 million, and stored all Tokens in the distribution account ben.

Since the smart contract code is executed asynchronously, we need to use the following code after the execution of the await u3. transaction code, waiting for the release token logic to be confirmed by the Ultrain chain, in order to truly confirm the successful execution of the code.

//wait util it was packed in a block
let tx_trace = await u3.getTxByTxId(tx.transaction_id);
while (!tx_trace.irreversible) {
    await U3Utils.wait(1000);
    tx_trace = await u3.getTxByTxId(tx.transaction_id);
    if (tx_trace.irreversible) {
        console.log(tx);
        break;
    }
}

Above, the release Token code is written.

  1. Compile, deploy, and run the Energy Smart Contract

a. Confirm that LongClaw has been launched; see previous tutorial XXXXXX

b. Modify migrate.js

at u3.deploy('build/MyContract', 'ben') , MyContract changed to energy;

By looking at config.js, we can find that we have deployed the smart contract to the chain specified by chainId under the address specified by httpEndpoint through the private key of the "ben" account;

c. Enter the terminal interface of the IDE and enter the command.

robin build
robin deploy

The following display appears, and there is no error message, indicating that the deployment is successful.

0_1543573990981_6196d246-6bc8-42e5-81dd-1a4766dbef47-image.png

d. Run issueCarbonCoin.js

0_1543574048456_ba23feb6-018a-4877-8870-fbc840383b49-image.png

The above information appears, indicating the successful launch of CarbonCoin;

  1. Add more features to energy smart contracts

6.1 Query account

Add @action before the getBalance method
0_1543574122658_f7c0399f-5104-4f79-9cb2-2ef879340384-image.png

The @action method allows the getBalance method to be accessed by external calls.

This method can query the number of specified Tokens owned by the specified account.

6.2 Record the calorific value sent by the device

The new energy device will send its heat value to the chain, and the smart contract saves the data to the database in the chain. At the same time, the smart contract sends an event notification generated by the calorific value to the registered server address;

In this section:
we will learn how to manipulate data on the chain;
how to send event notifications;

a. Introducing a new declaration in energy.ts

import { Block } from 'ultrain-ts-lib/src/block';
import { NAME,RNAME } from 'ultrain-ts-lib/src/account';
import "allocator/arena";
import { Log } from "ultrain-ts-lib/src/log";

modify

import { TransferParams, dispatchInline} from 'ultrain-ts-lib/src/action';

to

import { TransferParams, dispatchInline,Action } from 'ultrain-ts-lib/src/action';

b. Create Class HeatRecord to record calorific value

class HeatRecord implements Serializable {
    miner: string;
    timestamp: u64;
    heatValue: u64;
    primaryKey(): u64 { return NAME(this.miner) + this.timestamp; }

    prints(): void {
        Log.s("name = ").s(this.miner).s(", timestamp = ").i(this.timestamp).s(", heatValue = ").i(this.heatValue).flush();
    }
}

c. Add database operation declaration before “ export class CarbonToken extends Contract {”

const tblname = "heatvalue";
const scope = "carbon.heat";

@database(HeatRecord, tblname)

d. Add database operation code to class CarbonToken

db: DBManager<HeatRecord>;
public onInit(): void {
    this.db = new DBManager<HeatRecord>(NAME(tblname), this.receiver, NAME(scope));
}
public onStop(): void {
}
constructor(code: u64) {
    super(code);
    this._receiver = code;
    this.onInit();
}

e. Add the core code to record calorific value in class CarbonToken

@action
public recordHeat(quantity:u64):void{

    let r = new HeatRecord();
    r.miner = RNAME(Action.sender);
    r.timestamp = Block.timestamp;
    r.heatValue = quantity;

    let existing = this.db.exists(Action.sender+r.timestamp);
    ultrain_assert(!existing, "this record has existed in db yet.");
    r.prints();
    this.db.emplace(this.receiver,r);

    let value = r.heatValue+","+r.miner;
    emit("onHeatInvoked", EventObject.setString("heat",value));
}

The calorific value record mainly needs to record three data: the device account number of the sending data, the sending calorific value the sending time; for the device account, when the DApp client calls the smart contract method, it needs to be signed with the private key of the caller, so the smart contract can know who called this method, and at the same time guarantee the security of the system; we use the Action.sender method provided by the U3 framework to get the caller account. Since the account is a transferred u64 type field, we use RNAME to transfer it. Meaning string type, which is the account name;

For the timestamp, due to the characteristics of the blockchain technology, we take the timestamp of the previous block as the record;

We will use the account name + timestamp as the primary key of the record;

f. Trigger the onHeatInvoked event, we inform the operator's server program through this event, and the device uploads a new calorific value record;

import { EventObject, emit } from 'ultrain-ts-lib/src/events';

Add trigger code in the recordHeat method

emit("onHeatInvoked", EventObject.setString("heat",value));

6.3 Calorific value converted to CarbonCoin

Add the following code to energy.ts

@action
public exchangeCarbonCoin(from: account_name, to: account_name, quantity: Asset,memo:string): void {
    let carbonToken:Asset = quantity.divide(10);
    this.transfer(from,to,carbonToken,memo);
}

Among them, quantity.divide(10); is a simple calorific value converted to CarbonCoin's calculation formula. It can be found that the formula is recorded in the blockchain, and is open and transparent to everyone, and cannot be tampered with. And ensure the fairness of the exchange.

Then we call the transfer method, and Ben will send the CarbonCoin to the corresponding account;

6.4 Burning CarbonCoin to charity points

Add a charity point object

class ScoreRecord implements Serializable {
    name: string;
    score:u64;
    primaryKey(): u64 {return NAME(this.name)};
    prints():void{
        Log.s("name = ").s(this.name).s(",score = ").i(this.score);
    }
}

Configure the database parameters of the charity point object before energy.ts

const tblname_s = "score";
@database(ScoreRecord, tblname_s)

Add database operation code in energy.ts

db_s: DBManager<ScoreRecord>;


public onInit(): void {
    this.db_s = new DBManager<ScoreRecord>(NAME(tblname_s), this.receiver, NAME(scope));
}

Add core business logic code in energy.ts

@action
public exchangeScore(from: account_name,to : account_name, quantity: Asset,memo:string): void {

    let s = new ScoreRecord();

    let existing = this.db_s.exists(from);
    if (existing){
        this.db_s.get(from,s);
        s.score = s.score + quantity.amount;
        this.db_s.modify(this.receiver,s);
        Log.s("thi is a edit obj");
    }else{
        s.score = quantity.amount;
        s.name = RNAME(from);
        this.db_s.emplace(this.receiver, s);
        Log.s("thi is a new obj");
    }

    this.transfer(from,to,quantity,memo);

}

The score is divided into two steps. First, we define the ratio of CarbonCoin to public interest points is 1:1. We will add the redeemed points and save them to the chain database with the name of the redemption person; then it should be consumed after redemption. CarbonCoin is transferred to an account "jack" for destruction;

6.5 Update energy.ts smart contract

At this point, we complete the preparation of the smart contract, and execute the command again to complete the compilation and deployment of the smart contract.

robin build
robin deploy