DeFi agora é um dos principais tópicos de discussão no espaço das criptomoedas. DeFi significa "Decentralized Finance” (Finanças Descentralizadas), o que significa que não há nenhuma autoridade central vigiando e controlando a transferência de fundos. Isso também significa que as transações em DeFi são P2P (ponto a ponto), o que significa que nenhuma autoridade central é responsável pela transferência e os fundos são enviados diretamente de uma entidade para outra.
Neste artigo, aprenderemos como começar a usar DeFi criando um aplicativo DeFi full-stack na blockchain Polygon usando o Next.js como front-end. Este aplicativo irá vender e comprar OKToken (um token fictício) do usuário. No entanto, cada transação de compra reduz um token da quantidade de tokens que você pode obter por MATIC (a venda aumenta esse número em um). Esta não é uma demonstração ideal, mas desta forma você pode entender como usar sua própria lógica em contratos inteligentes do Solidity e aprender a criar seu próprio aplicativo DeFi full-stack usando a Polygon.
Conteúdo
- Criando um projeto do Hardhat
- Criando nossos contratos inteligentes
- Implantando nossos contratos inteligentes
- Criando um aplicativo DeFi com o Next.js
- Adicionando o provedor da Thirdweb
Requisitos
Para começar com este tutorial, verifique se você tem o seguinte:
- Node.js instalado
- VS Code instalado
- Conhecimento prático de React e Next.js
- Conhecimento prático de Solidity e ferramentas como o Hardhat
Agora que você verificou os requisitos, vamos continuar criando nosso projeto Hardhat para trabalhar com nossos contratos inteligentes Solidity.
Criando um projeto do Hardhat
Navegue até um diretório seguro e execute o seguinte comando no terminal para inicializar seu projeto Hardhat:
npx hardhat
Depois de executar o comando, você verá o seguinte assistente de inicialização do Hardhat em seu terminal.
Na lista, escolha Create an advanced sample project (Criar um projeto de amostra avançado). Em seguida, você será perguntado onde deseja inicializar o projeto Hardhat; não altere o campo, apenas pressione Enter para que o projeto seja inicializado no diretório atual.
Em seguida, você será perguntado se deseja ou não instalar as dependências necessárias para a execução do seu projeto Hardhat. Pressione a tecla y, pois precisaremos dessas dependências e instalá-las agora é a melhor ideia.
A instalação das dependências será iniciada e pode levar alguns segundos ou minutos, dependendo da máquina em execução. Agora, execute o seguinte comando no terminal para instalar outra dependência que precisaremos para facilitar o desenvolvimento do nosso contrato Solidity:
npm install @openzeppelin/contracts
O OpenZeppelin fornece padrões de contratos inteligentes que podemos usar em nossos próprios contratos inteligentes para criar facilmente contratos Ownable, ERC-20, ERC-721 e muito mais.
Depois que as dependências forem instaladas com êxito, abra o diretório em um editor de código. Usarei o VS Code para este tutorial.
Vamos criar dois contratos inteligentes: o primeiro será nosso próprio token ERC-20 e o segundo será um contrato de vendedor, que facilitará a compra e venda desses tokens.
Criando nossos contratos inteligentes
Agora, vá para a pasta contracts
e crie um novo arquivo Solidity chamado OKToken.sol
, que conterá nosso contrato de token ERC-20.
Use o seguinte código para este arquivo:
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract OKToken is ERC20 {
constructor() ERC20("OKT", "OKToken"){
_mint(msg.sender, 10000 * 10 ** 18);
}
}
No código acima, estamos importando o arquivo ERC20.sol
de @openzeppelin/contracts
, que nos ajudará a começar com um token ERC-20 com facilidade. Em seguida, no construtor, estamos fornecendo o símbolo "OKT"
e o nome "OKToken"
para nosso token.
Isso é tudo para o contrato do token! Agora, vamos trabalhar no contrato do vendedor. Na pasta contracts
, crie um novo arquivo chamado OKVendor.sol
com o seguinte código:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./OKToken.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract OKVendor is Ownable {
OKToken yourToken;
uint256 public tokensPerNativeCurrency = 100;
event BuyTokens(address buyer, uint256 amountOfNativeCurrency, uint256 amountOfTokens);
constructor(address tokenAddress) {
yourToken = OKToken(tokenAddress);
}
function buyTokens() public payable returns (uint256 tokenAmount) {
require(msg.value > 0, "Precisa enviar alguma moeda nativa para continuar");
uint256 amountToBuy = msg.value * tokensPerNativeCurrency;
uint256 vendorBalance = yourToken.balanceOf(address(this));
require(vendorBalance >= amountToBuy, "O contrato do vendedor nao tem tokens suficientes para realizar a transacao");
(bool sent) = yourToken.transfer(msg.sender, amountToBuy);
require(sent, "Falha ao transferir o token para o usuario");
tokensPerNativeCurrency = tokensPerNativeCurrency - 1;
emit BuyTokens(msg.sender, msg.value, amountToBuy);
return amountToBuy;
}
function sellTokens(uint256 tokenAmountToSell) public {
require(tokenAmountToSell > 0, "Especifique uma quantidade de tokens maior que zero");
uint256 userBalance = yourToken.balanceOf(msg.sender);
require(userBalance >= tokenAmountToSell, "Voce tem tokens insuficientes");
uint256 amountOfNativeCurrencyToTransfer = tokenAmountToSell / tokensPerNativeCurrency;
uint256 ownerNativeCurrencyBalance = address(this).balance;
require(ownerNativeCurrencyBalance >= amountOfNativeCurrencyToTransfer, "O vendedor nao tem fundos suficientes");
(bool sent) = yourToken.transferFrom(msg.sender, address(this), tokenAmountToSell);
require(sent, "Falha ao transferir tokens do usuario para o vendedor");
(sent,) = msg.sender.call{value: amountOfNativeCurrencyToTransfer}("");
tokensPerNativeCurrency = tokensPerNativeCurrency + 1;
require(sent, "Falha ao enviar moeda nativa para o usuario");
}
function getNumberOfTokensInNativeCurrency() public view returns(uint256) {
return tokensPerNativeCurrency;
}
function withdraw() public onlyOwner {
uint256 ownerBalance = address(this).balance;
require(ownerBalance > 0, "Nenhuma moeda nativa presente no contrato do vendedor");
(bool sent,) = msg.sender.call{value: address(this).balance}("");
require(sent, "Falha ao fazer saque");
}
}
Isso nos ajudará a facilitar a compra e venda de tokens.
No contrato acima, primeiro importamos nosso contrato de token, o qual precisamos para interagir com o contrato do vendedor e as funções de chamada.
Também estamos importando Ownable.sol
de @openzeppelin/contracts
. Isso significa que o proprietário do contrato inteligente pode transferir sua propriedade e ter acesso a funções exclusivas do proprietário.
Após inicializar o contrato inteligente, definimos a variável tokensPerNativeCurrency
que indica o número de tokens que podem ser adquiridos usando 1 MATIC. Vamos alterar este número com base nas transações realizadas.
Temos então um construtor que receberá o endereço do contrato do OKToken para que possamos nos comunicar com o contrato implantado e executar funções neles.
Na função buyTokens()
, estamos realizando verificações para garantir que a quantidade adequada de MATIC seja enviada ao contrato inteligente e que o contrato do vendedor tenha a quantidade necessária de tokens. Em seguida, chamamos a função transfer()
da instância OKToken que criamos anteriormente para transferir os tokens para o remetente da solicitação.
Na função sellTokens()
, estamos realizando verificações para garantir que o remetente da solicitação tenha tokens suficientes e que o contrato do vendedor tenha MATIC suficiente para enviar de volta ao remetente da solicitação. Em seguida, usamos a função transferFrom()
da instância OKToken que criamos anteriormente para transferir os tokens da carteira do remetente da solicitação para o contrato inteligente. No entanto, o remetente precisa aprovar esta transação; realizamos essa aprovação no lado do cliente antes de fazer a solicitação. Vamos cobrir esta parte quando fizermos o front-end deste aplicativo.
Por fim, temos a função withdraw()
, que só é acessível pelo dono dos contratos. Permite retirar todo o MATIC presente no contrato.
Agora que temos os contratos inteligentes prontos, vamos implantá-los na rede de testes Polygon Mumbai!
Implantando nossos contratos inteligentes
Vamos criar um script para implantar nosso contrato na Polygon Mumbai. Depois que os contratos forem implantados, enviaremos programaticamente todos os tokens armazenados na carteira do implantador para o contrato do vendedor.
Primeiro vá para hardhat.config.js
e abaixo de module.exports
, adicione o seguinte objeto para que o Hardhat saiba a qual rede se conectar:
networks: {
mumbai: {
url: "https://matic-mumbai.chainstacklabs.com",
accounts: ["CHAVE PRIVADA AQUI"],
}
}
Estamos fornecendo um nome à rede (mumbai
, neste caso) e fornecendo um URL RPC. O URL RPC mencionado é para Polygon Mumbai. Se você deseja usar a rede principal da Polygon, pode escolher seu URL RPC. Lembre-se de inserir sua própria chave privada de carteira com algum MATIC de teste para pagar as taxas de gás envolvidas no processo de implantação do contrato inteligente.
Agora, na pasta scripts
, crie um novo arquivo chamado deploy.js
. Cole o seguinte:
const { BigNumber, utils } = require("ethers");
const hardhat = require("hardhat");
async function main() {
const OKToken = await hardhat.ethers.getContractFactory("OKToken");
const oktoken = await OKToken.deploy();
await oktoken.deployed();
console.log("[📥] OKToken implantado no endereço: " + oktoken.address);
const OKVendor = await hardhat.ethers.getContractFactory("OKVendor");
const okvendor = await OKVendor.deploy(oktoken.address);
console.log("[📥] OKVendor implantado no endereço: " + okvendor.address);
await oktoken.deployed();
// Transferir oktokens para o vendedor
await oktoken.functions.transfer(okvendor.address, utils.parseEther("10000"));
console.log("[🚀] Tokens transferidos para OKVendor");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
No arquivo acima, estamos instruindo o Hardhat a implantar nosso contrato. A função main()
é o ponto de entrada aqui. Primeiro, obtemos o contrato OKToken
e o implantamos. Em seguida, obtemos o contrato OKVendor
, fornecemos o endereço do contrato OKToken
no construtor e implementamos o contrato. Em seguida, transferimos todos os fundos do contrato OKToken
para o contrato OKVendor
.
Digite o seguinte comando no terminal para executar o script e implantar nossos contratos na rede Polygon Mumbai:
npx hardhat run --network mumbai scripts/deploy.js --show-stack-traces
Observe que o nome da rede deve corresponder ao mencionado em hardhat.config.js
. Depois de executar o script, os contratos devem ser implantados e você deve ver o seguinte em seu terminal:
Se você visualizar uma saída semelhante a esta, seus contratos inteligentes foram implantados e configurados com sucesso. Agora, vamos prosseguir com a criação de nosso aplicativo Next.js.
Criando um aplicativo Next.js DeFi
No mesmo diretório, execute o seguinte comando no terminal para criar seu aplicativo Next.js:
npx create-next-app frontend
O comando acima criará um novo aplicativo e instalará automaticamente as dependências necessárias.
Navegue até a pasta frontend
e use o seguinte comando no terminal para instalar dependências adicionais, que nos ajudarão a interagir com nossos contratos inteligentes:
yarn add @thirdweb-dev/react @thirdweb-dev/sdk ethers web3
Estamos instalando @thirdweb-dev/react
e @thirdweb-dev/sdk
para que possamos facilmente autenticar o usuário e conectar suas carteiras ao nosso aplicativo usando a MetaMask. ethers
é uma dependência necessária para o thirdweb, então precisamos instalá-la também. Finalmente, estamos instalando o web3
para que possamos interagir com nosso contrato inteligente.
Adicionando o provedor da Thirdweb
Para começar, precisamos agrupar nosso aplicativo em um thirdwebProvider
para que o thirdweb possa funcionar corretamente.
Vá para o arquivo _app.js
na pasta pages
e adicione o seguinte:
import { thirdwebProvider, ChainId } from "@thirdweb-dev/react";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<thirdwebProvider desiredChainId={ChainId.Mumbai}>
<Component {...pageProps} />
</thirdwebProvider>
);
}
export default MyApp;
No código acima, estamos importando o thirdwebProvider
e colocando nosso aplicativo dentro dele. Também estamos fornecendo um desiredChainId
do ID da cadeia da Polygon Mumbai. Você também pode usar o ID da cadeia para a rede principal da Polygon (Mainnet), se desejar.
Crie um novo arquivo na raiz do aplicativo Next.js chamado contracts.js
e adicione o seguinte conteúdo:
export const oktoken = {
contractAddress: "0xE83DD81890C76BB8c4b8Bc6365Ad95E5e71495E5",
abi: [
{
inputs: [],
stateMutability: "nonpayable",
type: "constructor",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "owner",
type: "address",
},
{
indexed: true,
internalType: "address",
name: "spender",
type: "address",
},
{
indexed: false,
internalType: "uint256",
name: "value",
type: "uint256",
},
],
name: "Approval",
type: "event",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "from",
type: "address",
},
{
indexed: true,
internalType: "address",
name: "to",
type: "address",
},
{
indexed: false,
internalType: "uint256",
name: "value",
type: "uint256",
},
],
name: "Transfer",
type: "event",
},
{
inputs: [
{
internalType: "address",
name: "owner",
type: "address",
},
{
internalType: "address",
name: "spender",
type: "address",
},
],
name: "allowance",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "spender",
type: "address",
},
{
internalType: "uint256",
name: "amount",
type: "uint256",
},
],
name: "approve",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "account",
type: "address",
},
],
name: "balanceOf",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "decimals",
outputs: [
{
internalType: "uint8",
name: "",
type: "uint8",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "spender",
type: "address",
},
{
internalType: "uint256",
name: "subtractedValue",
type: "uint256",
},
],
name: "decreaseAllowance",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "spender",
type: "address",
},
{
internalType: "uint256",
name: "addedValue",
type: "uint256",
},
],
name: "increaseAllowance",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "name",
outputs: [
{
internalType: "string",
name: "",
type: "string",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "symbol",
outputs: [
{
internalType: "string",
name: "",
type: "string",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "totalSupply",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "to",
type: "address",
},
{
internalType: "uint256",
name: "amount",
type: "uint256",
},
],
name: "transfer",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "from",
type: "address",
},
{
internalType: "address",
name: "to",
type: "address",
},
{
internalType: "uint256",
name: "amount",
type: "uint256",
},
],
name: "transferFrom",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "nonpayable",
type: "function",
},
],
};
export const okvendor = {
contractAddress: "0xAa3b8cbB24aF3EF68a0B1760704C969E57c53D7A",
abi: [
{
inputs: [
{
internalType: "address",
name: "tokenAddress",
type: "address",
},
],
stateMutability: "nonpayable",
type: "constructor",
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: "address",
name: "buyer",
type: "address",
},
{
indexed: false,
internalType: "uint256",
name: "amountOfNativeCurrency",
type: "uint256",
},
{
indexed: false,
internalType: "uint256",
name: "amountOfTokens",
type: "uint256",
},
],
name: "BuyTokens",
type: "event",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "previousOwner",
type: "address",
},
{
indexed: true,
internalType: "address",
name: "newOwner",
type: "address",
},
],
name: "OwnershipTransferred",
type: "event",
},
{
inputs: [],
name: "buyTokens",
outputs: [
{
internalType: "uint256",
name: "tokenAmount",
type: "uint256",
},
],
stateMutability: "payable",
type: "function",
},
{
inputs: [],
name: "getNumberOfTokensInNativeCurrency",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "owner",
outputs: [
{
internalType: "address",
name: "",
type: "address",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "renounceOwnership",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "uint256",
name: "tokenAmountToSell",
type: "uint256",
},
],
name: "sellTokens",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "tokensPerNativeCurrency",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "newOwner",
type: "address",
},
],
name: "transferOwnership",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "withdraw",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
],
};
Lembre-se de substituir os endereços do contrato pelos seus para que o aplicativo Next.js tente se conectar ao contrato inteligente correto.
Agora vamos começar a codificar nosso aplicativo. Abra o arquivo index.js
na pasta pages
e adicione o seguinte:
import { useAddress, useContract, useMetamask } from "@thirdweb-dev/react";
import Head from "next/head";
import Image from "next/image";
import { oktoken, okvendor } from "../contracts";
import styles from "../styles/Home.module.css";
import { useEffect, useState } from "react";
import Web3 from "web3";
const web3 = new Web3(Web3.givenProvider);
export default function Home() {
const [tokensPerCurrency, setTokensPerCurrency] = useState(0);
const [tokens, setTokens] = useState(0);
const address = useAddress();
const connectUsingMetamask = useMetamask();
const account = web3.defaultAccount;
const purchase = async () => {
const contract = new web3.eth.Contract(
okvendor.abi,
okvendor.contractAddress
);
const ethToSend = tokens / tokensPerCurrency;
const purchase = await contract.methods.buyTokens().send({
from: address,
value: web3.utils.toWei(ethToSend.toString(), "ether"),
});
console.log(purchase);
await fetchPrice();
};
const sell = async () => {
const vendorContract = new web3.eth.Contract(
okvendor.abi,
okvendor.contractAddress
);
const tokenContract = new web3.eth.Contract(
oktoken.abi,
oktoken.contractAddress
);
const approve = await tokenContract.methods
.approve(
okvendor.contractAddress,
web3.utils.toWei(tokens.toString(), "ether")
)
.send({
from: address,
});
const sellTokens = await vendorContract.methods.sellTokens(tokens).send({
from: address,
});
await fetchPrice();
};
const fetchPrice = async () => {
const contract = new web3.eth.Contract(
okvendor.abi,
okvendor.contractAddress
);
const priceFromContract = await contract.methods
.getNumberOfTokensInNativeCurrency()
.call();
setTokensPerCurrency(priceFromContract);
};
useEffect(() => {
fetchPrice();
}, []);
return (
<div>
<Head>
<title>Troque OKTokens</title>
</Head>
{address ? (
<div>
<p>Tokens por moedas: {tokensPerCurrency}</p>
<div>
<input
type="number"
value={tokens}
onChange={(e) => setTokens(e.target.value)}
/>
</div>
<button onClick={purchase}>Compre</button>
<button onClick={sell}>Venda</button>
</div>
) : (
<div>
<button onClick={connectUsingMetamask}>Conecte usando a MetaMask</button>
</div>
)}
</div>
);
}
Este é um bloco de código longo, então vamos ver o que o código está fazendo passo a passo:
- Inicializando o pacote
web3
usando um provedor configurado pela thirdweb; - Usando ganchos da thirdweb
useMetamask()
para autenticar euseAddress()
para verificar o estado da autenticação e, em seguida, renderizar o botão de login se o usuário não tiver a carteira MetaMask conectada; - Definindo vários estados para mapear caixas de texto em nosso aplicativo
- Criando uma função
fetchPrice()
para interagir com nosso contrato inteligente e verificar quantos tokens um MATIC pode obter, além de criar umuseEffect
para verificar esse preço sempre que a página carregar - Criando uma função
purchase()
, que inicializa nosso contrato de vendedor e chama a funçãobuyTokens()
do contrato, e então envia algum MATIC junto com esta transação. Em seguida, chamamosfetchPrice()
para que o preço mais recente seja mostrado
Por fim, estamos criando uma função sell()
, que inicializa tanto o token quanto o contrato do vendedor. Primeiro, interagimos com a função approve()
do contrato de token e permitimos que o contrato do vendedor transfira fundos em nosso nome. Em seguida, estamos chamando a função sellTokens()
do contrato do vendedor para finalmente vender os tokens e receber o MATIC. Também estamos chamando fetchPrice()
para obter o preço mais recente após a transação.
Nosso aplicativo DeFi simples está completo! Você pode visualizar este aplicativo em seu navegador executando o seguinte comando:
yarn dev
Agora, após visitar http://localhost:3000, você verá a tela a seguir e poderá fazer transações.
Conclusão
Este foi um tutorial simples sobre como criar seu próprio aplicativo DeFi full-stack baseado na Polygon. Você pode implementar sua própria lógica nos contratos inteligentes para torná-los ainda melhores, dependendo da sua organização. Sugiro mexer no código para que você possa aprender da melhor maneira possível.
Artigo original escrito por Atharva Deosthale. Traduzido por Paulinho Giovannini.
Latest comments (0)