如何在Ultrain上构建一个端到端的DApp
B

本篇教程将指导开发者基于一个真实的商业场景,采用Ultrain的Robin Framework,编写一个DApp,在这个过程中您可以深入了解Ultrain在DApp开发方面的技术特点,快速掌握如何编写一个DApp,您会发现基于Robin Framework编写DApp是一件非常轻松的事情。本教程的所有源代码都可以在github中找到:https://github.com/ultrain-os/UltrainDappDemo

本教程属于中级教程,通过本教程,你可以学习DApp开发的基本概念,Ultrain Database和触发器的用法;

前提假设: 您已经学习过“快速编写XXXXX”教程,并通过该教程的指引,已经安装了Ultrain Robin开发环境,本地测试环境LongClaw; 系统要求: Mac操作系统(最佳体验)或Linux、Windows系统; IDE:WebStorm Docker 业务场景介绍:

NewEnergy公司生产的新能源设备,通过使用新的清洁能源,可以极大的降低碳排放,NewEnergy公司希望将其降低的碳排放量转换为相应的碳币,一方面使用NewEnergy公司设备的公司,通过该碳币可以获取额外收益;另一方面,碳排放较多的公司,可以通过购买碳币,为减排提供资金支持,提升企业在碳排放方面的公益形象;所以,该场景中存在如下的角色:

NewEnergy公司:碳币的运营方,创造并发行碳币,NewEnergy使用的Ultrain账户为ben; B公司:使用NewEnergy公司生产的新能源设备B,在使用设备的过程中,其消耗的热量值每30秒会纪录在区块链上,并由该值转换为碳币发放到B公司的Ultrain账户,tom; C公司:航空公司,化石能源使用大户,通过购买并消耗碳币,提升企业公益形象;C公司使用的Ultrain账户为tony D公司:汽车制造公司,化石能源使用大户,通过购买并消耗碳币,提升企业公益形象;D公司使用的Ultrain账户为jerry Ultrain账户jack,为兑换公益积分时,CarbonCoin燃烧掉打入的地址,打入该地址的CarbonCoin将不能再被使用;
程序架构介绍:

如上图所示,系统由4个程序构成:

Energy智能合约:运行在Ultrain链上的智能合约,基于TypeScript开发; MiningDApp:采集节能量数据的终端DApp,基于U3框架开发; UDApp:兑换公益积分DApp,基于U3框架开发 EnergyServer:监听并执行兑换的服务器程序,基于NodeJS框架开发;
Step1: Energy智能合约开发 1. 建立项目目录,初始化项目环境

mkdir CarbonProject
cd CarbonProject
robin init

2. 使用Javascript开发IDE WebStorm,打开项目CarbonProject


简单介绍一下项目中各目录的功能:

contract : 项目的智能合约目录,该目录下放置项目的主智能合约,每个项目中只有一个智能合约被编译,就是该目录下的智能合约;同时,Ultrain现在每个账户只支持部署一个智能合约;
migrations:智能合约部署参数的配置文件
node_modules:项目依赖的JS类库
template:用于示例的智能合约代码
test: 测试代码目录,测试代码都是javascript代码;
config.js:U3框架访问Ultrain链时,需要配置的默认参数;这些参数也可以在U3框架创建时动态赋值;

3. 编写Energy智能合约

a. 删除contract目录下的MyContract.ts文件,将template/token/token.ts文件拷贝到contract目录下,修改文件名为 energy.ts;修改类名为 energy; 需要注意的是:发行代币的合约需要遵循UIP的规范,本例中需要实现UIP06的接口,相关规范文档请参考:https://developer.ultrain.io/tutorial/UIP_Introduce.
b.编写调用energy.ts,发行CarbonCoin的代码。

在test目录下,建立一个新的javascript文件,命名为issueCarbonCoin.js
In the test directory, create a new javascript file named issueCarbonCoin.js 建立代码框架 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() { }); 在describe中编写发行CarbonCoin核心逻辑代码 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'); }); });

简单解析上述代码,我们设定了发行的Token的代码为CARB,发行的账户为ben,发行了1000万枚,并将所有Token存储到发行账户ben中。

由于智能合约代码是异步执行的,我们需要在await u3.transaction 代码执行后,采用下述代码,等待发行Token逻辑被Ultrain链确认,才能真正确认代码执行成功 //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; } }

