WEB3DEV

Cover image for Construa e implante um contrato inteligente de NFT alugável na Optimism
Fatima Lima
Fatima Lima

Posted on

Construa e implante um contrato inteligente de NFT alugável na Optimism

Construa e implante um contrato inteligente de NFT alugável na Optimism

Por Dhaiwat Pandya


Os NFTs são uma das coisas mais interessantes que aconteceram em torno da propriedade digital e da economia criadora. Elas abriram um mundo totalmente novo de possibilidades tanto para artistas quanto para desenvolvedores.

No entanto, os NFTs também são caros. A maioria das pessoas não pode se dar ao luxo de comprar um NFT apenas para ter acesso a uma determinada comunidade ou apenas para usá-lo como uma foto de perfil no Twitter.

Se a compra deles está fora de questão, que tal se você pudesse alugá-los? E se você pudesse pagar apenas uma fração do preço e ainda poder "usar" o NFT em todos os lugares da web3? É exatamente sobre isso que vamos falar e construir neste artigo!

Construindo um NFT alugável

Vamos construir uma coleção de NFT alugável. Por "alugável", queremos dizer que qualquer NFT desta coleção pode ser alugada a outra pessoa por seu proprietário por um determinado período de tempo. Durante este período de aluguel, a conta que aluga a NFT pode "usá-la" mas não pode vendê-la ou realizar nenhuma ação destrutiva, como transferência ou queima.

Para testar nossos NFTs alugáveis, usaremos um frontend restrito (gated) que lhe dará acesso a algum conteúdo, dependendo se você alugou ou não um determinado NFT.

Image description

Para tornar estes NFTs ainda mais acessíveis, vamos implantá-los na Testnet Kovan da Optimism. A Optimism é uma solução de Camada 2 escalável extremamente poderosa da Ethereum, e se você quiser ler mais sobre ela, verifique o site da Optimism.

Antes de escrevermos o contrato inteligente, vamos primeiro discutir um padrão para NFTs alugáveis que já existe - EIP-4907. Estaremos construindo nossa coleção de NFTs em cima deste padrão.

EIP-4907: O Padrão de NFT Alugável

Ethereum Improvement Proposal (Proposta de Melhoria Ethereum) (EIP) 4907 discute um novo padrão chamado ERC4907, que é uma extensão do padrão ERC721. Para algum contexto, o padrão ERC721 é o padrão mais comumente usado para os Tokens Não-Fungíveis (NFTs).

O ERC4907 propõe uma função adicional chamada (user-usuário) que pode ser concedida a endereços e um tempo em que a função é automaticamente revogada. A função do usuário representa permissão para "usar" o NFT, mas não a capacidade de transferi-lo ou definir usuários. - Sobre o documento oficial EIP-4907

