教程:在以太坊区块链上创建体育博彩dApp(第2部分)

大家好,欢迎回来。 我们开始开发的体育博彩dApp中的2个(第1部分在此处提供)。 这部分将涵盖使用React框架的所有前端开发。


让我们从列出前端所需的不同元素开始:一个div显示有关团队A的信息,一个div显示有关团队B的信息。每个div还将包含一个输入和一个按钮,以允许用户投注他最喜欢的球队。

我们还将创建一个按钮来触发“ A队获胜”或“ B队获胜”事件,以将以太币重新分配给获胜者。 对于真实的应用程序,可以将这些事件链接到运动结果API以自动更新获胜者团队。

由于我们使用React,因此我们将每个部分划分为不同的组件:

  • App.js已经是首页,将包含有关dApp的常规信息,并导入其他组件。
  • TeamA.jsx ,将包含团队A的元素
  • TeamB.jsx ,将包含团队B的元素

我个人使用 .jsx 格式,但可以随意使用自己喜欢的格式

在终端上进入bet-eth主文件夹,然后键入

cd src访问src文件夹,然后touch TeamA.jsx && touch TeamB.jsx以创建两个文件。

还记得web3吗? 我们在零件安装期间。 教程1,现在我们需要它来使前端与区块链进行交互。 为了加载它,我们将创建一个javascript文件。 我们还将创建一个文件夹,以将在本教程的第一部分中创建的已编译智能合约放入其中。

仍在src文件夹中,输入终端mkdir utils并使用touch utils/getWeb3.js创建getWeb3.js文件。 然后在src中创建一个名为Contracts的文件夹: mkdir contracts ,并将合同从build / contracts复制到此文件夹,如下图所示。 (因为无法使用React-create-app访问src目录之外的文件)

使用您喜欢的编辑器打开getWeb3.js文件,并插入相同的代码,以使您的网站能够检测到您正在使用的钱包:在本例中为Metamask。

 从“ web3”导入Web3,让getWeb3 = new Promise(function(resolve,reject){ 
//等待加载完成,然后再加载web3,以确保它是
//已经注入
window.addEventListener('load',function(){
var结果
var web3 = window.web3
//检查浏览器MetaMask是否已注入Web3
如果(typeof web3!=='未定义'){
//使用MetaMask的提供程序。
web3 =新的Web3(web3.currentProvider)结果= {
web3:web3
}
console.log('已检测到注入的web3。');
解决(结果)
}其他{
//如果未检测到web3,则将加载本地web3提供程序。
var provider = new Web3.providers.HttpProvider('http://127.0.0.1:9545')
web3 =新的Web3(提供者)
结果= {
web3:web3
}
console.log('未使用本地web3注入任何web3实例。');
解决(结果)
}
})
})导出默认的getWeb3

现在,从智能合约到web3的所有内容都已配置完毕,让我们开始通过编辑/ src中的App.js来编辑主页。

我们将从导入所需的内容开始: react-bootstrap ,两个称为TeamA.jsxTeamB.jsx的 Team组件以及用于加载web3的getWeb3.js文件。 在App.js顶部的导入中编写下一行:

   './utils/getWeb3.js' 导入 getWeb3; 
'react-bootstrap' 导入 {Grid,Row,Col};
'./TeamA.jsx' 导入 TeamA;
'./TeamB.jsx' 导入 TeamB;

您会注意到,在默认的App.js文件中,有一个App类,其中包含一个render()函数。 对于未使用React进行初始化的用户,函数render 用于编写html并将其呈现在Web页面上 。 在对此进行编辑之前,我们将从类的构造函数开始编写一些新函数:

 从'react'导入React,{组件}; 
从'./logo.svg'导入徽标;
导入'./App.css';从'./utils/getWeb3.js'导入getWeb3;
从'react-bootstrap'导入{Grid,Row,Col};
从'./TeamA.jsx'导入TeamA;
从'./TeamB.jsx'导入TeamB;类App扩展了组件{
超(); //每个构造函数都需要使用this,以允许使用'this' //我们在组件状态中定义了我们需要的两个变量,因此可以对其进行更新
this.state = {
web3:“,
地址: '',
};
} render(){
返回(


“

Bet-eth





);
}
}导出默认应用;