以上,发行Token代码编写完成。

4. 编译、部署并运行Energy智能合约

a. 确认LongClaw已经启动;参见之前教程XXXXXX;
b. 修改migrate.js

u3.deploy('build/MyContract', 'ben')中,MyContract修改为energy
at u3.deploy('build/MyContract', 'ben') , MyContract changed to energy;
通过查看config.js可以发现,我们通过“ben”账户的私钥,将该智能合约部署到httpEndpoint指定的地址下的chainId指定的链上;

c. 进入IDE的terminal界面,输入命令

robin build
robin deploy

出现如下显示,并没有错误提示,表示部署成功

d. 运行issueCarbonCoin.js

出现以上信息,说明CarbonCoin发行成功;

5.为 energy智能合约添加更多功能 5.1 查询账户

在getBalance方法前添加@action
@action方法让getBalance方法可以被外部调用访问;
该方法可以查询指定账户拥有的指定Token的数量

5.2 记录设备发送的热量值

新能源设备会将其产生的热量值发送到链上,智能合约将该数据保存到链上的数据库中,同时,智能合约向注册的服务器地址发送热量值产生的事件通知;

在本节,我们将学习如何操作链上数据;如何发送事件通知;
a. 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";

修改之前引入代码

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

b. 建立HeatRecord类,用于记录热量值

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. 在export class CarbonToken extends Contract { 前添加数据库操作申明

const tblname = "heatvalue"; const scope = "carbon.heat"; @database(HeatRecord, tblname)

d. 在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. 在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)); }

热量值记录主要需要记录三个数据,发送数据的设备账号,发送的热量值以及发送时间;针对设备账号,DApp客户端调用智能合约的方法时,需要用调用者的私钥进行签名,所以智能合约可以知道是谁调用的本方法,同时能保证系统的安全;我们使用U3框架提供的Action.sender方法得到调用者账号,由于该账号是一个转移后的u64类型字段,我们用RNAME将其转义为string类型,即账号名;

针对时间戳,由于区块链技术的特点,我们取上个区块的时间戳作为记录;

我们将 账号名+时间戳 作为记录的主键;

f. 触发onHeatInvoked事件,我们通过该事件告知运营方的服务器程序,有设备上传新的热量值记录了;

引入类:

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

在recordHeat方法中添加触发器代码

emit("onHeatInvoked", EventObject.setString("heat",value)); 5.3 热量值兑换CarbonCoin

在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); }

其中quantity.divide(10);是一个简单的热量值兑换CarbonCoin的计算公式,可以发现,该公式是记录在区块链中,对所有人都是公开和透明的,并且不能篡改,保证兑换的公平性;

然后我们调用transfer方法,由ben将兑换的CarbonCoin发给相应的账户;

5.4 燃烧CarbonCoin兑换公益积分

添加公益积分对象;

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); } }

在energy.ts前配置公益积分对象的数据库参数

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

在energy.ts内添加数据库操作代码

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

在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); }

对积分分为两步操作,首先我们定义CarbonCoin兑换公益积分的比例是1:1,我们将兑换到的积分累加起来,用兑换人name作为主键保存到链上数据库;然后将兑换后应该消耗掉的CarbonCoin转移到一个账户“jack”,用于销毁;

6. 更新energy.ts智能合约

至此,我们完成了智能合约的编写工作,再次执行命令完成对智能合约的编译和部署
robin build
robin deploy

Step2:DApp编写

本教程有两个DApp需要编写,一个是MiningDApp,一个是UDApp,我们采用基于Javascript语言的U3框架编写DApp,为简化,我们将这两个DApp包含在一个Project中;

1. 建立DApp项目

在WebStorm中,新建项目->Node.js Express App->Template选择Pug(Jade);项目名称修改为CarbonDApp;点击‘Create’创建项目

2. 导入项目依赖类库

将CarbonProject中node_modules中所有目录拷贝到CarbonDApp的node_modules目录下,重复目录跳过不用拷贝;

3. 修改DApp服务器端口

由于默认的3000端口已经被占用,需要修改DApp的服务器端口;
打开bin/www 文件,找到var port = normalizePort(process.env.PORT || '3000');将3000修改为3001

4. 建立账户信息浏览页面

