Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TRON 开发者入门完全指南 #1

Open
maxisacoder opened this issue Dec 1, 2018 · 0 comments

Comments

Projects
None yet
1 participant
@maxisacoder
Copy link
Owner

commented Dec 1, 2018

TRON 概况

TRON,2017年九月份, 做了千万美元级别的ICO,2018年六月tron主网上线。据官方数据,截至目前,主网上已经有69万个账户注册,每天新增账户量超过2万个。同时TRON每秒支持千量级tps,ETH现在主网还在十几tps量级。不可否认eth生态聚集了很多有天赋的开发者和researcher,但eth的PoW共识机制决定了其短时间内无法解决交易手续费高,交易确认速度,和并发交易的限制。目前从合约和交易体验上来讲,TRON相比ETH有比较大的优势。同时,从用户进入门槛和合约开发门槛上,由于TRON新用户几乎不需要成本(对比EOS复杂的邀请机制),和其支持solidtiy的TVM(相比EOS采用C++进行合约开发),TRON相比EOS也有显著优势。

对于dapp开发者,TRON一定是现阶段非常值得关注的一条公链,建立在其性能和生态的基础上, 已经出现了很多用户量和交易量都很大的dapp,爆品dapp也是完全有足够的机会诞生在这条公链之上。不过现在TRON公链生态中,对于开发者的引导性文章很少,高质量的也几乎没有看到,这也是我们写作这篇入门教程的初衷。下一章我们将正式开始介绍基于TRON的dapp开发基本概念,和一个样例来帮助大家快速入门。

TRON 开发工具链

TRON 提供了完整的工具链为开发者提供合约开发,测试,部署,接口。尤其是对于熟悉以太坊智能合约的开发者,从语言到工具一定都非常熟悉。

  • tron-web:JavaScript接口,用于提供常用的账户,地址,转账,合约相关操作。相当于Ethereum的web3js
  • tron-box:提供合约编译,部署,测试的命令行工具。作用相当于Ethereum的truffle工具链
  • tronLinktronPay: 提供浏览器环境的钱包插件,为dapp提供便利和安全的执行环境,相当于Ethereum的MetaMask,Scatter
  • tron-grid:社区维护的主网和测试网HTTP API接口,相当于Ethereum社区中的Infura
  • tron-studio: TRON集成开发环境,相当于以太坊中的Remix,不过现在功能和稳定性都有待提高,不推荐使用

全流程Tutorial

本章节我们通过一个Token合约,来讲解基本的合约开发和dapp开发流程。本例中的所有代码都可以在该Repo中找到。

环境搭建

如果希望自己搭建一条本地私链进行调试。根据官方文档,TRON的私链可以通过官方提供的docker镜像来部署,以下为部署的指令:

docker run -it -p 8091:8091 -p 8092:8092 -p 8090:8090 -p 50051:50051 -p 50052:50052 --rm --name tron trontools/quickstart:1.2.4

运行结果如下,可以看到,本地已经生成了10个可用用来测试的TRX的账户地址。
image

认真的读者可能已经发现了,我们在启动镜像的时候,一共要设置5个端口,这相比以太坊和比特币的RPC接口多了很多。这其中设计到TRON本身节点的一个区分,作为开发者我们有必要理解。TRON network中,一共有4种节点分别为:

  • Witness:作为超级节点,负责收集网络中的交易,负责出块,不为客户端暴露API。
  • FullNode:Full Node广播交易和区块,同时为提供的API进行账户,交易和区块的查询和操作。在本例中,端口8090为其HTTP接口,端口50051为其GRPC接口。
  • SolidityNode:SolidityNode只负责从FullNodo中拉取区块数据,不会主动发送数据,其区块数据落后于FullNode几个区块,故其暴露的API适合已经确认的交易。客户端需要同时和FullNode和SolidityNode想链接,以获得不同的API功能。其具体API列表在这里。在本例中,端口8091为其HTTP接口,端口50052为其GRPC接口。
  • EventSever: 在TRON的合约中,像Ehereum一样支持事件,不同的是,TRON中的事件是从EventServer中来监听。在本例中,端口8092为其HTTP接口。

