Construindo o login por e-mail no Mercado de NFT usando Paper na rede Polygon
ÍNDICE
O Sucesso do Reddit com NFTs até Agora
Evite Usar a Metamask Como Única Carteira para seu DApp
Motivos para a Má Experiência do Usuário (UX) na Maioria dos DApps
Uma Abordagem Alternativa para o Login em DApps
Solução do Paper para uma Má Experiência do Usuário
Vamos Construir Nosso Próprio Mercado
Funcionalidades
Pilha de Tecnologia
Pré-requisitos
Configuração do Ambiente
Inicializando o SDK do Paper
Habilitando o Login por E-mail
Compra e Transferência de NFT com um Único Clique
Resumo
De fato, no cenário atual, poucos mercados de NFT atendem aos usuários não-Web3, principalmente devido à má Experiência do Usuário (UX) que eles enfrentam.
Uma barreira significativa para esses usuários é a complexidade do ecossistema Web3, incluindo a necessidade de entender conceitos como carteiras, chaves privadas e taxas de gás. Além disso, o processo de integração pode ser tedioso e confuso, com os usuários tendo que configurar e gerenciar carteiras e adquirir criptomoedas para interagir com o mercado. Para resolver esses problemas e tornar os mercados de NFT mais acessíveis aos usuários não-Web3, é essencial focar na melhoria da UX.
Neste guia, você aprenderá como criar um mercado na Polygon e torná-lo fácil para os não usuários de criptomoedas usarem o mercado, permitindo que eles façam login com seus e-mails e transfiram NFTs com um único clique.
O Sucesso do Reddit com NFTs até o Momento
O Reddit é talvez o melhor exemplo de uma empresa que trouxe NFTs para as massas. Até agora, eles levaram NFTs para mais de 7 milhões de pessoas.
Os avatares do Reddit foram um grande sucesso, em parte porque a maioria dos clientes nem mesmo sabe que está usando NFTs. Eles os consideram como colecionáveis digitais.
O Reddit tornou a experiência de reivindicar (e agora comprar) NFTs perfeita. Os usuários não precisam saber nada sobre a Metamask, não precisam ter criptomoedas e não precisam se preocupar em lembrar suas frases-chave.
Se queremos que os NFTs alcancem ainda mais pessoas, devemos aprender com o sucesso do Reddit: tornar os NFTs simples para os usuários não nativos de criptomoedas aumentará a adoção.
Por isso, criei este guia: qualquer pessoa pode facilmente copiar o que funcionou para o Reddit, oferecendo especificamente o login baseado em e-mail e pagamentos via cartão de crédito. Faremos isso usando a Polygon (ótima para transações de baixo custo) e o [Paper] (SDKs para comércio de NFTs).
Evite Usar a MetaMask Como Única Carteira para seu DApp
Aqui estão os principais motivos pelos quais você deve evitar depender apenas da MetaMask:
- Conhecimento limitado sobre Web3 pode fazer com que usuários comuns hesitem em usar a MetaMask.
- Instalar e conectar a carteira a seu DApp pode confundir muitos usuários.
- Gerenciar chaves privadas pode ser complexo e trazer preocupações significativas para os usuários e o produto. Como não há solução para recuperar uma chave privada uma vez que ela é perdida ou roubada, isso representa um problema considerável.
Motivos para a Má Experiência do Usuário (UX) na Maioria dos DApps
Todo DApp tem como objetivo alcançar a descentralização para aproveitar plenamente o potencial da tecnologia blockchain. Para fazer isso, eles muitas vezes dependem de carteiras tradicionais que oferecem segurança e descentralização. No entanto, como no Trilema da Blockchain, esses DApps podem precisar de ajuda com escalabilidade.
Para os usuários que conseguem criar uma carteira e armazenar suas chaves privadas "de forma segura", o próximo desafio surge ao lidar com fundos. Os usuários precisam converter criptomoedas e, em seguida, gastá-las para interagir com o DApp, o que dificulta bastante a integração de novos usuários.
Vários outros fatores contribuem para uma má experiência do usuário, como a necessidade de assinar cada interação, ter a carteira sempre acessível, entre outros. Esses problemas podem criar uma experiência geral negativa para os usuários.
Uma Abordagem Alternativa para o Login em DApps
Em vez de depender de contas de propriedade externa (EOAs), os desenvolvedores podem criar contratos inteligentes que atuam como contas de usuário e executam transações em nome do usuário. Essa abordagem pode melhorar a integração de novos usuários e a experiência geral do usuário no DApp.
Vários Propostas de Melhoria da Ethereum (EIP) tentam definir o conceito de usar contratos inteligentes como carteiras. Após nove anos de pesquisa, a EIP-4337 foi oficialmente aprovada para a abstração de contas (AA).
A abstração de contas oferece lógica personalizável para criação de carteira, pagamento de taxas de gás de transação e tokens alternativos em vez de Ether para taxas de gás. Uma opção é criar uma carteira usando um endereço de e-mail, onde o e-mail do usuário gera uma chave privada. Essa chave é então dividida usando funções criptográficas e compartilhada entre o dispositivo do usuário e os provedores de serviços de aplicativos. O provedor de serviços pode então pagar as taxas de gás da transação, assinar transações e ajudar os usuários na recuperação da chave privada.
A Solução do Paper para uma Má Experiência do Usuário (UX)
O Paper oferece um SDK de carteira incorporada (Embedded Wallet SDK) que ajuda a resolver problemas de experiência do usuário, fornecendo as seguintes funcionalidades:
- Criar carteiras de usuário usando e-mails ou logins sociais.
- Assinar mensagens ou chamar métodos de blockchain sem exigir prompts ou taxas de gás.
- Permite que os usuários acessem ou recuperem suas carteiras em qualquer dispositivo.
- Permite que os usuários se conectem a outros aplicativos por meio do WalletConnect.
O Paper ajuda os desenvolvedores a criar experiências de usuário contínuas e expandir seus aplicativos sem precisar configurar sua própria infraestrutura.
Vamos CONSTRUIR Nosso Próprio Mercado
Hoje, vamos construir um mercado de NFT para minimizar o atrito na integração de usuários não-Web3. Faremos isso oferecendo a opção de criar uma carteira usando qualquer endereço de e-mail e possibilitando transferências de NFT sem taxas de gás.
Funcionalidades
Implementaremos as seguintes funcionalidades em nosso mercado:
- Login por e-mail para o DApp.
- Compra e transferência de NFT com um único clique.
Pilha de Tecnologia
Construiremos o mercado com as seguintes tecnologias:
- React.js
- Ethers.js
- SDK de carteira incorporada do Paper
Pré-requisitos
- NPM ou Yarn instalado no seu sistema.
- Compreensão básica do React.js.
Configuração do Ambiente
Para configurar o código inicial (contrato + UI) [Agradecimentos ao OMGWINNING por isso], abra seu terminal no diretório de sua preferência e digite os seguintes comandos:
git clone https://github.com/megabyte0x/NFT-Marketplace-Starter_Code.git
cd NFT-Marketplace-Starter_Code
npm install
Crie um novo arquivo no diretório raiz chamado .env
e cole o seguinte conteúdo:
REACT_APP_ALCHEMY_API_URL="<Cole o URL da Alchemy aqui>"
REACT_APP_PRIVATE_KEY="<Cole sua Chave Privada aqui>"
REACT_APP_PINATA_KEY="<Cole a chave da Pinata aqui>"
REACT_APP_PINATA_SECRET="<Cole a Chave Secreta da Pinata aqui>"
REACT_APP_PAPER_SECRET="<Cole a chave secreta do Paper SDK aqui>"
Obtenha os valores necessários ao se inscrever na Alchemy, Pinata e Paper.
npm install @paperxyz/embedded-wallet-service-sdk
Por fim, inicie o servidor de desenvolvimento com o seguinte comando:
npm start
Seu ambiente de desenvolvimento está pronto!
Inicializando o Paper SDK
Crie um arquivo na pasta src chamado paper.js
. É aqui que inicializaremos o SDK.
Copie e cole o seguinte código:
import { PaperEmbeddedWalletSdk } from "@paperxyz/embedded-wallet-service-sdk";
import { UserStatus } from "@paperxyz/embedded-wallet-service-sdk";
export const sdk = new PaperEmbeddedWalletSdk({
clientId: process.env.REACT_APP_PAPER_SECRET,
chain: "Mumbai",
});
export const socialLogin = async () => {
try {
await sdk.auth.loginWithPaperModal();
return sdk.getUser();
} catch (e) {
console.log(e);
}
};
export const socialLogout = async () => {
try {
await sdk.auth.logout();
} catch (e) {
console.log(e);
}
};
export const getUser = async () => {
const user = await sdk.getUser();
return user;
};
export const getSigner = async () => {
let signer;
const user = await getUser();
if (user.status === UserStatus.LOGGED_OUT) {
return;
}
try {
signer = await getUser().then((user) => {
return user.wallet.getEthersJsSigner();
});
} catch (e) {
console.log(e);
}
return signer;
};
-
sdk
inicializa o SDK de carteira incorporada do Paper, passando nossa Chave Secreta do Paper e a cadeia que estaremos usando, "Mumbai". -
socialLogin
é a função que abre o modal para login social usando e-mail. -
socialLogout
é a função para desconectar o usuário. -
getUser
é a função que recupera informações sobre o usuário, como sua carteira e endereço da carteira. -
getSigner
é uma função que retorna o signer Ethereum para assinar transações.
Agora que inicializamos nosso SDK, vamos conectá-lo ao restante do código.
Habilitando o Login por E-mail
Já que o Navbar
é o componente que nos ajudará a conectar a carteira, vamos para src/components/Navbar.js
Copie e cole isso:
import { Link } from "react-router-dom";
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router';
import { socialLogin, socialLogout, getUser } from "../paper.js";
import { UserStatus } from "@paperxyz/embedded-wallet-service-sdk";
function Navbar() {
const [connected, toggleConnect] = useState(false);
const location = useLocation();
const [currentAddress, updateAddress] = useState('0x');
const [currentUser, updateUser] = useState(null);
function updateButton() {
const ethereumButton = document.querySelector('.enableEthereumButton');
ethereumButton.classList.remove("hover:bg-blue-70");
ethereumButton.classList.remove("bg-blue-500");
ethereumButton.classList.add("hover:bg-green-70");
ethereumButton.classList add("bg-green-500");
}
async function connectWithPaperWallet() {
try {
await socialLogin().then((user) => {
if (UserStatus.LOGGED_IN_WALLET_INITIALIZED === user.status) {
setUser();
}
});
} catch (e) {
console.log(e);
}
}
async function logout() {
try {
await socialLogout().then(() => {
setUser();
});
} catch (error) {
console.log(error);
};
}
async function setUser() {
try {
await getUser().then((user) => {
if (user.status === UserStatus.LOGGED_OUT) {
toggleConnect(false);
updateUser(null);
updateAddress('0x');
return;
}
updateUser(user);
updateAddress(user.walletAddress);
toggleConnect(true);
updateButton();
})
} catch (error) {
console.error(error);
}
}
useEffect(() => {
setUser();
}, [currentUser]);
}
export default Navbar;
Aqui, definimos várias variáveis de estado e algumas funções para conectar a carteira usando o SDK do Paper:
-
updateButton()
atualiza a interface do usuário para o botão de Connect Wallet (Conectar Carteira). -
connectWithPaperWallet()
usa a funçãosocialLogin
definida empaper.js
para abrir um modal no qual os usuários podem fazer login usando seu e-mail. Se o usuário fizer login com sucesso, atualizaremos o estado decurrentUser
ecurrentAddress
. - A função
logout()
usa a funçãosocialLogout
definida empaper.js
para fazer o logout do usuário atual. Isso garante que, quando o usuário clicar no botão de logout, sua sessão será encerrada e sua carteira será desconectada do DApp. - Da mesma forma, a função
setUser()
é chamada sempre queconnectWithPaperWallet()
oulogout()
são acionados. Essa função configura as variáveis de estado, comocurrentUser
,currentAddress
econnected
. Ela obtém as informações mais recentes do usuário e atualiza as variáveis de estado de acordo. Isso atualiza a interface do usuário com o status de conexão do usuário e garante uma experiência tranquila para o usuário. -
useEffect
é iniciado quandocurrentUser
muda, recuperando as últimas alterações no estado do usuário e atualizando as variáveis de estado de acordo.
Em seguida, vamos criar o componente de retorno:
import { Link } from "react-router-dom";
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router';
import { socialLogin, socialLogout, getUser } from "../paper.js";
import { UserStatus } from "@paperxyz/embedded-wallet-service-sdk";
function Navbar() {
const [connected, toggleConnect] = useState(false);
const location = useLocation();
const [currentAddress, updateAddress] = useState('0x');
const [currentUser, updateUser] = useState(null);
function updateButton() {
const ethereumButton = document.querySelector('.enableEthereumButton');
ethereumButton.classList.remove("hover:bg-blue-70");
ethereumButton.classList.remove("bg-blue-500");
ethereumButton.classList.add("hover:bg-green-70");
ethereumButton.classList.add("bg-green-500");
}
async function connectWithPaperWallet() {
try {
await socialLogin().then((user) => {
if (UserStatus.LOGGED_IN_WALLET_INITIALIZED === user.status) {
setUser();
}
});
} catch (e) {
console.log(e);
}
}
async function logout() {
try {
await socialLogout().then(() => {
setUser();
});
} catch (error) {
console.log(error);
};
}
async function setUser() {
try {
await getUser().then((user) => {
if (user.status === UserStatus.LOGGED_OUT) {
toggleConnect(false);
updateUser(null);
updateAddress('0x');
return;
}
updateUser(user);
updateAddress(user.walletAddress);
toggleConnect(true);
updateButton();
})
} catch (error) {
console.error(error);
}
}
useEffect(() => {
setUser();
}, [currentUser]);
return (
<div className="">
<nav className="w-screen">
<ul className='flex items-end justify-between py-3 bg-transparent text-white pr-5'>
<li className='flex items-end ml-5 pb-2'>
<Link to="/">
<div className='inline-block font-bold text-xl ml-2'>
NFT Marketplace
</div>
</Link>
</li>
<li className='w-2/6'>
<ul className='lg:flex justify-between font-bold mr-10 text-lg'>
{location.pathname === "/" ?
<li className='border-b-2 hover:pb-0 p-2'>
<Link to="/">Marketplace</Link>
</li>
<p id="gdcalert1" ><span style="color: red; font-weight: bold">>>>>> gd2md-html alert: Definition ↓↓ outside of definition list. Missing preceding term(s)? </span><br>(<a href="#">Back to top</a>)(<a href="#gdcalert2">Next alert</a>)<br><span style="color: red; font-weight: bold">>>>>> </span></p>
:
<li className='hover:border-b-2 hover:pb-0 p-2'>
<Link to="/">Marketplace</Link>
</li>
}
{location.pathname === "/sellNFT" ?
<li className='border-b-2 hover:pb-0 p-2'>
<Link to="/sellNFT">List My NFT</Link>
</li>
<p id="gdcalert2" ><span style="color: red; font-weight: bold">>>>>> gd2md-html alert: Definition ↓↓ outside of definition list. Missing preceding term(s)? </span><br>(<a href="#">Back to top</a>)(<a href="#gdcalert3">Next alert</a>)<br><span style="color: red; font-weight: bold">>>>>> </span></p>
:
<li className='hover:border-b-2 hover:pb-0 p-2'>
<Link to="/sellNFT">List My NFT</Link>
</li>
}
{location.pathname === "/profile" ?
<li className='border-b-2 hover:pb-0 p-2'>
<Link to="/profile">Profile</Link>
</li>
<p id="gdcalert3" ><span style="color: red; font-weight: bold">>>>>> gd2md-html alert: Definition ↓↓ outside of definition list. Missing preceding term(s)? </span><br>(<a href="#">Back to top</a>)(<a href="#gdcalert4">Next alert</a>)<br><span style="color: red; font-weight: bold">>>>>> </span></p>
:
<li className='hover:border-b-2 hover:pb-0 p-2'>
<Link to="/profile">Profile</Link>
</li>
}
<li>
<button className="enableEthereumButton bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-sm" onClick={connectWithPaperWallet}>{connected ? "Connected" : "Connect Wallet"}</button>
</li>
<li>
{connected && <button className="enableEthereumButton bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-sm" onClick={logout}>Logout</button>}
</li>
</ul>
</li>
</ul>
</nav>
<div className='text-white text-bold text-right mr-10 text-sm'>
{currentAddress !== "0x" ? "Connected to" : "Not Connected. Please login to view NFTs"} {currentAddress !== "0x" ? currentAddress : ""}
</div>
</div>
);
}
export default Navbar;
Aqui, criamos uma barra de navegação básica para ajudar a navegar nas páginas "Marketplace" (Mercado), "List My NFT" (Listar Meus NFT) e "Profile" (Perfil). Também incluímos um botão dinâmico "Connect Wallet" (Conectar Carteira) que iniciará a função connectWithPaperWallet e um botão Logout (Sair) que acionará a função logout.
Compra (buy
) e Transferência (transfer
) de NFT com um Único Clique
Vamos para o arquivo src/components/NFTPage.js.
Copie e cole o código abaixo:
importaxios from "axios";
import { useParams } from 'react-router-dom';
import { useState } from "react";
import { ethers } from "ethers";
import Navbar from "./Navbar.js";
import { getSigner, getUser } from "../paper.js";
import MarketplaceJSON from "../Marketplace.json";
export default function NFTPage(props) {
const [data, updateData] = useState({});
const [dataFetched, updateDataFetched] = useState(false);
const [message, updateMessage] = useState("");
const [currAddress, updateCurrAddress] = useState("0x");
const [recieverAddress, updateRecieverAddress] = useState("0x");
async function getNFTData(tokenId) {
const signer = await getSigner();
const user = await getUser();
const addr = await user.walletAddress;
// Extrai a instância do contrato implantado
let contract = new ethers.Contract(MarketplaceJSON.address, MarketplaceJSON.abi, signer)
// Cria um Token NFT
const tokenURI = await contract.tokenURI(tokenId);
const listedToken = await contract.getListedTokenForId(tokenId);
let meta = await axios.get(tokenURI);
meta = meta.data;
let item = {
price: meta.price,
tokenId: tokenId,
seller: listedToken.seller,
owner: listedToken.owner,
image: meta.image,
name: meta.name,
description: meta.description,
}
updateData(item);
updateDataFetched(true);
updateCurrAddress(addr);
}
async function buyNFT(tokenId) {
try {
const signer = await getSigner();
updateMessage("Comprando o NFT... Por favor, aguarde (Até 1 minuto)")
const funcInterface = new ethers.utils.Interface(["function executeSale(uint256 tokenId) public"]);
const dataToSend = funcInterface.encodeFunctionData("executeSale", [tokenId]);
let tx = {
to: MarketplaceJSON.address,
value: ethers.utils.parseEther(data.price),
data: dataToSend
};
const txResponse = await signer.sendTransaction(tx);
const txReceipt = await txResponse.wait();
console.log("Transação enviada:", txReceipt.transactionHash);
alert('Você comprou o NFT com sucesso!');
updateMessage("");
}
catch (e) {
alert("Erro de Upload" + e)
}
}
async function transferNFT(tokenId) {
try {
const signer = await getSigner();
// Extrai a instância do contrato implantado
let contract = new ethers.Contract(MarketplaceJSON.address, MarketplaceJSON.abi, signer);
updateMessage("Transferindo o NFT... Por favor, aguarde (Até 1 minuto)")
// Executa a função transferNFT
let transaction = await contract.transferNFT(tokenId, recieverAddress);
await transaction.wait();
alert(“Você transferiu o NFT com sucesso!”);
updateMessage("");
}
catch (e) {
alert("Erro de Upload" + e)
}
}
const params = useParams();
const tokenId = params.tokenId;
if (!dataFetched)
getNFTData(tokenId);
}
O componente NFTPage
é uma página separada que inclui duas funções: uma para comprar NFTs e outra para transferir NFTs. Vamos entender como cada função funciona em detalhes:
-
getNFTData()
: esta função busca os dados de um NFT específico usando seutokenId
. -
buyNFT()
: esta função transfere o NFT e o preço do vendedor para o comprador. Ela usa osigner
para fazer transações e codifica os dados para o contrato.sendTransaction()
(fornecido pelo SDK de carteira incorporada do Paper) inicia a transação, o que melhora a experiência do usuário, pois os usuários não precisam assinar nenhuma transação. -
transferNFT()
: esta função transfere o NFT. Ela traz osigner
e usa o ethers.js para iniciar transações. Criando a instância do contrato com osigner
e iniciando a transação usando o método tradicional com o contrato, os usuários podem transferir NFTs com apenas um clique! Tudo o que é necessário é incluir o SDK de carteira incorporada do Paper como umsigner
na instância do contrato.
A seguir, vamos criar o componente para retornar:
// ...
if (!dataFetched)
getNFTData(tokenId);
return (
<div style={{ "min-height": "100vh" }}>
<Navbar></Navbar>
<div className="flex ml-20 mt-20">
<img src={data.image} alt="" className="w-2/5" />
<div className="text-xl ml-20 space-y-8 text-white shadow-2xl rounded-lg border-2 p-5">
<div>
Nome: {data.name}
</div>
<div>
Descrição: {data.description}
</div>
<div>
Preço: <span className="">{data.price + " MATIC"}</span>
</div>
<div>
Proprietário: <span className="text-sm">{data.owner}</span>
</div>
<div>
Vendedor: <span className="text-sm">{data.seller}</span>
</div>
<div>
{currAddress === data.owner || currAddress === data.seller ?
<div className="text-emerald-700">
Você é o proprietário deste NFT
<br></br>
<input type="text" placeholder="Digite o endereço do comprador" className="bg-gray-800 text-white rounded-lg p-2 mt-2"
onChange={(e) => updateRecieverAddress(e.target.value)}
></input>
<button className="enableEthereumButton bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-sm" onClick={() => transferNFT(tokenId)}>Transferir este NFT
</button>
</div>
<p id="gdcalert4" ><span style="color: red; font-weight: bold">>>>>> gd2md-html alert: Definition ↓↓ outside of definition list. Missing preceding term(s)? </span><br>(<a href="#">Back to top</a>)(<a href="#gdcalert5">Next alert</a>)<br><span style="color: red; font-weight: bold">>>>>> </span></p>
:
<button className="enableEthereumButton bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-sm" onClick={() => buyNFT(tokenId)}>Comprar este NFT</button>
}
<div className="text-green text-center mt-3">{message}</div>
</div>
</div>
</div>
</div>
)
}
O componente que retornamos exibe os detalhes sobre o NFT usando o tokenId
específico. A funcionalidade interessante aqui é que a interface mostra o botão "Transfer" se o usuário for o proprietário ou o vendedor do NFT; caso contrário, ela exibe o botão "Buy" (Comprar).
Após implementar tudo isso, aqui está o resultado que você obterá!
Agora, é só reiniciar todo o DApp fechando a aba e o terminal funcionará perfeitamente com a UX que seu usuário deseja.
Resumo
Uau!!! Você acabou de criar um mercado de NFT com uma UX muito melhor do que os existentes atualmente.
Você pode conferir o código completo aqui:
O Paper oferece várias soluções para melhorar a UX, como pagamento com cartões de crédito.
Saiba mais sobre o SDK do Paper aqui.
Conecte-se comigo no Lens[@megabyte0x.lens] ou no Twitter [@megabyte0x].
Sinta-se à vontade para compartilhar suas descobertas e entre em contato comigo se tiver alguma dúvida ou pergunta.
Feliz desenvolvimento!
WAGMI
Artigo escrito por megabyte. Traduzido por Marcelo Panegali.
Latest comments (0)