Retric
欢迎打赏。BTC:1MibfK26s4u8GBLEAsTy82uVEBPBUG4z3F
写了 327 篇文章
如何用Truffle, Oraclize, ethereum-bridge和Webpack创建Dapp
发表于 2018-06-04 14:26:19
看了那么多dapp,想不想自己亲手做一个?这篇文章的作者Lander Willems示范了如何从零开始开发一个可以显示coinbase余额的简单demo,非常适合拿来作为dapp之旅的第一次接触。

本文译者为Orangefans社区的robbinhan。robbinhan搞了10年开发,喜欢新技术,也是个摄影小白。Orangefans社区由一群真正对区块链感兴趣的小伙伴组成,如果你想和我们一起翻译学习国外优质文章,可以在本文下面评论说明,或者在微信后台联系我们。

这是一篇通过前端展示智能合约调用外部API数据的教程。

关于这篇教程

通过这篇教程你会开发一个Dapp,这个Dapp是通过智能合约是使用Oraclize调用coinbase的API获得数据。

我们会:

  • 部署一个智能合约
  • 在本地安装ethereum-bridge来和Oracle通信
  • 使用Oracle从coinbase的API获取数据
  • 通过前端展示获取到的API数据
  • 运行在本地开发环境

关于我

我叫Lander,来自比利时,我之前我没有涉猎Solidity的时候在TeamHut工作,它是一个帮助freelancers的SaaS平台。

前提条件

  • 有些前端基础
  • 你正好在学习Oraclize和Solidity的Events

我们要做什么

我们将会写一个使用本地运行的Oracle,来从外部API获取数据的智能合约,我们通过ethereum-bridge来模拟Oracle。合约从Coinbase的API获取ETH对美元的价格。

同时我们会创建一个HTML页面来展示获取到的数据,并且实时更新它。

使用到的工具

  • git
  • npm
  • Truffle, Truffle webpack box, oraclize-api
  • ethereum-bridge
  • javascript,webpack

Oracle是什么?

智能合约能够执行计算并存储和检索数据。 因为每个节点每次都会执行计算,所以从Ethereum合约中进行网络请求是不实际的(也是不可能的)。 Oracles通过监听区块链查看事件并通过将查询结果发布回合约来填补这一空白。 通过这种方式,合同可以与外链世界进行互动。

Oracles将信息发送到智能合约中,智能合约无需直接访问网络外部的信息,从而减轻工作量。 Oracles通常由第三方提供,并由使用它们的公司授权。

开始

以下所有执行命令请保证nodejs版本是LTS版本,亲测在10版本无法执行成功

  1. 安装Truffle

npm install -g truffle

  1. 创建一个目录

mkdir oraclize-test

  1. 使用webpack初始化

cd oraclize-test truffle unbox webpack

  1. 添加oraclize-api

truffle install oraclize-api

  1. 启动truffle测试网络

truffle develop

  1. 安装ethereum-bridge

git clone https://github.com/oraclize/ethereum-bridge cd ethereum-bridge npm install

  1. 启动ethereum-bridge

node bridge -a 9 -H 127.0.0.1 -p 9545 --dev

参数的含义:

-a:truffle develop启动时候的第几个账号的索引(develop启动的时候默认会创建10个账号,索引是从0-9的,这里9代表使用最后一个账号)。

-H:truffle develop运行的IP地址

-p: truffle develop运行的端口

—dev:启用开发模式

注意这行:

OAR = OraclizeAddrResolverI(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475)

现在你的环境已经准备完毕。

写一个Solidity智能合约

在oraclize-test目录下的contracts目录,创建一个OraclizeTest.sol文件,拷贝一下代码,注意修改其中的OAR变量

pragma solidity ^0.4.21;
import "installed_contracts/oraclize-api/contracts/usingOraclize.sol";

contract OraclizeTest is usingOraclize {

address owner;
string public ETHUSD;

event LogInfo(string description);
event LogPriceUpdate(string price);
event LogUpdate(address indexed _owner, uint indexed _balance);

// Constructor
function OraclizeTest()
payable
public {
owner = msg.sender;

emit LogUpdate(owner, address(this).balance);

// Replace the next line with your version:
OAR = OraclizeAddrResolverI(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475)

oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS);
update();
}

// Fallback function
function()
public{
revert();
}

function __callback(bytes32 id, string result, bytes proof)
public {
require(msg.sender == oraclize_cbAddress());

ETHUSD = result;
emit LogPriceUpdate(ETHUSD);
update();
}

function getBalance()
public
returns (uint _balance) {
return address(this).balance;
}

