WEB3DEV

Cover image for Criar Contrato Inteligente Vault
Banana Labs
Banana Labs

Posted on

Criar Contrato Inteligente Vault

Aprenda a criar, implantar e interagir com o Contrato Inteligente Vault no Ecossistema Celo

Introdução

Neste tutorial, vamos criar, implantar e interagir com nosso primeiro contrato inteligente Vault (cofre) no ecossistema Celo.

O que é um contrato inteligente Vault, a implantação e a interação?
Um Vault, conforme definido neste tutorial, prova que seus fundos contribuídos estão bloqueados por meio de contratos inteligentes invioláveis.

Este tutorial tem 3 partes conforme mencionado abaixo -

  • Em Primeiro lugar, vamos criar uma nova conta e configurar coisas essenciais para se conectar ao Alfajores.
  • Em segundo lugar, vamos implantar um Contrato Inteligente Vault na rede de teste da Alfajores usando DataHub e Truffle.
  • Em terceiro lugar, faremos a interface com o Contrato Inteligente Vault implantado e demonstraremos suas funcionalidades usando um front-end com React e Web3.

Pré-requisitos

Criando Projeto (Base Central)

Como o Celo executa a Máquina Virtual Ethereum (EVM), podemos usar o Truffle para compilar nosso contrato inteligente. Entretanto, apenas para fazer as coisas do jeito certo, vamos primeiro inicializar um projeto de nó (silenciosamente...).

$ mkdir vault-dapp
$ cd vault dapp
$ npm init -y --silent
Enter fullscreen mode Exit fullscreen mode

Adicionando nossas dependências (por enquanto):

$ npm install --save @openzeppelin/contracts truffle @celo/contractkit dotenv web3
Enter fullscreen mode Exit fullscreen mode

Inicialize um projeto básico no truffle executando o seguinte comando.

$ npx truffle init
Enter fullscreen mode Exit fullscreen mode

Defina (datahub url + api-key, consulte Pré-requisitos para a api-key) para uma variável de ambiente. Para isso crie um arquivo chamado ./.env

DATAHUB_NODE_URL=https://celo-alfajores--rpc.datahub.figment.io/apikey/<SUA API KEY>/
Enter fullscreen mode Exit fullscreen mode

Certifique-se de que nosso ./.env não seja enviado ao git (push) por engano (nossa chave está dentro). Para isso crie um novo arquivo chamado ./.gitignore

node_modules
.env
Enter fullscreen mode Exit fullscreen mode

Por fim, inicialize o git.

$ git init
Enter fullscreen mode Exit fullscreen mode

OK, eu adoraria parabenizá-lo, acabamos de inicializar um nó e um projeto truffle. Também adicionamos git e protegemos nossa chave como um profissional. Sua árvore de diretórios deve ficar assim:

04/17/2021  05:03 AM    <DIR>          .
04/17/2021  05:03 AM    <DIR>          ..
04/17/2021  05:03 AM    <DIR>          contracts
04/17/2021  05:03 AM    <DIR>          migrations
04/17/2021  05:00 AM    <DIR>          node_modules
04/17/2021  05:00 AM           678,735 package-lock.json
04/17/2021  05:00 AM               395 package.json
04/17/2021  05:03 AM    <DIR>          test
10/26/1985  04:15 AM             4,598 truffle-config.
04/17/2021  05:00 AM               395 .env
               3 File(s)        683,728 bytes
               6 Dir(s)  231,304,970,240 bytes free
Enter fullscreen mode Exit fullscreen mode

Obter Conta e Fundos

Nosso próximo passo é obter uma Conta (Endereço/Chave Privada) da Rede Celo Alfajores. Vamos usar o Truffle Console para obter uma bem rápido.

Copie e cole o seguinte no arquivo ./truffle-config.js que foi gerado pelo Truffle:

// CARREGUE VARIÁVEL ENV 
require("dotenv").config();

// PROVEDOR INIT USANDO CONTRACT KIT
const Kit = require("@celo/contractkit");
const kit = Kit.newKit(process.env.DATAHUB_NODE_URL);

