Skip to content

Crie Um Programa de Fidelidade Usando o Contrato de Cartão Fidelidade

Crie Um Programa de Fidelidade Usando o Contrato de Cartão Fidelidade

https://blog.thirdweb.com/content/images/size/w2000/2023/09/Build-a-Loyalty-Program-using-the-Loyalty-Card-Contract--1-.png

Este guia irá lhe dizer tudo o que você precisa saber sobre o novo contrato do Cartão Fidelidade. Posteriormente neste guia, também veremos isso em ação em um aplicativo Next.js, onde os usuários podem gerar e cancelar seus cartões fidelidade, e também construiremos um painel de administração para os administradores atualizarem e revogarem os cartões fidelidade!

Vamos começar!

Veja o código-fonte deste guia aqui:

https://github.com/thirdweb-example/loyalty-card/tree/main?ref=blog.thirdweb.com

O Que é o Contrato do Cartão Fidelidade?

O contrato do Cartão Fidelidade destina-se a ser utilizado para o lançamento de programas de fidelidade. Cada NFT representa um cartão fidelidade e os metadados do NFT contêm as informações do cartão fidelidade. É muito semelhante ao nosso contrato ERC721 NFT Collection pré-construído, mas tem algumas funções adicionais para gerenciar melhor os cartões.

Existe uma função de cancelamento para o proprietário do NFT, para que ele possa cancelar seu cartão, o que queima o NFT.

As funções revoke e update permitem que os administradores revoguem o cartão de um usuário e atualizem os metadados NFT para torná-lo mais/menos valioso com base no status de fidelidade dos usuários. Os cartões são emitidos aos usuários por meio de cunhagem de assinaturas.

Como o administrador tem algum controle extra sobre os NFTs dos usuários, este contrato não é ideal para a maioria das coleções, mas sim para coleções que representam um programa de fidelidade, como associação ou assinatura.

Implante o Contrato do Cartão Fidelidade

Para começar, vá para a página Contracts (Contratos) em seu painel da Thirdweb e clique em Deploy Contract (Implantar Contrato):

https://blog.thirdweb.com/content/images/size/w1000/2023/03/SCR-20230304-uu3-1-.png

Você será direcionado para a página Explore (Explorar) da Thirdweb - onde poderá navegar pelos contratos inteligentes criados pelos principais protocolos da Web3 e implantá-los com apenas alguns cliques!

Nota: você também pode usar a CLI daThirdweb para configurar um projeto de contrato inteligente para criar seus próprios contratos, executando o comando abaixo em seu terminal:


npx thirdweb create contract

Isso o guiará por um fluxo de etapas fáceis de seguir para criar seu contrato. Saiba mais sobre isso em nosso guia CLI.

Caso contrário, vamos voltar ao Explore:

https://blog.thirdweb.com/content/images/size/w1000/2023/09/thirdweb-explore.png

Aqui, selecione o Loyalty Card contract (contrato do Cartão Fidelidade). Isso o levará para a seguinte página:

https://blog.thirdweb.com/content/images/size/w1000/2023/09/loyalty-card-contract-preview.png

Quando estiver nessa página, clique em Deploy Now (Implantar agora) e você verá uma gaveta deslizar à direita. Preencha os parâmetros do contrato aqui:

https://blog.thirdweb.com/content/images/size/w1000/2023/09/fill-in-contract-parameters-of-the-loyalty-contract.png

Por fim, selecione a rede/cadeia para a qual deseja implantar e clique em "Deploy Now". Isso desencadeará duas transações em sua carteira que você precisará confirmar:

https://blog.thirdweb.com/content/images/size/w1000/2023/09/click-confirm-to-deploy-your-loyalty-card-program.png

Assim que as transações forem concluídas, você poderá ver o painel do seu contrato:

https://blog.thirdweb.com/content/images/size/w1000/2023/09/image.png

Não precisamos fazer mais nada com o contrato por enquanto. Vamos usar o contrato em um aplicativo Next.js!

Criando o Aplicativo Next.js

Agora, vamos criar um aplicativo Web onde os usuários possam:

  • Gerar novos cartões fidelidade.
  • Ver seus cartões fidelidade/cancelá-los.