function update()
payable
public {
// Check if we have enough remaining funds
if (oraclize_getPrice("URL") > address(this).balance) {
emit LogInfo("Oraclize query was NOT sent, please add some ETH to cover for the query fee");
} else {
emit LogInfo("Oraclize query was sent, standing by for the answer..");

// Using XPath to to fetch the right element in the JSON response
oraclize_query("URL", "json(https://api.coinbase.com/v2/prices/ETH-USD/spot).data.amount");
}
}

}

合约的逻辑是:构造方法OraclizeTest初始化的时候先设置owner,再调用update方法,update方法中会检查账户余额是否足够,然后发送请求。__callback方法会在请求完成后由Oraclize回调。

更新Truffle配置

修改truffle.js

module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
networks: {
development: {
host: "localhost",
port: 9545,
network_id: "*", // Match any network id
gas:500000,
},
}
};

更新Truffle migrations

修改oraclize-test/migrations/2_deploy_contracts.js

var OraclizeTest = artifacts.require("./OraclizeTest.sol");

module.exports = function(deployer, network, accounts) {
// Deploys the OraclizeTest contract and funds it with 0.5 ETH
// The contract needs a balance > 0 to communicate with Oraclize
deployer.deploy(
OraclizeTest,
{ from: accounts[9], gas:6721975, value: 500000000000000000 });
};

创建前端页面

1、修改app目录下的index.html文件

<!DOCTYPE html>
<html>
<head>
<title>Basic Truffle Webpack Oraclize ÐAPP w/ Frontend & Coinbase API communication.</title>

<script src="./app.js"></script>

<link href="https://fonts.googleapis.com/css?family=Roboto:400,700" rel="stylesheet">
</head>

<body>
<div id="container">
<h1 id="title">Oraclize</h1>
<h2> ÐApp</h2>

<div id="row">
<div>
<h3>ÐAPP BALANCE</h3>

<span><h1 id="balance">0.000000</h1> ETH</span>
</div>

<div>
<h3>USD VALUE</h3>
<span><h1 id="total">0.00</h1> USD</span>
</div>
</div>

<span id="status"></span>

<br />

<div>
<div><strong>Hint:</strong> open the browser developer console to view any errors and warnings.</div>
<div><strong>ETH/USD</strong> rates from <a href="https://api.coinbase.com/v2/prices/ETH-USD/spot">Coinbase</a>.</div>

<div><strong>Made by:</strong> <a href="https://twitter.com/WWWillems">WWWillems</a></div>
</div>
</div>
</body>
</html>

2、修改javascripts/app.js

// Import the page's CSS. Webpack will know what to do with it, 
// as it's been configured by truffle-webpack
import "../stylesheets/app.css";

// Import libraries we need.
import { default as Web3} from 'web3';
import { default as contract } from 'truffle-contract'

// Import our contract artifacts and turn them into usable abstractions.
// Make sure you've ran truffle compile first
import contract_build_artifacts from '../../build/contracts/OraclizeTest.json'

// OraclizeContract is our usable abstraction, which we'll use through the code below.
var OraclizeContract = contract(contract_build_artifacts);

var accounts;
var account;

window.App = {
currentBalance: 0,
ethPriceinUSD: 0,

// 'Constructor'
start: function() {
var self = this;

// Bootstrap the Contract abstraction for use with the current web3 instance
OraclizeContract.setProvider(web3.currentProvider);

// Get the initial account balance so it can be displayed.
web3.eth.getAccounts(function(err, accs) {
if (err != null) {
alert("There was an error fetching your accounts.");
return;
}

if (accs.length == 0) {
alert("Couldn't get any accounts! Make sure your Ethereum client is configured correctly.");
return;
}

accounts = accs;
account = accounts[0];

self.refreshBalance();
});
},

// Show an error
setStatus: function(message) {
var status = document.getElementById("status");
status.innerHTML = message;
},

// Opens a socket and listens for Events defined in our contract.
addEventListeners: function(instance){
var LogCreated = instance.LogUpdate({},{fromBlock: 0, toBlock: 'latest'});
var LogPriceUpdate = instance.LogPriceUpdate({},{fromBlock: 0, toBlock: 'latest'});
var LogInfo = instance.LogInfo({},{fromBlock: 0, toBlock: 'latest'});

//
LogPriceUpdate.watch(function(err, result){
if(!err){
App.ethPriceinUSD = result.args.price;
App.showBalance(App.ethPriceinUSD, App.currentBalance);
}else{
console.log(err)
}
})

// Emitted when the Contract's constructor is run
LogCreated.watch(function(err, result){
if(!err){
console.log('Contract created!');
console.log('Owner: ' , result.args._owner);
console.log('Balance: ' , web3.fromWei(result.args._balance, 'ether').toString(), 'ETH');
console.log('-----------------------------------');
}else{
console.log(err)
}
})

// Emitted when a text message needs to be logged to the front-end from the Contract
LogInfo.watch(function(err, result){
if(!err){
console.info(result.args)
}else{
console.error(err)
}
})
},

refreshBalance: function() {
var self = this;

var meta;

OraclizeContract.deployed().then(function(instance) {
meta = instance;

App.addEventListeners(instance);

return meta.getBalance.call(account, {from: account});
}).then(function(value) {
App.currentBalance = web3.fromWei(value.valueOf(), 'ether');
App.showBalance(App.ethPriceinUSD, App.currentBalance);
}).catch(function(e) {
console.log(e);
self.setStatus("Error getting balance; see console log.");
});
},

showBalance: function(price, balance){
// Balance updated, start CSS animation
var row = document.getElementById('row');
row.style.animation = 'heartbeat 0.75s';

// Removes CSS animation after 1100 ms
setTimeout(function(row){
var row = document.getElementById('row');
row.style.animation = null;
}, 1100)

var balance_element = document.getElementById("balance");
// Rounding can be more precise, this is just an example
balance_element.innerHTML = parseFloat(balance).toFixed(6);

var total_element = document.getElementById("total");
total_element.innerHTML = (price * balance).toFixed(2);
}
};