// AWAIT WRAPPER PARA FUNÇÃO ASYNC 
async function awaitWrapper() {
  let account = kit.connection.addAccount(process.env.PRIVATE_KEY); // ADICIONA A CONTA AQUI}

awaitWrapper();

// OBJETO TRUFFLE CONFIG 
module.exports = {
  networks: {
    alfajores: {
      provider: kit.connection.web3.currentProvider, // CeloProvider
      network_id: 44787, //  mais recente Alfajores network id
    },
  },
  // Configure seus compiladores
  compilers: {
    solc: {
      version: "0.8.3", // Buscar a versão exata de solc-bin (default: truffle's version)
    },
  },
  db: {
    enabled: false,
  },
};
Enter fullscreen mode Exit fullscreen mode

Aqui inicializamos um provedor usando nossa variável de ambiente DATAHUB_NODE_URL.

Conecte-se à Alfajores com o console

Agora podemos nos conectar ao Alfajores usando truffle console. Execute o seguinte comando em seu console.

$ npx truffle console --network alfajores
Enter fullscreen mode Exit fullscreen mode

Uma vez conectado, inicialize e exiba uma conta da seguinte maneira:

let account = web3.eth.accounts.create();
console.log(account);
Enter fullscreen mode Exit fullscreen mode

Esta é a minha saída:

$ truffle console --network alfajores
web3-shh package will be deprecated in version 1.3.5 and will no longer be supported.
web3-bzz package will be deprecated in version 1.3.5 and will no longer be supported.
truffle(alfajores)> let account = web3.eth.accounts.create()
undefined
truffle(alfajores)> console.log(account)
{
  address: '0x7cdf6c19E5491EA23aB14132f8a76Ff1C74ccAFC',
  privateKey: '0x167ed276fb95a17de53c6b0fa4737fc2f590f3e6c5b9de0793d9bcdf63140650',
  signTransaction: [Function: signTransaction],
  sign: [Function: sign],
  encrypt: [Function: encrypt]
}
Enter fullscreen mode Exit fullscreen mode

Você pode sair do console com ctrl+C ou ctrl+D.

A partir daqui, precisamos do endereço e da privateKey, como mencionei.

Sucesso! Temos uma conta, antes que nos esqueçamos, vamos salvá-la em nosso .env, pois a usaremos mais tarde. Seu .env deve ficar assim.

DATAHUB_NODE_URL=https://celo-alfajores--rpc.datahub.figment.io/apikey/<SUA CHAVE DE API>/
ADDRESS=0x7cdf6c19E5491EA23aB14132f8a76Ff1C74ccAFC # your address
PRIVATE_KEY=0x167ed276fb95a17de53c6b0fa4737fc2f590f3e6c5b9de0793d9bcdf63140650 # your private key
Enter fullscreen mode Exit fullscreen mode

Financiando a conta

Vamos adicionar fundos em nossa conta usando a Torneira (Faucet) Alfajores, vá lá com seu endereço. (Você também deseja ter um, mas para a Mainnet): Acesse aqui -> Alfajores Faucet e você vai conseguir:

cGLD => 5
cUSD => 10
cEUR => 10
Enter fullscreen mode Exit fullscreen mode

Você pode verificar seu saldo no Alfajores Blockscout pesquisando seu endereço.

Finalmente terminamos esta seção. Estamos nos aproximando a cada minuto.

Implante o contrato inteligente Vault

Agora que temos uma conta e recursos, vamos adicionar um contrato inteligente ao nosso projeto Truffle. No console, execute o seguinte:

npx truffle create contract Vault
Enter fullscreen mode Exit fullscreen mode

O comando acima criará um novo Contrato Inteligente no seguinte local:

ls -la vault-dapp/contracts # Listing directory: vault-dapp/contracts:

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           4/17/2021  6:12 AM            114 Vault.sol
Enter fullscreen mode Exit fullscreen mode

O código para o Contrato Inteligente Vault já foi preparado para nós. Copie, cole, leia:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Vault is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    struct Items {
        IERC20 token;
        address withdrawer;
        uint256 amount;
        uint256 unlockTimestamp;
        bool withdrawn;
        bool deposited;
    }

    uint256 public depositsCount;
    mapping (address => uint256[]) public depositsByTokenAddress;
    mapping (address => uint256[]) public depositsByWithdrawers;
    mapping (uint256 => Items) public lockedToken;
    mapping (address => mapping(address => uint256)) public walletTokenBalance;

    address public helpiMarketingAddress;

    event Withdraw(address withdrawer, uint256 amount);

    constructor() {

    }

    function lockTokens(IERC20 _token, address _withdrawer, uint256 _amount, uint256 _unlockTimestamp) external returns (uint256 _id) {
        require(_amount > 500, 'Token amount too low!');
        require(_unlockTimestamp < 10000000000, 'Unlock timestamp is not in seconds!');
        require(_unlockTimestamp > block.timestamp, 'Unlock timestamp is not in the future!');
        require(_token.allowance(msg.sender, address(this)) >= _amount, 'Approve tokens first!');
        _token.safeTransferFrom(msg.sender, address(this), _amount);

        walletTokenBalance[address(_token)][msg.sender] = walletTokenBalance[address(_token)][msg.sender].add(_amount);

        _id = ++depositsCount;
        lockedToken[_id].token = _token;
        lockedToken[_id].withdrawer = _withdrawer;
        lockedToken[_id].amount = _amount;
        lockedToken[_id].unlockTimestamp = _unlockTimestamp;
        lockedToken[_id].withdrawn = false;
        lockedToken[_id].deposited = true;

        depositsByTokenAddress[address(_token)].push(_id);
        depositsByWithdrawers[_withdrawer].push(_id);
        return _id;
    }

    function withdrawTokens(uint256 _id) external {
        require(block.timestamp >= lockedToken[_id].unlockTimestamp, 'Tokens are still locked!');
        require(msg.sender == lockedToken[_id].withdrawer, 'You are not the withdrawer!');
        require(lockedToken[_id].deposited, 'Tokens are not yet deposited!');
        require(!lockedToken[_id].withdrawn, 'Tokens are already withdrawn!');

        lockedToken[_id].withdrawn = true;

        walletTokenBalance[address(lockedToken[_id].token)][msg.sender] = walletTokenBalance[address(lockedToken[_id].token)][msg.sender].sub(lockedToken[_id].amount);

        emit Withdraw(msg.sender, lockedToken[_id].amount);
        lockedToken[_id].token.safeTransfer(msg.sender, lockedToken[_id].amount);
    }

    function getDepositsByTokenAddress(address _id) view external returns (uint256[] memory) {
        return depositsByTokenAddress[_id];
    }

    function getDepositsByWithdrawer(address _token, address _withdrawer) view external returns (uint256) {
        return walletTokenBalance[_token][_withdrawer];
    }

    function getVaultsByWithdrawer(address _withdrawer) view external returns (uint256[] memory) {
        return depositsByWithdrawers[_withdrawer];
    }

    function getVaultById(uint256 _id) view external returns (Items memory) {
        return lockedToken[_id];
    }

    function getTokenTotalLockedBalance(address _token) view external returns (uint256) {
       return IERC20(_token).balanceOf(address(this));
    }
}
Enter fullscreen mode Exit fullscreen mode

