今天我将向你展示如何在以太坊区块链上开发你自己的加密货币并将其出售!我将向你展示如何使用以太坊智能合约逐步创建自己的ERC-20代币和众筹销售,如何测试智能合约,如何将智能合约部署到以太坊区块链,以及如何构建ICO网站部署到网络上。我还将解释ERC-20代币是什么,以太坊代币如何工作,初始代币产品(ICO)如何工作。

什么是ERC-20代币?

以太坊区块链允许你创建自己的加密货币或代币,可以通过以太币(以太坊区块链的本地加密货币)购买。ERC-20只是一个标准,它指定了这些代币的行为方式,因此它们与加密货币交换等其他平台兼容。

那怎么做呢?让我们先来看看以太坊区块链的工作原理。

以太坊是像比特币一样的区块链。与比特币一样,以太坊也会跟踪拥有Ether的用户余额,以太坊的原生加密货币。与比特币不同,以太坊也是一个平台,允许你创建自己的代币而无需创建新的区块链。

你可以使用智能合约创建以太坊代币。ERC-20是一个标准,用于指定此代币智能合约应如何工作。

让我们用一个例子来理解ERC-20代币智能合约的工作原理。假设我们想要创建一个名为“My Token”的代币,其符号为“MTK”,并且存在100,000,000个这样的代币。

首先,代币智能合约跟踪一些基本代币属性。例如,它记录名称“My Token”,你在加密货币交换中看到的符号,以及存在多少总代币。

它还跟踪谁拥有“My Token”和多少。

ERC-20代币可以作为付款从一个帐户转移到另一个帐户,就像任何其他加密货币一样。

它们也可以在众筹销售中购买,如ICO,我们将在下一节中进行讨论。

它们也可以在加密货币交易所买卖。

ICO如何运作

ERC-20代币可以以多种方式分发。一种流行的方法是举行目标人群促销或初始代币发行(ICO)。众筹销售是公司通过创建自己的ERC-20代币来为其业务筹集资金的一种方式,该代币可以由以太币的投资者购买。

每当发生众筹销售时,公司就会以投资者支付的以太币形式获得流动资金,并持有在众筹销售中出售的预留金额的ERC-20代币。

为了参与众筹销售,投资者必须使用帐户连接到Etherum区块链。此帐户有一个可以存储以太币的钱包地址,以及在众筹销售中购买的ERC-20代币。

投资者必须访问与智能合约谈话的众筹销售网站。智能合约管理众筹销售如何运作的所有规则。

每当投资者在众筹销售网站上购买代币时,他们就会将以太币从他们的钱包发送到智能合约,而智能合约会立即将购买的代币分发到他们的钱包中。

智能合约在众筹销售中设定代币的价格并控制众筹销售的行为方式。

众筹销售可以采取各种形状和大小。它们可以具有多个层级或阶段,例如Pre ICO,ICO和ICO Bonus阶段。这些层中的每一层都可以在不同的时间点发生并且可以表现不同。

他们还可以使用白名单来限制哪些投资者可以购买代币。

他们还可以拥有预定数量的代币,这些代币不会在众筹销售中出售。这些储备通常留给每个公司的特定成员,如创始人和顾问。这些储备可以是固定数量的代币或百分比。

每当众筹销售结束时,它可以由管理员最终确定。每当发生这种情况时,所有预留的代币都将分发到相应的帐户,众筹销售将正式结束。

ERC-20代币的工作原理

正如我之前解释的那样,ERC-20代币是使用以太坊智能合约创建的。什么是智能合约?

以太坊允许开发人员使用智能合约编写在区块链上运行的应用程序,这些应用程序封装了这些应用程序的所有业务逻辑。它们使我们能够读取和写入区块链的数据,以及执行代码。智能合约使用名为Solidity的编程语言编写,看起来很像Javascript。它是一种完整的编程语言,它允许我们执行Javascript所能提供的许多相同类型的事情,但由于它的用例,它的行为有点不同,我们将在本教程中看到。

