🏞️

从Solidity入门到发行NFT

简介

从零开始发行NFT
  • 智能合约编写和部署
  • OpenSea上交易你的NFT!

Solidity基础

Remix IDE(Integrated Development Environment)

在这里你可以:
  • 编写Solidity
  • 编译代码并上链
  • 进行合约交互

Solidity简介:程序结构和面向合约编程

// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; /** * @title Storage * @dev Store & retrieve value in a variable * @custom:dev-run-script ./scripts/deploy_with_ethers.ts */ contract Storage { uint256 number; /** * @dev Store value in variable * @param num value to store */ function store(uint256 num) public { number = num; } /** * @dev Return value * @return value of 'number' */ function retrieve() public view returns (uint256){ return number; } }

账户结构和地址

// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; import "hardhat/console.sol"; /** * @title Owner * @dev Set & change owner */ contract Owner { address private owner; // event for EVM logging event OwnerSet(address indexed oldOwner, address indexed newOwner); // modifier to check if caller is owner modifier isOwner() { // If the first argument of 'require' evaluates to 'false', execution terminates and all // changes to the state and to Ether balances are reverted. // This used to consume all gas in old EVM versions, but not anymore. // It is often a good idea to use 'require' to check if functions are called correctly. // As a second argument, you can also provide an explanation about what went wrong. require(msg.sender == owner, "Caller is not owner"); _; } /** * @dev Set contract deployer as owner */ constructor() { console.log("Owner contract deployed by:", msg.sender); owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor emit OwnerSet(address(0), owner); } /** * @dev Change owner * @param newOwner address of new owner */ function changeOwner(address newOwner) public isOwner { emit OwnerSet(owner, newOwner); owner = newOwner; } /** * @dev Return owner address * @return address of owner */ function getOwner() external view returns (address) { return owner; } }

ERC-721

什么是ERC

Ethereum Improvement Proposals 以太坊改进提案 EIP
notion image
  • Ethereum Request for Comments 是应用层的开发标准

ERC721的接口

FULL

