Skip to content

Como Escrever um Contrato Inteligente de um NFT Alugável com ERC4907

Como Escrever um Contrato Inteligente de um NFT Alugável com ERC4907

/images/135c02544f4a.webp

NFTs não são apenas uma garantia criptográfica de propriedade de uma imagem (ok, não é uma imagem, eu sei, mas muita gente pensa assim). Com a mesma tecnologia por trás deles, as coisas mudam dependendo de como trabalhamos, interagimos e brincamos com eles.

Estou falando do NFT utilitário, que permite certos privilégios, direitos ou recompensas aos seus proprietários.

Por exemplo, em jogos, o NFT utilitário oferece uma nova abordagem para gerenciar a propriedade de ativos no jogo ou, em espaços sociais, ajuda a garantir acesso seguro a comunidades exclusivas (Bored Ape Yacht Club é um exemplo famoso).

Outro caso de uso pode ser um NFT vinculado a um “ativo físico inteligente alugável” (por exemplo, um armário, uma casa de férias ou um carro), onde o usuário pode enviar comandos (por exemplo, “trancar porta”, “destrancar porta”) depois de provar que ele é o atual proprietário do NFT. Mas cruzaremos essa ponte quando chegarmos a ela, em outro artigo.

Falando em NFT utilitário, em alguns casos, faz sentido que o proprietário e o usuário nem sempre sejam os mesmos. O proprietário do NFT poderia alugá-lo a um usuário por um determinado tempo. O usuário, durante esse tempo, ganha temporariamente os privilégios concedidos pelo NFT, mas não pode transferir sua propriedade.

O estado da arte

Bem, a boa notícia é que existe uma EIP para NFTs alugáveis (EIP 4907) que afirma:

“Este padrão é uma extensão da EIP-721. Propõe uma função adicional (de usuário) que pode ser concedida aos endereços e um tempo em que a função é automaticamente revogada (tempo de expiração). A função de usuário representa a permissão para “usar” o NFT, mas não a capacidade de transferi-lo ou definir usuários”.

Este padrão foi projetado para ser totalmente compatível com o ERC-721 e basicamente apresenta uma interface para implementação do contrato (IERC4907.sol) com os seguintes métodos:

function setUser(uint256 tokenId, address user, uint64 expires)

Este método define o novo usuário e o período de expiração do NFT, se o chamador for o proprietário do NFT ou de um endereço aprovado.

function userOf(uint256 tokenId) external view returns(address);

Isso retorna o endereço do usuário atual do NFT, onde o endereço zero indica que não há usuário ou o período de aluguel expirou.

function userExpires(uint256 tokenId) external view returns(uint256);

O último método retorna o tempo de expiração para o usuário do NFT, onde um valor zero indica “nenhum usuário”.

Observando a implementação de referência (ERC4907.sol), podemos notar como o contrato estende o padrão ERC721 e utiliza um mapeamento entre o NFT e seu eventual usuário com o tempo de expiração.

contract ERC4907 is ERC721, IERC4907 {
 struct UserInfo
 {
   address user; // endereço da função do usuário
   uint64 expires; // timestamp unix, expiração do usuário
 }
 mapping (uint256 => UserInfo) internal _users;
 ...

Uma possível melhoria

A primeira pergunta que me fiz foi: “Se o título da EIP é Rental NFT (NFT de Aluguel), por que o contrato IERC4907 ou a implementação de referência não possui um método de “aluguel””?

Ok, existe o método setUser, mas com ele, apenas o dono (ou endereço aprovado) do NFT pode definir um usuário. Nesse caso, o processo soa mais como um processo de empréstimo, onde o proprietário empresta os privilégios vinculados a um NFT utilitário a um usuário e, além disso, paga taxas de gás para isso.

O que eu tinha em mente era algo parecido com:

/images/f4220bd659de.webp

Aqui o Proprietário tem a opção:

  1. De definir seus NFTs como alugáveis;
  2. De definir a quantidade de ETH necessária para alugar seus NFTs por um determinado período.

Se o NFT estiver listado como alugável, um Usuário tem a possibilidade de alugá-lo e paga ao Proprietário por isso.

Um usuário que deseja alugar o NFT deve enviar uma transação ao Contrato especificando o NFT que deseja alugar e o período de tempo do aluguel. Além disso, a transação deve ter a quantidade correta de ETH para o período de tempo escolhido.

Vejamos as partes relevantes do código do Solidity:

contract RentableNFT is ERC4907, Ownable

Obviamente, o contrato RentableNFT estende a implementação de referência ERC4907 e o contrato OpenZeppelin Ownable (ok, concordo com você, a extensão Ownable não é estritamente necessária, mas neste contrato, quero que a cunhagem seja permitida apenas ao proprietário do contrato).

uint256 public baseAmount = 1000000000000000; //0.001 ETH
struct RentableItem {
  bool rentable;
  uint256 amountPerMinute;
}
mapping(uint256 => RentableItem) public rentables;
  • baseAmount é o valor padrão a ser pago por um minuto de aluguel;
  • RentableItem é uma struct onde se armazena informações sobre rentabilidade e taxas para um determinado NFT;
  • rentables é um mapeamento entre NFTs e a struct RentableItem.
function mint() public onlyOwner {
   currentTokenId.increment();
   uint256 newItemId = currentTokenId.current();
   _safeMint(owner(), newItemId);
   rentables[newItemId] = RentableItem({
       rentable: false,
       amountPerMinute: baseAmount
   });
}

Por padrão, na função _mint _(cunhagem), o novo NFT criado:

  • é cunhado para o proprietário do contrato;
  • é definido como não alugável;
  • suas taxas são definidas como baseAmount.
function setRentFee(uint256 _tokenId, uint256 _amountPerMinute) public {
  require(_isApprovedOrOwner(_msgSender(), _tokenId), "Chamador sem propriedade do token e desaprovado");
  rentables[_tokenId].amountPerMinute = _amountPerMinute;
}

Este é o método de configuração para o valor amountPerMinute do NFT. Este método pode ser chamado apenas pelo proprietário (ou endereço aprovado) do NFT.

function setRentable(uint256 _tokenId, bool _rentable) public {
  require(_isApprovedOrOwner(_msgSender(), _tokenId), "Chamador sem propriedade do token e desaprovado");
  rentables[_tokenId].rentable = _rentable;
}

Este método define o valor booleano de aluguel de um NFT. Este método pode ser chamado apenas pelo proprietário (ou endereço aprovado) do NFT.

function rent(uint256 _tokenId, uint64 _expires) public payable virtual {
  uint256 dueAmount = rentables[_tokenId].amountPerMinute * _expires;
  require(msg.value == dueAmount, "Valor incorreto");
  require(userOf(_tokenId) == address(0), "Alugado");
  require(rentables[_tokenId].rentable, "Aluguel desabilitado para o NFT");
  payable(ownerOf(_tokenId)).transfer(dueAmount);
  UserInfo storage info = _users[_tokenId];
  info.user = msg.sender;
  info.expires = block.timestamp + (_expires * 60);
  emit UpdateUser(_tokenId, msg.sender, _expires);
}

Aqui está o método principal de aluguel, que:

  • calcula o valor devido pelo aluguel do NFT;
  • verifica se o valor enviado está correto;
  • verifica se o NFT já não está alugado por outra pessoa;
  • verifica se o NFT foi alugado por seu proprietário;
  • transfere o valor devido ao proprietário do NFT;
  • atualiza informações sobre o usuário e o período de expiração.

O contrato proposto está disponível no github, neste link.

Artigo original escrito por Gold dev. Traduzido por Paulinho Giovannini.