Instalando as dependências e adicionando o arquivo de ABI
Antes de começarmos, instale a biblioteca ethers que é uma das principais bibliotecas para interagir com os contratos inteligentes do ecossistema Ethereum.
Para isso, abra o projeto x-front-end
, abra o terminal e rode o comando npm install ethers@6.7.1
para instalar as dependências.
Agora, adicione o arquivo gerado no deploy do contrato inteligente dentro do nosso app. Para isso, abra o projeto x-smart-contracts
e copie o arquivo XPost.json
que está dentro da pasta artifacts/contracts/XPost.sol
.
Depois disso, retorne para o projeto x-front-end
, crie uma pasta dentro da pasta src
chamada contracts
e cole o arquivo XPost.json
lá. Então, o seu diretório ficará mais ou menos assim:
Dica: O arquivo gerado toda vez que você compila seu contrato inteligente, chamado de XPost.json, é conhecido como ABI (Application Binary Interface). Pode ser comparado a uma API, pois contém todasos métodos necessários para interagir com o contrato inteligente. Clique aqui para saber mais.
Atualizando o app com as novas funcionalidades do contrato inteligente
Agora, resta apenas adicionar as novas funções do contrato inteligente dentro do seu app, para isso copie o código abaixo e cole dentro do arquivo App.vue
:
<script setup>
import XButton from './components/XButton.vue';
import XTextField from './components/XTextField.vue';
import XPost from './components/XPost.vue';
import { ethers } from "ethers";
import abi from "@/contracts/XPost.json";
import { ref, onMounted, watchEffect } from "vue";
const connectedWallet = ref(null);
const loading = ref(false);
const loadingPosts = ref(false);
const message = ref(null);
const contractAddress = "";
const contractABI = abi.abi;
const getEthereumObject = () => window.ethereum;
const findMetamaskAccount = async () => {
loading.value = true;
try {
const ethereum = getEthereumObject();
if (!ethereum) {
console.error("Instale a extensão do MetaMask!");
return null;
}
console.log("A extensão do MetaMask está instalada:", ethereum);
const accounts = await ethereum.request({ method: "eth_accounts" });
if (accounts.length !== 0) {
const account = accounts[0];
console.log("Conta MetaMask conectada:", account);
connectedWallet.value = account;
return account;
} else {
console.error("Nenhuma conta MetaMask conectada D:");
return null;
}
} catch (error) {
console.error(error);
return null;
} finally {
loading.value = false;
}
};
const connectWallet = async () => {
loading.value = true;
try {
const ethereum = getEthereumObject();
if (!ethereum) {
alert("Instale a extensão do MetaMask!");
return;
}
const accounts = await ethereum.request({
method: "eth_requestAccounts",
});
console.log("Connected", accounts[0]);
connectedWallet.value = accounts[0];
getAllPosts();
} catch (error) {
console.error(error);
}
loading.value = false;
};
const createPost = async () => {
loadingPosts.value = true;
const ethereum = getEthereumObject();
if (!ethereum) {
alert("Instale a extensão do MetaMask!");
return;
}
try {
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const xPostContract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
let count = await xPostContract.getTotalPosts();
console.log("Número total de posts antes de ser criado um novo:", Number(count));
const postTxn = await xPostContract.createPost(message.value, {
gasLimit: 300000,
});
console.log("Criando um post novo...", postTxn.hash);
await postTxn.wait();
console.log("Criado e salvo na blockchain!", postTxn.hash);
count = await xPostContract.getTotalPosts();
console.log("Número total de posts depois de ser criado um novo:", Number(count));
} catch (error) {
console.log(error);
} finally {
loadingPosts.value = false;
}
};
const allPosts = ref([]);
const getAllPosts = async () => {
loading.value = true;
const ethereum = getEthereumObject();
if (!ethereum) {
alert("Instale a extensão do MetaMask!");
return;
}
try {
const provider = new ethers.BrowserProvider(ethereum);
const signer = await provider.getSigner();
const xPostContract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
const posts = await xPostContract.getAllPosts();
const normalizedPosts = posts
.map((post) => {
return {
address: post[0],
timestamp: new Date(Number(post[2]) * 1000),
message: post[1],
};
})
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
allPosts.value = normalizedPosts;
} catch (error) {
console.log(error);
}
loading.value = false;
};
watchEffect(async (onCleanup) => {
let xPostContract;
const onNewPost = (from, timestamp, message) => {
try {
console.log("Evento de NewPost recebido:", {from, timestamp, message});
allPosts.value.unshift({
address: from,
timestamp: new Date(Number(timestamp) * 1000),
message: message,
})
} catch(e) {
console.error(e)
}
};
if (window.ethereum) {
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
xPostContract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
xPostContract.on("NewPost", onNewPost);
}
onCleanup(() => {
if (xPostContract) {
xPostContract.off("NewPost", onNewPost);
}
});
});
onMounted(async() => {
await findMetamaskAccount();
await getAllPosts();
})
</script>
<template>
<main
class="bg-gray-900 min-h-screen flex items-center justify-center flex-col p-20"
>
<div
class="rounded-md border border-gray-700 text-white bg-gray-800 p-6 mx-auto w-full max-w-[600px]"
>
<h1 class="text-2xl mb-4">𝕏 (Twitter) Descentralizado</h1>
<p class="text-base mb-4">
Esse é um twitter descentralizado, conecte sua sua carteira blockchain e
use seus Ethereums para enviar uma mensagem. Cada post enviado você terá
chance de ganhar um valor de Ethereum de volta.
</p>
<XButton v-if="!connectedWallet" text="Conectar carteira" @click="connectWallet" :loading="loading" />
<template v-else>
<XTextField v-model="message" label="Post" name="post" class="mb-2" type="text" id="post" placeholder="John" required />
<XButton text="Enviar post" @click="createPost" />
<div class="flex items-center">
<span
class="bg-green-100 text-green-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded dark:bg-green-200 dark:text-green-900 h-fit"
>
Carteira conectada!
</span>
<span class="truncate">
{{ connectedWallet }}
</span>
</div>
</template>
</div>
<div
class="mt-8 rounded-md border border-gray-700 text-white bg-gray-800 p-6 mx-auto w-full max-w-[600px]"
v-if="connectedWallet && allPosts?.length > 0"
>
<h1 class="text-white text-lg mb-4">Todos os posts</h1>
<div v-if="loadingPosts" class="text-center mb-4">Carregando...</div>
<div v-else>
<XPost v-for="post in allPosts" :key="post" :address="post.address" :timestamp="post.timestamp.toString()" :message="post.message" />
</div>
</div>
</main>
</template>
Antes de te explicar as atualizações, atualize o contractAddress
que está na linha 16 do código. Para isso, abra o projeto x-smart-contracts
, abra o arquivo dados.txt
, copie o endereço do deployedAddress
e cole dentro das aspas do contractAddress
. Então, a linha 16 do seu código deve ficar mais ou menos assim:
const contractAddress = "0xa182f4a8A38Aee4d08D240a9d657Dd7D554c8c67"; // Não use esse endereço, é somente um exemplo
Eu sei que é muito código que adicionamos, por isso, além de ter adicionado vários console.log
para você entender o que está acontecendo, irei te explicar as maiores mudanças que fizemos, ok?
Primeiramente, adicionamos uma função chamada createPost
voltada para a criação da publicação. Dentro dela nós usamos a biblioteca ethers que instalamos anteriormente para conectar com a carteira MetaMask do usuário com o código const provider = new ethers.BrowserProvider(window.ethereum);
.
Depois disso, usamos a função const signer = await provider.getSigner();
para obter os dados de assinatura do usuário visto que toda transação na blockchain precisa ser assinada por alguém.
Então, usamos o código abaixo para criar o contrato inteligente que consiste em três parâmetros: o contractAddress
referente ao o endereço que recebemos quando fazemos o deploy do contrato para produção, o contractABI
referente ao arquivo do contrato inteligente compilado do Solidity para o formato JSON e o signer
referente aos dados da pessoa que irá interagir com o contrato.
const xPostContract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
O restante das funções como getTotalPosts
, createPost
e getAllPosts
são todas as funções que criamos quando estávamos escrevendo o contrato inteligente.
Já lá em baixo na linha 151, há uma função chamada watchEffect
, essa é uma função do próprio Vue para escutar eventos e fazer alguma atualização.
No nosso caso, estamos usando ela para escutar os eventos NewPost
que criamos quando estávamos escrevendo o contrato inteligente, lembra? Então, toda vez que alguém criar uma publicação, você não precisará recarregar a página, a publicação aparecerá automaticamente devido ao watchEffect
escutar os eventos sendo criados na blockchain.
Agora, é só lembrar dos 3 comandos git para enviar seu app para o GitHub e rodar o comando npm run dev
para rodar seu projeto localmente.
É provável que alguns erros apareçam nos primeiros testes, por isso, na próxima aula ensinarei o que você deve ser feito antes de começar os testes.