在整个加密货币市场的市值超过7000亿美圆以后,加密货币市场在过去几个月太疯狂了,但这只是一个开始。随着区块链系统的不断发展和扩展,进入这一新领域并利用这项技术的一个好方法是使用去中心化应用程序,也称为dApps。php
CryptoKitties以其使以太坊区块链拥挤而闻名,是dApp的一个很好的例子,它将可养殖和可收藏的概念与区块链相结合。这个耸人听闻的游戏只是一个创造性的例子,几乎有无限的机会。 虽然看似很是复杂,但已经开发出某些框架和工具来抽象你与区块链和智能合约的交互。在这篇博文中,我将经过一种方式在以太坊上建立一个去中心化的投票应用程序。我将简要介绍以太坊,但你可能应该对它有所了解,以便充分利用本指南。另外,我但愿你知道Javascript。css
从本质上讲,利用区块链技术的去中心化应用程序容许你在没有可信赖的第三方的状况下执行与今天相同的操做(如转移资金)。最好的dApp具备特定的真实世界的用例,以便利用区块链的独特特征。html
- 从本质上讲,区块链是一个共享的,可编程的,加密安全的,可信赖的分类帐本,没有任何一个用户能够控制,任何人均可以查询。- Klaus Schwab
即便投票应用对你们来讲可能不是一个伟大的应用程序,可是我选择使用它做为本指南,这是由于区块链解决的主要问题:透明度,安全性,可访问性,可信任,是困扰当前民主选举的主要问题。前端
因为区块链是去中心化的交易(投票)的永久记录,所以每次投票均可以无可辩驳地追溯到它发生的时间和地点,而不会泄露选民的身份。此外,过去的投票也不能被改变,而如今也不能被黑客攻击,由于每一个交易都是由网络中的每一个节点验证的。任何外部或内部攻击者必须控制51%的节点才能改变记录。java
即便攻击者可以在伪造用户输入真实身份证投票时也能实现这一点,但端到端投票系统可让选民验证他们的投票是否在系统中正确输入,这使得系统极其安全。node
我但愿你读本指南的其他部分前,了解了区块链和以太坊。这里有一个很棒的指南,写了我想让你知道的核心组件的简要概述。python
addresses
。为简单起见,咱们实际上不会构建我以前描述的完整投票系统。为了便于说明,它只是一个单页应用程序,用户能够输入他们的ID并为候选人投票。还将有一个按钮,计算并显示每一个候选人的投票数。jquery
这样,咱们将可以专一于在应用程序中建立智能合约并与之交互的过程。整个应用程序的源代码将在此存储库中,你须要安装Node.js和npm。android
npm install -g truffle
要使用Truffle命令,必须在现有项目中运行它们。webpack
git clone https://github.com/tko22/truffle-webpack-boilerplate cd truffle-webpack-boilerplate npm install
这个存储库只是一个Truffle Box的框架,它是能够在一个命令中得到的样板或示例应用程序 - truffle unbox [box name]
。可是,带有webpack的Truffle box未使用最新版本进行更新,并包含一个示例应用程序。所以,我建立了这个repo。
你的目录结构应包括如下内容:
contracts/
:包括全部合约的文件夹。不要删除Migrations.sol
。migrations/
:包含Migration files
的文件夹,可帮助你将智能合约部署到区块链中。src/
:保存应用程序的HTML/CSS和Javascript文件。truffle.js
:truffle配置文件。build/
:在编译合约以前,你不会看到此文件夹。此文件夹包含构建文件,所以不要修改任何这些文件!构建文件描述了合约的功能和体系结构,并提供了有关如何与区块链中的智能合约进行交互的Truffle Contracts和web3信息。设置和介绍完,让咱们开始写代码吧!首先,咱们将编写咱们的智能合约,这是用Solidity编写的(其余语言不那么受欢迎)。这可能看起来很不爽,但事实并不是如此。
对于任何应用程序,你但愿智能合约尽量简单,甚至是很是简单。请记住,你必须为你所作的每笔计算/交易付费,而你的智能合约将永远存在于区块链中。因此,你真的但愿它可以完美地运做——也就是说,它越复杂,就越容易犯错误。
咱们的合约将包括:
calls
区块链,这不会花费任何以太,由于你所作的更改将被销毁(当咱们实际进行transactions
和call
时,在下面会有更多内容)。Candidates
只会有他们的名字和党派,但你绝对能够为他们添加更多属性。这里没有列出更多类型,但有些类型稍微复杂一些。这五个包含了智能合约一般使用的大部分结构。这里将更深刻地解释这些类型。
做为参考,这是智能合约的代码。请注意,此文件应该被称为Voting.sol
但我但愿Github gist
具备style
,因此我给它一个.js扩展名。与本指南的其他部分同样,我将在代码中提供注释解释它正在作什么,而后我将在指出某些警告和逻辑的同时解释大致思路。
pragma solidity ^0.4.18; // written for Solidity version 0.4.18 and above that doesnt break functionality contract Voting { // an event that is called whenever a Candidate is added so the frontend could // appropriately display the candidate with the right element id (it is used // to vote for the candidate, since it is one of arguments for the function "vote") event AddedCandidate(uint candidateID); // describes a Voter, which has an id and the ID of the candidate they voted for struct Voter { bytes32 uid; // bytes32 type are basically strings uint candidateIDVote; } // describes a Candidate struct Candidate { bytes32 name; bytes32 party; // "bool doesExist" is to check if this Struct exists // This is so we can keep track of the candidates bool doesExist; } // These state variables are used keep track of the number of Candidates/Voters // and used to as a way to index them uint numCandidates; // declares a state variable - number Of Candidates uint numVoters; // Think of these as a hash table, with the key as a uint and value of // the struct Candidate/Voter. These mappings will be used in the majority // of our transactions/calls // These mappings will hold all the candidates and Voters respectively mapping (uint => Candidate) candidates; mapping (uint => Voter) voters; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * These functions perform transactions, editing the mappings * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ function addCandidate(bytes32 name, bytes32 party) public { // candidateID is the return variable uint candidateID = numCandidates++; // Create new Candidate Struct with name and saves it to storage. candidates[candidateID] = Candidate(name,party,true); AddedCandidate(candidateID); } function vote(bytes32 uid, uint candidateID) public { // checks if the struct exists for that candidate if (candidates[candidateID].doesExist == true) { uint voterID = numVoters++; //voterID is the return variable voters[voterID] = Voter(uid,candidateID); } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * Getter Functions, marked by the key word "view" * * * * * * * * * * * * * * * * * * * * * * * * * * */ // finds the total amount of votes for a specific candidate by looping // through voters function totalVotes(uint candidateID) view public returns (uint) { uint numOfVotes = 0; // we will return this for (uint i = 0; i < numVoters; i++) { // if the voter votes for this specific candidate, we increment the number if (voters[i].candidateIDVote == candidateID) { numOfVotes++; } } return numOfVotes; } function getNumOfCandidates() public view returns(uint) { return numCandidates; } function getNumOfVoters() public view returns(uint) { return numVoters; } // returns candidate information, including its ID, name, and party function getCandidate(uint candidateID) public view returns (uint,bytes32, bytes32) { return (candidateID,candidates[candidateID].name,candidates[candidateID].party); } }
基本上,咱们有两个Structs(包含多个变量的类型),用于描述选民和候选人。使用Structs,咱们能够为它们分配多个属性,例如电子邮件,地址等。
为了跟踪选民和候选人,咱们将它们放入单独的映射中,它们是整数索引的。候选人或选民的索引/密钥——让咱们称之为ID——是函数访问它们的惟一方式。
咱们还会跟踪选民和候选人的数量,这将有助于咱们为他们编制索引。此外,不要忘记第8行中的事件,该事件将在添加时记录候选人的ID。咱们的界面将使用此事件,由于咱们须要跟踪候选人的ID以便为候选人投票。
numCandidates
和numVoters
未声明为public。默认状况下,这些变量具备internal
可见性,这意味着它们只能由当前合约或派生合约直接访问(不用担忧,咱们不会使用它)。32bytes
用于字符串而不是使用string
类型。咱们的EVM具备32字节的字大小,所以它被optimized
以处理32字节的块中的数据。(当数据不是32字节的块时,编译器,例如Solidity,必须作更多的工做并生成更多的字节码,这实际上会致使更高的自然气成本。)完成咱们的智能合约后,咱们如今须要运行咱们的测试区块链并将此合约部署到区块链上。咱们还须要一种方法来与它交互,这将经过web3.js完成。
在咱们开始测试区块链以前,咱们必须在/contracts
文件夹中建立一个名为2_deploy_contracts.js
的文件,告诉它在迁移时包含你的投票智能合约。
var Voting = artifacts.require("Voting") module.exports = function(deployer) { deployer.deploy(Voting) }
要开始开发以太坊区块链,请转到命令行并运行:
truffle develop
因为Solidity是一种编译语言,咱们必须首先将其编译为字节码,以便EVM执行。
compile
你如今应该在目录中看到一个文件夹build/
。此文件夹包含构建文件,这对Truffle的内部工做相当重要,所以请勿修改它们!
接下来,咱们必须迁移合约。migrations是一个truffle脚本,可帮助你在开发时更改应用程序合约的状态。请记住,你的合约已部署到区块链上的某个地址,所以不管什么时候进行更改,你的合约都将位于不一样的地址。 迁移可帮助你执行此操做,还可帮助你移动数据。
migrate
恭喜!你的智能合约如今永远在区块链上。好吧,还不是真的...... 由于truffle develop会在每次中止时刷新。
若是你想拥有一个持久的区块链,能够考虑一下由Truffle开发的Ganache。若是你使用的是Ganache,则无需调用truffle develop。相反,你将运行truffle compile
和truffle migrate
。要了解在没有Truffle的状况下部署合约须要什么,请查看此博客文章。
一旦咱们将智能合约部署到区块链,咱们将不得不在应用程序启动时在浏览器上使用Javascript设置web3.0实例。所以,下一段代码将放在js/app.js
的底部。请注意,咱们使用的是web3.0版本0.20.1。
// When the page loads, we create a web3 instance and set a provider. We then set up the app window.addEventListener("load", function() { // Is there an injected web3 instance? if (typeof web3 !== "undefined") { console.warn("Using web3 detected from external source like Metamask") // If there is a web3 instance(in Mist/Metamask), then we use its provider to create our web3object window.web3 = new Web3(web3.currentProvider) } else { console.warn("No web3 detected. Falling back to http://localhost: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://localhost:9545")) } // initializing the App window.App.start() })
若是你不理解这段代码,你真的没必要太担忧。只要知道这将在应用程序启动时运行,并将检查浏览器中是否已存在web3实例(Metamask)。若是没有,咱们将建立一个与localhost:9545
交互Truffle开发区块链。
若是你正在使用Ganache,你必须将端口更改成7545
.一旦建立了一个实例,咱们将调用start
函数。
咱们须要作的最后一件事是为应用程序编写接口。这涉及任何Web应用程序的基本要素——HTML,CSS和Javascript(咱们已经编写了一些用于建立web3实例的Javascript)。首先,让咱们建立咱们的HTML文件。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> <title>Ethereum Voting Dapp</title> <!-- Bootstrap --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy" crossorigin="anonymous"> </head> <body> <div class="container"> <div class="row"> <div> <h1 class="text-center">Ethereum Voting Dapp</h1> <hr/> <br/> </div> </div> <div class="row"> <div class="col-md-4"> <p>Add ID and click candidate to vote</p> <div class="input-group mb-3"> <input type="text" class="form-control" id="id-input" placeholder="Enter ID"> </div> <div class="candidate-box"></div> <button class="btn btn-primary" onclick="App.vote()">Vote</button> <div class="msg"></div> </div> <div class="col-md-6"> <button class="btn btn-primary" onclick="App.findNumOfVotes()">Count Votes</button> <div id="vote-box"></div> </div> </div> </div> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/js/bootstrap.min.js" integrity="sha384-a5N7Y/aK3qNeh15eJKGWxsqtnX/wWdSZSKp+81YjTmS15nvnvxKHuzaWwXHDli+4" crossorigin="anonymous"></script> <!-- Custom Scripts --> <script src="app.js"></script> </body> </html>
这是一个很是简单的页面,带有用户ID的输入表单,以及用于投票和计票的按钮。点击这些按钮后,他们将调用投票的特定功能,并找到候选人的投票数。
可是有三个重要的div元素,其中有id:candidate-box
,msg
和vote-box
,它们分别包含每一个候选者的复选框,一条消息和一个投票数。咱们还导入了JQuery,Bootstrap和app.js
。
如今,咱们只须要与合约互动并实施投票和计算每一个候选人的投票数量的功能。JQuery将控制DOM,当咱们进行交易或调用Blockchain时,咱们将使用Promises。如下是app.js
的代码。
// import CSS. Webpack with deal with it import "../css/style.css" // Import libraries we need. import { default as Web3} from "web3" import { default as contract } from "truffle-contract" // get build artifacts from compiled smart contract and create the truffle contract import votingArtifacts from "../../build/contracts/Voting.json" var VotingContract = contract(votingArtifacts) /* * This holds all the functions for the app */ window.App = { // called when web3 is set up start: function() { // setting up contract providers and transaction defaults for ALL contract instances VotingContract.setProvider(window.web3.currentProvider) VotingContract.defaults({from: window.web3.eth.accounts[0],gas:6721975}) // creates an VotingContract instance that represents default address managed by VotingContract VotingContract.deployed().then(function(instance){ // calls getNumOfCandidates() function in Smart Contract, // this is not a transaction though, since the function is marked with "view" and // truffle contract automatically knows this instance.getNumOfCandidates().then(function(numOfCandidates){ // adds candidates to Contract if there aren't any if (numOfCandidates == 0){ // calls addCandidate() function in Smart Contract and adds candidate with name "Candidate1" // the return value "result" is just the transaction, which holds the logs, // which is an array of trigger events (1 item in this case - "addedCandidate" event) // We use this to get the candidateID instance.addCandidate("Candidate1","Democratic").then(function(result){ $("#candidate-box").append(`<div class='form-check'><input class='form-check-input' type='checkbox' value='' id=${result.logs[0].args.candidateID}><label class='form-check-label' for=0>Candidate1</label></div>`) }) instance.addCandidate("Candidate2","Republican").then(function(result){ $("#candidate-box").append(`<div class='form-check'><input class='form-check-input' type='checkbox' value='' id=${result.logs[0].args.candidateID}><label class='form-check-label' for=1>Candidate1</label></div>`) }) // the global variable will take the value of this variable numOfCandidates = 2 } else { // if candidates were already added to the contract we loop through them and display them for (var i = 0; i < numOfCandidates; i++ ){ // gets candidates and displays them instance.getCandidate(i).then(function(data){ $("#candidate-box").append(`<div class="form-check"><input class="form-check-input" type="checkbox" value="" id=${data[0]}><label class="form-check-label" for=${data[0]}>${window.web3.toAscii(data[1])}</label></div>`) }) } } // sets global variable for number of Candidates // displaying and counting the number of Votes depends on this window.numOfCandidates = numOfCandidates }) }).catch(function(err){ console.error("ERROR! " + err.message) }) }, // Function that is called when user clicks the "vote" button vote: function() { var uid = $("#id-input").val() //getting user inputted id // Application Logic if (uid == ""){ $("#msg").html("<p>Please enter id.</p>") return } // Checks whether a candidate is chosen or not. // if it is, we get the Candidate's ID, which we will use // when we call the vote function in Smart Contracts if ($("#candidate-box :checkbox:checked").length > 0){ // just takes the first checked box and gets its id var candidateID = $("#candidate-box :checkbox:checked")[0].id } else { // print message if user didn't vote for candidate $("#msg").html("<p>Please vote for a candidate.</p>") return } // Actually voting for the Candidate using the Contract and displaying "Voted" VotingContract.deployed().then(function(instance){ instance.vote(uid,parseInt(candidateID)).then(function(result){ $("#msg").html("<p>Voted</p>") }) }).catch(function(err){ console.error("ERROR! " + err.message) }) }, // function called when the "Count Votes" button is clicked findNumOfVotes: function() { VotingContract.deployed().then(function(instance){ // this is where we will add the candidate vote Info before replacing whatever is in #vote-box var box = $("<section></section>") // loop through the number of candidates and display their votes for (var i = 0; i < window.numOfCandidates; i++){ // calls two smart contract functions var candidatePromise = instance.getCandidate(i) var votesPromise = instance.totalVotes(i) // resolves Promises by adding them to the variable box Promise.all([candidatePromise,votesPromise]).then(function(data){ box.append(`<p>${window.web3.toAscii(data[0][1])}: ${data[1]}</p>`) }).catch(function(err){ console.error("ERROR! " + err.message) }) } $("#vote-box").html(box) // displays the "box" and replaces everything that was in it before }) } } // When the page loads, we create a web3 instance and set a provider. We then set up the app window.addEventListener("load", function() { // Is there an injected web3 instance? if (typeof web3 !== "undefined") { console.warn("Using web3 detected from external source like Metamask") // If there is a web3 instance(in Mist/Metamask), then we use its provider to create our web3object window.web3 = new Web3(web3.currentProvider) } else { console.warn("No web3 detected. Falling back to http://localhost:9545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for deployment. 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://localhost:9545")) } // initializing the App window.App.start() })
请注意,我在上一步中用于建立web3实例的代码也在这里。首先,咱们导入必要的库和webpack内容,包括web3和Truffle Contracts。咱们将使用Truffle Contracts,它创建在web3之上,与Blockchain进行交互。
要使用它,咱们将获取在编译投票智能合约时自动构建的构建文件,并使用它们来建立Truffle Contracts
。最后,咱们在全局变量windows
中设置函数,用于启动应用程序,投票给候选人,以及查找投票数。
要实际与区块链交互,咱们必须使用deployed
的功能建立松露合约的实例。反过来,这将返回一个承诺,该实例做为你将用于从智能合约调用函数的返回值。
有两种方法能够与这些功能进行交互:交易和调用。交易是一种写操做,它将被广播到整个网络并由矿工处理(所以,成本为Ether)。若是要更改状态变量,则必须执行交易,由于它将更改区块链的状态。
call是一种读操做,模拟交易但丢弃状态变化。所以,它不会花费以太。这很是适合调用getter函数(查看咱们以前在智能合约中编写的四个getter函数)。
要使用Truffle Contracts进行交易,你能够编写instance.functionName(param1,param2)
,将instance
做为deployed
函数返回的实例(例如,检查第36行)。此事务将返回一个以交易数据做为返回值的promise。所以,若是在智能合约函数中返回一个值,可是使用相同的函数执行交易,则不会返回该值。
这就是为何咱们有一个事件会记录你想要写入要返回的交易数据的任何内容。在第36-37行,咱们进行交易以添加一个候选人即Candidate。当咱们肯定promise时,咱们在结果中有交易数据。
要获取咱们使用事件AddedCandidate()
记录的候选ID(检查智能合约以查看它0),咱们必须检查日志并检索它:result.logs[0].args.candidateID
。
要真正了解正在发生的事情,请使用Chrome开发人员工具打印result
并查看其result
结构。
要进行调用,你将编写instance.functionName.call(param1,param2)。可是,若是某个函数具备关键字
view,那么Truffle Contracts将自动建立一个调用,所以你无需添加
.call`。
这就是咱们的getter函数具备view关键字的缘由。与进行交易不一样,返回的调用promise将具备智能合约函数返回的任何返回值。
我如今将简要解释这三个函数,但若是你构建了从数据存储中检索/更改数据并相应地操做DOM的应用程序,那么这应该很是熟悉。将Blockchain视为你的数据库,将Truffle Contracts视为从数据库获取数据的API。
建立web3实例后当即调用此函数。要使Truffle Contracts正常工做,咱们必须将接口设置为建立的web3实例并设置默认值(例如你正在使用的账户以及你要为交易支付的gas量)。
因为咱们处于开发模式,咱们可使用任何数量的gas和任何账户。在生产过程当中,咱们将采用MetaMask提供的账户,并尝试找出你可使用的最少许的gas,由于它其实是真钱。
设置好全部内容后,咱们如今将显示每一个候选人的复选框,供用户投票。为此,咱们必须建立合约实例并获取候选人的信息。若是没有候选人,咱们将建立他们。为了让用户投票给候选人,咱们必须提供该特定候选人的ID。所以,咱们使每一个checkbox元素具备候选ID的id
(HTML元素属性)。另外,咱们将把候选数量添加到全局变量numOfCandidates
中,咱们将在App.findNumOfVotes()
中使用它。JQuery用于将每一个复选框及其候选名称附加到.candidate-box
。
此功能将根据单击的复选框及其id
属性为某个候选人投票。
交易完成后,咱们将解决退回的承诺并显示Voted
已经完成投票的消息。
最后一个函数将找到每一个候选人的投票数并显示它们。咱们将经过候选人并调用两个智能合约函数,getCandidate
和totalVotes
。咱们将解决这些承诺并为该特定候选人建立HTML元素。
如今,启动应用程序,你将在`http://localhost:8080/上看到它!
npm run dev
我知道,这不少......当你慢慢开发这个应用程序并真正了解正在发生的事情时,你可能会暂时打开这篇文章。但那是在学习!请使用以太网,truffle以及我在下面提供的全部文档补充本指南。我试图点击本文中的许多关键点,但这只是一个简短的概述,这些资源将有很大帮助。
在以太坊上构建应用程序很是相似于调用后端服务的常规应用程序。最难的部分是编写一份强大而完整的智能合约。我但愿本指南能够帮助你了解去中心化应用程序和以太坊的核心知识,并帮助你启动你对开发它们的兴趣。
若是你想建立咱们已经创建的东西,这里有一些想法。我实际上已经用这样的方式编写了智能合约,它能够很容易地实现我在本指南中提到的一切。
此外,这里有一些提示,能够防止一些错误发生:
7545
用于truffle开发,9545
用于Ganache。这些是默认值,所以若是你没法链接到区块链,你可能已经更改了它们。欢呼致力于去中心化和安全的互联网 - Web 3.0!
**另外咱们还提供一些加快学习过程和提供问答服务的以太坊教程以下: **
- web3j教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。 ,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
- php以太坊,主要是介绍使用php进行智能合约开发交互,进行帐号建立、交易、转帐、代币开发以及过滤器和事件等内容。
- 以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。
- 以太坊开发
- python以太坊教程,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
- C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括帐户管理、状态与交易、智能合约开发与交互、过滤器和事件等。
- php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如建立地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
- EOS入门教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、帐户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
汇智网原创翻译,转载请标明出处。这里是原文