- (https://eips.ethereum.org/EIPS/eip-4907)

Em outras palavras, o padrão de NFT ERC4907 possibilita que você "alugue" as utilidades desbloqueadas por seu NFT para outro endereço. Isto se torna possível ao acrescentar um novo papel ao NFT.

Image description

Por exemplo, como mencionado na própria EIP, os NFTs alugáveis podem ser alavancados fortemente em projetos onde os NFTs são usados para administrar a propriedade de bens imóveis virtuais ou "terrenos virtuais".

Pense em um projeto Metaverso como o Voxels onde, para adquirir um terreno no mundo virtual, é necessário adquirir um NFT correspondente.A posse do NFT faz de você o proprietário desse terreno.

Assim como os imóveis da IRL, o padrão de NFT ERC4907 permitiria que os proprietários de terras alugassem suas terras. Isto permite que os proprietários de terras criem alguma receita passiva a partir de seus ativos virtuais e torna as terras virtuais mais acessíveis, baixando a barreira em termos de dinheiro necessário. É uma situação em que todos ganham!

Agora que temos um resumo do padrão ERC4907, vamos começar a construir!

Configuração do Projeto

Usaremos o Hardhat para fixar nosso ambiente de desenvolvimento local. Assegure-se de ter o NodeJS instalado!

Vamos primeiro criar uma pasta vazia para nosso projeto.

mkdir optimism-rentable-nfts && cd optimism-rentable-nfts;
Enter fullscreen mode Exit fullscreen mode

Inicialize o Projeto Criando o Hardhat

Vamos agora usar uma ferramenta super conveniente, a CLI, fornecida pela Hardhat, para criar um modelo para nosso projeto. Execute isto em seu terminal:

# /optimism-rentable-nftsnpx hardhat;
Enter fullscreen mode Exit fullscreen mode

Selecione Create a Javascript project quando perguntado e diga sim a todas as perguntas restantes. A ferramenta CLI agora vai criar um modelo e instalar as dependências para nós.

Uma vez que isso esteja feito, precisamos instalar uma última dependência.

# /optimism-rentable-nftsnpm install @openzeppelin/contracts;
Enter fullscreen mode Exit fullscreen mode

O OpenZeppelin é uma biblioteca extremamente útil contendo implementações de algumas normas comuns como ERC20 e ERC721. Precisaremos desses contratos mais tarde.

Escrevendo o Contrato Inteligente

Primeiro, abra seu projeto em um editor de código de sua escolha e crie três novos arquivos vazios dentro da pasta de contratos.

Image description

IERC4907.sol

O arquivo IERC4907.sol conterá a interface Solidity para o padrão ERC4907 incluindo as interfaces Natspec & OpenZeppelin. Isto significa que este arquivo conterá o esqueleto ou o projeto da implementação do ERC4907.

Copie todo o conteúdo deste arquivo e cole-o em seu arquivo IERC4907.sol. Não precisamos tocar neste arquivo de novo.

File: ./contracts/IERC4907.sol

// SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.0; interface IERC4907 {
    // Logado quando o usuário de um token designa um novo usuário ou as atualizações expiram
    /// @notice Emitido quando o "user" (usuário) de um NFT ou o "expires" (o tempo de expiração) do "user" é alterado
    /// O endereço zero para o usuário indica que não há endereço de usuário
    event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires);

    /// @notice define o user e o expires de um NFT
    /// @dev O endereço zero indica que não há usuário
    /// Dispara se `tokenId` não for um NFT válido 
    /// @param user  O novo usuário do NFT
    /// @param expires  UNIX timestamp, O novo usuário pode usar o NFT antes de expirar
    function setUser(uint256 tokenId, address user, uint64 expires) external ;

    /// @notice Obtém o endereço do usuário de um NFT
    /// @dev O endereço zero indica que não há usuário ou que o usuário expirou
    /// @param tokenId O NFT para o qual se obtém o endereço do usuário
    /// @return O endereço do usuário para este NFT
    function userOf(uint256 tokenId) external view returns(address);

    /// @notice Obtém a validade do usuário de um NFT
    /// @dev O valor zero indica que não há usuário
    /// @param tokenId O NFT para o qual o usuário expira 
    /// @return O usuário expira para este NFT
    function userExpires(uint256 tokenId) visualização externa retorna(uint256);}
Enter fullscreen mode Exit fullscreen mode

ERC4907.sol

O arquivo ERC4907.sol conterá a implementação completa do padrão ERC4907. Precisamos deste arquivo porque vamos 'ampliar' nossa coleção de NFT real fora do padrão ERC4907.

Copie todo o conteúdo deste arquivo e cole no seu arquivo ERC4907.sol. Você pode notar que está copiando o código fonte do meu repositório ao invés de diretamente do EIP, como fizemos com o IERC4907.sol. Isto é porque temos que fazer alguns ajustes no arquivo original ERC4907.sol para compilá-lo e utilizá-lo em nosso projeto. Especificamente, tive que adicionar uma palavra-chave substituta para que algumas funções pudessem funcionar. Você pode ver o código fonte original não modificado aqui!

Temos que fazer isto manualmente porque o EIP-4907 ainda não foi aceito como padrão e não faz parte de nenhuma biblioteca como a OpenZeppelin.

Vamos passar brevemente por algumas das partes importantes do código fonte ERC4907.