对于ERC-20代币,智能合约管理有关代币如何工作的所有行为,并跟踪代币所有权和帐户余额。

ERC-20是关于如何构建以太坊代币的API规范。它是一种社区采用的标准,允许在各种用例中支持代币。我们希望构建一个符合此标准的代币,以便广泛接受。如果我们没有这样的标准,我们可以有无尽的方式来创建代币,它们可能彼此不兼容!

使用ERC-20标准可确保代币符合以下用例(以及更多):

  • 电子钱包转帐 - 将代币从一个帐户发送到另一个帐户
  • 在加密货币交易所买卖
  • 在众筹销售(ICO)中购买代币,就像我们将在本教程中演示一样

ERC-20规范基本上规定了智能合约必须响应的接口。它规定了智能合约的结构和智能合约必须具备的功能类型。它还提供了一些很好的建议功能,但最终是可选的。它规定了我们的代币必须具有的某些事件,例如transfer事件。请注意,智能合约可以发出消费者可以订阅的事件,并且使用此标准,我们可以订阅告诉我们何时销售代币的事件。

以下是ERC-20标准指定的transfer函数的示例实现。它是智能合约所要求的,并且管理某人如何将钱包中的ERC-20代币发送到另一个钱包。

contract ERC20Token {
    // ...

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value);

        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;

        Transfer(msg.sender, _to, _value);

        return true;
    }

    // ...
}

该函数通过以下方式实现ERC-20标准:

  • 该功能存在。
  • 它接受正确的参数。
  • 如果用户没有足够的代币进行支付,即余额不足,则失败。
  • 它将余额从发件人的帐户转移到收款人的帐户。
  • 它会触发sell事件。
  • 它返回正确的值,例如true

如果所有这些还没有完全有意义,请不要担心。你可以直接在以太坊改进提案github存储库中阅读有关ERC-20令牌标准的更多信息。这是围绕以太坊标准进行的所有社区讨论的地方。我强烈建议将该存储库加入书签并阅读提交内容,因为这是你可以观看以太坊技术实时增长和变化的地方!

我也推荐这篇维基百科文章

我们要建立的网站

我们将建立一个ICO网站,与区块链上的众筹销售智能合约进行对话。这个客户端网站将有一个表格,用户可以在众筹销售中购买令牌。它将显示众筹销售的进度,例如用户购买了多少令牌,所有用户购买了多少令牌,以及众筹销售中可用的令牌总数。它还会在“你的帐户”下显示我们与区块链相关联的帐户。

安装依赖项

为了构建我们的ERC-20令牌和销售,我们首先需要一些依赖。

节点包管理器(NPM)

我们需要的第一个依赖是节点包管理器,或NPM,它与Node.js一起提供。你可以通过你的终端并输入以下内容来查看你是否已安装节点:

$ node -v

Truffle框架

下一个依赖是Truffle Framework,它允许我们在以太坊区块链上构建去中心化的应用程序。它提供了一套工具,允许我们使用Solidity编程语言编写智能合约。它还使我们能够测试我们的智能合约并将其部署到区块链。它还为我们提供了开发客户端应用程序的地方。

你可以在命令行中使用NPM安装Truffle,如下所示:

$ npm install -g truffle

Ganache

下一个依赖项是Ganache,一个本地内存区块链。你可以通过从Truffle Framework网站下载来安装Ganache。它将为我们提供10个外部帐户,其中包含我们当地以太坊区块链的地址。每个帐户预装100个测试Ether。

Metamask

下一个依赖项是Google Chrome的Metamask扩展。为了使用区块链,我们必须连接它(记住,我说块链是一个网络)。我们必须安装一个特殊的浏览器扩展才能使用以太坊区块链。这就是Metamask的用武之地。我们将能够通过我们的个人账户连接到我们当地的以太坊区块链,并与我们的智能合约进行交互。