E os administradores podem:

  • Ver todos os cartões fidelidade presentes.
  • Revogar cartões de usuários.
  • Atualizar os metadados dos cartões fidelidade.

Usando a CLI da Thirdweb, crie um novo projeto Next.js e TypeScript com o React SDK, pré-configurado para você, usando o seguinte comando:


npx thirdweb create app --next --ts

💡 É necessária uma chave de API para usar os serviços de infraestrutura da Thirdweb, como armazenamento, RPCs e infraestrutura de carteira inteligente de dentro do SDK. Se você ainda não criou uma chave, poderá fazê-lo gratuitamente no painel da ThirdWeb.

Para usar uma chave de API com o React SDK, passe o clientId para o arquivo ThirdwebProvider. O modelo já vem com o ID do cliente, então você pode criar um novo arquivo .env.local e adicionar o clientId e o secretKey (que usaremos em breve) com os seguintes nomes:


NEXT_PUBLIC_TEMPLATE_CLIENT_ID=

TW_SECRET_KEY=

Agora, adicione também uma chave privada da carteira:


WALLET_PRIVATE_KEY=<your-private-key>

IMPORTANTE: Chaves Privadas.

Usar chaves privadas como variável env não é a melhor prática e é vulnerável a ataques. Estamos usando esse método neste guia por uma questão de brevidade, mas recomendamos fortemente o uso de um gerenciador de segredos para armazenar sua chave privada.

Certifique-se de armazenar e acessar sua chave privada com segurança.

  • Verifique se você precisa usar uma chave privada para sua aplicação.
  • Nunca exponha diretamente sua chave privada em seu código-fonte.
  • Nunca envie nenhum arquivo que possa conter sua chave privada para seu controle de origem.
  • Nunca use uma chave privada para uma aplicação de front-end (site/dApp).

Se você não tiver certeza de como armazenar e acessar sua chave privada com segurança, não prossiga.

Em seguida, precisamos atualizar a cadeia na qual nosso aplicativo funciona. Entre em _app.tsx e altere a variável activeChain para a cadeia na qual você implantou seu contrato. No meu caso, é Mumbai:


// Esta é a chainId em que seu dApp trabalhará.

const activeChain = "mumbai";

Como precisaremos do endereço do contrato em vários locais, crie um novo arquivo consts.ts e adicione o endereço do contrato:


export const CONTRACT_ADDRESS = "0x19B8e6c26C5d278905449bF447f98d133392bB3B";

Criando o Back-end

O back-end do nosso aplicativo irá gerar assinaturas, usando cunhagem de assinaturas, com alguns metadados que a carteira do usuário pode usar para cunhar um cartão. Portanto, crie um novo arquivo nomeado generate-sig.ts na pasta pages/api e na estrutura básica da API. Permitiremos apenas solicitações de postagem para esta API, por isso, se não for uma solicitação de postagem, enviaremos um erro:


import type { NextApiRequest, NextApiResponse } from "next";

const handler = async (req: NextApiRequest, res: NextApiResponse) => {

  if (req.method !== "POST") {

    return res.status(405).json({ error: "Método não permitido (Method not allowed)" });

  }

};

export default handler;

Abaixo disso, adicione um bloco try-catch onde inicializamos o SDK Thirdweb usando a chave secreta e a chave privada da carteira:


 try {

    const sdk = ThirdwebSDK.fromPrivateKey(

      process.env.WALLET_PRIVATE_KEY!,

      "mumbai",

      {

        secretKey: process.env.TW_SECRET_KEY!,

      }

    );

  } catch (error) {

    console.error(error);

    return res.status(500).json({ error: "Erro interno de servidor (Internal server error)" });

 }

Em seguida, precisamos acessar o contrato:


const contract = await sdk.getContract(CONTRACT_ADDRESS);

Estou usando a variável do arquivo consts.ts que criamos anteriormente neste guia. Você pode importá-la assim:


import { CONTRACT_ADDRESS } from "../../consts";

Agora, obtemos o endereço do corpo da solicitação e criamos uma carga útil de assinatura usando-o:


