WEB3DEV

Cover image for Utilizando NFTs como Licenças de Software
Paulo Gio
Paulo Gio

Posted on

Utilizando NFTs como Licenças de Software

https://miro.medium.com/v2/resize:fit:1100/0*86-ZNbLKmLH31o3U

Token Gating

Token Gating (restrição por token) é um princípio que só permite o acesso ao conteúdo (geralmente em um site ou servidor do Discord) se você tiver um token específico em sua carteira. Isso me deu uma ideia de como podemos estender esse conceito para permitir que as licenças de software sejam distribuídas e rastreadas usando as carteiras dos usuários, um contrato inteligente e NFTs.

Como Funciona

O conceito é simples: O usuário cunha um NFT usando sua carteira para iniciar seu teste gratuito, seu token é então marcado em um mapeamento no contrato inteligente contra seu endereço com o status "Trial" (Teste), bem como o tempo em que o token foi cunhado (usando block.timestamp).

O usuário então faz login no software com sua carteira para iniciar a consulta do contrato. Verificamos então se o usuário possui o NFT e, se encontrado, verificamos o mapeamento no contrato para os detalhes do tempo de subscrição do usuário e da última renovação.

Fluxo do Usuário

Toda vez que o contrato é consultado, verificamos se o teste expirou usando um limite de tempo predefinido que podemos alterar no contrato: se ultrapassarmos esse tempo ao consultar o contrato, marcaremos o token como "Desativado", o que acionará um fluxo de compra no software, veja o gráfico abaixo para uma visão geral visual de como isso funcionará.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*sNR99LDNiox5YfjBI7Mmhw.png

A Imagem

Faça upload de uma imagem que você usará para o seu token para o IPFS, você pode usar uma conta gratuita na plataforma de armazenamento de arquivos do NFT.STORAGE para enviar sua imagem para a blockchain. Uma vez carregada a imagem, copie o link para o arquivo de imagem, precisaremos dele para a próxima etapa.

Os Metadados

Antes de começarmos a criar o contrato, nosso NFT precisa de alguns metadados, isso permite que ele segure uma imagem para exibição em plataformas de negociação, como a OpenSea.

Configurar o NFT dessa maneira permitirá que as licenças sejam totalmente negociáveis ​​no mercado aberto. Nota: tokens desativados e de teste não podem ser vendidos, apenas licenças 'Pagas' ativas.

