智能合约示例:给跑步群发 Token,跑步赚 Token

在上文里我们介绍了如何将跑步数据上传到智能合约里,在本文里,将介绍如何给跑步群发行 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,只需要依次实现上述的接口要求即可。在本文的示例里,我们暂不实现 allownaceapprove 的接口。

在设计上,我们满足如下需求:

  • 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 所要求的全部接口,还有 approveallowance 没有实现,但参考上述的代码,这两个接口也是很容易实现的,只要还是看项目中对于此部分的需求要求。而且一般来说,并不建议手动来实现上述的需求,可以借助 OpenZepplion 这样的项目来发行 token、nft 等。

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