const address = req.body.address;

const payload: PayloadToSign721withQuantity = {

  to: address,

  metadata: {

    name: "Meu cartão fidelidade (My loyalty card)",

    description: "Alguma descrição de cartão fidelidade. Estou com muita preguiça para escrever uma (Some loyalty card description. Too lazy to write one).",

    image:    "https://15065ae3c21e0bff07eaf80b713a6ef0.ipfscdn.io/ipfs/bafybeie2mhmbriq4ndtl3i7enkovlm6njycdutobw4jczixdbfoensranm/blue_square.png",

    attributes: [

      {

        trait_type: "color",

        value: "blue",

      },

      {

        trait_type: "points",

        value: 100,

      },

    ],

  },

};

O PayloadToSign721withQuantity é o tipo de carga útil que precisamos passar, iremos importá-la do TypeScript SDK. Fique à vontade para verificar as outras propriedades que você pode passar nos metadados e alterar os valores das que estão aqui.

Por fim, usamos a função contract.erc721.signature.generate para gerar a assinatura e retorná-la:


const signedPayload = await contract.erc721.signature.generate(payload);

return res.status(200).json({ signedPayload });

Sua API final deve ser semelhante a isto:


import { PayloadToSign721withQuantity, ThirdwebSDK } from "@thirdweb-dev/sdk";

import type { NextApiRequest, NextApiResponse } from "next";

import { CONTRACT_ADDRESS } from "../../consts";

const handler = async (req: NextApiRequest, res: NextApiResponse) => {

  if (req.method !== "POST") {

    return res.status(405).json({ error: "Método não permitido (Method not allowed)" });

  }

  try {

    const sdk = ThirdwebSDK.fromPrivateKey(

      process.env.WALLET_PRIVATE_KEY!,

      "mumbai",

      {

        secretKey: process.env.TW_SECRET_KEY!,

      }

    );

    const contract = await sdk.getContract(CONTRACT_ADDRESS);

    const address = req.body.address;

    const payload: PayloadToSign721withQuantity = {

      to: address,

      metadata: {

        name: "Meu cartão fidelidade (My loyalty card)",

        description: "Alguma descrição de cartão fidelidade. Estou com muita preguiça para escrever uma (Some loyalty card description. Too lazy to write one).",

        image:

          "https://15065ae3c21e0bff07eaf80b713a6ef0.ipfscdn.io/ipfs/bafybeie2mhmbriq4ndtl3i7enkovlm6njycdutobw4jczixdbfoensranm/blue_square.png",

        attributes: [

          {

            trait_type: "cor (color)",

            value: "azul (blue)",

          },

          {

            trait_type: " pontos (points)",

            value: 100,

          },

        ],

      },

    };

    const signedPayload = await contract.erc721.signature.generate(payload);

    return res.status(200).json({ signedPayload });

  } catch (error) {

    console.error(error);

    return res.status(500).json({ error: "Erro interno de servidor (Internal server error)" });

  }

};

export default handler;

Criando o Front-end do Usuário

Vamos agora construir o front-end para um usuário comum! Para isso, vá para pages/index.tsx. Primeiramente, exclua tudo dentro do main, pois não vamos precisar dele:


import styles from "../styles/Home.module.css";

import { NextPage } from "next";

const Home: NextPage = () => {

  return <main className={styles.main}></main>;

};

export default Home;

Agora, adicionaremos um Web3Button dentro da tag principal, desta forma:


<Web3Button action={() => generate()} contractAddress={CONTRACT_ADDRESS}>

  Generate NFT

</Web3Button>

Como você pode ver, estamos usando uma função de geração externa que ainda não foi criada. Vamos criar isso agora!


 const generate = async () => {

    try {

      const res = await fetch("/api/generate-sig", {

        method: "POST",

        headers: {

          "Content-Type": "application/json",

        },

        body: JSON.stringify({

          address,

        }),

      });

      const data = await res.json();

      await contract?.erc721.signature.mint(data.signedPayload);

      alert("NFT cunhado! (NFT minted!)");

    } catch (err) {

      console.error(err);

    }

  };