在public目录下建立index.html页面,添加如下代码到页面:

<html> <head> <meta charset="UTF-8"> <title>test</title> <link rel="stylesheet" href="/stylesheets/style.css"> <script src="./javascripts/u3.js"></script> <script> let u3 = U3.createU3({ httpEndpoint: 'http://127.0.0.1:8888', httpEndpoint_history: 'http://127.0.0.1:3000', broadcast: true, debug: false, sign: true, logger: { log: console.log, error: console.error, debug: console.log }, chainId:'2616bfbc21e11d60d10cb798f00893c2befba10e2338b7277bb3865d2e658f58', symbol: 'UGAS' }); async function getBalanceInfo() { let SYMBOL = 'CARB'; let account = 'ben'; const ben_balance = await u3.getCurrencyBalance({ code:'ben', symbol: SYMBOL, account: account }); document.getElementById("carbon_balance").innerHTML = '账户余额:'+ben_balance; const bob_balance = await u3.getCurrencyBalance({ code:'ben', symbol: SYMBOL, account: 'bob' }); document.getElementById("bob_balance").innerHTML = '账户余额:'+ bob_balance; const tom_balance = await u3.getCurrencyBalance({ code:'ben', symbol: SYMBOL, account: 'tom' }); document.getElementById("tom_balance").innerHTML = '账户余额:'+tom_balance; const tony_balance = await u3.getCurrencyBalance({ code:'ben', symbol: SYMBOL, account: 'tony' }); document.getElementById("tony_balance").innerHTML = '账户余额:'+tom_balance; const jerry_balance = await u3.getCurrencyBalance({ code:'ben', symbol: SYMBOL, account: 'jerry' }); document.getElementById("jerry_balance").innerHTML = '账户余额:'+tom_balance; const jack_balance = await u3.getCurrencyBalance({ code:'ben', symbol: SYMBOL, account: 'jack' }); document.getElementById("jack_balance").innerHTML = '账户余额:'+tom_balance; } getBalanceInfo(); //transfer(); </script> </head> <body> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>CarbonCoin发行方</h1> <p>账户:ben</p> <p>描述:进行CarbonCoin发行的发行方,持有所有未分配的CarbonCoin</p> <p id="carbon_balance"></p> </div> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>节能设备1</h1> <p>账户:bob</p> <p>描述:通过节能产生CarbonCoin的节能设备1</p> <p id="bob_balance"></p> </div> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>节能设备2</h1> <p>账户:tom</p> <p>描述:通过节能产生CarbonCoin的节能设备2</p> <p id="tom_balance"></p> </div> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>航空公司C</h1> <p>账户:tony</p> <p>描述:大型航空公司,化石能源使用大户</p> <p id="tony_balance"></p> </div> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>汽车制造商D</h1> <p>账户:jerry</p> <p>描述:大型汽车制造商,化石能源使用大户</p> <p id="jerry_balance"></p> </div> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>CarbonCoin销毁账户</h1> <p>账户:jack</p> <p>描述:用于CarbonCoin销毁的账户,持有所有销毁的CarbonCoin</p> <p id="jack_balance"></p> </div> </body> </html> 5.启动DApp服务,测试页面

在npm界面,双击start按钮

服务器启动成功后,访问http://127.0.0.1:3001/index.html,出现如下页面:
可以看到,发行方ben账户中持有发行的所有CARB Token 1000万枚;其他账户还没有持有CARB Token;

5. 建立mining.html页面

mining.html页面,节能设备1和节能设备2每30秒将前30秒产生的热量值发送给energy智能合约,兑换CarbonCoin;
在public目录下建立mining.html页面,输入如下代码:

<html> <head> <meta charset="UTF-8"> <title>test</title> <link rel="stylesheet" href="/stylesheets/style.css"> <script src="./javascripts/u3.js"></script> <script> let u3_bob = U3.createU3({ httpEndpoint: 'http://127.0.0.1:8888', httpEndpoint_history: 'http://127.0.0.1:3000', broadcast: true, debug: false, sign: true, logger: { log: console.log, error: console.error, debug: console.log }, chainId:'2616bfbc21e11d60d10cb798f00893c2befba10e2338b7277bb3865d2e658f58', keyProvider: '5JoQtsKQuH8hC9MyvfJAqo6qmKLm8ePYNucs7tPu2YxG12trzBt',//bob's private_key symbol: 'CARB' }); let u3_tom = U3.createU3({ httpEndpoint: 'http://127.0.0.1:8888', httpEndpoint_history: 'http://127.0.0.1:3000', broadcast: true, debug: false, sign: true, logger: { log: console.log, error: console.error, debug: console.log }, chainId:'2616bfbc21e11d60d10cb798f00893c2befba10e2338b7277bb3865d2e658f58', keyProvider: '5KXYYEWSFRWfNVrMPaVcxiRTjD9PzHjBSzxhA9MeQKHPMxWP8kU',//tom's private_key symbol: 'CARB' }); function wait(ms = 0) { return new Promise(res => setTimeout(res, ms)); } function randomFrom(lowerValue,upperValue) { return Math.floor(Math.random() * (upperValue - lowerValue + 1) + lowerValue); } async function mining() { let owner_account = 'ben'; const tr_bob = await u3_bob.contract(owner_account); const tr_tom = await u3_tom.contract(owner_account); // let amount = U3.format.UDecimalPad(2000,4); let heat_bob = randomFrom(15000,20000); let heat_tom = randomFrom(15000,20000); await tr_bob.recordHeat(heat_bob,{ authorization: [`bob@active`] }); await tr_tom.recordHeat(heat_tom,{ authorization: [`tom@active`] }); var d=new Date(); var t=d.toLocaleTimeString(); document.getElementById("bob_log").innerHTML = 'time :'+t+', heat value :'+ heat_bob; document.getElementById("tom_log").innerHTML = 'time :'+t+', heat value :'+ heat_tom; } mining(); var int=self.setInterval("mining()",30*1000); </script> </head> <body> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>节能设备1</h1> <p>账户:bob</p> <p>描述:通过节能产生CarbonCoin的节能设备1</p> <p id="bob_log"></p> </div> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>节能设备2</h1> <p>账户:tom</p> <p>描述:通过节能产生CarbonCoin的节能设备2</p> <p id="tom_log"></p> </div> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <button onclick="int=window.clearInterval(int)">stop</button> </div> </body> </html>

访问http://127.0.0.1:3001/mining.html,出现如下页面:

这里需要注意两点,

给energy智能合约发送数据时,需要持有该账户的私钥才可以签名并发送,这里为了简化编程将私钥直接写到了网页上,实际程序中需要对私钥进行保护和隐藏; 为简化编写,bob和tom的DApp程序直接写到了一个页面中,实际应该是两个DApp程序实例,分别运行中两个设备上;

进展到这里,我们的节能设备已经可以发送数据给智能合约了,而energy智能合约接受到数据后,会将数据传递给运营商服务器,由运营商服务器再次调用energy智能合约,兑换carboncoin,为此,我们需要建立EnergyServer;

Step3: EnergyServer 1. 建立事件监听并处理事件

本节,我们来处理上面的热量值数据上传事件

1.1 建立监听事件服务器代码

在CarbonDApp项目中bin目录下,新建一个文件msgBroker.js,输入如下代码:

const { createU3 , format} = require('u3.js/src'); const http = require('http'); const port = 3002; let u3 = createU3({ httpEndpoint: 'http://127.0.0.1:8888', httpEndpoint_history: 'http://127.0.0.1:3000', broadcast: true, debug: false, sign: true, logger: { log: console.log, error: console.error, debug: console.log }, chainId:'2616bfbc21e11d60d10cb798f00893c2befba10e2338b7277bb3865d2e658f58', keyProvider:'5JbedY3jGfNK7HcLXcqGqSYrmX2n8wQWqZAuq6K7Gcf4Dj62UfL',//ben's private key symbol: 'UGAS' }); let server = http.createServer((request, response) => { const { headers, method, url } = request; console.log(method); console.log(url); let body = []; request.on('error', (err) => { console.error(err); }).on('data', (chunk) => { body.push(chunk); }).on('end', () => { body = Buffer.concat(body).toString(); console.log("received msg:", body); if (body != null && body.trim()!="") { var obj = JSON.parse(body); let heatValue = JSON.parse(obj[1]).heat.split(",")[0]; let account = JSON.parse(obj[1]).heat.split(",")[1]; exchangeCarbonCoin(account,heatValue); } response.on('error', (err) => { console.error(err); }); response.statusCode = 200; response.setHeader('Content-Type', 'application/json'); response.write("ok"); response.end(); }); }); server.keepAliveTimeout = 0; server.timeout = 0; server.listen(port, function () { console.log((new Date()) + " Server is listening on port " + port); }); function wait(ms = 0) { return new Promise(res => setTimeout(res, ms)); } async function exchangeCarbonCoin(account,heat) { let SYMBOL = 'CARB'; let code = 'ben'; const tr = await u3.contract(code); let heatValue = format.DecimalPad(heat,4); const result = await tr.exchangeCarbonCoin('ben', account, heatValue+' ' + SYMBOL, 'test', { authorization: [`ben@active`] }); let tx = await u3.getTxByTxId(result.transaction_id); while (!tx.irreversible) { await wait(1000); tx = await u3.getTxByTxId(result.transaction_id); if (tx.irreversible) { console.log(tx); break; } } }

该代码在3002端口建立了一个服务,在节能设备上传其热量值后,智能合约会给该服务发送哪台节能设备上传了热量值数据,该服务调用energy智能合约的exchangeCarbonCoin方法进行CarbonCoin兑换。

1.2 建立监听事件服务器启动脚本

打开CarbonDApp/package.json文件,在scripts属性中添加如下代码:"event": "node ./bin/msgBroker"

1.3 启动监听服务器

刷新npm窗口,出现‘event’选项
双击event,完成服务器启动

1.4 在链上注册事件监听机制

通过ifconfig命令,查询本机内网地址,比如:10.20.8.32
在CarbonProject/test目录下,建立registerEvent.js文件;在文件中输入如下代码:

const U3Utils = require('u3-utils/dist/es5'); const { createU3, format, listener } = 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('Test cases', function() { it('event register', async () => { let account = 'ben'; const u3 = createU3(config); await u3.registerEvent(account, 'http:// 10.20.8.32:3002'); U3Utils.wait(1000); }); it('event unregister', async () => { let account = 'ben'; const u3 = createU3(config); await u3.unregisterEvent(account, ' http:// 10.20.8.32:3002'); U3Utils.wait(1000); }); });

注意修改注册地址为本机的内网地址,并且不能使用127.0.0.1。
运行event register测试用例,完成事件注册;

1.5 测试热量值兑换CarbonCoin功能

访问http://127.0.0.1:3001/mining.html页面,该页面每30秒,bob和tom两个账户都会随机发送15000到20000之间的一个随机值作为热量值给智能合约兑换CarbonCoin;
访问并刷新http://127.0.0.1:3001/index.html页面,可以发现bob和tom的CarbonCoin账户每30秒都在增加;

Step4: 完善DApp功能 1. 添加转账功能

节能设备获取到CarbonCoin后,可以将CarbonCoin出售给企业,比如航空公司等获利,简单体现就是设备账户可以转账给企业公司账户;

打开/CarbonDApp/index.html页面,添加代码

1.1 建立转账时使用的u3对象 U3 object used when establishing the transfer let u3_bob = U3.createU3({ keyProvider:'5JoQtsKQuH8hC9MyvfJAqo6qmKLm8ePYNucs7tPu2YxG12trzBt',//bob's private key }) let u3_tom = U3.createU3({ keyProvider:'5KXYYEWSFRWfNVrMPaVcxiRTjD9PzHjBSzxhA9MeQKHPMxWP8kU',//tom's private key }) 1.2 添加转账的js代码,用于调用相应的智能合约方法 async function sendCoin(from,to){ let SYMBOL = 'CARB'; let code = 'ben'; let coins = U3.format.DecimalPad(this.randomFrom(100,500),4); if (from == 'bob'){ const tr = await u3_bob.contract(code); const result = await tr.transfer(from, to, coins + ' ' + SYMBOL, 'sendCoin', { authorization: [`bob@active`] }); let tx = await u3_bob.getTxByTxId(result.transaction_id); while (!tx.irreversible) { await wait(1000); tx = await u3_bob.getTxByTxId(result.transaction_id); if (tx.irreversible) { alert("bob send coin success:"+ coins); break; } } } else if (from == 'tom'){ const tr = await u3_tom.contract(code); const result = await tr.transfer(from, to, coins + ' ' + SYMBOL, 'sendCoin', { authorization: [`tom@active`] }); let tx = await u3_tom.getTxByTxId(result.transaction_id); while (!tx.irreversible) { await wait(1000); tx = await u3_tom.getTxByTxId(result.transaction_id); if (tx.irreversible) { alert("tom send coin success:"+ coins); break; } } } } 1.3 在页面添加对sendcoin()方法的调用,更新如下代码: <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>节能设备1</h1> <p>账户:bob</p> <p>描述:通过节能产生CarbonCoin的节能设备1</p> <p id="bob_balance"></p> <p><a href="#" OnClick="sendCoin('bob','tony')">给航空公司C转账</a></p> </div> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>节能设备2</h1> <p>账户:tom</p> <p>描述:通过节能产生CarbonCoin的节能设备2</p> <p id="tom_balance"></p> <p><a href="#" OnClick="sendCoin('tom','jerry')">给汽车制造商D转账</a></p> </div> 1.4 访问页面并测试功能

访问:http://127.0.0.1:3001/index.html,点击“给航空公司C转账”按钮,等待一段时间后(最长约10秒,为Ultrain交易确认时间),出现如下提示,表示转账成功。

2.添加企业公司将CarbonCoin兑换为公益积分功能

企业公司通过将CarbonCoin兑换为公益积分,对降低碳排放作出自己的贡献,提升企业声誉:

打开/CarbonDApp/index.html页面,添加代码

2.1 建立积分兑换时使用的u3对象 let u3_tony = U3.createU3({ keyProvider:'5KbHvFfDXovPvo2ACNd23yAE9kJF7Mxaws7srp6VapjMr7TrHZB',//tony's private key }) let u3_jerry = U3.createU3({ keyProvider:'5JFz7EbcsCNHrDLuf9VpHtnLdepL4CcAEXu7AtSUYfcoiszursr',//jerry's private key }) 2.2 添加兑换的js代码,用于调用相应的智能合约方法 async function exchangeScore(account){ let SYMBOL = 'CARB'; let code = 'ben'; let to = 'jack'; let coins = U3.format.DecimalPad(this.randomFrom(10,50),4); if (account == 'tony'){ const tr = await u3_tony.contract(code); const result = await tr.exchangeScore(account, to, coins + ' ' + SYMBOL, 'sendCoin', { authorization: [`tony@active`] }); let tx = await u3_tony.getTxByTxId(result.transaction_id); while (!tx.irreversible) { await wait(1000); tx = await u3_tony.getTxByTxId(result.transaction_id); if (tx.irreversible) { alert("tony buy score success:"+ coins); break; } } } else if (account == 'jerry'){ const tr = await u3_jerry.contract(code); const result = await tr.exchangeScore(account, to, coins + ' ' + SYMBOL, 'sendCoin', { authorization: [`jerry@active`] }); let tx = await u3_jerry.getTxByTxId(result.transaction_id); while (!tx.irreversible) { await wait(1000); tx = await u3_jerry.getTxByTxId(result.transaction_id); if (tx.irreversible) { alert("jerry buy score success:"+ coins); break; } } } } 2.3 在页面添加对exchangeScore()方法的调用,更新如下代码: <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>航空公司C</h1> <p>账户:tony</p> <p>描述:大型航空公司,化石能源使用大户</p> <p id="tony_balance"></p> <p><a href="#" OnClick="exchangeScore('tony');return false;">购买公益积分</a></p> </div> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>汽车制造商D</h1> <p>账户:jerry</p> <p>描述:大型汽车制造商,化石能源使用大户</p> <p id="jerry_balance"></p> <p><a href="#" OnClick="exchangeScore('jerry');return false;">购买公益积分</a></p> </div> 2.4 访问页面并测试功能

访问:http://127.0.0.1:3001/index.html 点击“购买公益积分”按钮,等待一段时间后(最长约10秒,为Ultrain交易确认时间),出现如下提示,表示积分兑换成功,同时可以看到页面最下方的CarbonCoin销毁账户中,销毁的CarbonCoin数量增多了。

3.查询公益积分兑换历史

通过查询公益积分的兑换历史,我们可以建立一个企业公益积分的排行榜

3.1 建立页面

在CarbonDApp/public/目录中,新建leaderboard.html,输入如下代码:

<html> <head> <meta charset="UTF-8"> <title>leaderboard</title> <link rel="stylesheet" href="/stylesheets/style.css"> <script src="./javascripts/u3.js"></script> <script> let u3 = U3.createU3({ httpEndpoint: 'http://127.0.0.1:8888', httpEndpoint_history: 'http://127.0.0.1:3000', broadcast: true, debug: false, sign: true, logger: { log: console.log, error: console.error, debug: console.log }, chainId:'2616bfbc21e11d60d10cb798f00893c2befba10e2338b7277bb3865d2e658f58', symbol: 'UGAS' }); function wait(ms = 0) { return new Promise(res => setTimeout(res, ms)); } async function getLeaderboard() { let owner_account = 'ben'; const tr = await u3.contract(owner_account); const result = await u3.getAllTxs(1,10000000,{"actions.name":"exchangeScore"},{_id:-1}); //console.log(result); let content = ''; for ( let i = 0 ;i< result.results.length;i++){ content = content + '<tr><td>'+ result.results[i].actions[0].data.from+":"+result.results[i].actions[0].data.quantity +'</td></tr>'; } document.getElementById("leaderboard").innerHTML = content; } getLeaderboard(); </script> </head> <body> <div style="padding:8px;border:1px solid #96c2f1;background:#eff7ff"> <h1>leaderboard</h1> <table id="leaderboard"> </table> </div> </body> </html>

我们通过

u3.getAllTxs(1,10000000,{"actions.name":"exchangeScore"},{_id:-1});

方法,查询到所有积分兑换的交易,将其具体数据打印到页面。

3.2 运行页面

访问http://127.0.0.1:3001/leaderboard.html,可以看到如下页面:

这里展示了所有积分兑换的历史记录。

总结

本教程系统的介绍了如何编写一个端到端的网页版DApp应用,虽然在编写的过程中做了相应的化简,但总体来说描绘了DApp应用开发的全貌,开发者可以做这个框架的基础上,丰富并完善自己的DApp应用。
关于在iOS和Android上开发DApp,可以将u3.js整合到相应的混合Native开发框架中实现,其原理与网页版DApp一致。u3.deploy('build/MyContract', 'ben')中,MyContract修改为energy
at u3.deploy('build/MyContract', 'ben') , MyContract changed to energy;
通过查看config.js可以发现,我们通过“ben”账户的私钥,将该智能合约部署到httpEndpoint指定的地址下的chainId指定的链上;

read more
新版Ultrain浏览器上线,终于可以查Token啦!
I

Ultrain测试网上线后,很多开发者尝试写了合约,并用Ultrain浏览器进行了合约的部署操作。其中最热门的就是发Token合约,大家也想亲身体验发Token的快感!连Ultrain团队的的好几个程序员都迫不及待地进行创建账户、申请资源、写合约、部署合约、调用合约等一系列流程,尝试发Token。

可是操作完,大家向产品经理抱怨:通过「超脑浏览器」对Token合约部署、调用操作之后,在浏览器找不到自己的Token信息。于是产品经理第一时间拉着视觉、开发们,完成了测试网超脑浏览器的Token查询的设计,同时新版的Ultrain浏览器也在很多方面做了优化,继续往下看吧!

1、新增首页数据
打开新版Ultrain浏览器,首页除了全新的视觉设计,一眼就能看到块高、交易数、账户数、合约数、Token数的信息,帮助用户直观地了解超脑链最新数据。

2、Token查询
部署完Token合约,直接在超脑浏览器就可以查询到Token信息,超方便啦!

3、查看账户内Token信息
除了Token列表,在自己的账户内也可以查看账户拥有的Token信息啦。不用手动搜索,浏览器自动同步账户内Token信息,很贴心了哦~

4、无线端兼容
新版的整体视觉做了优化,包括列表页、详情页、创建账户流程、合约部署和调用流程等,同时也特别对无线端做了兼容处理,让用户在手机上操作时更舒服。

5、其他
开发还在性能上、缓存策略上进行了优化,使页面加载速度有了明显提升。

Ultrain浏览器的地址(https://explorer.ultrain.io/),你也来试一试吧!有问题的话,可以到开发者门户-论坛(https://bbs.ultrain.io/)给我们留言哦!

read more
There are no topics in this category.
Why don't you try posting one?