我们将在本教程中使用Metamask chrome扩展,因此如果你还没有安装google chrome浏览器,则还需要安装它。要安装Metamask,请在Google Chrome网上应用店中搜索Metamask Chrome插件。安装完成后,请确保在扩展列表中选中它。安装后,你会在Chrome浏览器的右上角看到狐狸图标。

语法高亮显示

依赖项是可选的,但建议使用。我建议为Solidity编程语言安装语法高亮显示。大多数文本编辑器和IDE都没有开箱即用的Solidity语法高亮显示,因此你必须安装一个软件包才能支持此功能。我正在使用Sublime Text,我已经下载了“Ethereum”软件包,它为Solidity提供了很好的语法高亮显示。

ERC-20令牌智能合约

现在我们已经安装了依赖项,让我们开始构建我们的ERC-20令牌!这是完整的ERC-20令牌智能合约Solidity代码:

pragma solidity ^0.4.2;

contract DappToken {
    string  public name = "DApp Token";
    string  public symbol = "DAPP";
    string  public standard = "DApp Token v1.0";
    uint256 public totalSupply;

    event Transfer(
        address indexed _from,
        address indexed _to,
        uint256 _value
    );

    event Approval(
        address indexed _owner,
        address indexed _spender,
        uint256 _value
    );

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    function DappToken (uint256 _initialSupply) public {
        balanceOf[msg.sender] = _initialSupply;
        totalSupply = _initialSupply;
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value);

        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;

        Transfer(msg.sender, _to, _value);

        return true;
    }

    function approve(address _spender, uint256 _value) public returns (bool success) {
        allowance[msg.sender][_spender] = _value;

        Approval(msg.sender, _spender, _value);

        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_value <= balanceOf[_from]);
        require(_value <= allowance[_from][msg.sender]);

        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;

        allowance[_from][msg.sender] -= _value;

        Transfer(_from, _to, _value);

        return true;
    }
}

让我们来看看这个智能合约的功能,以及它如何实现ERC-20标准:

  • 它存储代币名称string public name =“DApp Token”。
  • 它存储用于加密货币交换的代币符号string public symbol =“DAPP”
  • 它存储了 uint256 public totalSupply公共代币总供应量。
  • 它使用Solidity映射来存储拥有代币映射mapping(address => uint256) public balanceOf的每个帐户的余额。
  • 它实现了一个transfer函数,允许用户将代币发送到另一个帐户。
  • 它实现了一个允许其他帐户使用代币的approve函数,例如加密货币交换。这会更新allowance映射,以查看帐户可以支出的金额。
  • 它实现了transferFrom,允许其他帐户转移令牌。

你还可以阅读此智能合约的测试,以了解有关其工作原理的更多信息。这些测试确保这种智能合约的行为符合我们的预期。这是一个完整的测试套件,可以检查智能合约的所有行为:

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