A função generate faz uma pós-solicitação à API que criamos anteriormente e, em seguida, usa a assinatura gerada para cunhar o NFT, solicitando ao usuário que confirme uma transação!

Também precisamos adicionar o gancho useContract para acessar nosso contrato:


const { contract } = useContract(CONTRACT_ADDRESS);

Se você tentar cunhar um cartão fidelidade, deve funcionar!

Agora, vamos exibir os cartões do usuário. Primeiramente, adicionaremos os ganchos necessários para obter o endereço do usuário conectado e seus NFTs:


const address = useAddress();

const { data: nfts, isLoading, isError } = useOwnedNFTs(contract, address);

Agora, vamos mapear os NFTs e renderizá-los. Como precisaremos renderizar os NFTs em vários locais, vou criar um componente NFTCard:


{nfts && (

    <div className={styles.nfts}>

      {nfts.map((nft) => (

        <NFTCard nft={nft} key={nft.metadata.id} />

      ))}

    </div>

)}

Vamos então criar um novo arquivo chamado NFTCard.tsx~ na pasta components e adicionar o seguinte:


import { NFT, ThirdwebNftMedia, Web3Button } from "@thirdweb-dev/react";

import Image from "next/image";

import { type FC } from "react";

import { CONTRACT_ADDRESS } from "../consts";

import styles from "../styles/Home.module.css";

interface NFTProps {

  nft: NFT;

}

export const NFTCard: FC<NFTProps> = ({ nft }) => {

  const id = nft.metadata.id;

  return (

    <div key={nft.metadata.id} className={styles.nft}>

      {nft.metadata.image ? (

        <ThirdwebNftMedia metadata={nft.metadata} />

      ) : (

        <Image

          src="https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg"

          alt=""

          width="360"

          height="200"

          style={{

            objectFit: "contain",

          }}

        />

      )}

      <h1>{nft.metadata.name}</h1>

      <p>{nft.metadata.description}</p>

      {nft.metadata.attributes && (

        <ul>

          {/* @ts-ignore */}

          {nft.metadata.attributes.map((attribute) => (

            <li key={attribute.trait_type}>

              <strong>{attribute.trait_type}:</strong> {attribute.value}

            </li>

          ))}

        </ul>

      )}

      <Web3Button

        action={(contract) => contract.erc721.cancel(id)}

        contractAddress={CONTRACT_ADDRESS}

      >

        Cancel

      </Web3Button>

    </div>

  );

};

Isso criará um cartão NFT simples que renderiza os metadados do NFT, usando o renderizador ThirdwebNFTMedia da Thirdweb e possui um botão cancelar que chama a função contract.erc721.cancel, que cancela/queima o NFT.

Também adicionei alguns estilos básicos para os NFTs. Você pode copiá-los/alterá-los como quiser:


.nfts {

  display: grid;

  grid-template-columns: repeat(3, 1fr);

  grid-gap: 24px;

  width: 100%;

  margin-top: 3rem;

}

.nft {

  display: flex;

  flex-direction: column;

  align-items: center;

  justify-content: center;

  margin: 0 auto;

  padding: 1rem;

  border: 1px solid #eaeaea;

  border-radius: 10px;

  width: fit-content;

}

Ele renderizará um NFT assim:

https://blog.thirdweb.com/content/images/2023/09/image-1.png

É isso para o lado do usuário. Sinta-se à vontade para ir além no caso de uso, mas, para simplificar o guia, nós o mantivemos simples!

Construindo o Painel de Administração

Vamos em frente e construir o painel de administração! Para isso, crie um novo arquivo admin.tsx na pasta pages. Adicionaremos a estrutura básica aqui também:


import styles from "../styles/Home.module.css";

import { NextPage } from "next";

const Admin: NextPage = () => {

  return <main className={styles.main}></main>;

};

export default Admin;

Então, ao invés do useOwnedNFTs, que usamos na página inicial, usaremos useNFTs, que obtém todos os NFTs do contrato:


const { contract } = useContract(CONTRACT_ADDRESS);

const { data: nfts, isLoading, isError } = useNFTs(contract);