// Front-end entry point
window.addEventListener('load', function() {
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (typeof web3 !== 'undefined') {
console.warn("Using web3 detected from external source. If you find that your accounts don't appear or you have 0 MetaCoin, ensure you've configured that source properly. If using MetaMask, see the following link. Feel free to delete this warning. :) http://truffleframework.com/tutorials/truffle-and-metamask")
// Use Mist/MetaMask's provider
window.web3 = new Web3(web3.currentProvider);
} else {
console.warn("No web3 detected. Falling back to http://127.0.0.1:9545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for development. More info here: http://truffleframework.com/tutorials/truffle-and-metamask");
// fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
window.web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:9545"));
}

// All systems go, start App!
App.start();
});

3、修改stylesheets/app.css

body {
margin-left: 25%;
margin-right: 25%;
margin-top: 10%;
font-family: "Open Sans", sans-serif;
}

label {
display: inline-block;
width: 100px;
}

input {
width: 500px;
padding: 5px;
font-size: 16px;
}

button {
font-size: 16px;
padding: 5px;
}

h1, h2 {
display: inline-block;
vertical-align: middle;
margin-top: 0px;
margin-bottom: 10px;
}

h2 {
color: #AAA;
font-size: 32px;
}

h3 {
font-weight: normal;
color: #AAA;
font-size: 24px;
}

.black {
color: black;
}

#balance {
color: black;
}

.hint {
color: #666;
}

body, html, p {
height: 100vh;
margin: 0;
padding: 0;
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #333333;

font-family: 'Roboto', sans-serif;
color: #ffc305;
}

a{
color: black;
}

#container{

}

#status {
font-size: 16pt;
color: red;
font-weight: bold;
margin: 5;
}

#row{
display: flex;
flex: 1;
flex-direction: row;
width: 650px;
z-index: 99999;

-webkit-box-shadow: 7px 3px 29px -2px rgba(11,12,15,0.27);
-moz-box-shadow: 7px 3px 29px -2px rgba(11,12,15,0.27);
box-shadow: 7px 3px 29px -2px rgba(11,12,15,0.27);

transition-timing-function: ease-in-out;
}

.yellow{
display: flex;
flex: 1;
width: 100%;
height: 150px;
max-height: 150px;
flex-direction: column;

color: black;
background-color: #ffc305;

align-items: center;
justify-content: center;
padding: 50px;
}

.yellow h3 {
color: black;
}

.dark{
display: flex;
flex: 1;
color: #ffc305;
background-color: black;
flex-direction: column;

align-items: center;
justify-content: center;
padding: 50px;

transform-origin: 50% 50%;
}

.dark h3{
color: #ffc305;
}

@keyframes heartbeat
{
0%
{
transform: scale( 1.1 );
}
20%
{
transform: scale( 1 );
}
40%
{
transform: scale( 1.1 );
}
60%
{
transform: scale( 1 );
}
80%
{
transform: scale( 1.1 );
}
100%
{
transform: scale( 1.1 );
}
}

部署合约并启动web服务

truffle compile truffle migrate --development --reset npm run dev

注意: 确保每次修改合约都重新编译和migrate

浏览器打开http://localhost:8080


欢迎打赏。BTC:1MibfK26s4u8GBLEAsTy82uVEBPBUG4z3F
写了 327 篇文章

评论

this comment section is using the amazing decentralized database engine - Gun.db

推荐阅读

如何用第一性原理创造新货币
谁来为区块链应用层的发展买单?| 预言家周报#45
Gitcoin:为项目发布eth币悬赏,促进开源软件的发展
谁才是DeFi的真实用户?
Coinbase 收购 Earn.com 的另一种理解
Cosmos最有意思的不是跨链,而是替代闪电网络?