contract('DappToken', function(accounts) {
  var tokenInstance;

  it('initializes the contract with the correct values', function() {
    return DappToken.deployed().then(function(instance) {
      tokenInstance = instance;
      return tokenInstance.name();
    }).then(function(name) {
      assert.equal(name, 'DApp Token', 'has the correct name');
      return tokenInstance.symbol();
    }).then(function(symbol) {
      assert.equal(symbol, 'DAPP', 'has the correct symbol');
      return tokenInstance.standard();
    }).then(function(standard) {
      assert.equal(standard, 'DApp Token v1.0', 'has the correct standard');
    });
  })

  it('allocates the initial supply upon deployment', function() {
    return DappToken.deployed().then(function(instance) {
      tokenInstance = instance;
      return tokenInstance.totalSupply();
    }).then(function(totalSupply) {
      assert.equal(totalSupply.toNumber(), 1000000, 'sets the total supply to 1,000,000');
      return tokenInstance.balanceOf(accounts[0]);
    }).then(function(adminBalance) {
      assert.equal(adminBalance.toNumber(), 1000000, 'it allocates the initial supply to the admin account');
    });
  });

  it('transfers token ownership', function() {
    return DappToken.deployed().then(function(instance) {
      tokenInstance = instance;
      // Test `require` statement first by transferring something larger than the sender's balance
      return tokenInstance.transfer.call(accounts[1], 99999999999999999999999);
    }).then(assert.fail).catch(function(error) {
      assert(error.message.indexOf('revert') >= 0, 'error message must contain revert');
      return tokenInstance.transfer.call(accounts[1], 250000, { from: accounts[0] });
    }).then(function(success) {
      assert.equal(success, true, 'it returns true');
      return tokenInstance.transfer(accounts[1], 250000, { from: accounts[0] });
    }).then(function(receipt) {
      assert.equal(receipt.logs.length, 1, 'triggers one event');
      assert.equal(receipt.logs[0].event, 'Transfer', 'should be the "Transfer" event');
      assert.equal(receipt.logs[0].args._from, accounts[0], 'logs the account the tokens are transferred from');
      assert.equal(receipt.logs[0].args._to, accounts[1], 'logs the account the tokens are transferred to');
      assert.equal(receipt.logs[0].args._value, 250000, 'logs the transfer amount');
      return tokenInstance.balanceOf(accounts[1]);
    }).then(function(balance) {
      assert.equal(balance.toNumber(), 250000, 'adds the amount to the receiving account');
      return tokenInstance.balanceOf(accounts[0]);
    }).then(function(balance) {
      assert.equal(balance.toNumber(), 750000, 'deducts the amount from the sending account');
    });
  });

  it('approves tokens for delegated transfer', function() {
    return DappToken.deployed().then(function(instance) {
      tokenInstance = instance;
      return tokenInstance.approve.call(accounts[1], 100);
    }).then(function(success) {
      assert.equal(success, true, 'it returns true');
      return tokenInstance.approve(accounts[1], 100, { from: accounts[0] });
    }).then(function(receipt) {
      assert.equal(receipt.logs.length, 1, 'triggers one event');
      assert.equal(receipt.logs[0].event, 'Approval', 'should be the "Approval" event');
      assert.equal(receipt.logs[0].args._owner, accounts[0], 'logs the account the tokens are authorized by');
      assert.equal(receipt.logs[0].args._spender, accounts[1], 'logs the account the tokens are authorized to');
      assert.equal(receipt.logs[0].args._value, 100, 'logs the transfer amount');
      return tokenInstance.allowance(accounts[0], accounts[1]);
    }).then(function(allowance) {
      assert.equal(allowance.toNumber(), 100, 'stores the allowance for delegated trasnfer');
    });
  });

  it('handles delegated token transfers', function() {
    return DappToken.deployed().then(function(instance) {
      tokenInstance = instance;
      fromAccount = accounts[2];
      toAccount = accounts[3];
      spendingAccount = accounts[4];
      // Transfer some tokens to fromAccount
      return tokenInstance.transfer(fromAccount, 100, { from: accounts[0] });
    }).then(function(receipt) {
      // Approve spendingAccount to spend 10 tokens form fromAccount
      return tokenInstance.approve(spendingAccount, 10, { from: fromAccount });
    }).then(function(receipt) {
      // Try transferring something larger than the sender's balance
      return tokenInstance.transferFrom(fromAccount, toAccount, 9999, { from: spendingAccount });
    }).then(assert.fail).catch(function(error) {
      assert(error.message.indexOf('revert') >= 0, 'cannot transfer value larger than balance');
      // Try transferring something larger than the approved amount
      return tokenInstance.transferFrom(fromAccount, toAccount, 20, { from: spendingAccount });
    }).then(assert.fail).catch(function(error) {
      assert(error.message.indexOf('revert') >= 0, 'cannot transfer value larger than approved amount');
      return tokenInstance.transferFrom.call(fromAccount, toAccount, 10, { from: spendingAccount });
    }).then(function(success) {
      assert.equal(success, true);
      return tokenInstance.transferFrom(fromAccount, toAccount, 10, { from: spendingAccount });
    }).then(function(receipt) {
      assert.equal(receipt.logs.length, 1, 'triggers one event');
      assert.equal(receipt.logs[0].event, 'Transfer', 'should be the "Transfer" event');
      assert.equal(receipt.logs[0].args._from, fromAccount, 'logs the account the tokens are transferred from');
      assert.equal(receipt.logs[0].args._to, toAccount, 'logs the account the tokens are transferred to');
      assert.equal(receipt.logs[0].args._value, 10, 'logs the transfer amount');
      return tokenInstance.balanceOf(fromAccount);
    }).then(function(balance) {
      assert.equal(balance.toNumber(), 90, 'deducts the amount from the sending account');
      return tokenInstance.balanceOf(toAccount);
    }).then(function(balance) {
      assert.equal(balance.toNumber(), 10, 'adds the amount from the receiving account');
      return tokenInstance.allowance(fromAccount, spendingAccount);
    }).then(function(allowance) {
      assert.equal(allowance.toNumber(), 0, 'deducts the amount from the allowance');
    });
  });
});