Podemos então reutilizar o NFTCard novamente para renderizar os cartões no contrato:


 {nfts && (

        <div className={styles.nfts}>

          {nfts.map((nft) => (

            <NFTCard nft={nft} key={nft.metadata.id} adminView={true} />

          ))}

        </div>

      )}

Se você notar, adicionamos outra propriedade chamada adminView, que também precisa ser adicionada no componente NFTCard, por isso, vamos adicionar essa lógica! Primeiramente, iremos adicioná-la ao tipo e aceitá-la como uma prop:


interface NFTProps {

  nft: NFT;

  adminView?: boolean;

}

export const NFTCard: FC<NFTProps> = ({ nft, adminView }) => {

Em seguida, adicionaremos uma condição para verificar se adminView está habilitado. Se estiver, precisamos renderizar algumas informações extras e um botão de revogação; caso contrário, um simples botão de cancelamento:


    {adminView ? (

        <>

          <p>

            <strong>Token ID:</strong> {nft.metadata.id}

          </p>

          <p>Owner: {nft.owner}</p>

          {nft.owner !== "0x0000000000000000000000000000000000000000" && (

            <Web3Button

              action={(contract) => contract.erc721.revoke(id)}

              contractAddress={CONTRACT_ADDRESS}

            >

              Revoke

            </Web3Button>

          )}

        </>

      ) : (

        <Web3Button

          action={(contract) => contract.erc721.cancel(id)}

          contractAddress={CONTRACT_ADDRESS}

        >

          Cancel

        </Web3Button>

      )}

A seguir, vamos adicionar a capacidade de editar a propriedade de pontos de um cartão fidelidade.

Para isso, primeiro, adicione dois ganchos useState para armazenar o novo valor dos pontos e o estado de edição:


const [points, setPoints] = useState(0);

const [isEditing, setIsEditing] = useState(false);

Em seguida, adicione a funcionalidade de edição abaixo do botão revoke (revogar):


 {isEditing ? (

            <>

              <input

                type="number"

                value={points}

                onChange={(e) => setPoints(Number(e.target.value))}

              />

              <Web3Button

                action={() => update()}

                contractAddress={CONTRACT_ADDRESS}

              >

                Update

              </Web3Button>

            </>

          ) : (

            <button

              onClick={() => setIsEditing(true)}

              className={styles.button}

            >

              Edit

            </button>

          )}

Aqui, estamos verificando se isEditing é verdade. Se for, então mostramos uma entrada e um Web3Button de atualizaç��o, que chama uma função update (escreveremos isso a seguir). Caso contrário, um simples botão para alterar o estado de edição.

Depois, vamos adicionar a função de atualização para que o administrador possa atualizar os pontos quando estiver no modo de edição:


 const { contract } = useContract(CONTRACT_ADDRESS);

  const update = async () => {

    try {

      const metadata = {

        ...nft.metadata,

        attributes: [

          // @ts-ignore

          ...nft.metadata.attributes.filter(

            // @ts-ignore

            (attribute) => attribute.trait_type !== "points"

          ),

          {

            trait_type: "points",

            value: points,

          },

        ],

      };

      await contract?.erc721.update(nft.metadata.id, metadata);

    } catch (err) {

      console.error(err);

    } finally {

      setIsEditing(false);

    }

  };

Aqui, estou apenas alterando os pontos do cartão e mantendo os outros metadados iguais ao que eram anteriormente, mas você pode adicionar mais entradas da mesma maneira para atualizar os outros metadados também.

Se você verificar a página de administração, poderá ver um cartão NFT semelhante a este:

https://blog.thirdweb.com/content/images/size/w1000/2023/09/image-2.png

Conclusão

Este guia nos ensinou como implantar um contrato de cartão fidelidade, construir um front-end de usuário para gerar e cancelar o cartão e um painel de administração para revogar e editar os cartões dos usuários.

Você aprendeu muito. Agora, celebre e compartilhe seus aplicativos incríveis conosco no Discord da Thirdweb!

Precisa de Ajuda?

Para obter apoio, junte-se ao servidor oficial da Thirdweb no Discord ou compartilhe suas idéias em nosso quadro de comentários.

Este artigo foi escrito por Avneesh Agarwal e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.