在构造函数之后,我们将使用React生命周期函数中的ComponentDidMount()函数。 挂载组件后立即调用此函数,我们可以使用它来加载Web3。

我们将通过兑现承诺来做到这一点。 承诺是异步任务,我们可以将它们链接起来以按选定的顺序执行所需的任务。 在这条链上,我们有两个承诺:第一个将web3加载到也称为web3的状态变量中(也保存了用户地址),第二个则调用getAmount()函数。

来自developer.mozilla.org的架构
 从'react'导入React,{组件}; 
从'./logo.svg'导入徽标;
导入'./App.css';从'./utils/getWeb3.js'导入getWeb3;
从'react-bootstrap'导入{Grid,Row,Col};
从'./TeamA.jsx'导入TeamA;
从'./TeamB.jsx'导入TeamB;类App扩展了组件{
构造函数(){
超();
this.state = {
web3:“,
地址: '',
};
} componentDidMount(){
getWeb3.then(结果=> {
/ *获取web3后,我们通过以下方式保存web3用户的信息:
编辑组件的状态变量* /
results.web3.eth.getAccounts((error,acc)=> {
//this.setState用于编辑状态变量
this.setState({
地址:acc [0],
web3:results.web3
})
});
})。catch(()=> {
//如果未找到任何web3提供程序,则将其记录在控制台中
console.log('查找web3时出错。')
})
} render(){
返回(


“

Bet-eth





);
}
}导出默认应用;

添加一些元素

让我们编辑render()函数以显示页面的某些元素。

当我们使用react-bootstrap时,我们需要在html页面上导入样式表,就像在基本的html网站上那样。 呈现react组件的html页面是 index.html ,位于 public / 文件夹中。 打开并添加:

  <link rel =“ stylesheet” href =“ https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css ”完整性=“ sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va + PmSTsz / K68vbousEjh” > 

块之间。

让我们回到index.js并编辑位于render()函数中的html代码 我添加了一个简单的短语来表示网站,显示用户钱包地址的行(可以调用状态变量地址)和包含两列的行, 对应一个团队 。 我们将在这些行中导入每个团队组件(TeamA和TeamB)。

 从'react'导入React,{组件}; 
从'./logo.svg'导入徽标;
导入'./App.css';从'./utils/getWeb3.js'导入getWeb3;
从'react-bootstrap'导入{Grid,Row,Col};
从'./TeamA.jsx'导入TeamA;
从'./TeamB.jsx'导入TeamB;类App扩展了组件{
构造函数(){
超();
this.state = {
web3:“,
地址: '',
};
} componentDidMount(){
getWeb3.then(结果=> {
/ *获取web3后,我们通过以下方式保存web3用户的信息:
编辑组件的状态变量* /
results.web3.eth.getAccounts((error,acc)=> {
//this.setState用于编辑状态变量
this.setState({
地址:acc [0],
web3:results.web3
})
});
})。catch(()=> {
//如果未找到任何web3提供程序,则将其记录在控制台中
console.log('查找web3时出错。')
})
} render(){
返回(

“

Bet-eth




欢迎来到我的以太坊博彩网站

您的电子钱包地址为{this.state.address}

{/ *我们定义一个网格* /}

{/ *对应于class =“ row” * /}

{/ *我们定义了两列。 引导网格除以12
因此,如果我们需要两列,则它们将各分为6个部分* /}
A {/ *我们将在此处导入Team A组件* /}
B {/ *我们将在此处导入Team B组件* /}


);
}
}导出默认应用;

完成所有这些步骤后,网站应如下所示(在终端上键入npm start来启动它),并显示您当前的钱包地址:

首页

如果看不到任何地址,请确保您已正确登录Metamask。

让我们为第一个团队Team A创建组件:打开TeamA.jsx。

此组件需要四个不同的功能:

  • Bet() ,允许玩家下注
  • getAmount() ,查看团队中已经有多少个以太币
  • setWinner(),设置获胜团队
  • handleInputChange() ,用于控制用户在其输入要下注值的位置的输入。

与往常一样,让我们​​从导入文件顶部所需的内容开始:

   'react' 导入 React,{组件}; 