你可以使用truffle从命令行运行测试,如下所示:

$ truffle test

众筹销售智能合约

现在我们可以建立一个众筹销售智能合约,允许投资者在最初的代币产品(ICO)中购买代币。这是完整的众筹销售智能合约Solidity代码:

pragma solidity ^0.4.2;

import "./DappToken.sol";

contract DappTokenSale {
    address admin;
    DappToken public tokenContract;
    uint256 public tokenPrice;
    uint256 public tokensSold;

    event Sell(address _buyer, uint256 _amount);

    function DappTokenSale(DappToken _tokenContract, uint256 _tokenPrice) public {
        admin = msg.sender;
        tokenContract = _tokenContract;
        tokenPrice = _tokenPrice;
    }

    function multiply(uint x, uint y) internal pure returns (uint z) {
        require(y == 0 || (z = x * y) / y == x);
    }

    function buyTokens(uint256 _numberOfTokens) public payable {
        require(msg.value == multiply(_numberOfTokens, tokenPrice));
        require(tokenContract.balanceOf(this) >= _numberOfTokens);
        require(tokenContract.transfer(msg.sender, _numberOfTokens));

        tokensSold += _numberOfTokens;

        Sell(msg.sender, _numberOfTokens);
    }

    function endSale() public {
        require(msg.sender == admin);
        require(tokenContract.transfer(admin, tokenContract.balanceOf(this)));

        // Just transfer the balance to the admin
        admin.transfer(address(this).balance);
    }
}

让我们来看看这个智能合约的功能,以及它如何进行众筹销售:

  • 它存储众筹销售address admin的地址管理员帐户。
  • 它引用了ERC-20代币智能合约DappToken public tokenContract
  • 它存储代币价格uint256 public tokenPrice
  • 它存储了代币销售的数量uint256 public tokensSold
  • 它实现了一个sell事件,以便消费者可以在出售代币时收到通知。
  • 它实现了buyTokens函数,允许用户在众筹销售中购买代币。
  • 它实现了一个endSale函数,允许管理员结束众筹销售并收集销售期间筹集的以太币。
