编写可rentable(可出租的) NFT 智能合约


原文链接:https://trufflesuite.com/guides/rentable-nft/

编写可rentable(可出租的) NFT 智能合约

概述

在本指南中,我们将涵盖 ERC-4907可出租 NFT 标准是什么,以及我们如何使用Truffle实现一个示例!

欢迎观看我们与来自 Double Protocol 的 Jesse Luong 的在 YouTube 上的直播录像,他是 ERC-4907标准的创建者,对该标准、 GameFi 和元宇宙的影响进行更深入的解释和探索!

ERC-4907是什么?

基于 NFT 的NFT 租赁已经有越来越多的用例——例如,元宇宙中的虚拟土地或游戏中的 NFT 资产。在上一篇文章中,我们了解到 ERC 是应用程序级别的标准,它为合约和 dapps 建立了一个共享接口,以便可靠地相互交互。ERC-4907通过分离使用者user和所有者owner的概念来实现 NFT 租赁。这使我们能够识别 NFT 上的授权角色。也就是说,user有能力使用 NFT,但没有权限出售它。此外,还引入了一个expires函数,这样user只有临时访问权限才能使用 NFT。

ERC-4907里有什么?

接口如下:

interface IERC4907 {
    
    

    // Logged when the user of a NFT is changed or expires is changed
    /// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed
    /// The zero address for user indicates that there is no user address
    event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires);

    /// @notice set the user and expires of a NFT
    /// @dev The zero address indicates there is no user
    /// Throws if `tokenId` is not valid NFT
    /// @param user  The new user of the NFT
    /// @param expires  UNIX timestamp, The new user could use the NFT before expires
    function setUser(uint256 tokenId, address user, uint64 expires) external;

    /// @notice Get the user address of an NFT
    /// @dev The zero address indicates that there is no user or the user is expired
    /// @param tokenId The NFT to get the user address for
    /// @return The user address for this NFT
    function userOf(uint256 tokenId) external view returns(address);

    /// @notice Get the user expires of an NFT
    /// @dev The zero value indicates that there is no user
    /// @param tokenId The NFT to get the user expires for
    /// @return The user expires for this NFT
    function userExpires(uint256 tokenId) external view returns(uint256);
}

解释:

  • userOf(uint256 tokenId) 函数可以作为pureview 函数实现。
  • userExpires(uint256 tokenId) 函数可以作为pureview 函数实现。
  • setUser(uint256 tokenId, address user, uint64 expires) 函数可以作为publicexternal 函数实现。
  • UpdateUser 事件必须在用户地址更改或用户过期时发出
  • 当用0xad092b5c 调用时,SupportsInterface 方法必须返回 true。

让我们写一个 ERC-4907

让我们开始写一个可租用的 NFT 吧!您可以在这里找到完整的代码。我们将导入 Open Zeppelin 的合同,这些合约提供了安全的、事先编写的 ERC 实现,我们的合约可以直接继承!

请注意,我们将不会涉及 ERC-721标准的基础知识。你可以在Infura 博客了解,原文详细介绍了它是什么以及如何实现它。

下载安装所需工具

你需要安装:

创建一个 Infura 帐户和项目

要将你的 DApp 连接到以太网和测试网,你需要一个 Infura 帐户。在这里注册一个帐户。

然后登录,创建一个项目! 我们将其命名为 rentable-nft,并从下拉列表中选择 Web3API

注册 MetaMask 钱包

要在浏览器中与你的 DApp 交互,你需要一个 MetaMask 钱包

下载VS Code

您可以随意使用任何您想要的 IDE,但是我们强烈推荐使用 VS Code!您可以使用 Truffle 扩展运行本教程的大部分内容来创建、构建和部署智能契约,而不需要使用 CLI!你可以在这里了解更多。

获取测试网eth

为了部署到公共测试网,你需要一些测试 Eth 来支付你的gas费用!Paradigm 有一个很棒的 MultiFaucet,它可以同时在8个不同的网络上存入资金。

配置项目

Truffle 可以为您的项目搭建脚手架,并添加示例合约和测试。我们将在一个名为 rentable-nft 的文件夹中构建项目。

truffle init rentable-nft
cd rentable-nft
truffle create contract RentablePets
truffle create contract IERC4907
truffle create contract ERC4907
truffle create test TestRentablePets

执行完成后,项目结构如下:

rentable-nft
├── contracts
│   ├── ERC4907.sol
│   ├── IERC4907.sol
│   └── RentablePets.sol
├── migrations
│   └── 1_deploy_contracts.js
├── test
│   └── test_rentable_pets.js
└── truffle-config.js

编写 ERC-4907接口

现在,让我们添加在 EIP 中定义的接口函数。为此,搜索找到IERC4907.sol。然后,我们只需复制和粘贴 EIP 上指定的内容!如下:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
interface IERC4907 {
    
    
  // Logged when the user of a NFT is changed or expires is changed
  /// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed
  /// The zero address for user indicates that there is no user address
  event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires);

  /// @notice set the user and expires of a NFT
  /// @dev The zero address indicates there is no user
  /// Throws if `tokenId` is not valid NFT
  /// @param user  The new user of the NFT
  /// @param expires  UNIX timestamp, The new user could use the NFT before expires
  function setUser(uint256 tokenId, address user, uint64 expires) external;

  /// @notice Get the user address of an NFT
  /// @dev The zero address indicates that there is no user or the user is expired
  /// @param tokenId The NFT to get the user address for
  /// @return The user address for this NFT
  function userOf(uint256 tokenId) external view returns(address);

  /// @notice Get the user expires of an NFT
  /// @dev The zero value indicates that there is no user
  /// @param tokenId The NFT to get the user expires for
  /// @return The user expires for this NFT
  function userExpires(uint256 tokenId) external view returns(uint256);
}

一旦你创建了这个文件,你应该不需要再次动它。

编写 ERC-4907智能合约

现在,让我们写一个 ERC-4907智能合约,继承 OpenZeppelin 的 ERC-721URIStorage 合约:

npm i @openzeppelin/[email protected]

ERC-721的基础知识在这个 Infura 博客中有所介绍。我们选择使用 ERC721URIStorage ,这样就不必使用静态元数据文件来填充 tokenURI。目前为止,我们导入了刚才自己创建的接口和 OpenZeppelin 的 ERC721URIStorage 实现,并让我们的 ERC4907智能合约继承它们的属性,如下:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

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

contract ERC4907 is ERC721URIStorage, IERC4907 {
    
    
  constructor() public {
    
    
  }
}

然后,我们修改构造函数,以便在部署契约时接受 NFT 集合名称和符号(symbol )。

contract ERC4907 is ERC721, IERC4907 {
    
    
  constructor(string memory _name, string memory _symbol) ERC721(_name,  _symbol){
    
    
  }
}

在开始实现 IERC4907中定义的函数之前,让我们设置两个状态变量 UserInfo 和 _ users,以帮助定义和存储用户的概念。

contract ERC4907 is ERC721URIStorage, IERC4907 {
    
    
  struct UserInfo {
    
    
    address user; // address of user role
    uint64 expires; // unix timestamp, user expires
  }

  mapping(uint256 => UserInfo) internal _users;
  • UserInfo 存储用户的地址和租赁到期日期
  • _users 映射NFT的tokenId到user (rentee) (承租人)

最后,让我们开始实现接口函数!

setUser

此函数只能由 NFT 的所有者调用。它允许所有者指定谁将是 NFT 的承租人。用户现在有 NFT 在他们的钱包,但不能执行任何动作,如烧毁或转移。将这个函数添加到 ERC4907.sol 文件中:

/// @notice set the user and expires of a NFT
/// @dev The zero address indicates there is no user
/// Throws if `tokenId` is not valid NFT
/// @param user  The new user of the NFT
/// @param expires  UNIX timestamp, The new user could use the NFT before expires
function setUser(uint256 tokenId, address user, uint64 expires) public virtual override {
    
    
  require(_isApprovedOrOwner(msg.sender, tokenId),"ERC721: transfer caller is not owner nor approved");
  UserInfo storage info = _users[tokenId];
  info.user = user;
  info.expires = expires;
  emit UpdateUser(tokenId, user, expires);
}

这个函数将用租用者的地址和租用期将过期的块时间戳更新 UserInfo 结构。我们使用从 ERC721继承的函数 _ isApprovedOrOwner 来表明只有所有者有权决定谁可以使用 NFT。最后,我们将发出在 IERC4907中定义的 UpdateUser 事件,以便在设置新用户时传递相关信息。

userOf

接下来,我们希望能够确定谁是 NFT 的当前用户。将 userOf 添加到你的合约中:

/// @notice Get the user address of an NFT
/// @dev The zero address indicates that there is no user or the user is expired
/// @param tokenId The NFT to get the user address for
/// @return The user address for this NFT
function userOf(uint256 tokenId)
  public
  view
  virtual
  override
  returns (address)
{
    
    
  if (uint256(_users[tokenId].expires) >= block.timestamp) {
    
    
    return _users[tokenId].user;
  } else {
    
    
    return address(0);
  }
}

该函数接受 tokenId 作为参数,如果该tokenId仍在被租用,则返回用户地址。否则,零地址表示 NFT 没有被租用。

userExpires

添加 userExires 功能,以便 dapps 可以检索特定 NFT 的到期日期信息:

/// @notice Get the user expires of an NFT
/// @dev The zero value indicates that there is no user
/// @param tokenId The NFT to get the user expires for
/// @return The user expires for this NFT
function userExpires(uint256 tokenId) public view virtual override returns(uint256){
    
    
  return _users[tokenId].expires;
}

如果 tokenId 不存在,那么将返回具有默认值的 UserInfo。在这种情况下,用户地址的默认值是 address (0) ,而 expires (uint64)的默认值是0。

supportsInterface

为了让 dapp 知道我们的 NFT 是否可以租用,它需要能够检查 interfaceId!为此,重写 EIP-165标准中定义的 SupportsInterface 函数。

/// @dev See {IERC165-supportsInterface}.
function supportsInterface(bytes4 interfaceId)
  public
  view
  virtual
  override
  returns (bool)
{
    
    
  return
    interfaceId == type(IERC4907).interfaceId ||
    super.supportsInterface(interfaceId);
}

_beforeTokenTransfer

这是我们将实现的最后一个函数!当令牌被转移(即所有者更改)或烧毁时,我们也希望删除租赁信息。请注意,这种行为是从 OpenZeppelin 的 ERC721实现继承而来的。我们将覆盖 ERC721中的 _beforeTokenTransfer,以添加以下功能:

function _beforeTokenTransfer(
  address from,
  address to,
  uint256 tokenId,
  uint256 batchSize
) internal virtual override {
    
    
  super._beforeTokenTransfer(from, to, tokenId, batchSize);

  if (from != to && _users[tokenId].user != address(0)) {
    
    
    delete _users[tokenId];
    emit UpdateUser(tokenId, address(0), 0);
  }
}

为了从映射中删除 UserInfo,我们希望确保所有权转移,并且存在 UserInfo。一旦验证,我们就可以删除并发出 UserInfo 已更新的事件!

请注意,这取决于您,合约编写者,来决定这是否是您所期望的令牌转移和销毁的行为方式。您可能会选择忽略这一点,并说即使所有权发生变化,租用者仍然保持其用户状态!

最终合约如下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract ERC4907 is ERC721URIStorage, IERC4907 {
    
    
  struct UserInfo {
    
    
    address user; // address of user role
    uint64 expires; // unix timestamp, user expires
  }

  mapping(uint256 => UserInfo) internal _users;

  constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {
    
    }

  /// @notice set the user and expires of a NFT
  /// @dev The zero address indicates there is no user
  /// Throws if `tokenId` is not valid NFT
  /// @param user  The new user of the NFT
  /// @param expires  UNIX timestamp, The new user could use the NFT before expires
  function setUser(
    uint256 tokenId,
    address user,
    uint64 expires
  ) public virtual override {
    
    
    require(
      _isApprovedOrOwner(msg.sender, tokenId),
      "ERC721: transfer caller is not owner nor approved"
    );
    UserInfo storage info = _users[tokenId];
    info.user = user;
    info.expires = expires;
    emit UpdateUser(tokenId, user, expires);
  }

  /// @notice Get the user address of an NFT
  /// @dev The zero address indicates that there is no user or the user is expired
  /// @param tokenId The NFT to get the user address for
  /// @return The user address for this NFT
  function userOf(uint256 tokenId)
    public
    view
    virtual
    override
    returns (address)
  {
    
    
    if (uint256(_users[tokenId].expires) >= block.timestamp) {
    
    
      return _users[tokenId].user;
    } else {
    
    
      return address(0);
    }
  }

  /// @notice Get the user expires of an NFT
  /// @dev The zero value indicates that there is no user
  /// @param tokenId The NFT to get the user expires for
  /// @return The user expires for this NFT
  function userExpires(uint256 tokenId)
      public
      view
      virtual
      override
      returns (uint256)
  {
    
    
      return _users[tokenId].expires;
  }

  /// @dev See {IERC165-supportsInterface}.
  function supportsInterface(bytes4 interfaceId)
    public
    view
    virtual
    override
    returns (bool)
  {
    
    
    return
      interfaceId == type(IERC4907).interfaceId ||
      super.supportsInterface(interfaceId);
  }

  function _beforeTokenTransfer(
    address from,
    address to,
    uint256 tokenId
  ) internal virtual override {
    
    
    super._beforeTokenTransfer(from, to, tokenId);

    if (from != to && _users[tokenId].user != address(0)) {
    
    
      delete _users[tokenId];
      emit UpdateUser(tokenId, address(0), 0);
    }
  }
}

编写可出租宠物智能合约

最后,我们可以编写一个利用刚刚实现的 ERC4907合约的 NFT。我们遵循与前面相同的 NFT 格式。您可以通过这些来获得更深入的了解。我们暴露burn 功能,以便测试。如果您不希望您的 NFT 可以转移,请不要使用此方法!

合约如下:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

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

contract RentablePets is ERC4907 {
    
    
  using Counters for Counters.Counter;
  Counters.Counter private _tokenIds;

  constructor() ERC4907("RentablePets", "RP") {
    
    }

  function mint(string memory _tokenURI) public {
    
    
    _tokenIds.increment();
    uint256 newTokenId = _tokenIds.current();
    _safeMint(msg.sender, newTokenId);
    _setTokenURI(newTokenId, _tokenURI);
  }

  function burn(uint256 tokenId) public {
    
    
    _burn(tokenId);
  }
}

启动本地区块链

为了部署和测试我们的智能合约,我们需要修改migrations/1_deploy_contracts.js:

const RentablePets = artifacts.require("RentablePets");

module.exports = function (deployer) {
    
    
  deployer.deploy(RentablePets);
};

接下来,让我们启动一个本地 Ganache 实例。有多种方法可以做到这一点: 通过 VS Code 扩展、 Ganache CLI 和 Ganche 图形用户界面。每个Ganche版本都有自己的优点,您可以在这里查看 Ganache v7版本的特性。

在本教程中,我们将使用 GUI。打开它,创建一个工作区,然后点击保存(随时添加您的项目来使用 Ganache UI 的一些功能特性) !

这会在 http://127.0.0.1:7545创建一个正在运行的 Ganache 实例。

接下来,在 truffle-config. js 中取消注释的开发网络,并将端口号修改为7545以匹配。

development: {
    
    
  host: "127.0.0.1",     // Localhost (default: none)
  port: 7545,            // Standard Ethereum port (default: none)
  network_id: "*",       // Any network (default: none)
}

测试你的智能合约

如果您想在不编写完整测试用例的情况下动态测试智能合约,可以通过truffle developtruffle console来完成。点击这里了解更多信息。

出于本教程的目的,我们将继续编写一个 Javascript 测试。请注意,使用 Truffle,您可以选择使用 Javascript、 Typecript 或 Solidy 编写测试。

我们想测试以下功能:

  1. RentablePets 是 ERC721和 ERC49072
  2. 除了所有者,其他人不能调用 setUser
  3. 所有者可以正确地调用 setUser
  4. burn 将正确删除用户信息

作为测试的一部分,我们希望确保正确地发出事件,as well as our require statement failing correctly。OpenZeppelin 有一些非常好的测试辅助工具,我们将使用它们。下载:

npm install --save-dev @openzeppelin/test-helpers

完整的测试如下:

require("@openzeppelin/test-helpers/configure")({
    
    
  provider: web3.currentProvider,
  singletons: {
    
    
    abstraction: "truffle",
  },
});

const {
    
     constants, expectRevert, expectEvent } = require('@openzeppelin/test-helpers');
const RentablePets = artifacts.require("RentablePets");

contract("RentablePets", function (accounts) {
    
    
  it("should support the ERC721 and ERC4907 standards", async () => {
    
    
    const rentablePetsInstance = await RentablePets.deployed();
    const ERC721InterfaceId = "0x80ac58cd";
    const ERC4907InterfaceId = "0xad092b5c";
    var isERC721 = await rentablePetsInstance.supportsInterface(ERC721InterfaceId);
    var isER4907 = await rentablePetsInstance.supportsInterface(ERC4907InterfaceId); 
    assert.equal(isERC721, true, "RentablePets is not an ERC721");
    assert.equal(isER4907, true, "RentablePets is not an ERC4907");
  });
  it("should not set UserInfo if not the owner", async () => {
    
    
    const rentablePetsInstance = await RentablePets.deployed();
    const expirationDatePast = 1660252958; // Aug 8 2022
    await rentablePetsInstance.mint("fakeURI");
    // Failed require in function
    await expectRevert(rentablePetsInstance.setUser(1, accounts[1], expirationDatePast, {
    
    from: accounts[1]}), "ERC721: transfer caller is not owner nor approved");
    // Assert no UserInfo for NFT
    var user = await rentablePetsInstance.userOf.call(1);
    var date = await rentablePetsInstance.userExpires.call(1);
    assert.equal(user, constants.ZERO_ADDRESS, "NFT user is not zero address");
    assert.equal(date, 0, "NFT expiration date is not 0");
  });
  it("should return the correct UserInfo", async () => {
    
    
    const rentablePetsInstance = await RentablePets.deployed();
    const expirationDatePast = 1660252958; // Aug 8 2022
    const expirationDateFuture = 4121727755; // Aug 11 2100
    await rentablePetsInstance.mint("fakeURI");
    await rentablePetsInstance.mint("fakeURI");
    // Set and get UserInfo
    var expiredTx = await rentablePetsInstance.setUser(2, accounts[1], expirationDatePast)
    var unexpiredTx = await rentablePetsInstance.setUser(3, accounts[2], expirationDateFuture)
    var expiredNFTUser = await rentablePetsInstance.userOf.call(2);
    var expiredNFTDate = await rentablePetsInstance.userExpires.call(2);
    var unexpireNFTUser = await rentablePetsInstance.userOf.call(3);
    var unexpiredNFTDate = await rentablePetsInstance.userExpires.call(3);
    // Assert UserInfo and event transmission
    assert.equal(expiredNFTUser, constants.ZERO_ADDRESS, "Expired NFT has wrong user");
    assert.equal(expiredNFTDate, expirationDatePast, "Expired NFT has wrong expiration date");
    expectEvent(expiredTx, "UpdateUser", {
    
     tokenId: "2", user: accounts[1], expires: expirationDatePast.toString()});
    assert.equal(unexpireNFTUser, accounts[2], "Expired NFT has wrong user");
    assert.equal(unexpiredNFTDate, expirationDateFuture, "Expired NFT has wrong expiration date");
    expectEvent(unexpiredTx, "UpdateUser", {
    
     tokenId: "3", user: accounts[2], expires: expirationDateFuture.toString()});
    // Burn NFT
    unexpiredTx = await rentablePetsInstance.burn(3);
    // Assert UserInfo was deleted
    unexpireNFTUser = await rentablePetsInstance.userOf.call(3);
    unexpiredNFTDate = await rentablePetsInstance.userExpires.call(3);
    assert.equal(unexpireNFTUser, constants.ZERO_ADDRESS, "NFT user is not zero address");
    assert.equal(unexpiredNFTDate, 0, "NFT expiration date is not 0");
    expectEvent(unexpiredTx, "UpdateUser", {
    
     tokenId: "3", user: constants.ZERO_ADDRESS, expires: "0"});
  });
});

这里有一个特殊的点要调用: 为了测试当 msg.sender 不是 owner 时 setUser 是否失败,我们可以通过添加 param 中的额外命令来伪造发送方

rentablePetsInstance.setUser(1, accounts[1], expirationDatePast, {
    
    from: accounts[1]})

如果您遇到问题测试,使用 Truffle 调试器是非常有帮助的!

创建一个 NFT 并在您的移动钱包或 OpenSea 中查看它

如果您想为自己创建一个 NFT,并在您的移动 MetaMask 钱包中查看它,那么您需要将您的合约部署到公共测试网或 主网。要做到这一点,你需要从你的 Infura 项目和你的 MetaMask 钱包密钥中获取你的 Infura 项目 API。在文件夹的根目录中,添加一个.env 文件,我们将在其中放入该信息。

警告: 不要公开或提交此文件。我们建议将 .env 添加到 .gitignore 文件中。

MNEMONIC="YOUR SECRET KEY"
INFURA_API_KEY="YOUR INFURA_API_KEY"

然后,在 truffle-config.js 的顶部,添加以下代码以获取该信息:

require('dotenv').config();
const mnemonic = process.env["MNEMONIC"];
const infuraApiKey = process.env["INFURA_API_KEY"];

const HDWalletProvider = require('@truffle/hdwallet-provider');

最后,将 Goerli 网络添加到 module.exports 下的 networks 列表中:

goerli: {
    
    
  provider: () => new HDWalletProvider(mnemonic, `https://goerli.infura.io/v3/${
     
     infuraApiKey}`),
  network_id: 5,       // Goerli's network id
  chain_id: 5,         // Goerli's chain id
  gas: 5500000,        // Gas limit used for deploys.
  confirmations: 2,    // # of confirmations to wait between deployments. (default: 0)
  timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
  skipDryRun: true     // Skip dry run before migrations? (default: false for public nets)
}

最终的 truffle-config. js 如下:

require('dotenv').config();
const mnemonic = process.env["MNEMONIC"];
const infuraApiKey = process.env["INFURA_API_KEY"];

const HDWalletProvider = require('@truffle/hdwallet-provider');

module.exports = {
    
    
  networks: {
    
    
    development: {
    
    
     host: "127.0.0.1",     // Localhost (default: none)
     port: 7545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
    },
    goerli: {
    
    
      provider: () => new HDWalletProvider(mnemonic, `https://goerli.infura.io/v3/${
      
      infuraApiKey}`),
      network_id: 5,       // Goerli's network id
      chain_id: 5,         // Goerli's chain id
      gas: 5500000,        // Gas limit used for deploys.
      confirmations: 2,    // # of confirmations to wait between deployments. (default: 0)
      timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
      skipDryRun: true     // Skip dry run before migrations? (default: false for public nets)
    }
  },

  // Set default mocha options here, use special reporters, etc.
  mocha: {
    
    
    // timeout: 100000
  },

  // Configure your compilers
  compilers: {
    
    
    solc: {
    
    
      version: "0.8.15",      // Fetch exact version from solc-bin (default: truffle's version)
    }
  },
};

然后,我们需要安装 dotenv@truffle/hdwallet-provider的开发依赖项!最后执行 truffle migrate --network goerli 部署。

npm i --save-dev dotenv
npm i --save-dev @truffle/hdwallet-provider
truffle migrate --network goerli

为了快速地与 goerli 网络交互,我们可以使用 truffle console --network goerli,并调用适当的合约函数。我们已经将一些元数据pinned到 IPFS 中,以便您将其用作 tokenURI: IPFS://bafybeiffapvkruv2vwtomswzxiaxdgm2dflet2cxmh6t4ixrgaezumbw4。它应该看起来有点像这样:

truffle migrate --network goerli
truffle(goerli)> const contract = await RentablePets.deployed()
undefined
truffle(goerli)> await contract.mintNFT("YOUR ADDRESS", "ipfs://bafybeiffapvkruv2vwtomswqzxiaxdgm2dflet2cxmh6t4ixrgaezumbw4")

如果你想使用你自己的元数据,有很多方法可以做到这一点——使用 Truffle 或 Infura。

要在您的移动钱包上查看您的 NFT,手机打开 MetaMask ,切换到 Goerli 网络,并打开 NFT 选项卡!要在 OpenSea 上查看,您必须部署到 mainnet 或 Polygon。否则,如果你将合约部署到 rinkeby,你可以在 https://testnets.opensea.io/上查看。注意到合并后 Rinkby 将被弃用。

如果你不想在 Infura 项目中监视你的交易,你也可以通过 Truffle Dashboard 部署,它允许你通过 MetaMask 部署和签署交易——因此永远不会泄露你的私钥!要做到这一点,只需运行:

truffle dashboard
truffle migrate --network dashboard
truffle console --network dashboard

未来扩展

恭喜你!你写了一份可租用的 NFT 合约!寻找一个更深入的指南上传您的元数据到 IPFS!要获得更详细的代码演练,请观看 YouTube 上的直播。在未来版本的 Web3 Unleached 中,我们能够将其集成到一个完整的 DApp 中。也就是说,NFT 租赁市场将使用 ERC-4907可租赁标准和 ERC-2981版税标准。

如果你想讨论这个内容,对你想看到的内容提出建议或者问一些关于这个系列的问题,在这里讨论。如果你想展示你建立了什么以及想加入Unleashed社区,加入我们的Discord!最后,别忘了在 Twitter 上关注我们关于 Truffle 的最新消息。

猜你喜欢

转载自blog.csdn.net/inthat/article/details/128308090
今日推荐