部署和使用 ERC721 通证
Creating your own ERC721 Token on MOAC blockchain
Before you read this page, you should:
- Understand the following general concepts: blockchain, smart contracts, ERC721 tokens;
- Be able to write a basic Ethereum smart contract in Solidity (see tutorial example);
After you read this page, you will be able to:
- Write your own ERC721 token contract;
- Compile and deploy it on the MOAC blockchain;
- Use the ERC721 token;
Write the ERC721 token smart contract
The ERC721 standard is developed by the Ethereum community. It is a standard interface for non-fungible tokens(NFT), also known as deeds.
contract ERC721 { function balanceOf(address _owner) external view returns (uint256); function ownerOf(uint256 _tokenId) external view returns (address); function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; function transferFrom(address _from, address _to, uint256 _tokenId) external payable; function approve(address _approved, uint256 _tokenId) external payable; function setApprovalForAll(address _operator, bool _approved) external; function getApproved(uint256 _tokenId) external view returns (address); function isApprovedForAll(address _owner, address _operator) external view returns (bool); function supportsInterface(bytes4 interfaceID) external view returns (bool); event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); }
User can develop its own ERC721 token. For that, we can either use a ready open source library to help with the task, or write the needed smart contracts directly from scratch. We will show all three methods below so you can master your ERC721 token development skills.
Using a ready open source library
Let’s see how you can implement this interface by taking advantage of the OpenZeppelin Solidity library, for instance. We will write a TestCoin based on, for example, the PausableToken contract in OpenZeppelin:
pragma solidity ^0.4.18; import "./ERC721Basic.sol"; import "./ERC721Receiver.sol"; import "../../math/SafeMath.sol"; import "../../AddressUtils.sol"; contract ERC721BasicToken is ERC721Basic { using SafeMath for uint256; using AddressUtils for address; // Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` bytes4 constant ERC721_RECEIVED = 0xf0b9e5ba; // Mapping from token ID to owner mapping (uint256 => address) internal tokenOwner; // Mapping from token ID to approved address mapping (uint256 => address) internal tokenApprovals; // Mapping from owner to number of owned token mapping (address => uint256) internal ownedTokensCount; // Mapping from owner to operator approvals mapping (address => mapping (address => bool)) internal operatorApprovals; modifier onlyOwnerOf(uint256 _tokenId) { require(ownerOf(_tokenId) == msg.sender); _; } modifier canTransfer(uint256 _tokenId) { require(isApprovedOrOwner(msg.sender, _tokenId)); _; } function balanceOf(address _owner) public view returns (uint256) { require(_owner != address(0)); return ownedTokensCount[_owner]; } function ownerOf(uint256 _tokenId) public view returns (address) { address owner = tokenOwner[_tokenId]; require(owner != address(0)); return owner; } function exists(uint256 _tokenId) public view returns (bool) { address owner = tokenOwner[_tokenId]; return owner != address(0); } function approve(address _to, uint256 _tokenId) public { address owner = ownerOf(_tokenId); require(_to != owner); require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); if (getApproved(_tokenId) != address(0) || _to != address(0)) { tokenApprovals[_tokenId] = _to; emit Approval(owner, _to, _tokenId); } } function getApproved(uint256 _tokenId) public view returns (address) { return tokenApprovals[_tokenId]; } function setApprovalForAll(address _to, bool _approved) public { require(_to != msg.sender); operatorApprovals[msg.sender][_to] = _approved; emit ApprovalForAll(msg.sender, _to, _approved); } function isApprovedForAll( address _owner, address _operator ) public view returns (bool) { return operatorApprovals[_owner][_operator]; } function transferFrom( address _from, address _to, uint256 _tokenId ) public canTransfer(_tokenId) { require(_from != address(0)); require(_to != address(0)); clearApproval(_from, _tokenId); removeTokenFrom(_from, _tokenId); addTokenTo(_to, _tokenId); emit Transfer(_from, _to, _tokenId); } function safeTransferFrom( address _from, address _to, uint256 _tokenId ) public canTransfer(_tokenId) { // solium-disable-next-line arg-overflow safeTransferFrom(_from, _to, _tokenId, ""); } function safeTransferFrom( address _from, address _to, uint256 _tokenId, bytes _data ) public canTransfer(_tokenId) { transferFrom(_from, _to, _tokenId); // solium-disable-next-line arg-overflow require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data)); } function isApprovedOrOwner( address _spender, uint256 _tokenId ) internal view returns (bool) { address owner = ownerOf(_tokenId); // Disable solium check because of // https://github.com/duaraghav8/Solium/issues/175 // solium-disable-next-line operator-whitespace return ( _spender == owner || getApproved(_tokenId) == _spender || isApprovedForAll(owner, _spender) ); } function _mint(address _to, uint256 _tokenId) internal { require(_to != address(0)); addTokenTo(_to, _tokenId); emit Transfer(address(0), _to, _tokenId); } function _burn(address _owner, uint256 _tokenId) internal { clearApproval(_owner, _tokenId); removeTokenFrom(_owner, _tokenId); emit Transfer(_owner, address(0), _tokenId); } function clearApproval(address _owner, uint256 _tokenId) internal { require(ownerOf(_tokenId) == _owner); if (tokenApprovals[_tokenId] != address(0)) { tokenApprovals[_tokenId] = address(0); emit Approval(_owner, address(0), _tokenId); } } function addTokenTo(address _to, uint256 _tokenId) internal { require(tokenOwner[_tokenId] == address(0)); tokenOwner[_tokenId] = _to; ownedTokensCount[_to] = ownedTokensCount[_to].add(1); } function removeTokenFrom(address _from, uint256 _tokenId) internal { require(ownerOf(_tokenId) == _from); ownedTokensCount[_from] = ownedTokensCount[_from].sub(1); tokenOwner[_tokenId] = address(0); } function checkAndCallSafeTransfer( address _from, address _to, uint256 _tokenId, bytes _data ) internal returns (bool) { if (!_to.isContract()) { return true; } bytes4 retval = ERC721Receiver(_to).onERC721Received( _from, _tokenId, _data); return (retval == ERC721_RECEIVED); } }
To understand better all the steps that the inheritance from PausableToken has achieved in making our TestCoin a useful ERC721-compliant token (and why it can be helpful to start from an open source library like OpenZeppelin), you can follow closely the code of each of the library’s files that were imported after each inheritance and import is fully expanded.
For that, browsing the OpenZeppelin ERC721 token github repo will be very helpful: our TestCoin is inheriting from PausableToken, which itself inherits from StandardToken, which inherits from BasicToken and ERC721, and so on all the way to ERC721Basic and the SafeMath library import.
Developing your smart contract directly
Another way of doing this is to simply write the ERC721 interface and your contract implementing it directly from scratch. As you’ll see, this is actually a fairly straightforward task:
pragma solidity ^0.4.16; contract ERC721Token { uint256 public totalSupply; function balanceOf(address _owner) public constant 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 constant returns (uint256 remaining); event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); } contract TestToken is ERC721Token { string public name = "Test Coin"; string public symbol = "TEST"; uint8 public decimals = 6; uint256 public INITIAL_SUPPLY = 100000000 * (10 ** uint256(decimals)); mapping (address => uint256) balances; mapping (address => mapping (address => uint256)) allowed; function TestToken() { totalSupply = INITIAL_SUPPLY; balances[msg.sender] = INITIAL_SUPPLY; } function transfer(address _to, uint256 _value) public returns (bool success) { require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]); require(_to != 0x0); balances[msg.sender] -= _value; balances[_to] += _value; emit Transfer(msg.sender, _to, _value); return true; } function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value); balances[_to] += _value; balances[_from] -= _value; allowed[_from][msg.sender] -= _value; emit Transfer(_from, _to, _value); return true; } function balanceOf(address _owner) public constant 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); return true; } function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { return allowed[_owner][_spender]; } }
You can get this file directly from OpenZeppelin’s GitHub repo or copy paste the code above and edit it as needed. Make sure to save your contract to a local file on your computer (e.g. ERC721BasicToken.sol). However, the code is only for demonstration only.
Compile and deploy ERC721 token smart contract
As a next step, you’ll need to generate the bytecode and ABI for your new smart contract. You can think of the bytecode as basically your contract’s compiled code. The ABI (Application Binary Interface) is a JavaScript Object that defines how to interact with your smart contract.
We will show three ways of achieving this as well - using MOAC wallet, Remix web IDE for Solidity development, or compiling using the solc compiler on your machine (i.e. the command line).
Using MOAC wallet
MOAC wallet is an online free, client-side interface for using MOAC wallets, make transactions and deploy contract. It was developed based on open-source software. To use the service, you need to run a local MOAC node with addition command line arguments:
--rpccorsdomain "http://wallet.moac.io"
This will allow the access of MOAC node using MOAC wallet.
Otherwise you will see the following error message:
ERC20_moacwallet01.png
Example command to start a MOAC node connecting with mainnet:
moac --rpccorsdomain "http://wallet.moac.io" --rpc --rpcport "8545" --rpcapi "chain3,mc,net,db"
A successful interface connecting to mainnet looks like this:
ERC20_moacwallet02.png
To compile the contract, click the “CONTRACTS” icon:
ERC20_moacwallet03.png
Then enter the contract deploy page:
ERC20_moacwallet04.png
Copy the source code and paste in the “SOLIDITY CONTRACT SOURCE CODE” section. After copying the source code, the compiling process will automatically started. If no errors are not found, the right side should show a “SELECT CONTRACT TO DEPLOY” menu. |ERC20\_moacwallet05.png| Select the contract name “TokenERC721”: |ERC20\_moacwallet06.png| Input the parameters values from the menu: |ERC20\_moacwallet07.png| You need to have enough balance to deploy the contract. You can choose the amount of fee to use when deploying the contract. Click the DEPLOY button: |ERC20\_moacwallet08.png|
This is the contract ready to send from Account 1. The Provide gas is estimated by the compiler and we suggest you use it or put a larger number. If gas is not enough, the contract cannot be created. To continue, be sure to unlock the account to create the contract. You can use a console attached to the MOAC to do this:
ERC20_moacwallet09.png
After deploying, the interface is returned to the main menu and you can see the following transaction is creating.
|ERC20\_moacwallet10.png| After 12 confirmations, you can start using the contract by click the admin page link. |ERC20\_moacwallet11.png|
MOAC wallet is good for beginners that only need basic contract development needs. It cannot debug contracts. To advanced developers, you can use Remix to work with contracts.
Remix
Remix is an online tool developed by Ethereum community to work with smart contracts. MOAC also supports the deploy of smart contract through Remix.
Open Remix on your browser, create a new file called ‘TestToken.sol’ and copy paste the code of your smart contract. Make sure you are including all the other Solidity files that your code is referencing with imports, especially if you are using the open source library approach.
Select ‘TestToken’ in the Compile window then click “Start to Compile” and the Details button next to TestToken. Upon scrolling in the popup details window for TestToken, you should be able to see similar sections to this Remix screenshot for the bytecode and ABI of your smart contract:
If the contract is compiled successfully, remix will show the interface like this:
|ERC20\_moacwallet12.png| To deploy the contract, you need to connect REMIX to a local or remote MOAC node. In addition to other arguments, be sure to enable the access of REMIX to the MOAC node with
moac --rpccorsdomain "http://remix.ethereum.org" --rpc --rpcport "8545" --rpcapi "chain3,mc,net,db"
Click the Run Tab and you should see the following menu: |ERC20\_moacwallet13.png|
Choose the Environment menu: JavaScript VM is a simulated environment of Remix, it can be use to debugging the contract without actually deploying the contract to a real network. Injected Web3 is the default web3 connecting to Ethereum network. To deploy MOAC contract, you need to choose Web3 Provider.
ERC20_moacwallet14.png
After choose “Web3 Provider”, you can see a message like this:
Click “OK”,
ERC20_moacwallet15.png
You need to make sure the port is the same as the local running node.
ERC20_moacwallet12.png
You may see the error message like this:
|ERC20\_moacwallet17.png| If you see this error message, check the local node that include both
--rpccorsdomain "http://remix.ethereum.org"
and
--rpcport "8545"
If the connection is established, you should see your accounts from the Account List.
ERC20_moacwallet18.png
Before you deploy the contract, you need to unlock the account that send the contract. You can do the unlock with the MOAC console:
ERC20_moacwallet09.png
After successfully deployed the contract, you should see the contract address and other information showed in the menu:
ERC20_moacwallet19.png
Remix is good for developing and debugging smart contracts. It is not very convenient to deploy multiple contracts. If your requires to deploy multiple contracts, you can use the Node.Js packages.
Using the Node.Js packages
You need to install solc package to compile the smart contract, and chain3 package to deploy the contract.
To use the latest stable version of the Solidity compiler via Node.js you can install it via npm:
npm install solc
var solc = require('solc') var input = 'contract x { function g() {} }' // Setting 1 as second paramateractivates the optimiser var output = solc.compile(input, 1) for (var contractName in output.contracts) { // code and ABI that are needed by web3 console.log(contractName + ': ' + output.contracts[contractName].bytecode) console.log(contractName + '; ' + JSON.parse(output.contracts[contractName].interface)) }
To deploy the contracts, you need to install the Chain3 package:
npm install chain3
There is an example file in the package: example/contract_deploy.js
After successfully deploy, you should see the contract is displayed
Succeed!: 0x95d703ea48477f48335ae9c477ce6d986bc68453dfe3d6582714045456b93405
Using solc compiler to generate the ABI and bytecode Another way of generating these two files is to compile your smart contract using the solc compiler on your machine. If you haven’t used solc yet, you can follow the