var DappToken = artifacts.require('./DappToken.sol');
  var DappTokenSale = artifacts.require('./DappTokenSale.sol');

  contract('DappTokenSale', function(accounts) {
    var tokenInstance;
    var tokenSaleInstance;
    var admin = accounts[0];
    var buyer = accounts[1];
    var tokenPrice = 1000000000000000; // in wei
    var tokensAvailable = 750000;
    var numberOfTokens;

    it('initializes the contract with the correct values', function() {
      return DappTokenSale.deployed().then(function(instance) {
        tokenSaleInstance = instance;
        return tokenSaleInstance.address
      }).then(function(address) {
        assert.notEqual(address, 0x0, 'has contract address');
        return tokenSaleInstance.tokenContract();
      }).then(function(address) {
        assert.notEqual(address, 0x0, 'has token contract address');
        return tokenSaleInstance.tokenPrice();
      }).then(function(price) {
        assert.equal(price, tokenPrice, 'token price is correct');
      });
    });

    it('facilitates token buying', function() {
      return DappToken.deployed().then(function(instance) {
        // Grab token instance first
        tokenInstance = instance;
        return DappTokenSale.deployed();
      }).then(function(instance) {
        // Then grab token sale instance
        tokenSaleInstance = instance;
        // Provision 75% of all tokens to the token sale
        return tokenInstance.transfer(tokenSaleInstance.address, tokensAvailable, { from: admin })
      }).then(function(receipt) {
        numberOfTokens = 10;
        return tokenSaleInstance.buyTokens(numberOfTokens, { from: buyer, value: numberOfTokens * tokenPrice })
      }).then(function(receipt) {
        assert.equal(receipt.logs.length, 1, 'triggers one event');
        assert.equal(receipt.logs[0].event, 'Sell', 'should be the "Sell" event');
        assert.equal(receipt.logs[0].args._buyer, buyer, 'logs the account that purchased the tokens');
        assert.equal(receipt.logs[0].args._amount, numberOfTokens, 'logs the number of tokens purchased');
        return tokenSaleInstance.tokensSold();
      }).then(function(amount) {
        assert.equal(amount.toNumber(), numberOfTokens, 'increments the number of tokens sold');
        return tokenInstance.balanceOf(buyer);
      }).then(function(balance) {
        assert.equal(balance.toNumber(), numberOfTokens);
        return tokenInstance.balanceOf(tokenSaleInstance.address);
      }).then(function(balance) {
        assert.equal(balance.toNumber(), tokensAvailable - numberOfTokens);
        // Try to buy tokens different from the ether value
        return tokenSaleInstance.buyTokens(numberOfTokens, { from: buyer, value: 1 });
      }).then(assert.fail).catch(function(error) {
        assert(error.message.indexOf('revert') >= 0, 'msg.value must equal number of tokens in wei');
        return tokenSaleInstance.buyTokens(800000, { from: buyer, value: numberOfTokens * tokenPrice })
      }).then(assert.fail).catch(function(error) {
        assert(error.message.indexOf('revert') >= 0, 'cannot purchase more tokens than available');
      });
    });

    it('ends token sale', function() {
      return DappToken.deployed().then(function(instance) {
        // Grab token instance first
        tokenInstance = instance;
        return DappTokenSale.deployed();
      }).then(function(instance) {
        // Then grab token sale instance
        tokenSaleInstance = instance;
        // Try to end sale from account other than the admin
        return tokenSaleInstance.endSale({ from: buyer });
      }).then(assert.fail).catch(function(error) {
        assert(error.message.indexOf('revert' >= 0, 'must be admin to end sale'));
        // End sale as admin
        return tokenSaleInstance.endSale({ from: admin });
      }).then(function(receipt) {
        return tokenInstance.balanceOf(admin);
      }).then(function(balance) {
        assert.equal(balance.toNumber(), 999990, 'returns all unsold dapp tokens to admin');
        // Check that the contract has no balance
        balance = web3.eth.getBalance(tokenSaleInstance.address)
        assert.equal(balance.toNumber(), 0);
      });
    });
  });

恭喜!你已成功学习了如何在以太坊上建立了ERC-20代币和众筹销售智能合约!

======================================================================

分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:

  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是原文在以太坊开发自己的加密货币


tualala
264 声望28 粉丝

java比特币开发详解: