# 智能合约示例：给跑步群发 Token，跑步赚 Token

By [0xdoux.eth](https://paragraph.com/@0xdoux) · 2022-03-30

---

在上文里我们介绍了如何将跑步数据上传到智能合约里，在本文里，将介绍如何给跑步群发行 Token。本文仅为演示如何使用智能合约发 Token，仅作为技术交流。

Token 介绍
--------

在区块链中，Token 也叫做代币，例如 ETH，它可以作为货币、积分、资产等的使用。在区块链世界里，token 是非常重要的基础设施，大部分的项目都会设计 token economy。得益于以太坊等平台的技术能力，项目发行 token 在技术层面是比较简单的，难的在于如何设计一个优秀的 token economy。

在以太坊上，ERC20 是 token 的标准，在标准中定义了一系列的接口规范，项目方只需要在合约里实现了这些接口规范，这个合约就是 token。这个接口要求如下：

    function name() public view returns (string)
    function symbol() public view returns (string)
    function decimals() public view returns (uint8)
    function totalSupply() public view returns (uint256)
    function balanceOf(address _owner) public view returns (uint256 balance)
    function transfer(address _to, uint256 _value) public returns (bool success)
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
    function approve(address _spender, uint256 _value) public returns (bool success)
    function allowance(address _owner, address _spender) public view returns (uint256 remaining)
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    event Approval(address indexed _owner, address indexed _spender, uint256 _value)
    

简单来说，一个 token 要有一个名字、符号、当前供给量，具有获取余额、转账、代持等操作，同时在执行特定操作的时候抛出日志信息即可。符号用来标识一个具体的 token，如 ETH、APE 等。

需求设计
----

因此，要发行 token，只需要依次实现上述的接口要求即可。在本文的示例里，我们暂不实现 `allownace`、`approve` 的接口。

在设计上，我们满足如下需求：

*   token 命名为 `RNC`，在上线时，管理员获得 `100000 RNC` 初始持仓
    
*   用户上传跑步数据，可以获得对应里程的 `RNC` 代币，一米对应一个 RNC，且最低里程为 2 公里
    
*   用户可以查询自己的 token 额度，可以转账
    

目前仅做了上述的设计，后续还可以加入更多的设计，比如质押 RNC 参加跑步挑战，完不成的挑战的 RNC 一部分做奖励、一部分做销毁等。感兴趣的读者可以留言提出需求，我们在后续的文章里可以来实现。

合约实现
----

首先，在构造器里，确定 token 的一些属性

    string private _name;
    string private _symbol;
    uint8 private _decimal;
    uint256 private _totalSupply;
    address owner;
    
    mapping(address => uint256) balances;
    
    constructor() {
        _name = "RunningClub";
        _symbol = "RNC";
        _decimal = 3;
        owner = msg.sender;
        balances[owner] = _totalSupply = 100000;
    }
    

通过 `balances` 来记录每个钱包的 token 数，通过 `_totalSupply` 来记录当前合约的总供给数

接下来，可以实现接口规范中的一些属性接口

    function name() public view returns (string memory) {
        return _name;
    }
    
    function symbol() public view returns (string memory) {
        return _symbol;
    }
    
    function decimals() public view returns (uint8) {
        return _decimal;
    }
    
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }
    

实现查询账户金额的接口，通过对合约数据的读取即可完成，no big deal

    function balanceOf(address _owner) public view returns (uint256) {
        return balances[_owner];
    }
    
    function balanceOfMe() public view returns (uint256) {
        return balances[msg.sender];
    }
    

还有转账的相关操作，通过对不同账户的增加和减少余额即可实现，需要注意的是，在转账钱要验证下账户的余额和要转出的余额之间的大小，不能出现余额不够的情况

    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    
    function transfer(address _to, uint256 _value) public returns (bool) {
        require(balances[owner] > _value, "amount is not enough to transfer");
        require(owner != msg.sender, "cannot transfer to self");
        balances[owner] -= _value;
        balances[_to] += _value;
        emit Transfer(owner, _to, _value);
        return true;
    }
    
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
        require(balances[_from] > _value, "amount is not enough to transfer");
        require(_from != _to, "cannot transfer to self");
        balances[_from] -= _value;
        balances[_to] += _value;
        emit Transfer(_from, _to, _value);
        return true;
    }
    

最后，就是实现上传跑步数据就能获得 RNC 奖励的需求

    mapping(address => uint[]) runningRecords;
    
    function _mint(address _user, uint _amount) private {
        balances[_user] += _amount;
        _totalSupply += _amount;
    }
    
    function checkIn(uint _miles) public {
        require(_miles >= MIN_MILES, "min miles should be 2000 miles");
        runningRecords[msg.sender].push(_miles);
        _mint(msg.sender, _miles);
    }
    

只要跑步的里程数达到合约的要求，就给该账户增加对应额度的 RNC，同时更新总供给即可。

总结
--

上述示例中，还没有去实现 ERC20 所要求的全部接口，还有 `approve` 和 `allowance` 没有实现，但参考上述的代码，这两个接口也是很容易实现的，只要还是看项目中对于此部分的需求要求。而且一般来说，并不建议手动来实现上述的需求，可以借助 `OpenZepplion` 这样的项目来发行 token、nft 等。

对于一个项目，更多的还是要去思考 token economy 的设计，现在大部分的项目代币，在我看来并无意义，token 也不仅仅可以用于货币，也可以用于更多其他的场景。

---

*Originally published on [0xdoux.eth](https://paragraph.com/@0xdoux/token-token)*
