web3-openzeppelin框架及demo
OpenZeppelin Contracts是以太坊生态中最权威的智能合约标准库,被超过30万个项目使用,是构建安全智能合约的黄金标准。 🚀 核心功能模块 1. 代币标准实现 ERC20 (同质化代币) // 基础ERC20代币 import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20 { constructor() ERC20("MyToken", "MTK") { _mint(msg.sender, 1000000 * 10**decimals()); } } ERC721 (NFT) // NFT合约 import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract MyNFT is ERC721 { uint256 private _tokenIds; constructor() ERC721("MyNFT", "MNFT") {} function mint(address to) public returns (uint256) { _tokenIds++; _mint(to, _tokenIds); return _tokenIds; } } ERC1155 (多标准代币) // 多功能代币合约 import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; contract MyMultiToken is ERC1155 { constructor() ERC1155("https://api.example.com/tokens/{id}.json") {} function mint(address to, uint256 id, uint256 amount) public { _mint(to, id, amount, ""); } } 2. 访问控制系统 Ownable (所有者模式) import "@openzeppelin/contracts/access/Ownable.sol"; contract MyContract is Ownable { function restrictedFunction() public onlyOwner { // 只有所有者可以调用 } } AccessControl (角色权限) import "@openzeppelin/contracts/access/AccessControl.sol"; contract MyContract is AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); constructor() { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(MINTER_ROLE, msg.sender); } function mint(address to) public onlyRole(MINTER_ROLE) { // 只有MINTER_ROLE可以铸造 } } 3. 安全工具 ReentrancyGuard (重入攻击防护) import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; contract MyContract is ReentrancyGuard { function withdraw() public nonReentrant { // 防止重入攻击 payable(msg.sender).transfer(balance); } } Pausable (暂停功能) import "@openzeppelin/contracts/utils/Pausable.sol"; contract MyContract is Pausable { function emergencyPause() public onlyOwner { _pause(); } function criticalFunction() public whenNotPaused { // 暂停时无法调用 } } 4. 可升级合约 import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; contract MyUpgradeableToken is Initializable, ERC20Upgradeable { function initialize() public initializer { __ERC20_init("MyToken", "MTK"); } } 🛠️ 完整Demo:使用Hardhat创建一个综合DApp 让我为您创建一个完整的示例项目: 项目初始化 # 创建项目目录 mkdir openzeppelin-demo cd openzeppelin-demo # 初始化npm项目 npm init -y # 安装依赖 npm install --save-dev hardhat npm install @openzeppelin/contracts npm install @openzeppelin/hardhat-upgrades Hardhat配置 // hardhat.config.js require("@nomicfoundation/hardhat-toolbox"); require("@openzeppelin/hardhat-upgrades"); module.exports = { solidity: "0.8.20", networks: { hardhat: { // 本地测试网络 }, sepolia: { url: `https://sepolia.infura.io/v3/${process.env.INFURA_KEY}`, accounts: [process.env.PRIVATE_KEY] } } }; 智能合约示例 // contracts/GameToken.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; // 游戏代币合约 contract GameToken is ERC20, Ownable, Pausable { constructor() ERC20("GameToken", "GAME") Ownable(msg.sender) { _mint(msg.sender, 1000000 * 10**decimals()); } function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); } function pause() public onlyOwner { _pause(); } function unpause() public onlyOwner { _unpause(); } function _update(address from, address to, uint256 value) internal override whenNotPaused { super._update(from, to, value); } } // NFT合约 contract GameNFT is ERC721, Ownable, ReentrancyGuard { uint256 private _tokenIds; uint256 public constant MAX_SUPPLY = 10000; uint256 public constant MINT_PRICE = 0.01 ether; mapping(uint256 => string) private _tokenURIs; constructor() ERC721("GameNFT", "GNFT") Ownable(msg.sender) {} function mint(address to, string memory tokenURI) public payable nonReentrant { require(_tokenIds < MAX_SUPPLY, "Max supply reached"); require(msg.value >= MINT_PRICE, "Insufficient payment"); _tokenIds++; _mint(to, _tokenIds); _setTokenURI(_tokenIds, tokenURI); } function _setTokenURI(uint256 tokenId, string memory tokenURI) internal { _tokenURIs[tokenId] = tokenURI; } function tokenURI(uint256 tokenId) public view override returns (string memory) { return _tokenURIs[tokenId]; } function withdraw() public onlyOwner { payable(owner()).transfer(address(this).balance); } } // 游戏逻辑合约 contract GameLogic is Ownable, ReentrancyGuard { GameToken public gameToken; GameNFT public gameNFT; mapping(address => uint256) public playerScores; mapping(address => uint256) public lastPlayTime; uint256 public constant PLAY_COOLDOWN = 1 hours; uint256 public constant REWARD_AMOUNT = 100 * 10**18; // 100 tokens event GamePlayed(address player, uint256 score, uint256 reward); constructor(address _gameToken, address _gameNFT) Ownable(msg.sender) { gameToken = GameToken(_gameToken); gameNFT = GameNFT(_gameNFT); } function playGame(uint256 score) public nonReentrant { require( block.timestamp >= lastPlayTime[msg.sender] + PLAY_COOLDOWN, "Cooldown not finished" ); playerScores[msg.sender] = score; lastPlayTime[msg.sender] = block.timestamp; // 奖励代币 uint256 reward = calculateReward(score); if (reward > 0) { gameToken.mint(msg.sender, reward); } emit GamePlayed(msg.sender, score, reward); } function calculateReward(uint256 score) public pure returns (uint256) { if (score >= 1000) { return REWARD_AMOUNT * 2; // 双倍奖励 } else if (score >= 500) { return REWARD_AMOUNT; } return 0; } } 测试脚本 // test/GameContracts.test.js const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("Game Contracts", function () { let gameToken, gameNFT, gameLogic; let owner, player1, player2; beforeEach(async function () { [owner, player1, player2] = await ethers.getSigners(); // 部署合约 const GameToken = await ethers.getContractFactory("GameToken"); gameToken = await GameToken.deploy(); const GameNFT = await ethers.getContractFactory("GameNFT"); gameNFT = await GameNFT.deploy(); const GameLogic = await ethers.getContractFactory("GameLogic"); gameLogic = await GameLogic.deploy( await gameToken.getAddress(), await gameNFT.getAddress() ); // 给GameLogic铸造权限 await gameToken.transferOwnership(await gameLogic.getAddress()); }); describe("GameToken", function () { it("Should have correct initial supply", async function () { const totalSupply = await gameToken.totalSupply(); expect(totalSupply).to.equal(ethers.parseEther("1000000")); }); it("Should pause and unpause", async function () { await gameToken.pause(); await expect( gameToken.transfer(player1.address, 100) ).to.be.revertedWithCustomError(gameToken, "EnforcedPause"); await gameToken.unpause(); await gameToken.transfer(player1.address, 100); expect(await gameToken.balanceOf(player1.address)).to.equal(100); }); }); describe("GameNFT", function () { it("Should mint NFT with correct price", async function () { await gameNFT.connect(player1).mint( player1.address, "ipfs://test-uri", { value: ethers.parseEther("0.01") } ); expect(await gameNFT.ownerOf(1)).to.equal(player1.address); expect(await gameNFT.tokenURI(1)).to.equal("ipfs://test-uri"); }); it("Should reject insufficient payment", async function () { await expect( gameNFT.connect(player1).mint( player1.address, "ipfs://test-uri", { value: ethers.parseEther("0.005") } ) ).to.be.revertedWith("Insufficient payment"); }); }); describe("GameLogic", function () { it("Should reward tokens for high scores", async function () { await gameLogic.connect(player1).playGame(1000); const balance = await gameToken.balanceOf(player1.address); expect(balance).to.equal(ethers.parseEther("200")); // 双倍奖励 }); it("Should enforce cooldown", async function () { await gameLogic.connect(player1).playGame(500); await expect( gameLogic.connect(player1).playGame(500) ).to.be.revertedWith("Cooldown not finished"); }); }); }); 部署脚本 // scripts/deploy.js const { ethers, upgrades } = require("hardhat"); async function main() { console.log("开始部署合约..."); // 部署GameToken const GameToken = await ethers.getContractFactory("GameToken"); const gameToken = await GameToken.deploy(); await gameToken.waitForDeployment(); console.log("GameToken deployed to:", await gameToken.getAddress()); // 部署GameNFT const GameNFT = await ethers.getContractFactory("GameNFT"); const gameNFT = await GameNFT.deploy(); await gameNFT.waitForDeployment(); console.log("GameNFT deployed to:", await gameNFT.getAddress()); // 部署GameLogic const GameLogic = await ethers.getContractFactory("GameLogic"); const gameLogic = await GameLogic.deploy( await gameToken.getAddress(), await gameNFT.getAddress() ); await gameLogic.waitForDeployment(); console.log("GameLogic deployed to:", await gameLogic.getAddress()); // 转移所有权 await gameToken.transferOwnership(await gameLogic.getAddress()); console.log("GameToken ownership transferred to GameLogic"); console.log("\n部署完成!"); console.log("合约地址:"); console.log("- GameToken:", await gameToken.getAddress()); console.log("- GameNFT:", await gameNFT.getAddress()); console.log("- GameLogic:", await gameLogic.getAddress()); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); 运行命令 # 编译合约 npx hardhat compile # 运行测试 npx hardhat test # 部署到本地网络 npx hardhat run scripts/deploy.js --network hardhat # 部署到测试网 npx hardhat run scripts/deploy.js --network sepolia 🌟 OpenZeppelin的优势 安全性: 经过多次审计,被广泛使用验证 标准化: 实现了所有主流的ERC标准 模块化: 可以按需组合不同功能 可升级: 支持代理模式的合约升级 文档完善: 详细的
问:
constructor() ERC721("MyNFT", "MNFT") {}这里的ERC721是调用了父类的构造函数吗?
答:
是的!你理解得完全正确!😊 🎯 构造函数继承机制 constructor() ERC721("MyNFT", "MNFT") {} 这里的 ERC721("MyNFT", "MNFT") 确实是在调用父类 ERC721 的构造函数。 📚 详细解释 1. 父类ERC721的构造函数 让我们看看OpenZeppelin的ERC721合约中的构造函数: // 在 @openzeppelin/contracts/token/ERC721/ERC721.sol 中 contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors { string private _name; string private _symbol; constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } function name() public view virtual returns (string memory) { return _name; } function symbol() public view virtual returns (string memory) { return _symbol; } } 2. 子类调用父类构造函数 contract MyNFT is ERC721 { // 调用父类构造函数,传入NFT的名称和符号 constructor() ERC721("MyNFT", "MNFT") { // 这里可以添加子类特有的初始化逻辑 } } 🔍 更复杂的继承示例 多重继承的构造函数调用 import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; contract AdvancedNFT is ERC721, Ownable, Pausable { uint256 private _tokenIds; // 调用多个父类的构造函数 constructor( string memory name, string memory symbol, address initialOwner ) ERC721(name, symbol) // 调用ERC721构造函数 Ownable(initialOwner) // 调用Ownable构造函数 { // Pausable没有构造函数参数,所以不需要显式调用 _tokenIds = 0; } function mint(address to) public onlyOwner whenNotPaused { _tokenIds++; _mint(to, _tokenIds); } } 带参数的构造函数示例 contract CustomNFT is ERC721 { uint256 public maxSupply; uint256 public mintPrice; constructor( string memory _name, string memory _symbol, uint256 _maxSupply, uint256 _mintPrice ) ERC721(_name, _symbol) // 将参数传递给父类 { maxSupply = _maxSupply; mintPrice = _mintPrice; } } // 部署时的调用 // new CustomNFT("MyCollection", "MC", 10000, 0.01 ether) 🛠️ 实际应用示例 游戏NFT合约 contract GameNFT is ERC721, Ownable, ReentrancyGuard { struct NFTAttributes { uint256 level; uint256 power; string rarity; } mapping(uint256 => NFTAttributes) public nftAttributes; uint256 private _tokenIds; constructor( string memory collectionName, string memory collectionSymbol ) ERC721(collectionName, collectionSymbol) // 父类构造函数 Ownable(msg.sender) // 设置合约所有者 { _tokenIds = 0; } function mintGameNFT( address to, uint256 level, uint256 power, string memory rarity ) public onlyOwner nonReentrant { _tokenIds++; // 设置NFT属性 nftAttributes[_tokenIds] = NFTAttributes({ level: level, power: power, rarity: rarity }); _mint(to, _tokenIds); } } 📝 构造函数调用顺序 当有多重继承时,构造函数的调用顺序很重要: contract A { constructor() { console.log("A constructor"); } } contract B { constructor() { console.log("B constructor"); } } contract C is A, B { constructor() A() B() { console.log("C constructor"); } } // 输出顺序: // A constructor // B constructor // C constructor 🎯 关键要点 必须调用: 如果父类有带参数的构造函数,子类必须显式调用 参数传递: 可以直接传递字面量或通过子类构造函数参数传递 执行顺序: 父类构造函数先执行,然后是子类构造函数 多重继承: 按照继承声明的顺序调用各父类构造函数 💡 最佳实践 contract BestPracticeNFT is ERC721, Ownable { // 清晰的参数命名 constructor( string memory _collectionName, string memory _collectionSymbol, address _initialOwner ) ERC721(_collectionName, _collectionSymbol) Ownable(_initialOwner) { // 子类特有的初始化逻辑 // 例如:设置初始状态、铸造创世NFT等 } } 这种继承机制让我们能够复用OpenZeppelin经过审计的安全代码,同时添加自己的业务逻辑,是智能合约开发的核心模式之一!
问:
ERC721.sol里
function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) { address from = _ownerOf(tokenId); // Perform (optional) operator check if (auth != address(0)) { _checkAuthorized(from, auth, tokenId); } // Execute the update if (from != address(0)) { // Clear approval. No need to re-authorize or emit the Approval event _approve(address(0), tokenId, address(0), false); unchecked { _balances[from] -= 1; } } if (to != address(0)) { unchecked { _balances[to] += 1; } } _owners[tokenId] = to; emit Transfer(from, to, tokenId); return from; }
这个函数的作用。请详解每一行代码。
答:
很好的问题!这个 _update 函数是 ERC721 合约的核心函数,负责处理所有的 NFT 所有权转移操作。让我逐行详细解释: 🎯 函数签名解析 function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) internal: 只能在合约内部或继承合约中调用 virtual: 允许子合约重写这个函数 参数: to: 接收者地址(可以是 address(0) 表示销毁) tokenId: 要转移的 NFT ID auth: 授权者地址(可以是 address(0) 跳过权限检查) 返回值: 原所有者地址 📝 逐行代码解析 第1行:获取当前所有者 address from = _ownerOf(tokenId); 调用 _ownerOf(tokenId) 获取当前 NFT 的所有者 如果 NFT 不存在,_ownerOf 会返回 address(0) from 可能的值: 具体地址:NFT 的当前所有者 address(0):NFT 不存在(铸造场景) 第2-4行:权限检查 // Perform (optional) operator check if (auth != address(0)) { _checkAuthorized(from, auth, tokenId); } 条件判断: 如果 auth 不是零地址,执行权限检查 _checkAuthorized: 验证 auth 是否有权限操作这个 NFT 权限包括: NFT 所有者本人 被授权的操作者(通过 approve 授权) 被授权操作所有 NFT 的操作者(通过 setApprovalForAll) 第5-12行:处理转出逻辑 // Execute the update if (from != address(0)) { // Clear approval. No need to re-authorize or emit the Approval event _approve(address(0), tokenId, address(0), false); unchecked { _balances[from] -= 1; } } 条件: 如果 from 不是零地址(即 NFT 存在且有所有者) 清除授权: _approve(address(0), tokenId, address(0), false) 将 NFT 的单独授权清零 最后的 false 参数表示不发出 Approval 事件 减少余额: _balances[from] -= 1 原所有者的 NFT 数量减 1 unchecked 用于跳过溢出检查(gas 优化) 第13-17行:处理转入逻辑 if (to != address(0)) { unchecked { _balances[to] += 1; } } 条件: 如果 to 不是零地址(即不是销毁操作) 增加余额: 新所有者的 NFT 数量加 1 unchecked: 跳过溢出检查以节省 gas 第18行:更新所有权 _owners[tokenId] = to; 直接更新 _owners 映射,将 NFT 的所有者设为 to 如果 to 是 address(0),表示销毁 NFT 第19行:发出事件 emit Transfer(from, to, tokenId); 发出标准的 ERC721 Transfer 事件 所有钱包和 DApp 都会监听这个事件来更新界面 第20行:返回原所有者 return from; 返回原所有者地址,供调用者使用 🎮 不同场景下的函数行为 1. 铸造 NFT (Minting) // 调用: _update(alice, 1, address(0)) // from = address(0) (NFT不存在) // to = alice // auth = address(0) (跳过权限检查) // 执行流程: // 1. from = address(0) // 2. 跳过权限检查 // 3. 跳过转出逻辑 (from == address(0)) // 4. alice余额 +1 // 5. _owners[1] = alice // 6. emit Transfer(address(0), alice, 1) 2. 正常转移 NFT // 调用: _update(bob, 1, alice) // from = alice (当前所有者) // to = bob // auth = alice (所有者自己操作) // 执行流程: // 1. from = alice // 2. 检查alice是否有权限 ✓ // 3. 清除NFT授权,alice余额 -1 // 4. bob余额 +1 // 5. _owners[1] = bob // 6. emit Transfer(alice, bob, 1) 3. 授权转移 NFT // 调用: _update(charlie, 1, bob) // from = alice (当前所有者) // to = charlie // auth = bob (被授权的操作者) // 执行流程: // 1. from = alice // 2. 检查bob是否有权限操作alice的NFT ✓ // 3. 清除NFT授权,alice余额 -1 // 4. charlie余额 +1 // 5. _owners[1] = charlie // 6. emit Transfer(alice, charlie, 1) 4. 销毁 NFT (Burning) // 调用: _update(address(0), 1, alice) // from = alice (当前所有者) // to = address(0) // auth = alice (所有者自己操作) // 执行流程: // 1. from = alice // 2. 检查alice是否有权限 ✓ // 3. 清除NFT授权,alice余额 -1 // 4. 跳过转入逻辑 (to == address(0)) // 5. _owners[1] = address(0) // 6. emit Transfer(alice, address(0), 1) 🔧 在实际合约中的使用 contract MyNFT is ERC721 { constructor() ERC721("MyNFT", "MNFT") {} // 铸造函数 function mint(address to, uint256 tokenId) public { _update(to, tokenId, address(0)); // 跳过权限检查 } // 销毁函数 function burn(uint256 tokenId) public { _update(address(0), tokenId, msg.sender); // 需要权限检查 } // 重写_update添加自定义逻辑 function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { address from = super._update(to, tokenId, auth); // 添加自定义逻辑 if (from == address(0)) { // 铸造时的逻辑 totalSupply++; } else if (to == address(0)) { // 销毁时的逻辑 totalSupply--; } return from; } } 🎯 关键设计思想 统一入口: 所有所有权变更都通过这一个函数 灵活权限: 通过 auth 参数控制是否检查权限 状态一致性: 确保 _owners 和 _balances 始终同步 事件完整性: 保证每次转移都发出正确的事件 可扩展性: virtual 关键字允许子合约添加自定义逻辑
问:
ERC721.sol里铸造 NFT (Minting), 正常转移 NFT, 授权转移 NFT,销毁 NFT (Burning)这几个场景,有没有相应的函数? 还有,代表某个NFT的tokenId,从哪里来?NTF是怎么创造出来的?
答:
非常好的问题!让我详细解释 ERC721 中这些场景的具体函数实现和 NFT 的创造过程。 🎯 ERC721.sol 中的核心函数 1. 铸造 NFT (Minting) // ERC721.sol 中的内部铸造函数 function _mint(address to, uint256 tokenId) internal { if (to == address(0)) { revert ERC721InvalidReceiver(address(0)); } address previousOwner = _update(to, tokenId, address(0)); if (previousOwner != address(0)) { revert ERC721InvalidSender(address(0)); } } // 安全铸造函数(检查接收者是否能处理NFT) function _safeMint(address to, uint256 tokenId) internal { _safeMint(to, tokenId, ""); } function _safeMint(address to, uint256 tokenId, bytes memory data) internal { _mint(to, tokenId); _checkOnERC721Received(address(0), to, tokenId, data); } 实际项目中的铸造实现: contract MyNFT is ERC721 { uint256 private _nextTokenId = 1; constructor() ERC721("MyNFT", "MNFT") {} // 公开的铸造函数 function mint(address to) public returns (uint256) { uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); return tokenId; } // 批量铸造 function batchMint(address to, uint256 quantity) public { for (uint256 i = 0; i < quantity; i++) { uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); } } } 2. 正常转移 NFT // ERC721.sol 中的转移函数 function transferFrom(address from, address to, uint256 tokenId) public virtual { if (to == address(0)) { revert ERC721InvalidReceiver(address(0)); } // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. address previousOwner = _update(to, tokenId, msg.sender); if (previousOwner != from) { revert ERC721IncorrectOwner(from, previousOwner, tokenId); } } // 安全转移函数 function safeTransferFrom(address from, address to, uint256 tokenId) public { safeTransferFrom(from, to, tokenId, ""); } function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual { transferFrom(from, to, tokenId); _checkOnERC721Received(from, to, tokenId, data); } 3. 授权转移 NFT // 单个NFT授权 function approve(address to, uint256 tokenId) public virtual { _approve(to, tokenId, msg.sender); } // 内部授权函数 function _approve(address to, uint256 tokenId, address auth) internal { _approve(to, tokenId, auth, true); } function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual { // Avoid reading the owner unless necessary if (emitEvent || auth != address(0)) { address owner = _requireOwned(tokenId); // We do not use _isAuthorized because single-token approvals should not be able to call approve if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) { revert ERC721InvalidApprover(auth); } if (emitEvent) { emit Approval(owner, to, tokenId); } } _tokenApprovals[tokenId] = to; } // 全部NFT授权 function setApprovalForAll(address operator, bool approved) public virtual { _setApprovalForAll(msg.sender, operator, approved); } 授权转移的完整流程: contract NFTExample { function demonstrateApprovalTransfer() public { // 1. Alice拥有tokenId = 1的NFT // 2. Alice授权Bob操作这个NFT nft.approve(bob, 1); // 3. Bob现在可以转移这个NFT给Charlie nft.transferFrom(alice, charlie, 1); // Bob调用 } } 4. 销毁 NFT (Burning) // ERC721.sol 中的销毁函数 function _burn(uint256 tokenId) internal { address previousOwner = _update(address(0), tokenId, address(0)); if (previousOwner == address(0)) { revert ERC721NonexistentToken(tokenId); } } 实际项目中的销毁实现: contract MyNFT is ERC721 { function burn(uint256 tokenId) public { // 只有所有者或被授权者可以销毁 address owner = ownerOf(tokenId); require( msg.sender == owner || getApproved(tokenId) == msg.sender || isApprovedForAll(owner, msg.sender), "Not authorized to burn" ); _burn(tokenId); } } 🆔 TokenId 的来源和管理 1. 简单递增策略 contract SimpleNFT is ERC721 { uint256 private _currentTokenId = 0; function mint(address to) public returns (uint256) { _currentTokenId++; _safeMint(to, _currentTokenId); return _currentTokenId; } } 2. 使用 OpenZeppelin 的 Counter import "@openzeppelin/contracts/utils/Counters.sol"; contract CounterNFT is ERC721 { using Counters for Counters.Counter; Counters.Counter private _tokenIds; function mint(address to) public returns (uint256) { _tokenIds.increment(); uint256 newTokenId = _tokenIds.current(); _safeMint(to, newTokenId); return newTokenId; } } 3. 基于时间戳的 TokenId contract TimestampNFT is ERC721 { mapping(uint256 => bool) private _exists; function mint(address to) public returns (uint256) { uint256 tokenId = block.timestamp; // 确保唯一性 while (_exists[tokenId]) { tokenId++; } _exists[tokenId] = true; _safeMint(to, tokenId); return tokenId; } } 4. 随机 TokenId contract RandomNFT is ERC721 { mapping(uint256 => bool) private _exists; uint256 private _nonce = 0; function mint(address to) public returns (uint256) { uint256 tokenId; do { tokenId = uint256(keccak256(abi.encodePacked( block.timestamp, block.difficulty, msg.sender, _nonce++ ))); } while (_exists[tokenId]); _exists[tokenId] = true; _safeMint(to, tokenId); return tokenId; } } 🎨 NFT 创造的完整流程 完整的 NFT 项目示例 // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; contract GameNFT is ERC721, ERC721URIStorage, Ownable, ReentrancyGuard { uint256 private _nextTokenId = 1; uint256 public constant MAX_SUPPLY = 10000; uint256 public mintPrice = 0.01 ether; // NFT属性 struct NFTAttributes { string name; uint256 level; uint256 power; string rarity; uint256 createdAt; } mapping(uint256 => NFTAttributes) public nftAttributes; constructor() ERC721("GameNFT", "GNFT") Ownable(msg.sender) {} // 1. 铸造 NFT function mint( address to, string memory name, string memory uri, uint256 level, uint256 power, string memory rarity ) public payable nonReentrant { require(_nextTokenId <= MAX_SUPPLY, "Max supply reached"); require(msg.value >= mintPrice, "Insufficient payment"); uint256 tokenId = _nextTokenId; _nextTokenId++; // 铸造NFT _safeMint(to, tokenId); _setTokenURI(tokenId, uri); // 设置属性 nftAttributes[tokenId] = NFTAttributes({ name: name, level: level, power: power, rarity: rarity, createdAt: block.timestamp }); } // 2. 正常转移(继承自ERC721) // transferFrom() 和 safeTransferFrom() 已经在父合约中实现 // 3. 授权转移(继承自ERC721) // approve() 和 setApprovalForAll() 已经在父合约中实现 // 4. 销毁 NFT function burn(uint256 tokenId) public { require( msg.sender == ownerOf(tokenId) || getApproved(tokenId) == msg.sender || isApprovedForAll(ownerOf(tokenId), msg.sender), "Not authorized to burn" ); _burn(tokenId); delete nftAttributes[tokenId]; } // 升级NFT function upgradeNFT(uint256 tokenId) public { require(msg.sender == ownerOf(tokenId), "Not the owner"); require(nftAttributes[tokenId].level < 100, "Max level reached"); nftAttributes[tokenId].level++; nftAttributes[tokenId].power += 10; } // 获取NFT属性 function getNFTAttributes(uint256 tokenId) public view returns (NFTAttributes memory) { require(_ownerOf(tokenId) != address(0), "Token does not exist"); return nftAttributes[tokenId]; } // 重写必要的函数 function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) { return super.supportsInterface(interfaceId); } // 提取合约余额 function withdraw() public onlyOwner { payable(owner()).transfer(address(this).balance); } } 使用示例 // JavaScript 使用示例 const gameNFT = await ethers.getContractAt("GameNFT", contractAddress); // 1. 铸造NFT await gameNFT.mint( userAddress, "Fire Dragon", "ipfs://QmFireDragon123...", 1, 100, "Legendary", { value: ethers.parseEther("0.01") } ); // 2. 转移NFT await gameNFT.transferFrom(fromAddress, toAddress, tokenId); // 3. 授权NFT await gameNFT.approve(operatorAddress, tokenId); // 或者授权所有NFT await gameNFT.setApprovalForAll(operatorAddress, true); // 4. 销毁NFT await gameNFT.burn(tokenId); // 5. 升级NFT await gameNFT.upgradeNFT(tokenId); 🎯 总结 铸造: _mint() / _safeMint() - 从零地址转移到用户 转移: transferFrom() / safeTransferFrom() - 用户间转移 授权: approve() / setApprovalForAll() - 授权他人操作 销毁: _burn() - 转移到零地址 TokenId 来源: 通常由合约内部生成(递增、时间戳、随机等) 开发者在铸造时指定 必须保证唯一性 NFT 创造过程: 部署 ERC721 合约 调用铸造函数,传入 tokenId 和接收者 合约更新状态(所有权、余额等) 发出 Transfer 事件 NFT 正式存在于区块链上
问:mintPrice这个属性的作用?给这个nft定的eth价值吗?
答:
非常好的问题!让我详细解释 mintPrice 的作用和 NFT 定价机制。 💰 mintPrice 的核心作用 mintPrice 不是给 NFT 定价,而是设定铸造成本!这是两个完全不同的概念: 铸造价格 vs NFT 价值 contract GameNFT is ERC721 { uint256 public mintPrice = 0.01 ether; // 铸造成本 function mint(address to, string memory uri) public payable { require(msg.value >= mintPrice, "Insufficient payment"); // 铸造逻辑... } } mintPrice: 用户需要支付多少 ETH 才能铸造一个新的 NFT NFT 价值: NFT 在二级市场的交易价格(由市场决定) 🎯 mintPrice 的具体作用 1. 防止滥用铸造 contract AntiSpamNFT is ERC721 { uint256 public mintPrice = 0.001 ether; // 很小的费用 function mint(address to) public payable { require(msg.value >= mintPrice, "Must pay mint fee"); // 防止用户无限制铸造垃圾NFT _safeMint(to, _nextTokenId++); } } 2. 项目方收入来源 contract RevenueNFT is ERC721, Ownable { uint256 public mintPrice = 0.05 ether; function mint(address to) public payable { require(msg.value >= mintPrice, "Insufficient payment"); _safeMint(to, _nextTokenId++); // 项目方获得收入 // 可以用于开发、运营、奖励等 } function withdraw() public onlyOwner { payable(owner()).transfer(address(this).balance); } } 3. 控制供应量 contract LimitedNFT is ERC721 { uint256 public mintPrice = 0.1 ether; // 较高价格 uint256 public constant MAX_SUPPLY = 1000; function mint(address to) public payable { require(msg.value >= mintPrice, "Insufficient payment"); require(_nextTokenId <= MAX_SUPPLY, "Sold out"); // 高价格 + 限量 = 稀缺性 _safeMint(to, _nextTokenId++); } } 📈 动态定价策略 1. 阶梯定价 contract TieredPricingNFT is ERC721 { uint256 private _nextTokenId = 1; function getCurrentMintPrice() public view returns (uint256) { uint256 supply = _nextTokenId - 1; if (supply < 1000) { return 0.01 ether; // 前1000个:0.01 ETH } else if (supply < 5000) { return 0.03 ether; // 1000-5000:0.03 ETH } else { return 0.05 ether; // 5000+:0.05 ETH } } function mint(address to) public payable { uint256 currentPrice = getCurrentMintPrice(); require(msg.value >= currentPrice, "Insufficient payment"); _safeMint(to, _nextTokenId++); } } 2. 荷兰拍卖定价 contract DutchAuctionNFT is ERC721 { uint256 public constant AUCTION_START_PRICE = 1 ether; uint256 public constant AUCTION_END_PRICE = 0.1 ether; uint256 public constant AUCTION_DURATION = 24 hours; uint256 public auctionStartTime; constructor() ERC721("DutchNFT", "DNFT") { auctionStartTime = block.timestamp; } function getCurrentPrice() public view returns (uint256) { if (block.timestamp >= auctionStartTime + AUCTION_DURATION) { return AUCTION_END_PRICE; } uint256 elapsed = block.timestamp - auctionStartTime; uint256 priceDecrease = (AUCTION_START_PRICE - AUCTION_END_PRICE) * elapsed / AUCTION_DURATION; return AUCTION_START_PRICE - priceDecrease; } function mint(address to) public payable { uint256 currentPrice = getCurrentPrice(); require(msg.value >= currentPrice, "Insufficient payment"); _safeMint(to, _nextTokenId++); // 退还多余的ETH if (msg.value > currentPrice) { payable(msg.sender).transfer(msg.value - currentPrice); } } } 3. 白名单定价 contract WhitelistNFT is ERC721, Ownable { uint256 public publicMintPrice = 0.08 ether; uint256 public whitelistMintPrice = 0.05 ether; mapping(address => bool) public whitelist; mapping(address => uint256) public whitelistMinted; uint256 public constant WHITELIST_LIMIT = 2; function addToWhitelist(address[] calldata addresses) external onlyOwner { for (uint256 i = 0; i < addresses.length; i++) { whitelist[addresses[i]] = true; } } function whitelistMint(address to, uint256 quantity) public payable { require(whitelist[msg.sender], "Not whitelisted"); require(whitelistMinted[msg.sender] + quantity <= WHITELIST_LIMIT, "Exceeds whitelist limit"); require(msg.value >= whitelistMintPrice * quantity, "Insufficient payment"); whitelistMinted[msg.sender] += quantity; for (uint256 i = 0; i < quantity; i++) { _safeMint(to, _nextTokenId++); } } function publicMint(address to, uint256 quantity) public payable { require(msg.value >= publicMintPrice * quantity, "Insufficient payment"); for (uint256 i = 0; i < quantity; i++) { _safeMint(to, _nextTokenId++); } } } 💡 实际项目中的定价策略 1. CryptoPunks 风格(免费铸造) contract FreeMintNFT is ERC721 { uint256 public constant MAX_FREE_MINT = 10000; mapping(address => bool) public hasMinted; function freeMint() public { require(!hasMinted[msg.sender], "Already minted"); require(_nextTokenId <= MAX_FREE_MINT, "Free mint ended"); hasMinted[msg.sender] = true; _safeMint(msg.sender, _nextTokenId++); } } 2. BAYC 风格(固定价格) contract FixedPriceNFT is ERC721 { uint256 public constant MINT_PRICE = 0.08 ether; uint256 public constant MAX_SUPPLY = 10000; uint256 public constant MAX_PER_TX = 20; function mint(uint256 quantity) public payable { require(quantity <= MAX_PER_TX, "Exceeds max per transaction"); require(_nextTokenId + quantity <= MAX_SUPPLY, "Exceeds max supply"); require(msg.value >= MINT_PRICE * quantity, "Insufficient payment"); for (uint256 i = 0; i < quantity; i++) { _safeMint(msg.sender, _nextTokenId++); } } } 3. 游戏 NFT(多种定价) contract GameItemNFT is ERC721 { enum ItemType { COMMON, RARE, EPIC, LEGENDARY } mapping(ItemType => uint256) public mintPrices; mapping(uint256 => ItemType) public tokenTypes; constructor() ERC721("GameItems", "ITEMS") { mintPrices[ItemType.COMMON] = 0.001 ether; mintPrices[ItemType.RARE] = 0.01 ether; mintPrices[ItemType.EPIC] = 0.05 ether; mintPrices[ItemType.LEGENDARY] = 0.2 ether; } function mintItem(ItemType itemType) public payable { uint256 price = mintPrices[itemType]; require(msg.value >= price, "Insufficient payment"); uint256 tokenId = _nextTokenId++; tokenTypes[tokenId] = itemType; _safeMint(msg.sender, tokenId); } } 🔄 铸造价格 vs 二级市场价格 contract MarketExample { // 铸造时:用户支付 0.05 ETH 给合约 function mint() public payable { require(msg.value >= 0.05 ether, "Mint price: 0.05 ETH"); // 铸造 NFT... } // 二级市场:用户之间交易,价格由市场决定 // OpenSea, LooksRare 等平台上的价格可能是: // - 0.1 ETH(涨价了) // - 0.02 ETH(跌价了) // - 10 ETH(非常稀有) } 🎯 关键要点总结 mintPrice = 铸造成本,不是 NFT 价值 NFT 价值 = 二级市场决定,可能远高于或低于铸造价格 定价策略: 免费铸造(吸引用户) 固定价格(简单直接) 动态定价(增加稀缺性) 分层定价(不同类型不同价格) 收入用途: 项目开发资金 运营成本 社区奖励 创作者收益 所以 mintPrice 更像是"入场费",而 NFT 的真正价值由市场、稀缺性、实用性、社区等因素共同决定!😊
问:
ERC20作为同质代币标准,那我部署了一个继承ERC20的代币合约,铸造了20万个代币给我的钱包账号,我可以用这20万代币给其它的合约使用吗(如购买商品,游戏道具,购买非同质化藏品)?
答:
非常好的问题!理论上可以,但实际上需要其他合约明确支持你的代币。让我详细解释这个过程: 🎯 核心概念 你铸造的 20 万代币就像你创造了一种"新货币",但这种货币能否被接受使用,完全取决于接收方是否认可。 💰 你的代币合约示例 // 你部署的代币合约 contract MyToken is ERC20 { constructor() ERC20("MyToken", "MTK") { // 给你铸造 20 万个代币 _mint(msg.sender, 200000 * 10**18); } } // 部署后,你的钱包里有 200,000 MTK 🛒 商城合约如何支持你的代币 1. 明确支持你的代币的商城 contract MyTokenShop { IERC20 public myToken; // 明确指定接受你的代币 struct Product { string name; uint256 price; // 以 MTK 计价 bool available; } mapping(uint256 => Product) public products; constructor(address myTokenAddress) { myToken = IERC20(myTokenAddress); // 设置接受你的代币 // 设置商品价格(以你的代币计价) products[1] = Product("游戏道具A", 100 * 10**18, true); // 100 MTK products[2] = Product("数字艺术品", 500 * 10**18, true); // 500 MTK } function buyProduct(uint256 productId) public { Product memory product = products[productId]; require(product.available, "Product not available"); // 用你的代币支付 myToken.transferFrom(msg.sender, address(this), product.price); // 发放商品逻辑... emit ProductPurchased(msg.sender, productId, product.price); } } 2. 支持多种代币的商城 contract MultiTokenShop { // 支持多种 ERC20 代币 mapping(address => bool) public acceptedTokens; mapping(address => uint256) public exchangeRates; // 相对于基准的汇率 struct Product { string name; uint256 basePrice; // 基准价格 bool available; } mapping(uint256 => Product) public products; constructor() { // 商城管理员可以添加接受的代币 products[1] = Product("游戏装备", 1000, true); // 基准价格 1000 } // 管理员添加你的代币 function addAcceptedToken(address tokenAddress, uint256 rate) public onlyOwner { acceptedTokens[tokenAddress] = true; exchangeRates[tokenAddress] = rate; // 例如:1 基准单位 = 10 MTK } function buyProduct(uint256 productId, address tokenAddress) public { require(acceptedTokens[tokenAddress], "Token not accepted"); Product memory product = products[productId]; uint256 tokenPrice = product.basePrice * exchangeRates[tokenAddress]; IERC20(tokenAddress).transferFrom(msg.sender, address(this), tokenPrice); // 发放商品... } } 🎮 游戏合约使用你的代币 contract GameContract { IERC20 public gameToken; // 指定你的代币作为游戏货币 IERC721 public gameItems; // 游戏道具 NFT mapping(uint256 => uint256) public itemPrices; // 道具价格(以你的代币计价) constructor(address myTokenAddress, address gameItemsAddress) { gameToken = IERC20(myTokenAddress); gameItems = IERC721(gameItemsAddress); // 设置道具价格 itemPrices[1] = 50 * 10**18; // 普通剑:50 MTK itemPrices[2] = 200 * 10**18; // 稀有盾:200 MTK itemPrices[3] = 1000 * 10**18; // 传奇装备:1000 MTK } function buyGameItem(uint256 itemType) public { uint256 price = itemPrices[itemType]; require(price > 0, "Item not available"); // 用你的代币支付 gameToken.transferFrom(msg.sender, address(this), price); // 铸造游戏道具 NFT 给玩家 gameItems.mint(msg.sender, itemType); emit ItemPurchased(msg.sender, itemType, price); } // 游戏内奖励,发放你的代币 function claimReward(address player, uint256 amount) public onlyGame { gameToken.transfer(player, amount); } } 🖼️ NFT 市场使用你的代币 contract NFTMarketplace { // 支持多种支付方式 IERC20 public myToken; struct Listing { address seller; uint256 ethPrice; // ETH 价格 uint256 tokenPrice; // 你的代币价格 bool acceptsToken; // 是否接受代币支付 bool active; } mapping(address => mapping(uint256 => Listing)) public listings; constructor(address myTokenAddress) { myToken = IERC20(myTokenAddress); } // 上架 NFT,同时支持 ETH 和你的代币 function listNFT( address nftContract, uint256 tokenId, uint256 ethPrice, uint256 tokenPrice, bool acceptsToken ) public { IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId); listings[nftContract][tokenId] = Listing({ seller: msg.sender, ethPrice: ethPrice, tokenPrice: tokenPrice, acceptsToken: acceptsToken, active: true }); } // 用你的代币购买 NFT function buyWithToken(address nftContract, uint256 tokenId) public { Listing memory listing = listings[nftContract][tokenId]; require(listing.active && listing.acceptsToken, "Not available for token payment"); // 用你的代币支付 myToken.transferFrom(msg.sender, listing.seller, listing.tokenPrice); // 转移 NFT IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId); delete listings[nftContract][tokenId]; } } 🔄 实际使用流程 1. 部署和铸造代币 // 1. 部署你的代币合约 const MyToken = await ethers.getContractFactory("MyToken"); const myToken = await MyToken.deploy(); // 2. 验证你有 20 万代币 const balance = await myToken.balanceOf(yourAddress); console.log(`You have ${ethers.formatEther(balance)} MTK`); 2. 在支持的商城使用 // 3. 授权商城使用你的代币 await myToken.approve(shopAddress, ethers.parseEther("1000")); // 4. 购买商品 await shop.buyProduct(1); // 购买商品 1 3. 在游戏中使用 // 5. 授权游戏合约 await myToken.approve(gameAddress, ethers.parseEther("500")); // 6. 购买游戏道具 await gameContract.buyGameItem(2); // 购买稀有盾牌 ⚠️ 实际限制和挑战 1. 接受度问题 // 大多数现有合约不会接受你的代币 contract ExistingShop { IERC20 public constant USDC = IERC20(0xA0b86a33E6441e1c8d2dB9E4E69D1C6C8C4E9D7F); // 只接受 USDC,不接受你的 MTK function buyProduct() public { USDC.transferFrom(msg.sender, address(this), 100 * 10**6); // 只要 USDC } } 2. 价值认知问题 contract RealisticShop { // 即使技术上支持,商家也需要相信你的代币有价值 function buyRealProduct(uint256 myTokenAmount) public { // 商家思考:这些 MTK 能换成真钱吗? // 如果不能,为什么要接受? } } 3. 流动性问题 // 你的代币需要有交易市场 // 否则商家收到后无法变现 // 创建流动性池(例如在 Uniswap) const uniswapFactory = await ethers.getContractAt("IUniswapV2Factory", factoryAddress); await uniswapFactory.createPair(myTokenAddress, wethAddress); // 添加流动性 const router = await ethers.getContractAt("IUniswapV2Router", routerAddress); await myToken.approve(routerAddress, ethers.parseEther("100000")); await router.addLiquidityETH( myTokenAddress, ethers.parseEther("100000"), // 10万 MTK 0, 0, yourAddress, deadline, { value: ethers.parseEther("10") } // 10 ETH ); 🎯 成功使用的策略 1. 创建自己的生态系统 contract MyTokenEcosystem { IERC20 public myToken; // 你的商城 MyTokenShop public shop; // 你的游戏 MyTokenGame public game; // 你的 NFT 市场 MyTokenNFTMarket public nftMarket; constructor(address tokenAddress) { myToken = IERC20(tokenAddress); // 部署所有支持你代币的合约 shop = new MyTokenShop(tokenAddress); game = new MyTokenGame(tokenAddress); nftMarket = new MyTokenNFTMarket(tokenAddress); } } 2. 与现有项目合作 contract PartnershipContract { IERC20 public myToken; IERC20 public partnerToken; // 代币兑换功能 function swapTokens(uint256 myTokenAmount) public { uint256 partnerTokenAmount = myTokenAmount * exchangeRate / 1000; myToken.transferFrom(msg.sender, address(this), myTokenAmount); partnerToken.transfer(msg.sender, partnerTokenAmount); } } 🎯 总结 可以使用,但需要满足条件: ✅ 技术上完全可行 - ERC20 标准保证互操作性 ⚠️ 需要对方合约支持 - 合约必须明确接受你的代币 ⚠️ 需要价值认知 - 对方要相信你的代币有价值 ⚠️ 需要流动性 - 要有地方可以交易你的代币 最佳策略: 构建自己的生态系统 寻找合作伙伴 在 DEX 上创建流动性 为代币赋予实际用途和价值 你的 20 万代币就像你印制的"游戏币",能否当钱花取决于有没有人愿意接受!😊
问:那我的代币和USDT都是遵循IERC20标准,怎么判断是哪个智能合币铸造的代币的?
答:
非常好的问题!虽然都遵循 ERC20 标准,但每个代币都有唯一的合约地址来区分。让我详细解释如何识别不同的代币: 🎯 核心区别:合约地址 每个 ERC20 代币都有唯一的智能合约地址,这是区分不同代币的关键: // USDT 在以太坊主网的合约地址 address constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; // 你的代币合约地址(部署后获得) address constant MY_TOKEN = 0x1234567890123456789012345678901234567890; // 示例地址 📍 如何获取和验证代币信息 1. 通过合约地址获取代币信息 contract TokenIdentifier { function getTokenInfo(address tokenAddress) public view returns ( string memory name, string memory symbol, uint8 decimals, uint256 totalSupply ) { IERC20Metadata token = IERC20Metadata(tokenAddress); name = token.name(); symbol = token.symbol(); decimals = token.decimals(); totalSupply = token.totalSupply(); } } 2. 实际查询示例 // JavaScript 查询代币信息 async function getTokenDetails(tokenAddress) { const tokenContract = await ethers.getContractAt("IERC20Metadata", tokenAddress); const name = await tokenContract.name(); const symbol = await tokenContract.symbol(); const decimals = await tokenContract.decimals(); const totalSupply = await tokenContract.totalSupply(); console.log(`代币名称: ${name}`); console.log(`代币符号: ${symbol}`); console.log(`小数位数: ${decimals}`); console.log(`总供应量: ${ethers.formatUnits(totalSupply, decimals)}`); console.log(`合约地址: ${tokenAddress}`); } // 查询 USDT await getTokenDetails("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // 输出: // 代币名称: Tether USD // 代币符号: USDT // 小数位数: 6 // 总供应量: 约 83,000,000,000 // 合约地址: 0xdAC17F958D2ee523a2206206994597C13D831ec7 // 查询你的代币 await getTokenDetails("你的代币合约地址"); // 输出: // 代币名称: MyToken // 代币符号: MTK // 小数位数: 18 // 总供应量: 200,000 // 合约地址: 你的合约地址 🔍 智能合约中如何区分代币 1. 明确指定代币合约 contract TokenSpecificContract { // 明确指定只接受 USDT IERC20 public constant USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); // 明确指定只接受你的代币 IERC20 public myToken; constructor(address myTokenAddress) { myToken = IERC20(myTokenAddress); } function payWithUSDT(uint256 amount) public { // 只能用 USDT 支付 USDT.transferFrom(msg.sender, address(this), amount); } function payWithMyToken(uint256 amount) public { // 只能用你的代币支付 myToken.transferFrom(msg.sender, address(this), amount); } } 2. 支持多代币但区分处理 contract MultiTokenContract { // 代币白名单 mapping(address => bool) public acceptedTokens; mapping(address => string) public tokenNames; mapping(address => uint256) public exchangeRates; address public constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; constructor() { // 添加 USDT acceptedTokens[USDT] = true; tokenNames[USDT] = "USDT"; exchangeRates[USDT] = 1; // 1:1 基准汇率 } function addToken(address tokenAddress, string memory name, uint256 rate) public onlyOwner { acceptedTokens[tokenAddress] = true; tokenNames[tokenAddress] = name; exchangeRates[tokenAddress] = rate; } function payWithToken(address tokenAddress, uint256 amount) public { require(acceptedTokens[tokenAddress], "Token not accepted"); // 根据代币地址区分处理 if (tokenAddress == USDT) { // USDT 特殊处理(6位小数) require(amount >= 1 * 10**6, "Minimum 1 USDT"); } else { // 其他代币处理(通常18位小数) require(amount >= 1 * 10**18, "Minimum 1 token"); } IERC20(tokenAddress).transferFrom(msg.sender, address(this), amount); emit PaymentReceived(msg.sender, tokenAddress, tokenNames[tokenAddress], amount); } } 💼 钱包和DApp如何识别代币 1. 代币列表管理 // 钱包中的代币配置 const tokenList = { "0xdAC17F958D2ee523a2206206994597C13D831ec7": { name: "Tether USD", symbol: "USDT", decimals: 6, logoURI: "https://tokens.1inch.io/0xdac17f958d2ee523a2206206994597c13d831ec7.png", verified: true }, "你的代币地址": { name: "MyToken", symbol: "MTK", decimals: 18, logoURI: "你的代币图标URL", verified: false } }; // 根据地址获取代币信息 function getTokenByAddress(address) { return tokenList[address.toLowerCase()] || null; } 2. 动态获取代币信息 class TokenManager { constructor(provider) { this.provider = provider; this.tokenCache = new Map(); } async getTokenInfo(tokenAddress) { // 检查缓存 if (this.tokenCache.has(tokenAddress)) { return this.tokenCache.get(tokenAddress); } try { const contract = new ethers.Contract( tokenAddress, ['function name() view returns (string)', 'function symbol() view returns (string)', 'function decimals() view returns (uint8)'], this.provider ); const [name, symbol, decimals] = await Promise.all([ contract.name(), contract.symbol(), contract.decimals() ]); const tokenInfo = { address: tokenAddress, name, symbol, decimals, isCustom: !this.isKnownToken(tokenAddress) }; // 缓存结果 this.tokenCache.set(tokenAddress, tokenInfo); return tokenInfo; } catch (error) { console.error(`Failed to get token info for ${tokenAddress}:`, error); return null; } } isKnownToken(address) { const knownTokens = [ "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT "0xA0b86a33E6441e1c8d2dB9E4E69D1C6C8C4E9D7F", // USDC "0x6B175474E89094C44Da98b954EedeAC495271d0F" // DAI ]; return knownTokens.includes(address.toLowerCase()); } } 🔐 安全验证:防止假代币 1. 验证代币合约 contract TokenVerifier { // 官方代币注册表 mapping(address => bool) public verifiedTokens; mapping(string => address) public symbolToAddress; function registerToken(address tokenAddress, string memory symbol) public onlyOwner { // 验证是否为有效的 ERC20 合约 require(isValidERC20(tokenAddress), "Invalid ERC20 contract"); verifiedTokens[tokenAddress] = true; symbolToAddress[symbol] = tokenAddress; } function isValidERC20(address tokenAddress) public view returns (bool) { try IERC20(tokenAddress).totalSupply() returns (uint256) { try IERC20(tokenAddress).balanceOf(address(this)) returns (uint256) { return true; } catch { return false; } } catch { return false; } } function getVerifiedUSDT() public view returns (address) { return symbolToAddress["USDT"]; } } 2. 前端安全检查 class TokenSecurity { static OFFICIAL_TOKENS = { "USDT": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "USDC": "0xA0b86a33E6441e1c8d2dB9E4E69D1C6C8C4E9D7F", "DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F" }; static async verifyToken(tokenAddress, expectedSymbol) { // 检查是否为官方代币 const officialAddress = this.OFFICIAL_TOKENS[expectedSymbol]; if (officialAddress && officialAddress.toLowerCase() === tokenAddress.toLowerCase()) { return { verified: true, type: "official" }; } // 检查代币信息 try { const contract = new ethers.Contract(tokenAddress, ERC20_ABI, provider); const symbol = await contract.symbol(); const name = await contract.name(); if (symbol === expectedSymbol) { // 可能是山寨币,警告用户 return { verified: false, type: "potential_fake", warning: `这可能是假的 ${expectedSymbol},官方地址是 ${officialAddress}` }; } return { verified: true, type: "custom", name, symbol }; } catch (error) { return { verified: false, type: "invalid" }; } } } // 使用示例 const verification = await TokenSecurity.verifyToken(userInputAddress, "USDT"); if (!verification.verified) { alert(verification.warning || "无效的代币合约"); } 🎯 实际应用场景 1. DEX 中的代币识别 contract SimpleSwap { struct TokenInfo { address tokenAddress; string symbol; uint8 decimals; } mapping(address => TokenInfo) public supportedTokens; function addSupportedToken(address tokenAddress) public onlyOwner { IERC20Metadata token = IERC20Metadata(tokenAddress); supportedTokens[tokenAddress] = TokenInfo({ tokenAddress: tokenAddress, symbol: token.symbol(), decimals: token.decimals() }); } function swap( address tokenIn, address tokenOut, uint256 amountIn ) public { require(supportedTokens[tokenIn].tokenAddress != address(0), "Input token not supported"); require(supportedTokens[tokenOut].tokenAddress != address(0), "Output token not supported"); // 根据代币地址执行不同的兑换逻辑 IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn); uint256 amountOut = calculateSwapAmount(tokenIn, tokenOut, amountIn); IERC20(tokenOut).transfer(msg.sender, amountOut); } } 2. 支付网关的代币处理 class PaymentGateway { constructor() { this.acceptedTokens = new Map([ ["0xdAC17F958D2ee523a2206206994597C13D831ec7", { symbol: "USDT", decimals: 6, rate: 1.0 }], // 你可以添加自己的代币 ]); } async processPayment(tokenAddress, amount, orderId) { const tokenInfo = this.acceptedTokens.get(tokenAddress.toLowerCase()); if (!tokenInfo) { throw new Error(`Token ${tokenAddress} not accepted`); } console.log(`Processing payment of ${amount} ${tokenInfo.symbol} for order ${orderId}`); // 根据代币类型执行不同的处理逻辑 if (tokenInfo.symbol === "USDT") { return this.processUSDTPayment(tokenAddress, amount, orderId); } else { return this.processCustomTokenPayment(tokenAddress, amount, orderId); } } } 🎯 总结 区分不同 ERC20 代币的方法: 合约地址 - 最重要的唯一标识符 代币信息 - name(), symbol(), decimals() 验证机制 - 检查是否为官方代币 白名单管理 - 维护受信任的代币列表 关键要点: 每个代币都有唯一的合约地址 相同的符号可能有多个假代币 始终通过合约地址而不是符号来识别代币 对于重要应用,建议维护官方代币白名单 你的代币和 USDT 虽然都是 ERC20,但有完全不同的合约地址,这就是区分它们的关键!
问:ERC20代币的转移机制
答:
1 你好!让我为你做一个全面的汇总结论,把ERC20代币转移的所有情况都梳理清楚!😊 2 ERC20代币转移机制完全指南 3 核心原则 4 5 只有代币的拥有者才能转移自己的代币,或者通过授权让别人代为转移。 6 两种转移函数对比 7 函数 转移谁的代币 是否需要授权 使用场景 8 transfer(to, amount) 调用者自己的 ❌ 不需要 直接转账 9 transferFrom(from, to, amount) 指定地址的 ✅ 需要 代理转账 10 完整示例分析 11 场景1:用户直接转账 - 无需授权 12 13 // A用户直接转100 USDT给B用户 14 await usdt.connect(userA).transfer(userB.address, 100); 15 // ✅ 成功:A转移自己的代币给B 16 17 场景2:通过合约购买代币 - 需要授权 18 19 // 用户想用USDT购买MYK代币 20 21 // 步骤1:用户必须先授权MYK合约 22 await usdt.connect(user).approve(mykContract.address, 100); 23 24 // 步骤2:调用购买函数 25 await mykContract.connect(user).buyTokens(100); 26 27 // MYK合约内部实现: 28 contract MYKToken { 29 function buyTokens(uint256 usdtAmount) external { 30 // 从用户账户转移USDT到合约(需要授权) 31 usdt.transferFrom(msg.sender, address(this), usdtAmount); 32 33 // 合约转移自己的MYK代币给用户(无需授权) 34 uint256 mykAmount = calculateTokens(usdtAmount); 35 _transfer(address(this), msg.sender, mykAmount); 36 } 37 } 38 39 场景3:合约拥有代币储备 - 部分需要授权 40 41 contract DEX { 42 IERC20 public usdt; 43 IERC20 public tokenA; 44 45 // 用户用TokenA换USDT 46 function swapTokenAForUSDT(uint256 tokenAmount) external { 47 // 用户转TokenA给合约(需要用户先授权) 48 tokenA.transferFrom(msg.sender, address(this), tokenAmount); 49 50 // 合约转自己的USDT给用户(无需授权) 51 uint256 usdtAmount = calculatePrice(tokenAmount); 52 usdt.transfer(msg.sender, usdtAmount); 53 } 54 55 // 管理员向合约充值USDT储备 56 function depositUSDT(uint256 amount) external onlyOwner { 57 // 管理员转USDT给合约(需要管理员先授权) 58 usdt.transferFrom(msg.sender, address(this), amount); 59 } 60 } 61 62 场景4:复杂的多方交易 63 64 contract ComplexTrade { 65 function threeWayTrade( 66 address userA, 67 address userB, 68 address userC, 69 uint256 amount 70 ) external { 71 // A → B:需要A授权合约 72 tokenX.transferFrom(userA, userB, amount); 73 74 // B → C:需要B授权合约 75 tokenY.transferFrom(userB, userC, amount); 76 77 // 合约 → A:合约转自己的代币给A,无需授权 78 tokenZ.transfer(userA, amount); 79 } 80 } 81 82 授权管理最佳实践 83 1. 精确授权 84 85 // ✅ 好:只授权需要的数量 86 await usdt.approve(contract.address, exactAmount); 87 88 // ❌ 避免:无限授权(安全风险) 89 await usdt.approve(contract.address, ethers.constants.MaxUint256); 90 91 2. 授权检查 92 93 // 检查当前授权额度 94 const allowance = await usdt.allowance(user.address, contract.address); 95 if (allowance.lt(requiredAmount)) { 96 await usdt.approve(contract.address, requiredAmount); 97 } 98 99 3. 撤销授权 100 101 // 交易完成后撤销授权 102 await usdt.approve(contract.address, 0); 103 104 总结规律 105 106 用户 → 用户:直接 transfer(),无需授权 107 用户 → 合约:通过 transferFrom(),用户需要先授权合约 108 合约 → 用户:合约用 transfer() 转自己的代币,无需授权 109 合约 → 合约:同样遵循上述规则 110 111 安全要点 112 113 ✅ 授权是ERC20的安全特性,不是bug 114 ✅ 永远不要授权不信任的合约 115 ✅ 及时撤销不需要的授权 116 ✅ 定期检查和管理授权状态 117 118 希望这个全面的汇总帮你彻底理解了ERC20的转移机制!还有任何疑问都可以继续问我哦!

浙公网安备 33010602011771号