Skip to content

Criar Contrato Inteligente Vault

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

Adicionando nossas dependências (por enquanto):

$ npm install --save @openzeppelin/contracts truffle @celo/contractkit dotenv web3

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

$ npx truffle init

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>/

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

Por fim, inicialize o git.

$ git init

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

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,
  },
};

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

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

let account = web3.eth.accounts.create();
console.log(account);

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]
}

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

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

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

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

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));
    }
}

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;
    }

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

lockTokens(IERC20, address, amount, time)

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.

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)
address _withdrawer => The address which was registered in our contract when the deposit was made _withdrawer

Compilando o contrato

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

npx truffle compile

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

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

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);
};

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

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

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

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

npm install @celo/contractkit web3 dotenv

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)

É 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

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
  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
    Directory: vault-dapp/my-vault-interface/src/contract

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

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;

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();

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
);

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([]);

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());
  }
};

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());
  }
};

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());
  }
};

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());
  }
};

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] (https://learn.figment.io/tutorials/create-vault-smart-contract)