As funções às quais queremos dar atenção são setUser e userOf.

setUser

File: ./contracts/ERC4907.sol

/// @notice define o usuário e o tempo de expiração de um NFT
/// @dev O endereço zero indica que não existe usuário
/// Dispara se `tokenId` não for um NFT válido 
/// @param user O próximo usuário do NFT
/// @param expira o timestamp UNIX, O novo usuário poderá usar o NFT antes que ele expire
function setUser(uint256 tokenId, address user, uint64 expires) public virtual{
    require(_isApprovedOrOwner(msg.sender, tokenId),"ERC721: o chamador de transferência não é o proprietário nem foi aprovado");
    UserInfo storage info =  _users[tokenId];
    info.user = user;
    info.expires = expires;
    emit UpdateUser(tokenId,user,expires);}
Enter fullscreen mode Exit fullscreen mode

Como discutido anteriormente, o padrão ERC4907 expande o padrão ERC721 e gerencia duas funções para cada token

  1. O proprietário (arrendatário) do token
  2. O usuário (locatário) do token

Apenas para recapitular, as contas com a função do usuário podem usar o token mas não podem realizar nenhuma ação destrutiva como transferência ou queima.

A função setUser permite aos proprietários alugarem seu NFT para outra conta. Ela só pode ser chamada pelos proprietários do token (ou contas "aprovadas").

A função então define a struct de informação no contrato com o usuário recém definido e expira valores - sendo o usuário o endereço para o qual queremos alugar o NFT e expira sendo o timestamp do bloco quando queremos que o período de aluguel termine. Eu definitivamente recomendo a leitura de Block Timestamps da Ethereum..

Um detalhe chave que não queremos perder é que a setUser não requer um pagamento na forma de ETH ou de qualquer outro token para poder funcionar. Isto porque ela só pode ser chamada pelo proprietário e, bem, não será você a pagar se estiver alugando seu NFT!

Deve ser o usuário que deve pagar uma taxa. É por isso que se você quiser usar o padrão token ERC4907, terá que usar a função setUser como base e construir sua própria funcionalidade de pagamento em cima dela.

Vamos passar para a função userOf agora.

userOf

Digamos que você tenha alugado um NFT para uma pessoa chamada Bob.

Agora, como o Bob realmente usa seu NFT? Como Bob tem acesso, por exemplo, aos condomínios fechados por tokens ou ao conteúdo a que seu NFT dá acesso?

Para responder a isso, precisamos entender como a maioria dos condomínios fechados de tokens define os critérios de acesso. A comunidade vai tirar proveito da função balanceOf, que vem embutida na extensão do nosso contrato ERC721, o que lhe permite verificar que uma determinada carteira possui uma certa quantia.

Com o ERC4907, estas ferramentas restritivas ainda poderão conceder o acesso correto ao proprietário original. Se olharmos para trás, para o arquivo ERC4907.sol, podemos ver que ele expande o padrão ERC721, o que traz consigo toda a sua funcionalidade balanceOf padrão. É por isso que ele possui exatamente o mesmo que vem com o ERC721.

Esta funcionalidade de restrição de token não funcionará fora da caixa para os usuários (locatários) do NFT porque:

  1. A função balanceOf somente funciona para proprietários
  2. Não existe contrapartida da balanceOf para usuários

É aí que surge a função userOf.

File: ./contracts/ERC4907.sol

/// @notice Obter o endereço de usuário de um NFT
/// @dev O endereço zero indica que não há usuário ou que o usuário expirou
/// @param tokenId O NFT pelo qual se obtém o endereço do usuário
/// @return O endereço do usuário para este NFT
function userOf(uint256 tokenId)public view virtual returns(address){
    if( uint256(_users[tokenId].expires) >=  block.timestamp){
        return  _users[tokenId].user;
    }
    else {
        return address(0);
    }
}
Enter fullscreen mode Exit fullscreen mode

Quando enviamos um tokenId ao userOf ele retorna o endereço da carteira de seu locatário.

Devido a esta função userOf, as ferramentas restritivas de tokens também podem suportar contas que tenham alugado NFTs! Tudo que eles teriam que fazer é também chamar a função userOf e verificar se a conta para a qual está tentando obter acesso alugou um NFT.

A única peculiaridade aqui é que você precisa passar um tokenId para userOf.

Agora que temos um entendimento bastante razoável do funcionamento interno do padrão de tokens ERC4907, vamos passar a escrever o código fonte para nossa coleção de NFT alugável!

RentableNFT.sol

Este arquivo conterá o código fonte para o contrato inteligente da nossa coleção NFT. Isto é tudo que precisaremos para colocar em funcionamento nossa coleção de NFT ERC4907:

File: ./contracts/RentableNFT.sol

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

import "./ERC4907.sol";

// Estamos ampliando nosso contrato a partir de ERC4907. Ele nos permite tornar nossos NFTs alugáveis e nos dá acesso a funções como setUser e userOf.contract RentableNFT is ERC4907 {
    mapping(uint256 => string) public tokenURIs;

    // Estamos aceitando o nome da coleção e seu símbolo em nosso construtor. cópia 1:1 de como é feito em um contrato ERC721.
        constructor(string memory _name, string memory _symbol)
        ERC4907(_name, _symbol)
    {}

    function mint(uint256 _tokenId, string memory _tokenURI) public{
        _mint(msg.sender, _tokenId);
        tokenURIs[_tokenId] = _tokenURI;
    }

        // É aí que✨ a mágica ✨ acontece. Esta função permitirá que qualquer proprietário alugue seus NFTs. Ela chama internamente a função setUser a partir do padrão ERC4907
    function rentOut(
        uint256 _tokenId,
        address _user,
        uint64 _expires
    ) public onlyOwner(_tokenId) {
        setUser(_tokenId, _user, _expires);
    }
        // Retorna o tokenURI para um determinado _tokenId
    function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
    {
        return tokenURIs[_tokenId];
    }

        // Verifica se a conta que chama a função realmente possui o determinado token.
    modifier onlyOwner(uint256 _tokenId) {
        require(
            _isApprovedOrOwner(msg.sender, _tokenId),
            "caller is not owner nor approved"
        );
        ;
    }
}
Enter fullscreen mode Exit fullscreen mode

Você deve ter notado que a única maneira de um NFT ser alugado agora é se o proprietário for e chamar esta função rentOut. Não há como uma pessoa, que esteja procurando alugar um NFT, possa pagar uma certa taxa e obter um NFT de aluguel sem que o proprietário tenha que intervir. Vamos rever esta funcionalidade perto do final do artigo.

Isso é tudo para o código fonte do nosso contrato. Vamos agora escrever um conjunto de testes automatizados que verifica todo o fluxo de aluguel do início ao fim, para que saibamos que nossa nova e brilhante coleção de NFT alugável realmente faz algo!

Escrevendo Testes para o ERC4907

Testes! Quem não gosta de escrever alguns testes?

Exclua o arquivo sample-test.js em seu diretório de testes e crie um novo arquivo chamado tests.js.

Vamos começar escrevendo duas funções de utilidade que serão úteis.

File: ./test/tests.js

const { expect } = require("chai");
const { ethers, network } = require("hardhat");

 // esta função irá implantar o contrato de nossa coleção de NFT e retornar uma instância do contrato para que possamos interagir withconst setupContract = async () => {
  const RentableNFT = await ethers.getContractFactory("RentableNFT");
  const rentableNFT = await RentableNFT.deploy("RentableNFT", "RNFT");
  await rentableNFT.deployed();
  return rentableNFT;
};

 // esta função nos dará simplesmente *acesso total* a duas contas, também conhecidas como *'signers'* (signatários). Usando estas duas contas, poderemos simular duas contas diferentes interagindo com nosso contrato da maneira que quisermos
const setupAccounts = async () => {
  const accounts = await ethers.getSigners();
  return [accounts[0], accounts[1]];
};
Enter fullscreen mode Exit fullscreen mode

Com esta base, podemos agora estruturar o que queremos testar para diferentes cenários.