'react-bootstrap' 导入 {Grid,Row,Col};
'./utils/getWeb3.js' 导入 getWeb3;
'./contracts/Betting.json' 导入 BettingContract;
导入 './App.css';

App.js唯一值得注意的区别是我们还导入了智能合约,因此我们可以与其进行交互。

导入之后,让我们在类中定义React组件:

 从'react'导入React,{组件}; 
从'react-bootstrap'导入{Grid,Row,Col};
从'./utils/getWeb3.js'导入getWeb3;
从'./contracts/Betting.json'导入BettingContract;
导入'./App.css'; TeamA类扩展了Component { } 导出默认TeamA;

然后,对于App.js,让我们用所需的不同状态变量编写构造函数:

  • web3 ,用于存储web3实例。
  • Amount ,用来存储当前一支球队的总数。
  • InputAmount ,用于存储玩家想要下注的以太数量。
  • weiConversion ,将weis转换为以太并具有更好的用户体验(1 ETH = 1000000000000000000 wei)
 构造函数(){ 
超();
this.state = {
web3:“,
金额:“”,
InputAmount:'',
wei转换:1000000000000000000
}
}

下一步是使用生命周期ComponentDidMount()函数加载web3 。 至于App.js,promise是链式的。

第一个承诺返回已加载的web3。 在第二个承诺中,将调用getAmount()函数,并传递结果var(包含web3),以便我们可以在getAmount()函数中使用它。

为什么将变量传递给函数而不使用刚刚保存的web3状态变量? 因为当我们调用函数时仍处于ComponentDidMount()中,所以TeamA组件的状态不会更新,因此调用this.state.web3仍将返回null

  componentDidMount(){ 
getWeb3.then(结果=> {
/ *获取web3后,我们通过以下方式保存web3用户的信息:
编辑组件的状态变量* /
results.web3.eth.getAccounts((error,acc)=> {
//this.setState用于编辑状态变量
this.setState({
web3:results.web3
})
});
//在第一个承诺的末尾,我们返回加载的web3
返回results.web3
})。then(结果=> {
//在下一个承诺中,我们将web3(结果)传递给getAmount函数
this.getAmount(结果)
})。catch(()=> {
//如果未找到任何web3提供程序,则将其记录在控制台中
console.log('查找web3时出错。')
})
}

现在让我们编写getAmount函数:

  getAmount( web3 ){ 
//获取合同
const contract = require('truffle-contract');
const Betting =合同(BettingContract);
Betting.setProvider( web3 .currentProvider);
var BettingInstance;
web3 .eth.getAccounts((错误,帐户)=> {
Betting.deployed()。then((instance)=> {//在承诺中实例化合同
BettingInstance = instance})。then((result)=> {
//调用智能合约的AmountOne函数
返回BettingInstance.AmountOne.call({from:accounts [0]})
})。then((result)=> {
//然后将返回的值存储在Amount状态var中。
//除以10000即可转换为以太币。
this.setState({
数量:result.c / 10000
})
});
})
}

如您所见,我们使用将函数调用到ComponentDidMount()函数时传递的web3变量。

首先,我们获取合同并向其加载松露合同。 然后,将合同的提供者设置为当前已加载的提供者(在本例中为Metamask)。 在第一个Javascript Promise中,我们实例化合同,然后在下一个Javascript Promise中,从智能合同中调用AmountOne()函数。 我们使用from: accounts[0]因此该函数由您使用的Metamask帐户调用。
然后,在最后一个中,我们使用智能合约功能给出的结果编辑金额状态值。

在上面的函数中 使用 this 时,我们需要将其绑定到构造函数中,因为 当我们调用 this.setState ,我们希望 this 引用App,而不是函数

因此,添加以下行: this.getAmount = this.getAmount.bind(this); 在TeamA.jsx的构造函数中。 对于接下来要编写的所有功能,我们都必须这样做。


Bet()函数将用于下注团队,与getAmount函数非常相似:

 打赌(){ 
const contract = require('truffle-contract');
const Betting =合同(BettingContract);
Betting.setProvider(this.state.web3.currentProvider);
var BettingInstance;
this.state.web3.eth.getAccounts((错误,帐户)=> {
Betting.deployed()。then((instance)=> {
BettingInstance =实例
})。then((result)=> {
//从合同中获取价值以证明其有效。
return BettingInstance.bet(1,{来自:accounts [0],
值:this.state.InputAmount})
})。catch(()=> {
console.log(“投注错误”)
})
})
}