同时,社区提供了Shasta测试网,用户也可以方便的输入自己地址来获取测试代币,同时提供测试网的区块链浏览器,非常方便开发调试,由于TRON测试网速度比以太坊的快非常多,我们在开发测试中,本教程直接使用测试网进行调试和部署,以简化读者的操作和学习路径。

合约编写

TRON中的合约采用Solidity来编写,故以太坊开发者在合约开发层,可以零学习成本迁移至TRON网络。下面我们编写一个标准的ERC20的代币Transfer合约,并将其部署至TRON。

pragma solidity ^0.4.18;


contract Token {

    uint256 constant private MAX_UINT256 = 2**256 - 1;
    mapping (address => uint256) public balances;
    mapping (address => mapping (address => uint256)) public allowed;
    /*
    NOTE:
    The following variables are OPTIONAL vanities. One does not have to include them.
    They allow one to customise the token contract & in no way influences the core functionality.
    Some wallets/interfaces might not even bother to look at this information.
    */
    string public name;                   //fancy name: eg Simon Bucks
    uint8 public decimals;                //How many decimals to show.
    string public symbol;                 //An identifier: eg SBX

    function Token(
        uint256 _initialAmount,
        string _tokenName,
        uint8 _decimalUnits,
        string _tokenSymbol
    ) public {
        balances[msg.sender] = _initialAmount;               // Give the creator all initial tokens
        totalSupply = _initialAmount;                        // Update total supply
        name = _tokenName;                                   // Set the name for display purposes
        decimals = _decimalUnits;                            // Amount of decimals for display purposes
        symbol = _tokenSymbol;                               // Set the symbol for display purposes
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balances[msg.sender] >= _value);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        emit Transfer(msg.sender, _to, _value); //solhint-disable-line indent, no-unused-vars
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        uint256 allowance = allowed[_from][msg.sender];
        require(balances[_from] >= _value && allowance >= _value);
        balances[_to] += _value;
        balances[_from] -= _value;
        if (allowance < MAX_UINT256) {
            allowed[_from][msg.sender] -= _value;
        }
        emit Transfer(_from, _to, _value); //solhint-disable-line indent, no-unused-vars
        return true;
    }

    function balanceOf(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }

    function approve(address _spender, uint256 _value) public returns (bool success) {
        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value); //solhint-disable-line indent, no-unused-vars
        return true;
    }

    function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
        return allowed[_owner][_spender];
    }
}

合约部署

合约代码完成后, 我们新建一个目录,利用tronbox来初始化一个基础的dapp工程。

npm install -g tronbox
tronbox init
ls

我们可以看到,路径下已经生成了合约样板工程。
image

为了部署合约,我们首先下载一个tronPay插件来创建我们的TRON账户地址(要求Chrome浏览器),之后导出私钥。
image

打开路径下的tronbox.js,粘贴将私钥替换为自己的私钥, 其余的参数如上对不同节点功能的描述,我们需要分别配置节点API路径:

module.exports = {
  networks: {
    shasta: {
      privateKey: '你的私钥',
      consume_user_resource_percent: 30,
      fee_limit: 100000000,
      fullNode: "https://api.shasta.trongrid.io",
      solidityNode: "https://api.shasta.trongrid.io",
      eventServer: "https://api.shasta.trongrid.io",
      network_id: "*"
    },
    
  }
};

最后一步,我们在migrations文件夹中,创建名为2_deploy_contracts.js的部署脚本,其作用与truffle中的 migration scripts完全相同。

var token = artifacts.require("./Token.sol");

module.exports = function(deployer) {
  deployer.deploy(token, 1000000, "GUIDE", 6, "GD");
};

现在,我们可以完成合约部署。

tronbox migrate --network shasta

image

至此,合约已经可以在区块浏览器中找到。下面的章节我们讲解如何卡发dapp与合约进行交互。

集成TronLink,TronPay

TronLink,TronPay的实现原理是在dapp加载之前在浏览器的全局对象中注入tronweb对象,故作为dapp开发者,我们需要实现的逻辑是等待tronWeb加载之后在进行tronWeb相关的调用。如代码注释,在没有检测到浏览器环境中注入的tronWeb对象时,我们需要阻塞应用加载并等待。

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

