Este guia foi originalmente publicado em https://docs.filebase.com. Confira o site de documentação do Filebase para obter os tutoriais Web3 mais recentes usando o IPFS por meio do Filebase.
Aprenda a criar um aplicativo de doação Web3 usando IPFS e Polygon.
O que é a Polygon?
A Polygon é uma rede blockchain de camada dois que é projetada como uma solução sidechain para escalar junto com a rede blockchain Ethereum.
Leia abaixo para saber como criar um aplicativo de doação em Web3 usando IPFS e Polygon.
Pré-requisitos:
- Uma carteira criptográfica, como MetaMask.
- Configure sua carteira criptográfica para usar a Rede Polygon Mumbai.
- Instalar Node.js e npm.
- Baixe e instale um IDE de sua escolha, como VSCode.
- Inscrever-se para uma conta gratuita do Filebase.
- Tenha seu acesso à base de arquivos e chaves secretas. Saiba como visualizar suas chaves de acesso aqui.
- Crie um bucket Filebase. Aprenda a criar um bucket aqui.
1. Comece instalando o pacote Truffle NPM com o seguinte comando:
npm install -g truffle
2. Em seguida, crie um aplicativo NextJS com os comandos:
npm install next
npx create-next-app --typescript
Quando solicitado, forneça um nome para seu aplicativo. Este tutorial usa o nome ‘polygon-dapp’.
3. Navegue até o diretório do aplicativo recém-criado e inicialize o projeto:
cd polygon-dapp
truffle init
O comando ‘init’ criará os seguintes arquivos e diretórios:
-
contracts/
: Usado para armazenar os contratos inteligentes do projeto. -
migrations/
: Usado para armazenar os scripts Truffle para implantar os contratos inteligentes. -
test/
: Usado para armazenar os scripts de teste do projeto para validar os contratos inteligentes. -
truffle-config.js
: O arquivo de configuração de Truffle.
4. Navegue até o diretório contracts
e crie um novo arquivo chamado DonationContract.sol
.
No arquivo, insira o seguinte código:
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.5.16;
contract DonationContract {
// Acompanha o número total de imagens no contrato
uint256 public imageCount = 0;
// Estrutura de dados para armazenar os dados das imagens
struct Image {
uint256 id;
string hash;
string description;
uint256 donationAmount;
address payable author;
}
mapping(uint256 => Image) public images;
// Evento emitido quando a imagem é criada
event ImageCreated(
uint256 id,
string hash,
string description,
uint256 donationAmount,
address payable author
);
// Evento emitido quando há uma doação
event DonateImage(
uint256 id,
string hash,
string description,
uint256 donationAmount,
address payable author
);
// Crie uma imagem
function uploadImage(string memory _imgHash, string memory _description) public {
require(bytes(_imgHash).length > 0);
require(bytes(_description).length > 0);
require(msg.sender != address(0x0));
imageCount++;
images[imageCount] = Image(
imageCount,
_imgHash,
_description,
0,
msg.sender
);
emit ImageCreated(imageCount, _imgHash, _description, 0, msg.sender);
}
function donateImageOwner(uint256 _id) public payable {
require(_id > 0 && _id <= imageCount);
Image memory _image = images[_id];
address payable _author = _image.author;
address(_author).transfer(msg.value);
_image.donationAmount = _image.donationAmount + msg.value;
images[_id] = _image;
emit DonateImage(
_id,
_image.hash,
_image.description,
_image.donationAmount,
_author
);
}
}
Este contrato inteligente lida com o processo de doação quando os usuários interagem com nosso aplicativo. Salve este arquivo e compile-o com o comando:
truffle compile
Você deve receber uma saída semelhante à seguinte:
5. Navegue até o diretório migrations
e, em seguida, crie um novo arquivo chamado 1_initial_migration.js
.
Neste arquivo, insira o seguinte código:
const Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};
6. Salve este arquivo. Em seguida, navegue de volta para o diretório contracts
e crie um novo arquivo chamado Migrations.sol
.
Insira o seguinte código neste arquivo:
pragma solidity >=0.4.22 <0.7.0;
contract Migrations {
address public owner;
uint256 public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
constructor() public {
owner = msg.sender;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
7. No diretório migrations
, crie outro novo arquivo chamado 2_donation_contract_migration.js
.
Neste arquivo, insira o seguinte código:
const DonationContract = artifacts.require("DonationContract");
module.exports = function (deployer) {
deployer.deploy(DonationContract);
};
8. Em seguida, instale o Ganache.
Você pode tanto instalar o aplicativo GUI ou a ferramenta CLI. Vamos usar o aplicativo GUI. Após o download, inicie o aplicativo GUI e selecione ‘Quickstart Ethereum’ quando solicitado a criar um espaço de trabalho.
9. Em seguida, selecione 'Salvar' para iniciar seu ambiente blockchain de desenvolvimento Ganache.
10. Em seguida, abra o arquivo truffle-config.js
e substitua seu conteúdo existente pelo seguinte:
module.exports = {
networks: {
development: {
host: "localhost",
port: 7545,
network_id: "*",
},
},
contracts_directory: "./contracts",
contracts_build_directory: "./abis",
compilers: {
solc: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
db: {
enabled: false,
},
};
11. Agora é hora de implantar nossa configuração Truffle na rede de desenvolvimento Ganache com o comando:
truffle migrate
A saída deve se parecer com o seguinte:
12. Antes de prosseguir com nosso aplicativo, executaremos alguns scripts de teste. No diretório tests
, crie um novo arquivo chamado donation-contract-tests.js
que contém o seguinte código:
const { assert } = require("chai");
const DonationContract = artifacts.require("./DonationContract.sol");
require("chai").use(require("chai-as-promised")).should();
contract("DonationContract", ([deployer, author, donator]) => {
let donationContract;
before(async () => {
donationContract = await DonationContract.deployed();
});
describe("deployment", () => {
it("should be an instance of DonationContract", async () => {
const address = await donationContract.address;
assert.notEqual(address, null);
assert.notEqual(address, 0x0);
assert.notEqual(address, "");
assert.notEqual(address, undefined);
});
});
describe("Images", () => {
let result;
const hash = "abcd1234";
const description = "This is a test image";
let imageCount;
before(async () => {
result = await donationContract.uploadImage(hash, description, {
from: author,
});
imageCount = await donationContract.imageCount();
});
it("Check Image", async () => {
let image = await donationContract.images(1);
assert.equal(imageCount, 1);
const event = result.logs[0].args;
assert.equal(event.hash, hash);
assert.equal(event.description, description);
});
it("Allow users to donate", async () => {
let oldAuthorBalance;
oldAuthorBalance = await web3.eth.getBalance(author);
oldAuthorBalance = new web3.utils.BN(oldAuthorBalance);
result = await donationContract.donateImageOwner(imageCount, {
from: donator,
value: web3.utils.toWei("1", "Ether"),
});
const event = result.logs[0].args;
let newAuthorBalance;
newAuthorBalance = await web3.eth.getBalance(author);
newAuthorBalance = new web3.utils.BN(newAuthorBalance);
let donateImageOwner;
donateImageOwner = web3.utils.toWei("1", "Ether");
donateImageOwner = new web3.utils.BN(donateImageOwner);
const expectedBalance = oldAuthorBalance.add(donateImageOwner);
assert.equal(newAuthorBalance.toString(), expectedBalance.toString());
});
});
});
13. Em seguida, instale o pacote chai
, que é o pacote de script de teste do Truffle usado no script acima:
npm install chai chai-as-promised
14. Em seguida, execute o script de teste com o comando:
truffle test
Sua saída deve ficar assim:
15. No diretório raiz do seu projeto, crie um novo diretório chamado contexts
, em seguida, crie um novo arquivo chamado DataContext.tsx
.
Neste arquivo, insira o seguinte conteúdo:
declare let window: any;
import { createContext, useContext, useEffect, useState } from "react";
import Web3 from "web3";
import DonationContract from "../abis/DonationContract.json";
interface DataContextProps {
account: string;
contract: any;
loading: boolean;
images: any[];
imageCount: number;
updateImages: () => Promise<void>;
donateImageOwner: (id: string, donateAmout: any) => Promise<void>;
}
const DataContext = createContext<DataContextProps | null>(null);
export const DataProvider: React.FC = ({ children }) => {
const data = useProviderData();
return <DataContext.Provider value={data}>{children}</DataContext.Provider>;
};
export const useData = () => useContext<DataContextProps | null>(DataContext);
Este script usa o pacote Web3, então vamos instalá-lo com o comando:
npm install web3
16. Em seguida, abra o arquivo pages/_app.tsx e insira o seguinte código:
import "tailwindcss/tailwind.css";
import { DataProvider } from "../contexts/DataContext";
function MyApp({ Component, pageProps }) {
return (
<>
<DataProvider>
<Component {...pageProps} />
</DataProvider>
</>
);
}
export default MyApp;
17. Para a interface de usuário do nosso aplicativo, usaremos o pacote TailwindCSS. Instale-o, junto com HeadlessUI, com o comando:
npm install tailwindcss @headlessui/react
18. Abra o arquivo pages/index.tsx
e substitua o código existente pelo seguinte conteúdo:
import Head from "next/head";
import { useState } from "react";
import Body from "../components/Body";
import Header from "../components/Layout/Header";
import { UploadImage } from "../components/UploadImage";
import { useData } from "../contexts/DataContext";
export default function Home() {
let [isOpen, setIsOpen] = useState(false);
const { loading } = useData();
function closeModal() {
setIsOpen(false);
}
function openModal() {
setIsOpen(true);
}
return (
<div className="flex flex-col items-center justify-start min-h-screen py-2">
<Head>
<title>Decentragram</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<UploadImage isOpen={isOpen} closeModal={closeModal} />
<Header />
<div
className="max-w-2xl w-full bg-blue-100 rounded-xl flex justify-center items-center py-2 mt-3 hover:bg-blue-200 cursor-pointer"
onClick={openModal}
>
<span className="text-blue-500 font-bold text-lg">Upload Image</span>
</div>
{loading ? (
<div className="font-bold text-gray-400 mt-36 text-4xl">Loading...</div>
) : (
<Body />
)}
</div>
);
}
19. Há três componentes referenciados neste código que ainda não existem —Body.tsx
,UploadImage.tsx
, e Header.tsx
.
Crie um novo diretório chamado components
, e crie um novo arquivo para cada um deles no diretório de componentes. Em seguida, insira o seguinte código em cada um:
**Body.tsx
:
declare let window: any;
import Identicon from "identicon.js";
import React from "react";
import { useData } from "../contexts/DataContext";
const Body = () => {
const { images } = useData();
return (
<>
{images.length > 0 &&
images.map((image, index) => (
<BodyItem
key={index}
totalDonationss={image.donationAmount}
address={image.author}
description={image.description}
hash={image.hash}
id={image.id}
/>
))}
</>
);
};
export default Body;
const BodyItem = ({ address, description, totalDonationss, hash, id }) => {
const { donateImageOwner, updateImages } = useData();
var data = new Identicon(address, 200).toString();
return (
<div className="w-full md:mx-0 md:max-w-2xl mt-5 p-3 border rounded-xl flex flex-col">
<div className="flex flex-row space-x-5 bg-gray-100 rounded-t-xl py-3 px-4 border-t border-l border-r font-mono items-center">
<img width={35} height={35} src={`data:image/png;base64, ${data}`} />
<div className="overflow-ellipsis w-52 overflow-hidden">{address}</div>
</div>
<img src={`https://ipfs.filebase.io/ipfs/${hash}`} />
<div className="py-3 px-4 flex flex-col border-l border-r">
<span className="font-sans font-bold">Description</span>
<span className="font-sans pt-2">{description}</span>
</div>
<div className="bg-gray-100 rounded-b-xl py-3 px-4 border-b border-l border-r font-mono flex flex-row justify-between">
<span>
Total DONATIONS: {window.web3.utils.fromWei(totalDonations, "Ether")}{" "}
MATIC
</span>
<div
onClick={async () => {
let donationAmount = window.web3.utils.toWei("0.1", "Ether");
await donationImageOwner(id, donationAmount);
await updateImages();
}}
>
<span className="cursor-pointer font-bold text-blue-400">
DONATE: 0.1 MATIC
</span>
</div>
</div>
</div>
);
};
**Header.tsx
:
import Identicon from "identicon.js";
import React, { useEffect } from "react";
import { useData } from "../../contexts/DataContext";
function Header() {
const { account } = useData();
const [data, setData] = React.useState();
useEffect(() => {
if (account !== "0x0") {
setData(new Identicon(account, 200).toString());
}
}, [account]);
return (
<div className="container items-center">
<div className="flex flex-col md:flex-row items-center md:justify-between border py-3 px-5 rounded-xl">
<span className="font-mono">Polygon MATIC</span>
<div className="flex flex-row space-x-2 items-center">
<div className="h-5 w-5 rounded-full bg-blue-500"></div>
<span className="font-mono text-xl font-bold">Decentagram</span>
</div>
<div className="flex flex-row space-x-2 items-center">
<span className="font-mono overflow-ellipsis w-52 overflow-hidden">
{account}
</span>
{account && data && (
<img
width={35}
height={35}
src={`data:image/png;base64, ${data}`}
/>
)}
</div>
</div>
</div>
);
}
export default Header;
UploadImage.tsx:
import { Dialog, Transition } from "@headlessui/react";
import { create } from "ipfs-http-client";
import { Fragment, useState } from "react";
import { useData } from "../contexts/DataContext";
interface Props {
isOpen: boolean;
closeModal: () => void;
}
export const UploadImage: React.FC<Props> = ({ isOpen, closeModal }) => {
const [buttonTxt, setButtonTxt] = useState<string>("Upload");
const [file, setFile] = useState<File | null>(null);
const { contract, account, updateImages } = useData();
const client = create({ url: "https://api.filebase.io/v1/ipfs/" });
const [description, setDescription] = useState<string>("");
const uploadImage = async () => {
setButtonTxt("Uploading to IPFS...");
const added = await client.add(file);
setButtonTxt("Creating smart contract...");
contract.methods
.uploadImage(added.path, description)
.send({ from: account })
.then(async () => {
await updateImages();
setFile(null);
setDescription("");
setButtonTxt("Upload");
closeModal();
})
.catch(() => {
closeModal();
});
};
return (
<>
<Transition appear show={isOpen} as={Fragment}>
<Dialog
as="div"
className="fixed inset-0 z-10 overflow-y-auto"
onClose={closeModal}
>
<Dialog.Overlay className="fixed inset-0 bg-black opacity-40" />
<div className="min-h-screen px-4 text-center ">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span
className="inline-block h-screen align-middle"
aria-hidden="true"
>
​
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div className="inline-block w-full p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl max-w-xl">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
>
Upload Image to IPFS
</Dialog.Title>
<div className="mt-2">
<input
onChange={(e) => setFile(e.target.files[0])}
className="my-3"
type="file"
/>
</div>
{file && (
<div className="mt-2">
<img src={URL.createObjectURL(file)} />
</div>
)}
<div className="mt-4">
<textarea
onChange={(e) => {
setDescription(e.target.value);
}}
value={description}
placeholder="Description"
className="px-3 py-1 font-sourceSansPro text-lg bg-gray-100 hover:bg-white focus:bg-white rounded-lg border-4 hover:border-4 border-transparent hover:border-green-200 focus:border-green-200 outline-none focus:outline-none focus:ring w-full pr-10 transition-all duration-500 ring-transparent"
/>
</div>
<div className="mt-4">
<button
type="button"
disabled={buttonTxt !== "Upload"}
className="inline-flex justify-center px-4 py-2 text-sm font-medium text-blue-900 bg-blue-100 border border-transparent rounded-md hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
onClick={() => {
if (file) uploadImage();
}}
>
{buttonTxt}
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
};
20. Agora, para usar o cliente IPFS, vamos instalar o pacote do cliente IPFS HTTP:
npm install ipfs-http-client
21. Por fim, antes de publicarmos este contrato, precisamos configurar a autenticação por meio de nossa carteira criptográfica.
Primeiro, certifique-se de que sua carteira esteja conectada à rede de teste Polygon Mumbai. Para obter instruções sobre como fazer isso, confira este guia aqui.
22. Em seguida, crie um novo arquivo chamado .secret
na raiz do seu projeto.
Neste arquivo, insira a frase mnemônica de sua carteira criptográfica ou Frase de recuperação secreta. Observe que esta frase NUNCA deve ser compartilhada com ninguém e deve ser colocada em seu arquivo .gitignore
se você planeja publicar este projeto no GitHub.
23. Para transações enviadas, você precisará de algum MATIC na rede de testes, que pode ser obtido na Torneira Polygon Matic.
24. Para cobrir as taxas de gás, usaremos o pacote hdwallet-provider
, que pode ser instalado com o comando:
npm install @truffle/hdwallet-provider
25. Agora que temos esses detalhes de configuração, atualize seu arquivo truffle-config.js
com o seguinte:
const HDWalletProvider = require("@truffle/hdwallet-provider");
const fs = require("fs");
const mnemonic = fs.readFileSync(".secret").toString().trim();
module.exports = {
networks: {
development: {
host: "localhost",
port: 7545,
network_id: "*",
},
matic: {
provider: () =>
new HDWalletProvider({
mnemonic: {
phrase: mnemonic,
},
providerOrUrl: `https://matic-mumbai.chainstacklabs.com/`,
chainId: 80001,
}),
network_id: 80001,
confirmations: 2,
timeoutBlocks: 200,
skipDryRun: true,
chainId: 80001,
},
},
contracts_directory: "./contracts",
contracts_build_directory: "./abis",
compilers: {
solc: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
db: {
enabled: false,
},
};
26. Agora é hora de implantar o contrato inteligente do seu aplicativo de doação na rede Polygon Mumbai com o comando:
truffle migrate --network matic
Uma implantação bem-sucedida produzirá a seguinte saída:
27. Em seguida, inicie o aplicativo da web com o comando:
npm run build
npm start
28. O aplicativo será executado em localhost:3000
. Vai parecer algo assim:
29. Selecione ‘Upload Image’ para carregar uma imagem para o aplicativo.
30. Selecione um arquivo do seu computador local, forneça uma descrição opcional e selecione ‘Upload’.
Quer saber mais sobre o Filebase? Você pode se inscrever para uma Conta gratuita hoje para começar com o IPFS.
Este artigo foi escrito por Jessie Mongeon e traduzido por Diogo Jorge. O artigo original pode ser encontrado aqui.
Latest comments (0)