WEB3DEV

Cover image for Aplicativo Descentralizado de Staking com Solidity e React em 40 minutos
Panegali
Panegali

Posted on • Atualizado em

Aplicativo Descentralizado de Staking com Solidity e React em 40 minutos

O que é Staking

Staking é o bloqueio de suas criptomoedas por um período de tempo específico e, durante esse período, você pode ganhar juros.

Recentemente, a Ethereum passou do mecanismo de prova de trabalho (PoW) para o mecanismo de consenso de prova de participação (PoS). Aqui, o Staker (quem faz staking) bloqueará sua criptomoeda e poderá ganhar juros sobre ela, pois a blockchain colocará os valores que estão em stake para trabalhar para validar as transações.

NOTA: Presumo que você tenha alguma experiência com Solidity, React e Truffle. Se você está apenas começando, verifique nosso blog anterior.

Fluxo do Aplicativo

  1. O usuário irá fazer stake e desfazer o stake do montante.
  2. O staker precisa fazer stake antes de um horário específico.
  3. O staker precisa desfazer o stake (retirada) antes de um período específico.
  4. O staker ganhará juros durante o tempo de staking.
  5. Após o período de bloqueio, o staker pode desfazer o stake (retirar) o montante em stake + os juros auferidos.

Excelente, agora vamos botar a mão na massa com o React e o Solidity.

Este tutorial é inspirado no dapp demo alchemy.

  • Criar um novo aplicativo React
yarn create react-app stacking
Enter fullscreen mode Exit fullscreen mode
  • Mover para a pasta
cd stacking
Enter fullscreen mode Exit fullscreen mode
  • Inicializar o Truffle
truffle init
Enter fullscreen mode Exit fullscreen mode
  • Substituir truffle-config.jspelo código abaixo
module.exports = {
 networks: {
   development: {
    host: "127.0.0.1",     // Localhost (padrão: none)
    port: 8545,            // Porta padrão Ethereum (padrão:none)
    network_id: "*",       // Qualquer rede (padrão: none)
   },
 },
 contracts_directory: "./src/contracts",
 contracts_build_directory: "./src/contracts/build",
 // Configure seus compiladores
 compilers: {
   solc: {
     version: "0.8.16",
     settings: {         
      optimizer: {
        enabled: false,
        runs: 200
      },
     }
   }
 }
};
Enter fullscreen mode Exit fullscreen mode
  • Mover a pasta do contrato src/contracts sob o diretório src
  • Criar um novo contrato Staking.sol
  • É necessário rastrear o montante em stake e a hora em que o usuário fez stake do montante, então precisamos de 2 mapeamentos
mapping(address=>uint) balances;
mapping(address=>uint) stackingTime;
Enter fullscreen mode Exit fullscreen mode
  • É necessário especificar o tempo limite do staking.
uint stakingDeadline = block.timestamp +  1 minutes;
Enter fullscreen mode Exit fullscreen mode
  • É necessário especificar o horário do prazo de retirada
uint withdrawalDeadline = block.timestamp +  2 minutes;
Enter fullscreen mode Exit fullscreen mode
  • Ponto de recompensa por segundo
uint constant rewardRatePerSecond = 0.1 ether
Enter fullscreen mode Exit fullscreen mode
  • Eventos de staking e retirada
event Stake(address indexed sender, uint256 amount);
event Withdrawal(address, uint);
Enter fullscreen mode Exit fullscreen mode
  • Função Stack
function stack() public payable {
       uint stakingTimeLeft = stakingDeadline - block.timestamp;
       require(stakingTimeLeft>0, "Staking deadline has been reached");
       require(msg.value>0, "Amount should be > 0");
       uint stackedAmount = balances[msg.sender];

       require(stackedAmount==0, "Can not stack 2nd time");
       balances[msg.sender] = msg.value;
       stackingTime[msg.sender] = block.timestamp;
       emit Stake(msg.sender, msg.value);
   }
Enter fullscreen mode Exit fullscreen mode
  • Função Withdrawal
function withdraw() public payable {
        //não pode Retirar até o tempo de stacking acabar
       uint stakingTimeLeft = block.timestamp - stakingDeadline;
       require(stakingTimeLeft>0, "Staking deadline has been reached");
       //não pode Retirar depois de withdrawalDealline
       uint withdrawalTimeLeft = withdrawalDeadline - block.timestamp;
       require(withdrawalTimeLeft>0, "Withdrawal deadline has been reached");

       //obtenha o montante em stake e verifique que deve ser > 0
       uint stackedAmount = balances[msg.sender];
       require(stackedAmount>0, "you have no balance to withdraw");

       // calcule os juros ganhos durante o tempo de staking.
       uint amountWithInterest = stackedAmount + ((block.timestamp - stackingTime[msg.sender]) * rewardRatePerSecond);
       balances[msg.sender] = 0;
       (bool sent,) = msg.sender.call{value: amountWithInterest}("");

       require(sent, "RIP; withdrawal failed :( ");
       emit Withdrawal(msg.sender, amountWithInterest);
   }