{
  "description": "Uma licença para o Software da ACME Co.",
  "external_url": "https://acmeco.com",
  "image": "https://bafkreifh6hd2fv7fi5rtczovbjc7gefvvr3qbafnavufi5r36sevrmnvn4.ipfs.nftstorage.link/",
  "name": "Licença ACME Co.",
  "attributes": [
    { "Status": "status do token será inserido aqui" },
    { "Time Minted": 109989090908809 },
    { "Subscription Length": 1098989099 }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Mantenha isso à parte, pois vamos utilizar no contrato inteligente para que nossos metadados estejam completamente na blockchain.

O Contrato Inteligente

Agora que nossa imagem está armazenada na blockchain e nossos metadados estão estruturados, é hora de mergulhar no contrato inteligente. Nosso contrato permitirá que o administrador cunhe muitas licenças de um tipo em uma única chamada, facilitando a distribuição de tokens de teste gratuitos para as pessoas.

O contrato limitará os tokens a 1 por carteira para não ficarmos confusos entre diferentes licenças para o mesmo produto/usuário. Tokens desativados ou de teste só podem ser transferidos pelo administrador. Isso impede que licenças inválidas cheguem aos mercados secundários.

O status será mostrado nos metadados. Toda vez que um token é consultado, verificamos se o teste expirou, se foi pago e quanto tempo a licença dura. Se o tempo atual for além desse ponto, desativamos o token no contrato.

Usaremos o padrão de economia de gás 721A para tornar as transferências e interações o mais baratas possível. Implantar na Polygon ou Avalanche seria a melhor opção para um sistema como este, pois as taxas de gás são nominais na pior das hipóteses.

Temos mapeamentos que irão armazenar todas as informações sobre usuários e tokens. O status e o tempo cunhado serão usados como pontos-chave de informação, bem como a duração da subscrição.

Iniciando o Código do Contrato

Usaremos o contrato 721a, que é Ownable, com uma guarda de reentrância e várias outras bibliotecas auxiliares para fazer este contrato funcionar. Devemos começar com algo simples como a estrutura abaixo.

// SPDX-License-Identifier: MIT
// Criado por Robert McMenemy

pragma solidity ^0.8.14;

import "https://raw.githubusercontent.com/chiru-labs/ERC721A/main/contracts/ERC721A.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

contract SoftwareLicense is ERC721A, Ownable, ReentrancyGuard {

}
Enter fullscreen mode Exit fullscreen mode

Estrutura de Dados

Vamos configurar nossas variáveis para a duração do teste, status de cunhagem e custo da licença. Configuramos uma estrutura para os dados do token, um mapeamento para armazenar uma struct para cada identificador de token, um mapeamento para armazenar as posses de token dos usuários e, finalmente, nosso construtor para o contrato, que inclui o nome e o ticker. Coloque isso dentro do código do nosso contrato do passo anterior.

// Usando Strings para uints para permitir uma conversão mais fácil para string
using Strings for uint256;

// Struct para armazenar informações sobre um token
struct TokenInfo {
    uint256 timeMinted; // Tempo em que o token foi criado
    uint256 subscriptionLength; // Duração da subscrição
    uint256 lastRenewal; // Última renovação
    string status; // Status
}

// Definindo variáveis iniciais
bool public mintStarted = false; // Indica se a criação de tokens foi iniciada
uint256 trialLength = 7 days; // Duração do período de teste
uint256 public licenceCost = 0.15 ether; // Custo da licença em ether

// Mapeamento para armazenar informações para cada identificador de token
mapping(uint256 => TokenInfo) private tokenData;

// Mapeamento para armazenar o token de cada usuário
mapping(address => uint256) private tokenHoldings;

// Nome e ticker do token definidos nos parâmetros do construtor
constructor() ERC721A("Software License: ACME CO.", "SLCN") { }
Enter fullscreen mode Exit fullscreen mode

Função de Metadados

Agora vamos criar nossa função para gerar metadados a partir das informações que criamos anteriormente:

// Sobrescrever a função tokenURI() com metadados personalizados na cadeia
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
    bytes memory dataURI = abi.encodePacked(
        '{',
            '"description": "Uma licença para o Software ACME Co.",',
            '"external_url": "https://acmeco.com",',
            '"image": "https://bafkreifh6hd2fv7fi5rtczovbjc7gefvvr3qbafnavufi5r36sevrmnvn4.ipfs.nftstorage.link/",',
            '"name": "Licença ACME Co.",',
            '"attributes": [',
                '{ "Status": "', tokenData[tokenId].status, '" },',
                '{ "Time Minted": "', Strings.toString(tokenData[tokenId].timeMinted), '" },',
                '{ "Subscription Length": "', Strings.toString(tokenData[tokenId].subscriptionLength), '" },',
                '{ "Last Renewal": "', Strings.toString(tokenData[tokenId].lastRenewal), '" }'
            ']',
        '}'
    );
    return string(
        abi.encodePacked(
            "data:application/json;base64,",
            Base64.encode(dataURI)
        )
    );
}
Enter fullscreen mode Exit fullscreen mode

Isso significa que quando alguém consultar o ID do token de uma exchange, carteira ou em qualquer outro lugar, receberá os valores atuais do contrato inteligente dentro do URI dos metadados.

Isso significa que qualquer um pode ver, a partir das características num primeiro olhar, o status e a duração da subscrição de qualquer licença, tornando fácil impedir que licenças inativas ou de teste sejam revendidas em mercados secundários.

Cunhagem

A seguir, vamos configurar duas funções de cunhagem, uma para o proprietário e uma para o público. A cunhagem pública só permite a cunhagem de tokens de teste gratuitos. O proprietário pode cunhar quantos tokens de um status quiser, por chamada à função devMint.

Cada cunhagem atualiza nossos mapeamentos para tokens e participações (para a cunhagem pública).

Impedir a Revenda de Licenças Desativadas

Isso substituirá a função de transferência e realizará algumas verificações antes de permitir que a transferência continue. O saldo do usuário receptor deve ser zero. Se msg.value for maior que 0, então esta é uma venda e devemos verificar se o status do token não está “desativado” ou “em teste”.

Finalmente, se o limite de tempo for excedido na transferência do token, consideramos o token como desativado e não permitimos a conclusão da transferência.

// Verificar se o usuário possui apenas um token
// Verificar se o token está sendo vendido, se sim, permitir apenas a transferência de tokens ativos
// Se o token passou do período ativo, desativá-lo
function transferFrom(address from, address to, uint256 tokenId) public payable virtual override {
    if (from != address(owner()) && from != address(this) && to != address(owner()) && to != address(this)) {
        require(balanceOf(to) < 1, "[Erro] O usuário pode possuir apenas uma licença");
        if (msg.value > 0) {
            if (block.timestamp > tokenData[tokenId].lastRenewal + tokenData[tokenId].subscriptionLength) {
                tokenData[tokenId].status = "Desativado";
                tokenData[tokenId].subscriptionLength = 0 days;
            }
            require(keccak256(abi.encodePacked(tokenData[tokenId].status)) == keccak256(abi.encodePacked("Pago")), "[Erro] Apenas tokens ativos podem ser vendidos");
        }
    }
    super.transferFrom(from, to, tokenId);
}
Enter fullscreen mode Exit fullscreen mode

Comprando licenças

Com nossa lógica, permitiremos que as licenças sejam compradas uma vez por ano. A lógica aqui pode ser personalizada para seu caso de uso, mas como ponto de partida básico, ela ajudará.

// Função para permitir que os detentores de licenças comprem subscrições a partir de seus tokens de teste
function buyLicense() external payable nonReentrant {
    require(msg.value >= licenceCost, "[Erro] Por favor, envie a quantidade correta de ETH para o custo da licença");
    tokenData[tokenHoldings[msg.sender]].status = "Pago";
    tokenData[tokenHoldings[msg.sender]].subscriptionLength = 365 days;
}
Enter fullscreen mode Exit fullscreen mode

Contrato Completo

O código completo pode ser baixado aqui.

Implantação

Implante o contrato em qualquer cadeia de camada 1 ou 2 compatível com a EVM. Neste caso, a Polygon ou a Avalanche seriam ótimas escolhas devido às baixas taxas.

Verificar Licenças

O processo de verificação de licenças dependerá da pilha em que sua aplicação foi construída, mas o fluxo para cada linguagem será o mesmo:

Instale uma biblioteca que permita aos usuários fazer login com a MetaMask.

Uma vez configurado, redirecione o usuário para o aplicativo principal que usará a API do Etherscan para consultar as participações de token do usuário e o status desse token usando o endereço do contrato implantado nas etapas acima.

Conclusão

Este método de utilizar a natureza única, não fungível e imutável dos NFTs é a forma como podemos aumentar a segurança ao não exigir informações sensíveis do usuário para comprar. Tudo o que armazenamos é o endereço dos usuários e sua licença na cadeia, todo o resto é completamente anônimo.

Permitir que os usuários paguem por licenças de software usando criptomoedas e NFTs permitiria que usuários em regiões não bancarizadas acessassem ferramentas pagas que normalmente exigem um provedor bancário com suporte internacional.

As possibilidades são realmente infinitas! Este é apenas um exemplo básico, mas poderia ser usado de formas muito mais complexas. Espero que tenha gostado deste artigo. Se achou útil, clique no botão “Seguir” para se manter atualizado com o meu novo conteúdo.

Artigo original publicado por Robert McMenemy. Traduzido por Paulinho Giovannini.

Top comments (0)