var waitForGlobal = async () =>{
    // 1. check variable, 检查tronweb是否已经加载
    if (window.tronWeb) {
        let tronWeb = window.tronWeb;
        // 2. check node connection,检查所需要的API是否都可以连通
        const nodes = await tronWeb.isConnected();
        const connected = !Object.entries(nodes).map(([name, connected]) => {
            if (!connected) {
                console.error(`Error: ${name} is not connected`);
            }
            return connected;
        }).includes(false);
        if (connected){
            // 3. 如果一切正常,启动react应用。
            ReactDOM.render(<App />, document.getElementById('root'));
        } else {
            console.error(`Error: TRON node is not connected`);
            console.error('wait for tronLink');
            setTimeout(async () => {
                await waitForGlobal();
            }, 100);
        }

    } else {
        // 如果检测到没有注入tronWeb对象,则等待100ms后重新检测
        console.error('wait for tronLink');
        setTimeout(async () => {
            await waitForGlobal();
        }, 100);
    }
};

waitForGlobal().then();

合约和交易

合约调用在tronWeb中略有不同,下面我们分几个场景来分别说明:

  1. 查询当前用户trx余额:
        let tronWeb = window.tronWeb;
        this.state.address && (this.setState({balance : await tronWeb.trx.getBalance(this.address)}));
  1. 发起交易,转账trx
        let tronWeb = window.tronWeb;
        const sendTransaction = await tronWeb.trx.sendTransaction("TKPzfsXRaDmdKh2GuouXw2eyK2HNH9FNQS", 1000);
  1. 构造合约并查询当前的token余额
        import * as artifact from './contracts/Token'
        let tronWeb = window.tronWeb;
        let address = tronWeb.address.fromHex(artifact.networks['*'].address);
        this.contract = tronWeb.contract(artifact.abi, address);
        this.state.address &&  this.contract.balances(this.state.address).call().then(output => {
            console.group('Contract "call" result');
            console.log('- Output:', output, '\n');
            this.setState({tokenBalance: output.toString()});
            console.groupEnd();
        });
  1. 对token进行转账
        // 2. send token
         let tx = this.contract.transfer("TKPzfsXRaDmdKh2GuouXw2eyK2HNH9FNQS", 100).send().then(output => {
            console.group('Contract "getLast" result');
            console.log('- Output:', output, '\n');
            console.groupEnd();
        });
  1. 监听链上Transfer事件
        this.contract && this.contract.Transfer().watch((err, event) => {
            if(err)
                return console.error('Error with "Message" event:', err);

            console.group('New event received');
            console.log('- Contract Address:', event.contract);
            console.log('- Event Name:', event.name);
            console.log('- Transaction:', event.transaction);
            console.log('- Block number:', event.block);
            console.log('- Result:', event.result, '\n');
            console.groupEnd();
        });

image

以上几个case包含了所有主要的dapp与智能合约的交互逻辑,在实际使用中,基本也可以涵盖大部分场景,更多的用法由于TronWeb文档的暂时不完善,读者可以尝试看一点源码,并不是非常复杂,更多的问题也期待与作者交流讨论。

总结

以上通过讲解一个Token合约的dapp开发全流程,我们讲解了合约编写,合约部署,dapp集成,dapp与合约交互,本文章所有代码均开源,欢迎读者star。并在每一个章节突出了从开发者角度需要关注的重点,并与以太坊开发进行了类比和关联。总体上来说,如果dapp开发者熟悉以太坊生态的开发,迁移至TRON生态的学习成本和现有代码的迁移成本都非常低。欢迎大家拥抱TRON生态,也请读者期待下一期教程《TRON开发者入门2:带宽,能量为什么对Dapp开发者如此重要?》。

最后推荐一个技术社区,在波场开发社区中他们做的非常不错。感谢阅读。

image

参考

本例中的所有参考链接已经附加在文档中,推荐两个官方文档:

  1. DeveloperHub: https://developers.tron.network
  2. Offical Document: https://github.com/tronprotocol/Documentation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.