Uma demonstração de paywall usando apenas client-side com a rede Ethereum Base
Paywalls são ferramentas fundamentais na indústria que ajudam criadores a monetizar seu conteúdo, funcionalidades específicas e jogos. São essenciais para uma criação de conteúdo e prestação de serviços sustentável.
Recentemente, considerei implementar um paywall para uma funcionalidade especial em uma web app que desenvolvi. No entanto, eu não queria usar nenhuma tecnologia backend - não por falta de habilidade, mas porque não queria manter nenhuma infraestrutura de backend no momento.
Analisei várias opções como Cardano (com o qual tenho alguma familiaridade), Coinbase, BitPay e Stripe. Porém, todas essas opções familiares exigiam algum backend para verificar informações importantes.
Foi quando percebi que com Ethereum, é possível interagir com smart contracts sem precisar de um backend.
Então abri o Cursor AI e comecei a esboçar o que se tornaria meu proof of concept de paywall com Ethereum.
Você pode ver todas as informações do contrato em: https://basescan.org/address/0xd7827bb72b456c64c7a18617e6510b7a9c95558a
A demo está disponível em https://tbbc.app/ethereum-paywall/
Há uma gravação em vídeo demonstrando o fluxo de compra, que estou incorporando abaixo:
Métodos de leitura do contrato são geralmente gratuitos - você tipicamente paga mais para escrever em um contrato do que para ler dele.
Isso significa que os usuários pagam uma pequena taxa de gas ao comprar seu acesso, mas quando a aplicação recarrega e verifica se o usuário tem acesso, a operação é gratuita.
Vamos começar entendendo os requisitos fundamentais para criar um smart contract no Ethereum. Primeiro, você precisará acessar o Remix IDE em https://remix.ethereum.org/.
Remix é um IDE web poderoso onde você pode construir seu smart contract, validá-lo e fazer deploy na rede de produção. Ele fornece tudo que você precisa para começar com desenvolvimento Ethereum sem instalar nenhuma ferramenta local.
Não vou entrar nos detalhes de escrever um contrato aqui porque é bem simples. O contrato que construí para meu paywall Ethereum pode ser visto aqui: https://basescan.org/address/0xd7827bb72b456c64c7a18617e6510b7a9c95558a
Depois que obtive meu endereço do contrato e ABIs, pedi ajuda ao Cursor com o código. Aqui estão as partes mais relevantes do código que ele gerou:
// Detalhes do contrato
const contractAddress = '0xd7827bb72b456c64c7a18617e6510b7a9c95558a'; // Seu endereço do contrato aqui
// Seu ABI do contrato aqui
const contractABI = []
O ABI é fácil de encontrar dentro do Remix IDE em um arquivo JSON. Ele essencialmente descreve cada método definido no seu contrato, incluindo seus argumentos e saídas.
Instância do contrato: Então apenas combinamos esses dois dados para criar a instância do contrato:
const web3 = new Web3(window.ethereum);
contract = new web3.eth.Contract(contractABI, contractAddress);
Verificar acesso: Verifica se uma carteira comprou acesso ou não:
const hasAccess = await contract.methods.hasAccess(account).call();
Compra: Ao chamar este método, ele dispara o processo de assinatura da carteira com todas as informações para que o usuário possa tomar uma decisão informada de compra:
const web3 = new Web3(window.ethereum);
const priceWei = web3.utils.toWei(ACCESS_PRICE_ETH.toString(), 'ether');
const transaction = await contract.methods.purchaseAccess().send({
from: account,
value: priceWei
});
O método acima vai disparar este diálogo:
Isso é basicamente TODO o código que está atualmente interagindo com o contrato - isso é incrível!
Precisamos obter a conta do usuário da carteira instalada:
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
account = accounts[0];
Antes de verificar com contract.hasAccess(account), precisamos pedir ao usuário para assinar um texto para verificar se ele é realmente o dono da conta:
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
const address = accounts[0];
const message = `Verify wallet ownership for ${address} at ${Date.now()}`;
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, address]
});
Isso vai disparar um diálogo como este:
O método acima retorna uma assinatura que é armazenada no client-side, e a cada reload, verificamos se a assinatura foi assinada pela carteira atual:
const web3 = new Web3(window.ethereum);
const recoveredAddress = await web3.eth.personal.ecRecover(message, signature);
const isValid = recoveredAddress.toLowerCase() === address.toLowerCase();
Isso fornece algum nível de segurança de que a carteira fornecida ao método contract.hasAccess é realmente de propriedade do usuário.
A segurança poderia ser melhorada tendo um backend que possa persistir cookies que apenas este backend pode assinar.
Todo o problema de segurança do frontend é que qualquer informação pode ser alterada, e o maior beneficio de qualquer backend é que apenas você dono da aplicação pode alterar algumas informações trazendo um alto nível de controle e segurança.
Você poderia argumentar que se um usuário que comprou acesso compartilhar sua assinatura e endereço de carteira com outra pessoa, eles poderiam ser personificados, mas isso também é verdade com muitos cookies de sites hoje em dia.
Como eu queria algo que fosse fácil de verificar o acesso em recarregamentos subsequentes da página, esta solução é bem confiável - e não é como se eu estivesse resolvendo um grande problema da indústria.
Eu só quero me divertir construindo pequenas ferramentas utilitárias e ajudar indivíduos a pagar com menos fricção enquanto mantenho menos código e burocracia.
Obrigado por ler! Estou no 𝕏 como @imfelquis. Confira meus outros experimentos web no meu site em https://tbbc.app/
Também, agradecimento especial ao @MarcoWorms por financiar minha carteira original e recomendar a rede Ethereum L2 Base