首先,合同被加载,实例化,然后我们从智能合同中调用所需的函数:此处为“ bet”。 此处的调用有所不同,因为我们有一个传递给函数的参数。 正如我们在第1部分前面所定义的那样,整数“ 1 ”定义团队A ,而整数“ 2 ”定义团队B。 因此,当我们想在此函数中押注A队时 ,我们将在函数的参数中传递“ 1 ”。 第二个区别是我们将一个值传递给函数,以将以太币发送给合约。 我们使用value : this.state.InputAmount因为该值存储在状态变量InputAmount中

不要忘记添加 this.bet = this.bet.bind(this); 在构造函数中


MakeWin()函数将用于使团队获胜,并测试我们的应用程序是否运行良好(例如,该函数可能由返回游戏结果的API触发,但我们将为其按钮)

  MakeWin(){ 
const contract = require('truffle-contract');
const Betting =合同(BettingContract);
Betting.setProvider(this.state.web3.currentProvider);
var BettingInstance;
this.state.web3.eth.getAccounts((错误,帐户)=> {
Betting.deployed()。then((instance)=> {
BettingInstance =实例
})。then((result)=> {
返回BettingInstance.distributePrizes(1,{来自:帐户[0]})
})。catch(()=> {
console.log(“分配奖品时出错”)
})
})
}

该函数仍然与其他函数相似,我们在参数中传递“ 1 ”,因为它是Team A。

不要忘记添加 this.MakeWin = this.MakeWin.bind(this); 在构造函数中。


最后,我们必须编写一个小函数HandleInputChange() ,以根据用户在输入中的内容来更改状态变量InputAmount的值(即某人要押在团队中的金额)。

  handleInputChange(e){ 
this.setState({InputAmount:e.target.value * this.state.weiConversion});
}

这是一个非常简单的函数,但是仅当您照常将其绑定到构造函数时才可以使用: this.handleInputChange = this.handleInputChange.bind(this);


现在我们可以编写render()函数,以及与我们之前编写的函数进行交互的所有组件:

  render(){ 
返回(

A队


总金额: {this.state.Amount} ETH




输入下注金额


<input type =“ text” className =“ form-control” onChange = {this.handleInputChange}必需的样式=“ [0-9] * [。,] [0-9] *” />
ETH



<button onClick = {this.Bet} >投注




<button onClick = {this.MakeWin} >使该团队获胜

)}

如您所见,有:

  • 关于下注总金额的信息
  • 用于编辑用户想要下注的值的输入(必需的模式仅允许用户下注int或float)
  • 下注按钮,调用Bet()函数
  • 使团队获胜的按钮,调用MakeWin()函数

团队A组件现已完成。 返回到render。函数中的App.js。 在应该出现A组div的列中,编写

运行npm start来查看结果:

您可以像在上面的屏幕截图中以0.2以太进行的操作一样,对团队下注。 如果您用所需的金额填写输入,然后单击“投注”,则将打开“ Metamask”界面:将汽油费编辑为大约100–200 GWEI,然后单击“确认”。

Metamask界面,法语。

交易确认后(花了我15秒钟),它会在Metamask上显示为:

这是TeamA.jsx的完整代码

团队B组件与团队A几乎相同 ,您只需调用与团队A相同的功能即可,但对于另一个团队, 只需AmountOne()替换为AmountTwo()并将Bet( )MakeWin()函数。

这是TeamB.jsx代码

让我们像对TeamA一样在App.js中导入它, 方法是在右栏中编写 ,并且dApp已经结束!

我们美丽的网站

您可以使用不同的帐户玩游戏,使其更漂亮,甚至将其部署在您的服务端上,以便您可以与朋友一起玩(并且不要忘记在下注后重新加载页面)

完整的项目可以在这里找到:

https://github.com/StephaneGcrd/bet-eth


如果您有任何问题或疑问,请随时与我联系,我会很乐意回答🙂