使用 Solidity 智能合约实现 NFT 代币门控

代币门控的概念用于限制访问,并为特定代币或一组代币的持有者提供独家内容、权利或成员资格。智能合约应该通过自动和手动来进行测试。使用HardHatTruffle可以编写一组测试,以确保合约及其功能会按预期工作,最重要的是确保合约的安全性很高。

1.jpg

代币门控的概念用于限制访问,并为特定代币或一组代币的持有者提供独家内容、权利或成员资格。

智能合约

在这个场景中,我们将编写一个简单的智能合约,它在提供对NFT社区的成员访问时实现了相同的概念。

在这个社区中,新成员通过从社区中购买至少一个NFT来参加,这给了他们出售自己的NFT的权利,以及对任何已发布的NFT发表评论的权利。

2.jpg

安装和设置

我们将跳过所有的安装和设置,因为这不是本文的重点,但是需要安装的最相关的库是@openzeppelin。

首先,我们将设置Solidity版本,并从Openzeppelin导入两个重要的合约,这两个合约是实现ERC721所必需的。

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

import "@openzeppelin/contracts/utils/Counters.sol";

ERC721URIStorage.sol是一个ERC721代币,带有基于存储的代币URI管理,Counters.sol这是一个实用程序,将帮助我们增加代币ID。

接下来,我们将创建PrivateMarket合约并从ERC721URIStorage继承,声明计数器和两个主要结构来存储代币数据和消息。

contract PrivateMarket is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    address private _owner;

    struct TokenData {
        uint256 price;
        string name;
        string image;
        uint256 tokenId;
    }

    struct Message {
        address sender;
        string message;
        uint date;
    }

 }

我们声明_owner变量来允许合约所有者发布第一组NFT,并允许社区成员购买并获得初始发布权。

在我们的构造函数中,我们将把_owner的值初始化为发布智能合约的地址。

contract PrivateMarket is ERC721URIStorage {

    constructor() ERC721("NFTPrivateMarket", "NFTPM") {
        _owner = msg.sender;
    }

}

映射存储和易于访问的值

在我们创建了上面的框架之后,我们将声明一组变量来保存和映射代币、代币数据、所有者和附加到代币的消息:

contract PrivateMarket is ERC721URIStorage {

    // An array to store the data of all the tokens for a quick listing of all NFTs
    TokenData[] public tokensData;

    // Mapping of owners to an array of all the tokens they own
    mapping(address => uint256[]) private ownerByTokenId;

    // Mapping of tokenIds to the owner of the token
    mapping(uint256 => address) private tokenIdByOwner;

    // Mapping of tokenIds to the data of the token
    mapping(uint256 => TokenData) private tokenIdByTokenData;

    // Mapping of tokenIds to the comments of the token
    mapping(uint256 => Message[]) private messages;

}

TokensData将保存所有代币的数据,并将返回以在前端列出所有代币。

ownerByTokenId是所有者到其代币ID的映射,该代ID将用于检索属于某个地址的所有代币,或检查给定地址是否拥有代币,以便授予发布或评论的权利。

使用修饰符进行访问检查

为了验证访问权限,我们将创建ownsToken修饰符,它只允许拥有代币的地址或_owner在要被调用的函数中执行操作。

contract PrivateMarket is ERC721URIStorage {

  modifier ownsToken() {
    require(
        ownerByTokenId[msg.sender].length > 0 || msg.sender == _owner,
        "You must own a token"
    );
    _;
  }

}

铸造新代币

使用ownsToken修饰符,就可以创建合约中最重要的功能之一,它生成一个新的NFT,并将代币分配给发送者地址。