Enter fullscreen mode Exit fullscreen mode
  • Arquivo final Staking.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
contract Staking {
   mapping(address=>uint) balances;
   mapping(address=>uint) stackingTime;
   uint stakingDeadline = block.timestamp +  1 minutes;
   uint withdrawalDeadline = block.timestamp +  2 minutes;
   uint constant rewardRatePerSecond = 0.0000001 ether;
   // Eventos
   event Stake(address indexed sender, uint256 amount);
   event Withdrawal(address, uint);
   function stack() public payable {
       uint stakingTimeLeft = stakingDeadline - block.timestamp;
       require(stakingTimeLeft>0, "Staking deadline has been reached");
       require(msg.value>0, "Amount should be > 0");
       uint stackedAmount = balances[msg.sender];

       require(stackedAmount==0, "Can not stack 2nd time");
       balances[msg.sender] = msg.value;
       //precisamos obter tempo para calcular os juros
       stackingTime[msg.sender] = block.timestamp;
       emit Stake(msg.sender, msg.value);
   }
   function withdraw() public payable {
        //não pode Retirar até que o tempo de staking termine
       uint stakingTimeLeft = block.timestamp - stakingDeadline;
       require(stakingTimeLeft>0, "Staking is still in progress");
       //não pode Retirar após withdrawalDealline
       uint withdrawalTimeLeft = withdrawalDeadline - block.timestamp;
       require(withdrawalTimeLeft>0, "Withdrawal deadline has been reached");

       //obtenha o montante em stake e verifique que deve ser > 0
       uint stackedAmount = balances[msg.sender];
       require(stackedAmount>0, "you have no balance to withdraw");

       // calcular os juros ganhos durante o tempo de staking.
       uint amountWithInterest = stackedAmount + ((block.timestamp - stackingTime[msg.sender]) * rewardRatePerSecond);
       balances[msg.sender] = 0;
       (bool sent,) = msg.sender.call{value: amountWithInterest}("");

       require(sent, "RIP; withdrawal failed :( ");
       emit Withdrawal(msg.sender, amountWithInterest);
   }
   function getStakedBalance() public view returns (uint) {
       return address(this).balance;
   }
   function myStakedAmount() public view returns (uint) {
       return balances[msg.sender];
   }
}
Enter fullscreen mode Exit fullscreen mode
  • Crie um 2_deploy.js na pasta migrações
const Staking = artifacts.require("Staking");
module.exports = function (deployer) {
 deployer.deploy(Staking);
};
Enter fullscreen mode Exit fullscreen mode
  • Compilar Smartcontract
truffle compile
Enter fullscreen mode Exit fullscreen mode
  • Implante o contrato, certifique-se de que a Ganache esteja sendo executado localmente
truffle deploy
Enter fullscreen mode Exit fullscreen mode

EXCELENTE! Seu contrato está implantado na Ganache agora

  • Instale ether.js
npm i ethers
Enter fullscreen mode Exit fullscreen mode
  • Substitua App.js pelo código abaixo
import './App.css';
import {ethers} from 'ethers';
import { Component } from 'react';
import Staking from './contracts/build/Staking.json'
class App extends Component {
 render() {
   return (
     <div className="App">
         Hello

     </div>
   );
 };
}
export default App;
Enter fullscreen mode Exit fullscreen mode
  • Vamos escrever o código de conexão à metamask que você pode consultar aqui

  • Agora precisamos escrever funções para interagir com o contrato inteligente

  • Obtenha o saldo total de stake para retornar o valor total em stake no endereço do contrato inteligente

getStakedBalance = async () => {
   const contract = this.state.contract;
   const stakedBalance = await contract.getStakedBalance();
   const balanceInEth = ethers.utils.formatEther(stakedBalance);
   this.setState({stakedBalance:balanceInEth});
 }
Enter fullscreen mode Exit fullscreen mode
  • Montante em stake
stake = async() => {
   const contract = this.state.contract;
   await contract.stack({from:this.state.account,value:ethers.utils.parseEther(this.state.stakeAmount)});
 }
Enter fullscreen mode Exit fullscreen mode
  • Montante de retirada