pragma solidity ^0.4.20; /// @title ERC-721 Non-Fungible Token Standard /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md /// Note: the ERC-165 identifier for this interface is 0x80ac58cd interface ERC721 /* is ERC165 */ { /// @dev This emits when ownership of any NFT changes by any mechanism. /// This event emits when NFTs are created (`from` == 0) and destroyed /// (`to` == 0). Exception: during contract creation, any number of NFTs /// may be created and assigned without emitting Transfer. At the time of /// any transfer, the approved address for that NFT (if any) is reset to none. event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); /// @dev This emits when the approved address for an NFT is changed or /// reaffirmed. The zero address indicates there is no approved address. /// When a Transfer event emits, this also indicates that the approved /// address for that NFT (if any) is reset to none. event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); /// @dev This emits when an operator is enabled or disabled for an owner. /// The operator can manage all NFTs of the owner. event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); /// @notice Count all NFTs assigned to an owner /// @dev NFTs assigned to the zero address are considered invalid, and this /// function throws for queries about the zero address. /// @param _owner An address for whom to query the balance /// @return The number of NFTs owned by `_owner`, possibly zero function balanceOf(address _owner) external view returns (uint256); /// @notice Find the owner of an NFT /// @dev NFTs assigned to zero address are considered invalid, and queries /// about them do throw. /// @param _tokenId The identifier for an NFT /// @return The address of the owner of the NFT function ownerOf(uint256 _tokenId) external view returns (address); /// @notice Transfers the ownership of an NFT from one address to another address /// @dev Throws unless `msg.sender` is the current owner, an authorized /// operator, or the approved address for this NFT. Throws if `_from` is /// not the current owner. Throws if `_to` is the zero address. Throws if /// `_tokenId` is not a valid NFT. When transfer is complete, this function /// checks if `_to` is a smart contract (code size > 0). If so, it calls /// `onERC721Received` on `_to` and throws if the return value is not /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. /// @param _from The current owner of the NFT /// @param _to The new owner /// @param _tokenId The NFT to transfer /// @param data Additional data with no specified format, sent in call to `_to` function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; /// @notice Transfers the ownership of an NFT from one address to another address /// @dev This works identically to the other function with an extra data parameter, /// except this function just sets data to "" /// @param _from The current owner of the NFT /// @param _to The new owner /// @param _tokenId The NFT to transfer function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE /// THEY MAY BE PERMANENTLY LOST /// @dev Throws unless `msg.sender` is the current owner, an authorized /// operator, or the approved address for this NFT. Throws if `_from` is /// not the current owner. Throws if `_to` is the zero address. Throws if /// `_tokenId` is not a valid NFT. /// @param _from The current owner of the NFT /// @param _to The new owner /// @param _tokenId The NFT to transfer function transferFrom(address _from, address _to, uint256 _tokenId) external payable; /// @notice Set or reaffirm the approved address for an NFT /// @dev The zero address indicates there is no approved address. /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized /// operator of the current owner. /// @param _approved The new approved NFT controller /// @param _tokenId The NFT to approve function approve(address _approved, uint256 _tokenId) external payable; /// @notice Enable or disable approval for a third party ("operator") to manage /// all of `msg.sender`'s assets. /// @dev Emits the ApprovalForAll event. The contract MUST allow /// multiple operators per owner. /// @param _operator Address to add to the set of authorized operators. /// @param _approved True if the operator is approved, false to revoke approval function setApprovalForAll(address _operator, bool _approved) external; /// @notice Get the approved address for a single NFT /// @dev Throws if `_tokenId` is not a valid NFT /// @param _tokenId The NFT to find the approved address for /// @return The approved address for this NFT, or the zero address if there is none function getApproved(uint256 _tokenId) external view returns (address); /// @notice Query if an address is an authorized operator for another address /// @param _owner The address that owns the NFTs /// @param _operator The address that acts on behalf of the owner /// @return True if `_operator` is an approved operator for `_owner`, false otherwise function isApprovedForAll(address _owner, address _operator) external view returns (bool); } interface ERC165 { /// @notice Query if a contract implements an interface /// @param interfaceID The interface identifier, as specified in ERC-165 /// @dev Interface identification is specified in ERC-165. This function /// uses less than 30,000 gas. /// @return `true` if the contract implements `interfaceID` and /// `interfaceID` is not 0xffffffff, `false` otherwise function supportsInterface(bytes4 interfaceID) external view returns (bool); } interface ERC721TokenReceiver { /// @notice Handle the receipt of an NFT /// @dev The ERC721 smart contract calls this function on the /// recipient after a `transfer`. This function MAY throw to revert and reject the transfer. Return /// of other than the magic value MUST result in the transaction being reverted. /// @notice The contract address is always the message sender. /// @param _operator The address which called `safeTransferFrom` function /// @param _from The address which previously owned the token /// @param _tokenId The NFT identifier which is being transferred /// @param _data Additional data with no specified format /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` /// unless throwing function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4); }
 

Short

event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); 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);

开始编程吧!

打开Remix

上传图片

制作Metadata

发布合约

参考合约代码
// SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import "@openzeppelin/contracts@4.8.1/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts@4.8.1/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts@4.8.1/access/Ownable.sol"; import "@openzeppelin/contracts@4.8.1/utils/Counters.sol"; import "@openzeppelin/contracts@4.8.1/utils/Strings.sol"; contract FudanToken is ERC721, ERC721URIStorage, Ownable { using Counters for Counters.Counter; Counters.Counter private _tokenIdCounter; constructor() ERC721("FudanToken", "FDU") {} function _baseURI() internal pure override returns (string memory) { return "https://depetrol.mypinata.cloud/ipfs/QmYAa9fNyN1RtKhh2RJ8sEstKJz7ZNW4YKTafHqJgxQCuY/"; } function safeMint(address to, string memory uri) public onlyOwner { uint256 tokenId = _tokenIdCounter.current(); _tokenIdCounter.increment(); _safeMint(to, tokenId); _setTokenURI(tokenId, uri); } // The following functions are overrides required by Solidity. function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { super._burn(tokenId); } function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { _requireMinted(tokenId); string memory baseURI = _baseURI(); return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, Strings.toString(tokenId), ".json")) : ""; } }
  1. Compile
  1. Test on Local VM
  1. Deploy to Goerli Testnet

Mint Your NFT!

Check On Opensea

制作人员

  • 主讲人Robert Li
  • NFT系列图片提供