contract PrivateMarket is ERC721URIStorage {

  function createToken(
      string memory tokenURI,
      uint256 price,
      string memory name
) public ownsToken returns (uint256) {

      // Increment to the new tokenId
      _tokenIds.increment();

      // Mint a new token
      uint256 currentId = _tokenIds.current();
      _mint(msg.sender, currentId);
      _setTokenURI(currentId, tokenURI);

      // Assign the token to the owner
      ownerByTokenId[msg.sender].push(currentId);

      // Store the data of the token
      TokenData memory tokenData = TokenData(
          price,
          name,
          tokenURI,
          currentId
      );
      tokenIdByTokenData[currentId] = tokenData;
      tokensData.push(tokenData);

      // Map the token to the owner
      tokenIdByOwner[currentId] = msg.sender;

      // Get token transfer approval
      _approve(msg.sender, currentId);

      return currentId;
  }

}

购买代币

为了购买代币,发送者将向purchaseToken函数提起一个交易,作为回报,该函数将代币转移到发送者那边,并向代币所有者付款。

contract PrivateMarket is ERC721URIStorage {

  function purchaseToken(uint256 tokenId) public payable returns (uint256) {
      // Get the token price
      TokenData memory tokenData = tokenIdByTokenData[tokenId];
      uint256 price = tokenData.price;

      require(msg.value == price, "You must pay the full price");

      // Get the token owner
      address owner = tokenIdByOwner[tokenId];

      require(owner != msg.sender, "You cannot buy your own token");

      // Transfer the token
      _transfer(owner, msg.sender, tokenId);
      tokenIdByOwner[tokenId] = msg.sender;
      delete ownerByTokenId[owner][tokenId];
      ownerByTokenId[msg.sender].push(tokenId);

      // Make a payment to the owner of the token
      (bool sent,) = payable(owner).call{value:msg.value}("");
      require(sent, "Payment failed");

      return tokenId;
  }

}

为了避免重入攻击,我们将首先转移代币,然后向代币的所有者进行支付,但在此之前,我们首先需要检查代币是否属于调用该函数的同一地址。

评论NFT

我们将使用Message结构体并创建一个函数,该函数只允许社区成员(至少拥有一个代币的地址)添加代币的消息,并创建一个函数来检索属于代币的所有消息。

contract PrivateMarket is ERC721URIStorage {

  function createMessage(
      uint256 tokenId,
      string memory message
) public ownsToken {
      Message memory messageData = Message(msg.sender, message, block.timestamp);
      messages[tokenId].push(messageData);
  }

  function getMessages(uint256 tokenId)
      public
      view
      returns (Message[] memory)
{
      return messages[tokenId];
  }

}

支持前端附加功能

最后,我们将添加一组附加函数来获取与代币和代币所有者相关的数据。

contract PrivateMarket is ERC721URIStorage {

  function getTokenData(uint256 tokenId)
      public
      view
      returns (TokenData memory)
{
      return tokenIdByTokenData[tokenId];
  }

  function getOwnerTokens() public view returns (uint256[] memory) {
      return ownerByTokenId[msg.sender];
  }

  function getAddressTokens(address owner)
      public
      view
      returns (uint256[] memory)
{
      return ownerByTokenId[owner];
  }

  function getAllTokenData() public view returns (TokenData[] memory) {
      return tokensData;
  }

}

结论

智能合约应该通过自动和手动来进行测试。使用HardHat或Truffle可以编写一组测试,以确保合约及其功能会按预期工作,最重要的是确保合约的安全性很高。

Source:https://medium.com/better-programming/implementing-the-concept-of-nft-token-gating-with-a-solidity-smart-contract-35cc77e45315

关于

ChinaDeFi - ChinaDeFi.com 是一个研究驱动的DeFi创新组织,同时我们也是区块链开发团队。每天从全球超过500个优质信息源的近900篇内容中,寻找思考更具深度、梳理更为系统的内容,以最快的速度同步到中国市场提供决策辅助材料。

本文首发于:https://mp.weixin.qq.com/s/OT_dBkgEdxMKWndiZfGtiQ

  • 发表于 2022-05-18 16:11
  • 阅读 ( 521 )
  • 学分 ( 1 )
  • 分类:DeFi

0 条评论

请先 登录 后评论
ChinaDeFi 去中心化金融社区
ChinaDeFi 去中心化金融社区

94 篇文章, 66 学分