withdraw = async() => {
   const contract = this.state.contract;
   await contract.withdraw({from:this.state.account});
 }
Enter fullscreen mode Exit fullscreen mode
  • myStakedAmount que exibe o valor total em stake por login de usuário
myStakedAmount = async() => {
   const contract = this.state.contract;
   const amount = await contract.myStakedAmount({from:this.state.account});
   const balanceInEth = ethers.utils.formatEther(amount);
   this.setState({myStakedAmount:balanceInEth});
 }
Enter fullscreen mode Exit fullscreen mode
  • Arquivo App.js completo
import './App.css';
import {ethers} from 'ethers';
import { Component } from 'react';
import Staking from './contracts/build/Staking.json'
class App extends Component {
 constructor(props) {
   super(props);
   this.state = {
     provider: null,
     account: null,
     contract: null,
     myBalance:0,
     stakedBalance:0,
     stakeAmount:0,
     myStakedAmount:0,
   }
 }
 async componentDidMount() {
   await this.loadWallet();
   await this.setContract();
 };
 loadWallet = async () => {
   // Um Web3Provider envolve um provedor Web3 padrão, que é
   // o que MetaMask injeta como window.ethereum em cada página
     const provedor = novo
   const provider = new ethers.providers.Web3Provider(window.ethereum);
   // // MetaMask requer permissão para conectar contas de usuários   await provider.send("eth_requestAccounts", []);
   const accounts = await provider.listAccounts();
   const account = accounts[0];
   const balance = await provider.getBalance(account);
   const balanceInEth = ethers.utils.formatEther(balance);

   this.setState({provider: provider, account: account, myBalance: balanceInEth});
 }
 setContract = async() => {
   const provider = new ethers.providers.Web3Provider(window.ethereum);

   const { chainId } = await provider.getNetwork();

   const networkData = Staking.networks[5777];

   if(networkData) {
     const abi = Staking.abi;
     const contractAddress = networkData.address;
     const signer = provider.getSigner();
     const contract = await new ethers.Contract(contractAddress, abi, signer);

     this.setState({contract:contract});
   }
 }
 getStakedBalance = async () => {
   const contract = this.state.contract;
   const stakedBalance = await contract.getStakedBalance();
   const balanceInEth = ethers.utils.formatEther(stakedBalance);
   this.setState({stakedBalance:balanceInEth});
 }
 stake = async() => {
   const contract = this.state.contract;
   await contract.stack({from:this.state.account,value:ethers.utils.parseEther(this.state.stakeAmount)});
 }
 withdraw = async() => {
   const contract = this.state.contract;
   await contract.withdraw({from:this.state.account});
 }
 myStakedAmount = async() => {
   const contract = this.state.contract;
   const amount = await contract.myStakedAmount({from:this.state.account});
   const balanceInEth = ethers.utils.formatEther(amount);
   this.setState({myStakedAmount:balanceInEth});
 }
 handleChange = event => {
   this.setState({stakeAmount:event.target.value});
 };
 render() {
   return (
     <div className="App">
         Hello {this.state.account}
         <div>
           <b>Balance:</b> {this.state.myBalance}
         </div>
         <div>
           <b>My Staked Amout:</b> {this.state.myStakedAmount} <button onClick={this.myStakedAmount}>Refresh</button>
         </div>
         <div>
           <b>Total Staked Amount: </b> {this.state.stakedBalance} <button onClick={this.getStakedBalance}>Refresh</button>
         </div>
         <div>
         <div>
           <input type="text" id="amount" name="amount" onChange={this.handleChange} value={this.state.stakeAmount} autoComplete="off"></input>
           <button onClick={this.stake}>Stake</button>
         </div>
         <button onClick={this.withdraw}>Withdraw</button>
         </div>
     </div>
   );
 };
}
export default App;
Enter fullscreen mode Exit fullscreen mode
  • Execute o aplicativo React usando
yarn start
Enter fullscreen mode Exit fullscreen mode
  • Você pode adicionar várias contas na metamask e testar a funcionalidade do aplicativo. Faça o teste de stake e retirada no seu fim.

2![Image description](https://web3dev-forem-production.s3.amazonaws.com/uploads/articles/bx3f3wzh6i916p2jzcs2.png)<br>


CÓDIGO GitHub: https://github.com/HiteshR90/Staking

Outro código para explorar: https://github.com/HiteshR90/defi-staking-app

Autor: Hitesh Umaletiya

Siga-nos em https://www.linkedin.com/company/brilworks

Entre em contato https://www.brilworks.com/contact-us/


Artigo escrito por Brilworks e traduzido por Marcelo Panegali.

Latest comments (0)