Vamos percorrer as principais seções do nosso Contrato Inteligente Vault:

Estrutura para armazenar nossos depósitos:

struct Items {
        IERC20 token;
        address withdrawer;
        uint256 amount;
        uint256 unlockTimestamp;
        bool withdrawn;
        bool deposited;
    }
Enter fullscreen mode Exit fullscreen mode

A função lockTokens aceita os seguintes parâmetros ao ser invocada:

lockTokens(IERC20, address, amount, time)
Enter fullscreen mode Exit fullscreen mode

e bloqueará uma quantidade de tokens ERC20 no contrato por um tempo arbitrário.

IERC20 _token, => An ERC20 Token.
address _withdrawer => The Address which can withdraw (usually same as deposing address).
uint256 _amount => Amount the ERC20 Token.
uint256 _unlockTimestamp => When to unlock deposit.
Enter fullscreen mode Exit fullscreen mode

A função withdrawTokens aceita um endereço e verifica sua existência como _withdrawer em nossa Estrutura. A função também verifica se os fundos passaram do _unlockTimestamp.

withdrawTokens(address)
Enter fullscreen mode Exit fullscreen mode
address _withdrawer => The address which was registered in our contract when the deposit was made _withdrawer
Enter fullscreen mode Exit fullscreen mode

Compilando o contrato

Agora estamos prontos para compilar nosso código de solidity usando Truffle. No seu terminal, execute o seguinte:

npx truffle compile
Enter fullscreen mode Exit fullscreen mode

Esta é a minha saída, tudo bem se a sua for parecida:

web3-shh package will be deprecated in version 1.3.5 and will no longer be supported.
web3-bzz package will be deprecated in version 1.3.5 and will no longer be supported.

Compiling your contracts...
===========================
> Compiling @openzeppelin/contracts/access/Ownable.sol
> Compiling @openzeppelin/contracts/token/ERC20/IERC20.sol
> Compiling @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
> Compiling @openzeppelin/contracts/utils/math/SafeMath.sol
> Compiling @openzeppelin/contracts/token/ERC20/IERC20.sol
> Compiling @openzeppelin/contracts/utils/Address.sol
> Compiling @openzeppelin/contracts/utils/Context.sol
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/Vault.sol
> Artifacts written to vault-dapp/build/contracts
> Compiled successfully using:
   - solc: 0.8.3+commit.8d00100c.Emscripten.clang
