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
- Vá se conectar à rede de teste Alfajores do Celo através do DataHub e volte quando tiver uma chave de API do Celo, isso é necessário para as próximas etapas do tutorial.
Certifique-se de ter concluído os tutoriais:
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:
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
Latest comments (0)