Image description

Vamos escrever agora o código para nosso conjunto de testes.

File: ./test/tests.js

it("Rent flow ", async () => {
  const rentableNFT = await setupContract();
  const [owner, renter] = await setupAccounts();

  // cunha o tokenId 0 para o proprietário. A função .connect nos permite interagir explicitamente com a instância do contrato a partir de uma conta de nossa escolha. Neste caso, essa conta é proprietária.
    const tx = await rentableNFT.connect(owner).mint(0, "QmPf2x91DoemnhXSZhGDP8TX9Co8AScpvFzTuFt9BGAoBY");
    await tx.wait();
  // verifica proprietário do tokenId 0
    const ownerOf = await rentableNFT.ownerOf(0);
    expect(ownerOf).to.equal(owner.address);

  // aluga o nft para o locatário por 1 hora
    const expiryTimestamp = Math.round(new Date().getTime() / 1000) + 3600;
    const tx2 = await rentableNFT
      .connect(owner)
      .rentOut(0, renter.address, expiryTimestamp);
    await tx2.wait();

  // verifica o locatário i.e. 'usuário' do tokenId 0
    const renterOf = await rentableNFT.userOf(0);
  expect(renterOf).to.equal(renter.address);

  // avança rapidamente a cadeia para 2 horas depois e verifique se o nft ainda está alugado
    await network.provider.send("evm_increaseTime", [3601]);// 3601 -> 3600 segundos = 1 hora + 1 segundo
    await network.provider.send("evm_mine");

  // verifica o locatário i.e. 'usuário' do tokenId 0
    const renterOf2 = await rentableNFT.userOf(0);
    expect(renterOf2).to.not.equal(renter.address);      
 expect(renterOf2).to.equal("0x0000000000000000000000000000000000000000");});
Enter fullscreen mode Exit fullscreen mode

Agora vamos executar npx hardhat test e verificar se todo o nosso fluxo funciona como previsto.

O teste pode não passar na primeira rodada. Basta executá-lo novamente. Às vezes ele não consegue avançar com sucesso na primeira tentativa.

# /optimism-rentable-nftsnpx hardhat test  ✔ Rent flow (85ms)  1 passing (618ms)
Enter fullscreen mode Exit fullscreen mode

E...é claro que foi! Acabamos de testar todo o ciclo de vida de um ERC4907 de NFT alugável. Uhuuuu!🎉

Vamos agora tentar implantar nosso contrato inteligente em uma Tesnet!

Implantando a Testnet

Vamos implantar nosso contrato para a Testnet Kovan da Optimism.

A grande vantagem da Optimism é que mesmo a implantação de algo na Mainnet da Optimism é extremamente barata (um par de dólares, no máximo).

Mas vamos fazer a coisa certa e não entupir a Mainnet com nossos contratos de teste temporários. É para isso que servem as Testnets.

Em vez de usar o Hardhat para a implantação, vamos usar a Thirdweb Deploy. É uma ferramenta que nos permite implantar contratos inteligentes sem a necessidade de lidar com chaves privadas simples. Você pode saber mais sobre a ferramenta aqui: https://portal.thirdweb.com/thirdweb-deploy

Vamos começar executando npx thirdweb deploy no seu terminal.Certifique-se de executar este comando a partir da raiz de seu projeto.

# /optimism-rentable-nftsnpx thirdweb deploy
Enter fullscreen mode Exit fullscreen mode

Image description

Selecione o contrato RentableNFT somente quando solicitado. Você não precisa implantar o contrato the ERC4907 separadamente. Se tudo der certo, você deverá ver algo assim no seu navegador:

Image description

Preencha os parâmetros do construtor do contrato, selecione a Testnet Optimism no menu suspenso da Rede e depois clique em Deploy Now. Você verá dois pedidos de confirmação de transação aparecerem.

O primeiro popup será para a implantação real do contrato. Certifique-se de confirmar este aqui.

Image description

A segunda transação é opcional. Esta transação adicionará seu contrato ao proxy da Thirdweb para que você possa acessá-lo posteriormente a partir de seu painel de controle da Thirdweb. Isto não dá nenhuma propriedade de seu contrato à Thirdweb.