Enter fullscreen mode Exit fullscreen mode

Truffle coloca automaticamente nosso bytecode de contrato inteligente Vault e ABI dentro do seguinte arquivo json. Certifique-se de que você pode vê-lo!

$ ls -la  vault-dapp/build/contracts # Directory Listing: vault-dapp/build/contracts

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           4/17/2021  7:12 AM         870362 Vault.json
Enter fullscreen mode Exit fullscreen mode

Espero que você esteja pronto para a implantação, porque é a próxima etapa!

Implantando o contrato

Uma última etapa, é necessário fazer uma nova migração para implantar o Vault Contract, crie o arquivo migrations/2_vault_deployment.js para conseguir isso

const Vault = artifacts.require("Vault");

module.exports = function (deployer) {
  deployer.deploy(Vault);
};
Enter fullscreen mode Exit fullscreen mode

Com isso, estamos prontos para executar o npx truffle migrate --network alfajores, esperamos implantar nosso Vault Smart Contract na rede Celo Alfajores. Em seu console, execute.

npx truffle migrate --network alfajores
Enter fullscreen mode Exit fullscreen mode

Uma implantação bem-sucedida se parece com isto:

Starting migrations...
======================
> Network name:    'alfajores'
> Network id:      44787
> Block gas limit: 0 (0x0)


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0x9223481ec81ab8efe26c325a61bf87369fa451210f2be6a08237df769952af45
   > Blocks: 0            Seconds: 0
   > contract address:    0xC58c6144761DBBE7dd7633edA98a981cb73169Df
   > block number:        4661754
   > block timestamp:     1618659322
   > account:             0x7cdf6c19E5491EA23aB14132f8a76Ff1C74ccAFC
   > balance:             4.93890836
   > gas used:            246292 (0x3c214)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00492584 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00492584 ETH


2_vault_deployment.js
=====================

   Deploying 'Vault'
   -----------------
   > transaction hash:    0x8b112defb0ed43eee6009445f452269e18718094cbd949e6ff7f51ef078abd84
   > Blocks: 0            Seconds: 0
   > contract address:    0xB017aD96e31B43AFB670dAB020561dA8E2154C5B
   > block number:        4661756
   > block timestamp:     1618659332
   > account:             0x7cdf6c19E5491EA23aB14132f8a76Ff1C74ccAFC # save this address to next steps
   > balance:             4.89973486
   > gas used:            1916762 (0x1d3f5a)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.03833524 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.03833524 ETH


Summary
=======
> Total deployments:   2
> Final cost:          0.04326108 ETH
Enter fullscreen mode Exit fullscreen mode

Fantástico!, nosso Contrato Inteligente Vault agora está na Alfajores, e podemos depositar fundos nele, trancá-los e retirá-los. Vamos construir uma interface em React para nosso Vault Smart Contract a seguir.

Dê uma pausa!

Nesta seção, aprendemos como criar nosso primeiro contrato do Celo Vault, compilá-lo e implantá-lo no Alfajores, também sobre alguns dos construtores de linguagem do Solidity. Parabéns por chegar até aqui! Faça uma pausa, levante-se e alongue-se, pegue um pouco de água e depois volte para o final com React!

Criar o React dApp

Agora que temos nosso contrato vault, vamos conectar os métodos a uma interface.

Interface com o Contrato Inteligente Vault

Primeiro, vamos inicializar nosso aplicativo react, podemos fazer isso dentro do diretório do projeto node/truffle no qual estamos trabalhando:

Uma coisa importante, certifique-se de ter a versão mais recente do aplicativo create-react-app instalado. Se você já o instalou e encontrou algum erro, consulte este artigo para obter ajuda.

npx create-react-app my-vault-interface
cd my-vault-interface
Enter fullscreen mode Exit fullscreen mode

Precisamos adicionar as seguintes dependências ao nosso novo projeto react:

npm install @celo/contractkit web3 dotenv
Enter fullscreen mode Exit fullscreen mode

Vamos também transferir nosso arquivo .env para este novo projeto, com uma correção rápida:

REACT_APP_DATAHUB_NODE_URL=https://alfajores-forno.celo-testnet.org
REACT_APP_ADDRESS=0x7cdf6c19E5491EA23aB14132f8a76Ff1C74ccAFC #seu endereço da conta recém-criado
REACT_APP_PRIVATE_KEY=0x167ed276fb95a17de53c6b0fa4737fc2f590f3e6c5b9de0793d9bcdf63140650 # sua chave privada da conta recém-criada
REACT_APP_VAULT_ADDRESS=0xB017aD96e31B43AFB670dAB020561dA8E2154C5B # o endereço recém-criado do contrato Vault (see 2_vault_deployment.js deploy output above)
Enter fullscreen mode Exit fullscreen mode

É assim que o meu se parece, como você pode ver, ainda temos as mesmas variáveis, mas adicionamos o prefixo REACT_APP.

Em seguida, vamos transferir o arquivo json onde reside nosso bytecode de contrato e ABI (arquivo VAULT.json) e colar dentro de uma nova pasta contract na raiz do nosso novo projeto React, veja abaixo:

Original Location (Truffle Compile)
Directory: vault-dapp/build/contracts/Vault.json

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           4/17/2021  7:35 AM         870803 Vault.json

React App Location
Directory: vault-dapp/my-vault-interface/src/contract/Vault.json

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           4/17/2021  7:35 AM         870803 Vault.json
Enter fullscreen mode Exit fullscreen mode

Com isso, completamos os requisitos do nosso frontend, para recapitular, é assim que a árvore de diretórios do React Project se parece:

Directory: vault-dapp/my-vault-interface

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----           4/17/2021  9:03 AM                node_modules
d----           4/17/2021  7:51 AM                public
d----           4/17/2021  9:46 AM                src
-a---           4/17/2021  9:59 AM            287 .env
-a---           4/17/2021  7:51 AM            310 .gitignore
-a---           4/17/2021  9:03 AM         766889 package-lock.json
-a---           4/17/2021  9:03 AM            903 package.json
-a---           4/17/2021  7:51 AM           3362 README.md
-a---           4/17/2021  7:51 AM         507434 yarn.lock
Enter fullscreen mode Exit fullscreen mode
  Directory: vault-dapp/my-vault-interface/src

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----           4/17/2021  9:46 AM                contract
-a---           4/17/2021  7:51 AM            564 App.css
-a---           4/17/2021 10:01 AM           1520 App.js
-a---           4/17/2021  7:51 AM            246 App.test.js
-a---           4/17/2021  7:51 AM            366 index.css
-a---           4/17/2021  7:51 AM            500 index.js
-a---           4/17/2021  7:51 AM           2632 logo.svg
-a---           4/17/2021  7:51 AM            362 reportWebVitals.js
-a---           4/17/2021  7:51 AM            241 setupTests.js
Enter fullscreen mode Exit fullscreen mode
    Directory: vault-dapp/my-vault-interface/src/contract

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           4/17/2021  7:35 AM         870803 Vault.json
Enter fullscreen mode Exit fullscreen mode

Preencha nosso aplicativo principal

Nossa próxima etapa é copiar e colar algum código no App React Component App.js.

import React, { useState, useEffect } from "react";
import { newKit } from "@celo/contractkit";
import dotenv from "dotenv";
import Vault from "./contract/Vault.json";

// CARREGUE ENV VAR
dotenv.config();

const kit = newKit(process.env.REACT_APP_DATAHUB_NODE_URL);
const connectAccount = kit.addAccount(process.env.REACT_APP_PRIVATE_KEY);
// INSTÂNCIA DE CONTRATO
const VaultO = new kit.web3.eth.Contract(
  Vault.abi,
  process.env.REACT_APP_VAULT_ADDRESS
);

