本篇教程将指导开发者基于一个真实的商业场景,采用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指定的链上;