Image description

Uma vez que seu contrato seja implantado, você deverá ser redirecionado para o Contract Explorer que lhe permite interagir com as funções do contrato.

Image description

Certifique-se de copiar o endereço de seu contrato a partir do topo. Você vai precisar dele mais tarde.

LFG (Looking for Group)! Acabamos de implantar com sucesso uma coleção NFT alugável para a Testnet Kovan da Optimism!

Você pode ir ver seu contrato em Explorador de blocos da Testnet da Optimism também.

Interagindo com nosso Contrato Implantado

Vamos usar a UI da Thirdweb para interagir com nosso contrato e passar por todo o fluxo de aluguel que abordamos quando escrevemos todos esses testes.

Vamos começar cunhando um NFT, selecionando a função mint na barra lateral esquerda, especificando um tokenId e tokenURI (nosso URL da IPFS), e depois clicando em Execute (Executar).

Image description

Em seguida, vamos alugar esta NFT para uma conta. Mas certifique-se de ter acesso a esta conta! Usaremos esta conta para interagir com aquele front-end restrito do qual falamos no início.

Vamos alugar o NFT selecionando a função rentOut a partir da barra lateral esquerda, especificando um timestamp _tokenId, um _user e um _expires.

Você pode executar este pedaço de código no console do seu navegador para obter um timestamp de 1 hora a partir de agora.

Math.round(new Date().getTime() / 1000) + 3600
Enter fullscreen mode Exit fullscreen mode

Além disso, você também pode usar o Node.

node;> Math.round(new Date().getTime() / 1000) + 3600> 1657242017
Enter fullscreen mode Exit fullscreen mode

Image description

Verifique se o NFT foi alugado com sucesso, chamando a função userOf para o token com a identificação ID1.

Image description

Você também pode ir ver seu NFT na Quixotic, que é um mercado de NFT para a Optimism. Insira seu endereço de contrato e o ID do token neste link para visualizar o seu:

https://testnet.quixotic.io/asset/CONTRACT_ADDRESS/TOKEN_ID
Enter fullscreen mode Exit fullscreen mode

Aqui está um NFT que já foi implantado e cunhado.

https://testnet.quixotic.io/asset/0x1d269Cf95A2732ce98fAaF909642D756b59Af0aF/2

Image description

Vendo os NFTs alugáveis em ação

Agora vamos utilizar o NFT que acabamos de alugar para acessar um front-end restrito. É um site que lhe dá acesso a algum conteúdo se e somente se você tiver alugado um NFT de uma certa coleção ERC4907. Este website ajudará você a ter uma ideia de um caso de uso prático dos NFTs alugáveis que acabamos de construir.

Para economizar tempo e esforços, já criei este website para você. Você pode clonar este repo de front-end restrito de nft alugável e seguir as instruções do README.md para você se preparar e executar.

GitHub - Dhaiwat10/rentable-nft-gated-frontend

Conecte-se com a carteira para a qual você alugou seu NFT e poderá ver o conteúdo restrito!

NOTA: Tente isto novamente após uma hora e você notará que seu NFT alugado já terá expirado. Ele não permitirá mais que você chegue ao conteúdo restrito.

Próximos Passos

Um próximo passo interessante, após a construção deste projeto, seria a construção de um mercado para estes NFTs alugáveis. Se você se lembra, o contrato inteligente que escrevemos para nossa coleção de NFTs não permite que ninguém receba um NFT a pedido. Ele exige que o proprietário do NFT chame uma função que alugue o NFT.

O ideal é que qualquer pessoa que queira alugar um NFT possa pagar uma certa taxa e obtê-la sem esperar por mais ninguém. É por isso que você precisa de um contrato inteligente no mercado que funcione como uma ponte entre os proprietários do NFT e as pessoas que procuram alugá-lo.

Esse artigo foi escrito por Dhaiwat Pandya e traduzido por Fátima Lima. O original pode ser lido aqui.

.

Latest comments (0)