function App() {
  const [balances, setBalances] = useState({ CELO: 0, cUSD: 0, Vault: 0 });
  const [info, setInfo] = useState("");
  const [lockAmount, setLockAmount] = useState("0.3");
  const [idVault, setIdVault] = useState("0");
  const [listOfVaults, setListOfVaults] = useState([]);

  const update = () => {
    getBalanceHandle();
    getLockerIdsInfo();
  };

  const getBalanceHandle = async () => {
    const goldtoken = await kit._web3Contracts.getGoldToken();
    const totalLockedBalance = await VaultO.methods
      .getTokenTotalLockedBalance(goldtoken._address)
      .call();
    const totalBalance = await kit.getTotalBalance(
      process.env.REACT_APP_ADDRESS
    );

    const { CELO, cUSD } = totalBalance;
    setBalances({
      CELO: kit.web3.utils.fromWei(CELO.toString()),
      cUSD: kit.web3.utils.fromWei(cUSD.toString()),
      Vault: kit.web3.utils.fromWei(totalLockedBalance.toString()),
    });
  };

  const approve = async () => {
    setInfo("");
    // PERMISSÃO MÁXIMA
    const allowance = kit.web3.utils.toWei("1000000", "ether");
    // ESTIMADOR DE GAS
    const gasEstimate = kit.gasEstimate;
    // ATIVO PARA PERMITIR
    const goldtoken = await kit._web3Contracts.getGoldToken();
    // TX OBJETO E ENVIE
    try {
      const approveTxo = await goldtoken.methods.approve(
        process.env.REACT_APP_VAULT_ADDRESS,
        allowance
      );
      const approveTx = await kit.sendTransactionObject(approveTxo, {
        from: process.env.REACT_APP_ADDRESS,
        gasPrice: gasEstimate,
      });
      const receipt = await approveTx.waitReceipt();
      // EXIBA RESULTADO TX 
      console.log(receipt);
      setInfo("Approved!!");
    } catch (err) {
      console.log(err);
      setInfo(err.toString());
    }
  };

  const lock = async () => {
    setInfo("");
    try {
      // TIMESTAMP
      const lastBlock = await kit.web3.eth.getBlockNumber();
      let { timestamp } = await kit.web3.eth.getBlock(lastBlock);
      var timestampObj = new Date(timestamp * 1000);
      // TIME TO LOCK + 10 MINS
      var unlockTime =
        timestampObj.setMinutes(timestampObj.getMinutes() + 10) / 1000; // 10 minutes by default
      // SALDO PARA BLOQUEAR
      const amount = kit.web3.utils.toWei(lockAmount + "", "ether");
      // ERC20 PARA BLOQUEAR
      const goldtoken = await kit._web3Contracts.getGoldToken();
      // TX OBJETO E ENVIE
      const txo = await VaultO.methods.lockTokens(
        goldtoken._address,
        process.env.REACT_APP_ADDRESS,
        amount,
        unlockTime
      );
      const tx = await kit.sendTransactionObject(txo, {
        from: process.env.REACT_APP_ADDRESS,
      });
      // EXIBIR RESULTADO TX 
      const receipt = await tx.waitReceipt();
      update();
      setInfo("Celo locked!");
      console.log(receipt);
    } catch (err) {
      console.log(err);
      setInfo(err.toString());
    }
  };

  const withdraw = async () => {
    setInfo("");
    try {
      const txo = await VaultO.methods.withdrawTokens(idVault);
      const tx = await kit.sendTransactionObject(txo, {
        from: process.env.REACT_APP_ADDRESS,
      });
      const receipt = await tx.waitReceipt();
      update();
      console.log(receipt);
      setInfo("Celo unlocked!");
    } catch (err) {
      console.log(err);
      setInfo(err.toString());
    }
  };

  const getLockerIdsInfo = async () => {
    setInfo("");
    try {
      const ids = await VaultO.methods
        .getVaultsByWithdrawer(process.env.REACT_APP_ADDRESS)
        .call();
      let vaults = [];
      for (let id of ids)
        vaults.push([id, ...(await VaultO.methods.getVaultById(id).call())]);
      console.log("IDS:", vaults);
      setListOfVaults(vaults);
    } catch (err) {
      console.log(err);
      setInfo(err.toString());
    }
  };

  useEffect(update, []);

  return (
    <div>
      <h1>ACTIONS:</h1>
      <button onClick={approve}>APPROVE</button>
      <button onClick={getBalanceHandle}>GET BALANCE</button>
      <button onClick={getLockerIdsInfo}>GET LOCKER IDS</button>
      <div style={{ display: "flex" }}>
        <div style={{ margin: "0.5rem" }}>
          <h1>Lock Celo Token:</h1>
          <input
            type="number"
            value={lockAmount}
            min="0"
            onChange={(e) => setLockAmount(e.target.value)}
          />
          <button onClick={lock}>LOCK</button>
        </div>
        <div style={{ margin: "0.5rem" }}>
          <h1>Withdraw Celo Token:</h1>
          <input
            type="number"
            value={idVault}
            min="0"
            onChange={(e) => setIdVault(e.target.value)}
          />
          <button onClick={withdraw}>WITHDRAW</button>
        </div>
      </div>
      <h1>DATA WALLET</h1>
      <ul>
        <li>CELO BALANCE IN ACCOUNT: {balances.CELO}</li>
        <li>cUSD BALANCE IN ACCOUNT: {balances.cUSD}</li>
        <li>TOTAL VALUE LOCKED IN CONTRACT: {balances.Vault}</li>
      </ul>
      <h1>INFO:</h1>
      <h2 style={{ color: "red" }}>{info}</h2>
      <h2>Your Vaults:</h2>
      <table>
        <thead>
          <th>ID</th>
          <th>Value</th>
          <th>Withdraw until</th>
          <th>Withdrawn</th>
          <th>deposited</th>
        </thead>
        <tbody>
          {listOfVaults.map((item) => (
            <tr>
              <td>{item[0]}</td>
              <td>{kit.web3.utils.fromWei(item[3].toString())}</td>
              <td>{new Date(item[4] * 1000).toLocaleTimeString()}</td>
              <td>{item[5] ? "yes" : "no"}</td>
              <td>{item[6] ? "yes" : "no"}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Vamos rever nosso App Component, vai ser muito divertido aqui, prometo…

O contractKit nos ajudará a interagir com a Blockchain Celo de maneira eficaz e eficiente usando a web3 sob o capô. As variáveis de ambiente estão presentes para usar informações constantes, consulte o uso de dotenv. Por fim importamos nosso json representando o contrato (recentemente aprendi esse json com bytecode, ABI é chamado de artefato truffe/contrato)

import React, { useState } from "react";
import { newKit } from "@celo/contractkit";
import dotenv from "dotenv";
import Vault from "./contract/Vault.json";

// Carregue ENV VAR
dotenv.config();
Enter fullscreen mode Exit fullscreen mode

Em seguida, inicializamos nossa instância de contractKit, usando nossa URL do DataHub Node, também adicionamos ao kit nossa conta de teste usando sua chave privada. Por fim, o objeto de contrato é instanciado para uso posterior.

const kit = newKit(process.env.REACT_APP_DATAHUB_NODE_URL);
const connectAccount = kit.addAccount(process.env.REACT_APP_PRIVATE_KEY);

// INSTÂNCIA DE CONTRATO
const VaultO = new kit.web3.eth.Contract(
  Vault.abi,
  process.env.REACT_APP_VAULT_ADDRESS
);
Enter fullscreen mode Exit fullscreen mode

Estaremos usando useState para salvar, modificar e exibir nossos saldos (carteira e contratos de cofre), os depósitos, o ID do Vault para retirada e a lista de contratos

const [balances, setBalances] = useState({ CELO: 0, cUSD: 0, Vault: 0 });
const [info, setInfo] = useState("");
const [lockAmount, setLockAmount] = useState("0.3");
const [idVault, setIdVault] = useState("0");
const [listOfVaults, setListOfVaults] = useState([]);
Enter fullscreen mode Exit fullscreen mode

Antes de podermos interagir com nosso novo contrato inteligente Vault, precisamos aprovar o uso desse contrato inteligente por nossa carteira, definindo uma permissão allowance padrão. A função de aprovação cria e envia um objeto de transação Transaction Object indicando que estamos aprovando, embora também definindo um limite máximo para este contrato inteligente usar. Depois de fazermos o console.log do recibo

const approve = async () => {
  setInfo("");
  // PERMISSÃO MÁXIMA
  const allowance = kit.web3.utils.toWei("1000000", "ether");
  // ESTIMADOR DE GAS
  const gasEstimate = kit.gasEstimate;
  // ATIVO PARA PERMITIR
  const goldtoken = await kit._web3Contracts.getGoldToken();
  // TX OBJETO E ENVIE
  try {
    const approveTxo = await goldtoken.methods.approve(
      process.env.REACT_APP_VAULT_ADDRESS,
      allowance
    );
    const approveTx = await kit.sendTransactionObject(approveTxo, {
      from: process.env.REACT_APP_ADDRESS,
      gasPrice: gasEstimate,
    });
    const receipt = await approveTx.waitReceipt();
    // EXIBA RESULTADO TX 
    console.log(receipt);
    setInfo("Approved!!");
  } catch (err) {
    console.log(err);
    setInfo(err.toString());
  }
};
Enter fullscreen mode Exit fullscreen mode

Vamos dar uma olhada na função de bloqueio lock a seguir. Aqui, obtemos nosso timestamp de desbloqueio (10 minutos após o envio da transação), estimamos o gás, especificamos que bloquearemos 1 celo, instanciamos nosso objeto de contrato usando sua abi e endereço. Nosso objeto de transação (txo) usará o método lockTokens disponível em nosso objeto de contrato, e passará nossos parâmetros coletados/necessários (endereço do token, endereço da conta e claro o valor a ser bloqueado e o timestamp que representa quantas vezes ele será bloqueado). Por fim, o objeto de transação será incluído em uma nova transação (tx).

Após isto aguardamos nosso recibo, e fazermos o console.log dele.

const lock = async () => {
  setInfo("");
  try {
    // TIMESTAMP
    const lastBlock = await kit.web3.eth.getBlockNumber();
    let { timestamp } = await kit.web3.eth.getBlock(lastBlock);
    var timestampObj = new Date(timestamp * 1000);
    // TIME TO LOCK + 10 MINS
    var unlockTime =
      timestampObj.setMinutes(timestampObj.getMinutes() + 10) / 1000; // 10 minutes by default
    // SALDO PARA BLOQUEAR
    const amount = kit.web3.utils.toWei(lockAmount + "", "ether");
    //ERC20 PARA BLOQUEAR
    const goldtoken = await kit._web3Contracts.getGoldToken();
    // TX OBJETO E ENVIE
    const txo = await VaultO.methods.lockTokens(
      goldtoken._address,
      process.env.REACT_APP_ADDRESS,
      amount,
      unlockTime
    );
    const tx = await kit.sendTransactionObject(txo, {
      from: process.env.REACT_APP_ADDRESS,
    });
    // EXIBA RESULTADO TX 
    const receipt = await tx.waitReceipt();
    update();
    setInfo("Celo locked!");
    console.log(receipt);
  } catch (err) {
    console.log(err);
    setInfo(err.toString());
  }
};
Enter fullscreen mode Exit fullscreen mode

Nossa próxima parada é a função de retirada withdraw, ela usa o método withdrawTokens no contrato e precisa do ID do Vault onde você deseja retirar, você pode ver esses ids na tabela gerada

const withdraw = async () => {
  setInfo("");
  try {
    const txo = await VaultO.methods.withdrawTokens(idVault);
    const tx = await kit.sendTransactionObject(txo, {
      from: process.env.REACT_APP_ADDRESS,
    });
    const receipt = await tx.waitReceipt();
    update();
    console.log(receipt);
    setInfo("Celo unlocked!");
  } catch (err) {
    console.log(err);
    setInfo(err.toString());
  }
};
Enter fullscreen mode Exit fullscreen mode

getLockerIdsInfo obtém a lista de vaults/lockers que a conta atual possui no contrato. Ele usa o método getVaultsByWithdrawer do contrato, retornando uma matriz de informações completas do usuário:

const getLockerIdsInfo = async () => {
  setInfo("");
  try {
    const ids = await VaultO.methods
      .getVaultsByWithdrawer(process.env.REACT_APP_ADDRESS)
      .call();
    let vaults = [];
    for (let id of ids)
      vaults.push([id, ...(await VaultO.methods.getVaultById(id).call())]);
    console.log("IDS:", vaults);
    setListOfVaults(vaults);
  } catch (err) {
    console.log(err);
    setInfo(err.toString());
  }
};
Enter fullscreen mode Exit fullscreen mode

E por último a marcação que define os botões e etiquetas, define 3 botões para aprovar o uso do contrato, obter o saldo atual da conta e obter os IDs do Vault no contrato; Também duas entradas para depósito e retirada de alguma quantia de Celos. Algumas etiquetas apresentam CELO, cUSD na conta corrente e TVL (Total Value Locked) no contrato. A última é a tabela com os cofres (vault) no contrato do usuário atual.

Você pode interagir com a interface e ver o comportamento do contrato:

demonstração

Você pode verificar se as restrições do contrato estão preservadas, como os minutos de time-locked (tempo de bloqueio), IDs inexistentes, lockers já retirados, etc.

É importante observar que sempre usamos sentenças try/catch para verificar as condições inversas e outros erros que possam ocorrer.

O repositório do código está aqui: Link

Conclusão

Este tutorial teve como objetivo fornecer uma implementação básica de um dApp no Ecossistema Celo. Cobrimos o desenvolvimento e a implantação do Contrato Inteligente Vault, juntamente com a inicialização de uma Aplicação React para interagir com suas funções básicas (aprovar, bloquear, retirar). Esperamos continuar ampliando esta documentação.

O tutorial foi um esforço da equipe da Celo Helpi.


Esse artigo é uma tradução feita por @bananlabs. Você pode encontrar o artigo original aqui

Top comments (0)