描述除BTC以外的所有加密Token的术语。Token的名字来自于它们是Token和传统法币的替代品。
第一批Token于2011年推出,到现在,已经有成千上万种Token。早期的Token旨在改善BTC的各个方面,如交易速度或能源效率。最近的Token根据开发者的目标,有各种不同的目的。
由于Token在市场上占有如此大的比重,每个Token投资者都应该了解它们是如何运作的。
建立在以太坊网络上的区块链项目token,
需遵从以下几种token标准: ERC-20, ERC-223, ERC-621, ERC-721, ERC-827;
其中 ERC 是 Ethereum Request for Comments 的简称,直译过来是以太坊注释请求,本身这是一个包含结构化信息的网络指令。
ERC-Standard 是由以太坊社区定义出来,用户和以太坊网络进行交互的规则。但本身这个标准并不是一尘不变的,社区开发者可以提出自己定义的新标准,但是这个标准需要被整个以太坊社区接纳才能应用在以太坊网络上。
在以太坊中,ERC是指以太坊评论请求(Ethereum Request for Comments),这些是概述以太坊编程标准的技术文档。ERC旨在建立使应用程序和合约更容易相互交互的约定。
ERC-20由Vitalik Buterin和Fabian Vogelsteller于2015年撰写,为基于以太坊的token提出了一种相对简单的格式。通过遵循大纲,开发人员无需重新发明轮子。相反,他们可以建立一个已经在整个行业中使用的基础。
创建新的ERC-20代币后,它们会自动与支持ERC-20标准的服务和软件(软件钱包、硬件钱包交易所等)实现互操作。
需要说明的是,ERC-20标准已发展为EIP(具体来说是EIP-20)。由于其广泛使用,这发生在最初提案的几年后。然而,即使是多年后,“ERC-20”这个名字仍然存在。
ERC-20标准还有待完善。
其中一个障碍是,将令牌直接发送给令牌的智能合同将导致资金损失。这是因为一个令牌的合同只会跟踪和分配资金。例如,当您从钱包中向另一个用户发送令牌时,该钱包将调用令牌的合约来更新数据库。所以如果您试图将令牌直接传输到令牌的合约中,那么由于该令牌的合约无法响应,所以金钱就“丢失”了。
ERC20标准无法通过接收方合同处理传人的交易。这是该令牌存在的最大问题,也是开发者一直希望改进的地方。ERC20令牌无法将令牌发送给一个与这些令牌不兼容的契约,也正因为这样部分资金存在丢失的风险。
ERC-223通过允许用户将代币转移到具有相同功能的智能合同和钱包来解决这个问题。此外,ERC-223Token通过使传输只需1步而不是2步来提高ERC-20的效率。这意味着与ERC-20传输相比,ERC-223Token传输只需要一半的GAS(即更便宜)。
最重要的是,ERC-223Token向后兼容ERC-20Token,这意味着它们在解决上述bug的同时维护所有原始功能。
由于这些优点,ERC-223标准可能有一天取代ERC-20成为以太坊Token使用最广泛的标准。然而,大多数以太坊钱包还不支持ERC-223Token,因此项目开发人员采用它的速度很慢。
ERC-721Token首次名声鹊起是在2017年底,这款以加密Token为基础的收藏品游戏CryptoKitties(加密猫)大受欢迎。
ERC-721Token标准与其他ERC标准的关键区别在于,ERC-721允许开发人员轻松创建不可替换代币(NFTs)。换句话说,一个Token的价值可能与同一平台/生态系统中交换的另一个Token的价值不同,每一个Token的价值都是独立的。
不可替换的Token非常有用,因为它们支持对唯一的单个资产进行Token化。这包括精心培育的加密猫,但也包括传统上更有价值的资产,如艺术品、葡萄酒、房地产、文凭等。
以太坊生态系统中最好的项目之一,0x协议,正在计划于2018年7月底发布的v2版本中添加对ERC-721Token和其他新Token标准的支持。
ERC-721Token:
interface ERC20 {
// 方法
function name() view returns (string name);
function symbol() view returns (string symbol);
function decimals() view returns (uint8 decimals);
function totalSupply() view returns (uint256 totalSupply);
function balanceOf(address _owner) view returns (uint256 balance);
function transfer(address _to, uint256 _value) returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
function approve(address _spender, uint256 _value) returns (bool success);
function allowance(address _owner, address _spender) view returns (uint256 remaining);
// 事件
event Transfer(address indexed_from, address indexed_to, uint256 _value);
event Approval(address indexed_owner, address indexed_spender, uint256 _value);
}
标准函数 | 含义 |
---|---|
totalSupply() | 代币总量 |
balanceOf(address account) | account地址上的余额 |
transfer(address recipient, uint256 amount) | 向recipient发送amount个代币 |
allowance(address owner, address spender) | 查询owner给spender的额度(总配额) |
approve(address spender, uint256 amount) | 批准给spender的额度为amount(当前配额) |
transferFrom(address sender, address recipient, uint256 amount) | recipient提取sender给自己的额度 |
Transfer(address indexed from, address indexed to, uint256 value) | 代币转移事件:从from到to转移value个代币 |
Approval(address indexed owner, address indexed spender, uint256 value) | 额度批准事件:owner给spender的额度为value |
举个例子,假设账户A有1000个token:
这种交易模式是用来转让部分代币给另一个合约使用的。
继承这些接口,补充这些接口的具体实现
增加一些其他的必要的方法,实现其他功能,比如销毁token、增发token
OpenZeppelin的Token中实现了ERC20的一个安全的合约代码,我们在写自己的代币的时候也可以直接继承OpenZeppelin中的合约。
OpenZeppelin的实现:
contract DetailedERC20 is ERC20 {
using SafeMath for uint256;
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) internal allowed;
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply_;
constructor(string _name, string _symbol, uint8 _decimals) public {
name = _name;
symbol = _symbol;
decimals = _decimals;
}
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function transfer(address _to, uint256 _value) public returns (bool) {
// 做相关的合法验证
require(_to != address(0));
require(_value <= balances[msg.sender]);
// msg.sender余额中减去额度,_to余额加上相应额度
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
// 触发Transfer事件
emit Transfer(msg.sender, _to, _value);
return true;
}
}
上面是对ERC20的相关实现,只是一部分,并不全面。另一方面,OpenZeppelin还有一些其他的方法实现对ERC20进行了补充,比如BurableToken.sol,MintableToken.sol, TokenTimelock.sol,SafeERC20.sol等,从而实现销毁token,增发token,设置交易锁定期以及安全操作库。
由于ERC20代币本身是一个智能合约,因此以太坊无法通过将智能合约代币发送到智能合约来直接调用它。因为该交易发生在ERC20代币合约上,而不是发生在DeFi合约。
如若用户将USDT存入Aave以赚取利息,他们先要授权Aave合约从用户的钱包中提取USDT。然后调用Aave合约函数指定用户要存人的USDT数量。然后,Aave合约使用transferFrom()功能从您的钱包中提取相应金额的USDT来完成交易。
在授权使用DeFi时,您可以选择授权一次,即只同意本次交易,也可以选择无限次,允许合约在未来
无限次地在您钱包中操作此代币。
目前,DeFi所依赖的以太坊基础设施并不完善。因此,无限授权DeFi合约是改善DeFi体验的有效方式避免了每次使用前都要授权的麻烦,以及每次交易前授权造成的GAS费用消耗。在设置了无限授权后用户只需同意一次,即可以避免在此后的存款中重复该流程。
但这种设置有很大缺点。因为用户授予的不仅仅是对转移到合约中的代币的操作权,还有包括钱包中代币的控制权,即一旦合约被黑客攻击,不仅DeFi项目中存人的代币,我们自己钱包中的代币也会受到威胁因为这个授权是通过自己的私钥签名授权的,一旦被攻击,即使使用冷钱包也无法防止被盗。
Transfer(address from, address to,uint256 value)
当代币被一个地址转移到另一个地址时触发。注意转移的值可能是 0
Approval(address owner, address spender,uint256 value)
当代币所有者授权别人使用代币时触发,即调用 approve 方法。
下面来看ERC-20实例:
contract ERC20 {
//**********9个函数**********
//1.代币的名字,如:"黑马币"
function name() constant public returns (string name);
//2.代币的简称,例如:HMB
function symbol() public constant returns (string symbol);
//3.代币的最小分割量 token使用的小数点后几位。比如如果设置为3,就是支持0.001表示function decimals() public constant returns (uint8 decimals);
//4.token的总量
function totalSupply() public constant returns (uint totalSupply);
//5.余额 返回某个地址(账户)的账户余额
function balanceOf(address _owner) public constant returns (uint balance);
//6.转账交易代币 从消息发送者账户中往_to账户转数量为_value的token,从代币合约的调用者地址上转移_value的数量token到地址_to【注意:并且必须触发transfer事件】*/
function transfer(address _to, uint _value) public returns (bool success);
//7.两个地址转账从账户_from中往账户_to转数量为_value的token,与approve方法配合使用从地址_from发送数量为_value的tokent到地址_to【注意:并且必须触发transfer事件】
transferFrom方法用于允许合约代理某人转移token。条件是从账户必须经过了approve。*/
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
//8.批准_spender能从合约调用账户中转出数量为_value的token
function approve(address _spender, uint _value) public returns (bool success);
//9.获取_spender可以从账户_owner中转出token的剩余数量
function allowance(address _owner, address _spender) public constant returns (uint remaining);
//**********2个事件**********
//1.发生转账时必须要触发的事件,transfer 和 transferFrom 成功执行时必须触发的事件event Transfer(address indexed _from, address indexed _to, uint _value);
//2.当函数approve(address _spender, uint256 _value)成功执行时必须触发的事件event Approval(address indexed _owner, address indexed _spender, uint _value);
}
pragma solidity ^0.4.25;
interface tokenRecipient {
function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external;
}
contract TokenERC20 {
string public name; // ERC20标准
string public symbol; // ERC20标准
uint8 public decimals = 2; // ERC20标准,decimals 可以有的小数点个数,最小的代币单位。18 是建议的默认值
uint256 public totalSupply; // ERC20标准 总供应量
// 用mapping保存每个地址对应的余额 ERC20标准
mapping(address => uint256) public balanceOf;
// 存储对账号的控制 ERC20标准
mapping(address => mapping(address => uint256)) public allowance;
// 事件,用来通知客户端交易发生 ERC20标准
event Transfer(address indexed from, address indexed to, uint256 value);
// 事件,用来通知客户端代币被消费 ERC20标准
event Burn(address indexed from, uint256 value);
/**
* 初始化构造
*/
constructor(uint256 initialSupply, string tokenName, string tokenSymbol) public {
totalSupply = initialSupply * 10 ** uint256(decimals); // 供应的份额,份额跟最小的代币单位有关,份额 = 币数 * 10 ** decimals。
balanceOf[msg.sender] = totalSupply;
name = tokenName;
symbol = tokenSymbol;
}
// 创建者拥有所有的代币
// 代币名称
// 代币符号
}
/**
* 代币交易转移的内部实现
*/
function _transfer(address _from, address _to, uint _value) internal {
//确保目标地址不为0x0,因为0x0地址代表销毁
require(_to != 0x0);
//检查发送者余额
require(balanceOf[_from] >= _value);
//确保转移为正数个
require(balanceOf[_to] + _value > balanceOf[_to]);
//以下用来检查交易,
uint previousBalances = balanceOf[_from] + balanceOf[_to];
//Subtract from the sender
balanceOf[_from] -= _value;
//Add the same to the recipient
balanceOf[_to] += _value;
Transfer(_from, _to, _value);
//用assert来检查代码逻辑。
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
/**
* 代币交易转移
* 从自己(创建交易者)账号发送_value个代币到_to账号
* ERC20标准
* @param _to 接收者地址
* @param _value 转移数额
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
/**
* 账号之间代币交易转移
* ERC20标准
* @param _from 发送者地址
* @param _to 接收者地址
* @param _value 转移数额
*/
function transferFrom(address _from, address _to, uint256 _value) public returns bool success){
require(_value <= allowance[_from][msg.sender]); // Check allowance
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
/**
* 设置某个地址(合约)可以创建交易者名义花费的代币数。
* 允许发送者`_spender`花费不多于`_value`个代币
* ERC20标准
* @param _spender The address authorized to spend
* @param _value the max amount they can spend
*/
function approve(address _spender, uint256 _value) public returns (bool success){
allowance[msg.sender][_spender] = _value;
}
/**
* 设置允许一个地址(合约)以我(创建交易者)的名义可最多花费的代币数。
* 非ERC20标准
* @param _spender 被授权的地址(合约)
* @param _value 最大可花费代币数
* @param _extraData 发送给合约的附加数据
*/
function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
tokenRecipient spender = tokenRecipient(_spender);
if (approve(_spender, _value)) {
// 通知合约
spender.receiveApproval(msg.sender, _value, this, _extraData);
return true;
}
}
/**
* 销毁我(创建交易者)账户中指定个代币
* 非ERC20标准
*/
function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value); // 检查发送者是否有足够的代币
balanceOf[msg.sender] -= _value; // 从发送者中减去代币
totalSupply -= _value; // 更新总供应量
emit Burn(msg.sender, _value);
return true;
}
const solc = require('solc');
const path = require('path');
const fs = require('fs');
// 1. 合约代码文件路径
const sourceFilePath = path.resolve(__dirname, './TokenERC20.sol');
// 2. 合约编译后的文件路径
const bytecodeFilePath = path.resolve(__dirname, './TokenERC20.bytecode');
// 3. 编译
const result = solc.compile(source, 1);
// console.log(result);
console.log('1. 编译完成:' + sourceFilePath);
fs.writeFileSync(bytecodeFilePath, JSON.stringify(result.contracts[':TokenERC20']), 'utf-8');
console.log('2. 字节文件写入完成:' + bytecodeFilePath);
// 4. 暴露外部访问
module.exports = result.contracts[':TokenERC20'];
// 1.1 导入 编译好的 合约的 字节码 和 api
const path = require('path');
const fs = require('fs');
// 1.2 合约编译后的文件路径
const bytecodeFilePath = path.resolve(__dirname,'./TokenERC20.bytecode');
//读取字节码文件
const bytecodeJsonStr = fs.readFileSync(bytecodeFilePath,'utf-8');
const bytecodeJsonObj = JSON.parse(bytecodeJsonStr);
//const {bytecode,interface} = require('./compilaiCaiPiao');
const bytecode = bytecodeJsonObj.bytecode;
const interface = bytecodeJsonObj.interface;
// 2 导入hd钱包provider
const HDWalletProvider = require("truffle-hdwallet-provider");
// 3 助记词(相当于我们的私钥)
const mnemonic = "jeb... cat beef"; // 12 word mnemonic
// 4 创建 provider, 可以来访问 以太坊真实网络节点
const provider = new HDWalletProvider(
mnemonic,
"https://rinkeby.infura.io/v3/3a60f2b160....",1); // 最后的一个 是获取 助记词 的第一个地址
// 5 创建web对象
const Web3 = require('web3');
const web3= new Web3(provider);
async function main(){
console.log('开始与以太网交互.....');
const usrAdr = await web3.eth.getAccounts();
web3.eth.defaultAccount = usrAdr[0];
console.log('当前调用者的地址:'+ web3.eth.defaultAccount);
// 6 部署合约到 以太坊节点
//let contractObj =await deployContract();
// 7 调用合约
// 7.0 创建远程智能合约
const contractObj =await new web3.eth.Contract(JSON.parse(interface),'0x46495b091cd3Fcb789cC336c3B5e9041E28555b0');
console.log('获取【合约】对象成功!');
}
// 7.1 获取指定地址余额
await getBalanceAt(contractObj, '0x85BCc0F34718e0C332d41C513B72f8640B05249');
await transferTo(contractObj, '0x83E9e99B7f501860930baEC7801555C850D9C5', 1000000);
await approveTo(contractObj, '0x83E9e99B7f501860930baEC7801555C850D9C5', 1000000000);
await allowanceAt(contractObj, '0x85BCc0F34718e0C332d41C513B72f8640B05249', '0x83E9e99B7f501860930baEC7801555C850D9C5');
await transferFrom(contractObj, '0x85BCc0F34718e0C332d41C513B72f8640B05249', '0x737Df786F6e86625258960970c6752Fd7926F752', 1);
await allowanceAt(contractObj, '0x85BCc0F34718e0C332d41C513B72f8640B05249', '0x83E9e99B7f501860930baEC7801555C850D9C5');
//await getBalanceAt(contractObj, '0x737Df786F6e86625258960970c6752Fd7926F752');
////7.2 显示合约账户余额
//await showContracMoney(contractObj);
//查看调用者购买的号码LuckNum(contractObj);
////7.3 开奖+
//await withdrawLottery(contractObj);
////7.4 显示买家账户列表
//await showUsrList(contractObj);
////7.5 重置数据
//await resetContract(contractObj);
//await showManageAddress(contractObj);
//await killContract(contractObj);
// 启动
main();
// 1. 部署合约
async function deployContract() {
console.log('开始部署合约……');
let contractObj = await new web3.eth.Contract(JSON.parse(interface))
.deploy({
data: bytecode,//TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol)
arguments: ['10000000000','梦想零钱','DreamCoin']
})
.send({
from:web3.eth.defaultAccount,
gas:'1000000'
});
console.log('部署成功,合约地址:'+contractObj.options.address);
return contractObj;
}
// 2. 查询余额
async function getBalanceAt(contractObj,usrAdr) {
let usrMoney = await contractObj.methods.balanceOf(usrAdr).call();
console.log('地址['+usrAdr+'']余额:'+usrMoney);
}
//3. 将当前调用者的钱 转 money 金额给 usrAdrTo
async function transferTo(contractObj,usrAdrTo,money) {
let result = await contractObj.methods.transfer(usrAdrTo,money).send({
from:web3.eth.defaultAccount,
gas:'1000000'
});
console.log(web3.eth.defaultAccount+'向['+usrAdrTo+']转账['+money+']完毕');
console.log('转账完毕--记录如下:');
console.log(result);
}
//4. 授权(当前调用者授权给 spenderAdr 操作额为 money 的代币)
async function approveTo(contractObj,spenderAdr, money){
let result = await contractObj.methods.approve(spenderAdr,money).send({
from:web3.eth.defaultAccount,
gas:'1000000'
});
console.log(web3.eth.defaultAccount+'向['+spenderAdr+']授权['+money+']完毕!');
//console.log(result);
}
//5.查询授权
allowanceAt(contractObj,ownerAdr,spenderAdr) {
let result = await contractObj.methods.allowance(ownerAdr,spenderAdr).call();
console.log('查询:['+ownerAdr+']给['+spenderAdr+']剩余授权余額为:'+result);
}
//6.转帐(汇款人地址,收款人地址,金额)
async function transferFrom(contractObj,usrAdrFrom,usrAdrTo,money){
let result = await contractObj.methods.transferFrom(usrAdrFrom,usrAdrTo,money).send({
from:web3.eth.defaultAccount,
gas:'1000000'
});
console.log(usrAdrFrom+'向['+usrAdrTo+']转账['+money+']完毕');
//console.log(result);
}
销毁数字资产的行为涉及将其转移到一个永远无法被检索到的地方,也称销毁地址,通过永久锁定数字资产,有效阻止这类数字资产的流通。
销毁地址是一个无法访问的数字钱包,因为它没有附带私钥,就像一把从未有人为其建造过钥匙孔的锁。Burn销毁地址有时也称为eater吃地址。
向销毁地址发送token实际上将数字资产从其整体供应中移除,将其锁定在任何人的手中,并防止该资产再次被交易。
实现代币增发,token增发就如同央行印钞票一样,想必很多人都需要这样的功能
function mintToken(address _target, uint256 _mintedAmount) onlyOwner
internal
{
balanceOf[_target] += _mintedAmount;
totalSupply += _mintedAmount;
emit Transfer(address(0), owner, _mintedAmount);
emit Transfer(owner, _target, _mintedAmount);
}
注意onlyOwner修改器添加在函数末尾,这表示只有ower才能调用这用函数功能很简单,就是给指定的账户增加代币,同时增加总供应量。
有时为了监管的需要,需要实现冻结某些账户,冻结后,其资产仍在账户,但是不允许交易,之道解除冻结。
给合约添加以下的变量和方法(可以添加到合约的任何地方,但是建议把mapping加到和其他mapping一起,event也是如此):
mapping (address => bool) public frozenAccount;
event FrozenFunds(address target, bool frozen);
function freezeAccount(address target, bool freeze) onlyOwner {
frozenAccount[target] = freeze;
FrozenFunds(target, freeze);
}
Here's the content from the image presented in Markdown format:
单单以上的代码还无法冻结,需要把他加入到transfer函数中才能真正生效,因此修改transfer函数。这样在转账前,对发起交易的账号做一次检查,只有不是被冻结的账号才能转账。
function transfer(address _to, uint256 _value) {
require(!frozenAccount[msg.sender]);
...
}
uint256 public sellPrice;
uint256 public buyPrice;
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}
setPrices()增加了onlyOwner修饰器,注意买卖的价格单位是wei(最小的货币单位:1 eth = 10^18 wei)
添加来添加买卖函数:
function buy() payable returns (uint amount){
amount = msg.value / buyPrice; // calculates the amount
require(balanceOf[this]>=amount); // checks if it has enough to sell
balanceOf[msg.sender] += amount; // adds the amount to buyer's balance
balanceOf[this] -= amount; // subtracts amount from seller's balance
Transfer(this, msg.sender, amount); // execute an event reflecting the change
return amount; // ends function and returns
}
function buy() payable returns (uint amount){
amount = msg.value / buyPrice; // calculates the amount
require(balanceOf[this]>=amount); // checks if it has enough to sell
balanceOf[msg.sender] += amount; // adds the amount to buyer's balance
balanceOf[this] -= amount; // subtracts amount from seller's balance
Transfer(this, msg.sender, amount); // execute an event reflecting the change
return amount; // ends function and returns
}
以太坊中的交易时需要gas(支付给矿工的费用,费用以ether来支付)。而如果用户没有以太token,就需要自动补充gas的功能。这个功能将使我们token更加好用。
先设定余额阈值:
uint minBalanceForAccounts;
function setMinBalance(uint minimumBalanceInFinney) onlyOwner {
minBalanceForAccounts = minimumBalanceInFinney * 1 finney;
}
finney 是货币单位 1 finney = 0.001eth。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。