En esta tercera sesión del curso veremos cómo crear contrato solidity para ethereum con base en una lotería. Crearemos un contrato que permita a varias personas conectar sus wallet de metamask, añadir saldo para participar y el contrato escogerá un ganador, transfiriendo el saldo recaudado 😎
Claro que veremos en medio de todo esto conceptos importantes y claves del lenguaje de programación solidity y cómo funciona en conjunto con react, node js y la web3. Esta es la sesión más completa del curso blockchain así que vamos a empezar!
Contenido
Lo que vas a necesitar
- Hoja de excel: haz click aquí para ver la hoja de de excel con los cálculos de costos para nuestras ejecuciones.
- Repositorio: haz click aquí para ver el repositorio en github. Recuerda ver los commits para posicionarte en la sesión que deseas ver.
- Mocha: haz click aquí para ver la web de mocha.
- Chai: haz click aquí para ver la web de chai.
- solidity: haz click aquí para ver la documentación de solidity.
Qué es solidity
Cómo implementar Sepolia: Red de prueba de Ethereum
Antes de implementar un contrato en una red de mainnet como Ethereum debemos primero saber cómo impl…
Cómo crear contrato solidity: lotería en ethereum
En esta tercera sesión del curso veremos cómo crear contrato solidity para ethereum con base en una …
Qué es Gas y GasPrice: transacciones y congestión en blockchain
Cuando ejecutamos transacciones en blockchain es probable que desconozcamos los costos o tiempos de …
Solidity es un lenguaje de programación creado por el mismo equipo desarrollador de ethereum, con el objetivo de crear contratos inteligentes. En si, no podemos utilizar otro método para crear contratos en ethereum y así mismo, usar solidity para otras tareas. Este tiene 3 características especiales:
- Especificidad: solidity está creado específicamente para crear contratos inteligentes en ethereum. Este no puede cumplir otra tarea.
- Seguridad: Solidity está diseñado para garantizar la seguridad y la integridad de los contratos inteligentes, como la prevención de bucles infinitos y la gestión de la transferencia de fondos de manera segura.
- Tipado estático: Solidity es un lenguaje de tipado estático, lo que significa que los tipos de datos de las variables deben ser declarados explícitamente. Si declaramos una variable con tipo de dato INT, luego no podremos cambiarla a STRING, por ejemplo.
Teniendo esto claro, vamos a iniciar a crear nuestro contrato. Pero recuerda que vamos a avanzar conforme al video, es decir, si deseas ver más explícitamente, puedes deslizar abajo, suscribirte y ver la sesión 3 totalmente gratuita y de la mano con la documentación, para completarla correctamente.
Creando el contrato inteligente en solidity
Importante: para ver todo el código, debes ver el respositorio. Allí se encuentra en el commit sesión 3 finalizada, este código que hablaré a continuación.
Este contrato va a contener varias funciones, eventos, variables clave que son clave para entender el lenguaje y la creación así que iré paso a paso, iniciando por las declaraciones:
pragma solidity ^0.8.19;
contract ContratoCurso {
Aunque las mencioné en la sesión 1, lo vuelvo a mencionar aquí: la versión que usamos es de vital importancia. En este caso, la versión de solidity 0.8.19 es la que usaremos. Si cambias la versión es probable que el contrato no funcione igual ya que por cada versión cambia demasiado detección de errores, declaración de variables, llamada de funciones y demás.
Crear dirección propietaria
esta dirección guardada en variable «administrador» será la propietaria del contrato, es decir sobre la cual se va a desplegar el contrato y cobrar el saldo. Esta tendrá ciertos permisos para ejecutar funciones que veremos más adelante. Adicional, usamos «public» como un tipo de variable que podrá ser usada en todo el contrato y por fuera del mismo.
address public administrador;
Crear eventos en solidity
Cuando ejecutamos funciones del contrato, vamos a necesitar una retroalimentación o retorno de los resultados de la misma y los eventos nos permiten lograr esto. En si, dentro de las funciones los ejecutaremos y les añadiremos información clave para así usarla por fuera del mismo.
event jugadorIngresado(address indexed player, string name);
event ganadorEscogido(address indexed winner, uint256 premio, string name, uint posicion);
Tendremos 2 eventos, el primero que nos dará la información del jugador que acaba de ingresar y el segundo sobre el jugador que ha ganado la lotería. Ambos con información clave del proceso.
Crear estructrura del jugador
En este paso vamos a crear como en una base de datos, la estructura sobre la cual se va a basar la creación de un jugador. Es decir: cada vez que un jugador sea creado o añadido a la lotería, cumplirá estrictamente con estas propiedades.
struct Jugador{
address payable direccionJugador;
string name;
}
Jugador[] public jugadores;
La estructura es sencilla, solo contendrá una dirección del jugador, sobre la cual participa o comparte el saldo de la wallet de metamask, y otro el nombre. El primero hay un concepto demasiado importante y es «payable». Este quiere decir que esta dirección (o función en otros casos) es capaz de recibir ethereum.
Adicional, creamos el arreglo que contendrá a todos los jugadores.
Crear constructor
Este es un conepto demasiado clave en solidity. Solamente es llamado una única vez y es en el momento de la creación del contrato. Esta función es usada generalmente para crear o asignar valores a varables clave, ejecutar sub contratos heredados, entre otros. Pero en este caso usaremos el constructor para asignar la dirección creadora como «administrador».
Otro concepto clave aquí es msg.sender. Este hace referencia a la dirección que está interactuando en este momento con el contrato. Entonces, como justamente es nuestra dirección la que va a implementar y desplegar el contrato en eth, pues es nuestra wallet la propietaria o bien asignada a «administrador» del contrato.
constructor(){
administrador = msg.sender;
}
Función para ingresar un usuario a la lotería
Esta es la primera función del contrato. Esta se encargará de recibir un argumento, el cual será el nombre del usuario y con esto, junto con la dirección del jugador, va a crear justamente un nuevo jugador.
function ingresar(string memory nombreJugador) public payable{
require(msg.value >= 0.00055 ether, "Se requiere una cantidad de 0.00055 eth para participar.");
jugadores.push(Jugador(payable(msg.sender), nombreJugador));
emit jugadorIngresado(msg.sender, nombreJugador);
}
Aquí suceden varias cosas importantes:
- El argumento es declarado de una vez con su tipo de valor «string», y también como «memory». Memory hace referencia que esta variable será temporal, prácticamente solo la usaremos dentro de esta función y no saldrá de aquí.
- El primer condicional «require» indica que si o si debe cumplir un saldo mayor o igual a 0.00055 eth para poder crear un nuevo jugador. De lo contrario la función no continuará.
- Creamos una nueva posición en el arreglo jugadores, donde esta contendrá un nuevo jugador con el nombre del jugador ingresado y su dirección (msg.sender).
- Ejecutamos el evento del jugador ingresado para poder recuperar esta información por fuera del contrato y usarla en front-end.
Función para escoger un ganador de la lotería
Esta función es la encargada de seleccionar un ganador o una posición ganadora del arreglo de forma aletaoria. Luego transferir el saldo al ganador.
function escogerGanador() public restricted{
require(jugadores.length > 0, "No hay suficientes jugadores para escoger un ganador");
uint index = random();
uint premioTotal = address(this).balance;
jugadores[index].direccionJugador.transfer(premioTotal);
emit ganadorEscogido(jugadores[index].direccionJugador, premioTotal, jugadores[index].name, index);
delete jugadores;
}
Esta función tiene varios pasos imporantes:
- El término «restricted» lo veremos más abajo ejecutado, pero básicamente lo que hace es que esta función solo podrá ser ejecutada por el administrador, es decir nosotros.
- Al igual que la anterior, condicionamos su uso pero en este caso, solo puede ejecutarse si hay más de una posición en el arreglo jugadores. Es decir si ya ha participado aunque sea 1 solo jugador.
- Creamos un entero que guarde un número random. Función que veremos abajo.
- Creamos una variable que guarda el saldo total del contrato. IMPORTANTE: EL CONTRATO PUEDE GUARDAR ETH. Si! el contrato cuenta con una dirección la cual puede almacenar saldo. En este caso, cada vez que un usuario participa, dinero se va directo al lado del contrato. Este lo encontramos en código como address(this).balance.
- Transferimos el saldo del contrato, o bien el total recaudado de todos los jugadores a la dirección del ganador.
- Ejecutamos el evento del jugador ganador, enviando toda la información resultante.
- Borramos el arreglo jugadores para iniciar desde ceros.
Función random y modificador restricted
En este caso vamos a crear a continuación la función que permite encontrar un número o entrero random y adicional, el modificador restricted.
function random() private view returns (uint){
return uint( keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % jugadores.length;
}
modifier restricted(){
require(msg.sender == administrador, "solo el administrador puede ejecutar esta funcion");
_;
}
Entonces sobre random hay varios conceptos importantes:
- Esta es una función «private» y «view». Private hace referencia a que esta función solo estará disponible dentro del contrato y view a que será prácticamente solo de lectura, es decir no afectará en nada por fuera de la misma. Adicional retorna un entero.
- Dentro del entero encontramos primero 3 argumentos clave: block.timestamp, block.prevrandao, msg.sender. El primero hace referencia a la marca de tiempo que se crea en segundos cuando la transacción es ejecutada. El segundo proporciona un valor aleatorio basado en la cadena de anclaje. Y el tercero ya lo conocemos. Los tres argumentos son concatenados en una cadena por abi.encodePacked. Luego, la función keccak256 se encarga de tomar esta cadena y crear un hash de una única longitud. Por último, se divide entre la longitud del arreglo jugadores y se guarda el residuo de esta división en el entero que se retorna.
En conclusión, creamos un valor aleatorio dentro de la longitud del arreglo jugadores, para que si o si escoja un ganador.
A continuación, creamos el modificador restricted, el cual solicita que la dirección que está interactuando sea exactamente igual a la guardada en administrador. Si si, entonces el guión «_;» representa el cuerpo de la función restringida y será ejecutada.
Función para obtener los jugadores actuales
Esta función es la encargada de devolver los nombres y las direcciones de los jugadores actuales de la lotería. Básicamente creamos 2 arreglos: direccionesJugadores y nombreJugadores. Ambos inician siendo vacíos pero con la misma longitud del arreglo jugadores.
function obtenerJugadores() public view returns (address payable[] memory, string[] memory){
address payable[] memory direccionesJugadores = new address payable[](jugadores.length);
string[] memory nombreJugadores = new string[](jugadores.length);
for(uint i = 0; i < jugadores.length; i++){
direccionesJugadores[i] = jugadores[i].direccionJugador;
nombreJugadores[i] = jugadores[i].name;
}
return(direccionesJugadores, nombreJugadores);
}
A continuación, procedemos a copiar el arreglo jugadores, es decir guardar las direcciones en un arreglo y los nombres en otros. Por último retornamos estos dos arreglos.
Función para obtener la cantidad de jugadores y el saldo del contrato.
Estas dos funciones son las finales y las más sencillas de todas. Básicamente retornamos la longitud del areglo jugadores, y la segunda retorna el balance del contato.
Estas dos funciones las creo porque las usaremos dentro de las pruebas y del contrato en si.
function cantidadDeJugadores() public view returns (uint) {
return jugadores.length;
}
function obtenerSaldoContrato() public view returns (uint256) {
return address(this).balance;
}
Creando el archivo de pruebas test.js
Este archivo lo cremos porque es prácticamente una necesidad para truffle. Antes de desplegar el contrato en la red que sea, truffle primero hace pruebas y estas deben ser cumplidas a cabalidad al ejecutar truffle test. Ahora, ¿cuántas pruebas podemos hacer?, las que desemos. Pero aquí solo haremos 2 principales. Así que vamos a empezar.
const ContratoCurso = artifacts.require("./ContratoCurso.sol");
require('chai').use(require('chai-as-promised')).should(); // uso de aserciones.
contract('ContratoCurso', (accounts) => {
Iniciamos solicitando el contrato y el paquete chai. El cual está al inicio del post. Este nos permite usar aserciones como should.be, entre otras. Por último, iniciamos declarando el contrato y como argumento, las cuentas que van a interactuar.
Prueba para permitir a los jugadores participar
Esta prueba se encarga de ferificar que la longitud del arreglo jugadores aumente cuando ingresa un nuevo jugador, ¿ pero cómo lo hace?, vamos a revisar.
it("Debería permitir a los jugadores participar", async() => {
const loteriaInstancia = await ContratoCurso.deployed();
const cuentaInicialJugadores = (await loteriaInstancia.cantidadDeJugadores()).toNumber();
// Simulamos la entrada de un usuario
await loteriaInstancia.ingresar("Jugador 1", { from: accounts[0], value: web3.utils.toWei("0.00055", "ether")});
// Comprobar el número de jugadores
const cuentaFinalJugadores = (await loteriaInstancia.cantidadDeJugadores()).toNumber();
assert.equal(cuentaFinalJugadores, cuentaInicialJugadores + 1, "El número de jugadores no aumentó");
});
- Creamos la instancia del contrato. Es decir, ejecutamos el contrato y lo guardamos en una variable.
- Obtenemos la cantidad de jugadores de la función cantidadDeJugadores mostrada anteriormente, y guardamos en variable.
- Simulamos el ingreso de un usuario con el argumento del nombre y adicional con datos importantes de transfrencia como el valor y desde donde se realiza. accounts[0] hace referencia a la primera cuenta dentro del arreglo accounts. En si es la dirección que está interactuando en este momento, pero como estamos en pruebas, simplemente se asignan varias direcciones a una arreglo llamado accounts y se pretende que todas son diferentes.
- Volvemos a obtener el largo o longitud del arreglo jugadores.
- Usamos la aserción para verificar que sean iguales la cuenta final a la cuenta inicial + 1. Si si, entonces pasa la prueba.
Prueba para verificar la selección de un ganador y transferir el premio.
Por último esta prueba verifica que si se haya escigido un ganador y se le transfiera el saldo total del contrato. Esta prueba es bastante extensa entonces la veremos paso a paso:
it('Debería seleccionar un ganador y transferir el premio', async() => {
const loteriaInstancia = await ContratoCurso.deployed();
// Simulamos jugadores
await loteriaInstancia.ingresar("Jugador 2", {from: accounts[1], value: web3.utils.toWei("1", "ether")});
await loteriaInstancia.ingresar("Jugador 3", {from: accounts[2], value: web3.utils.toWei("1", "ether")});
// Obtener saldos anteriores de todas las cuentas de jugadores
const saldosAnteriores = [];
for (let i = 0; i < accounts.length; i++) {
const saldoAnterior = await web3.eth.getBalance(accounts[i]);
saldosAnteriores.push(saldoAnterior);
}
const saldoInicialContrato = (await loteriaInstancia.obtenerSaldoContrato()).toString();
// Llamar a la función escogerGanador
await loteriaInstancia.escogerGanador();
// Obtener saldos posteriores de todas las cuentas de jugadores
const saldosPosteriores = [];
for (let i = 0; i < accounts.length; i++) {
const saldoPosterior = await web3.eth.getBalance(accounts[i]);
saldosPosteriores.push(saldoPosterior);
}
// PRUEBA 1 /////////////////////////////////////////////////
// Obtener los eventos pasados de tipo 'ganadorEscogido'
const eventos = await loteriaInstancia.getPastEvents('ganadorEscogido', {
fromBlock: 0,
toBlock: 'latest'
});
// Verificar que se emitió el evento ganadorEscogido
assert.isNotEmpty(eventos, "No se emitió el evento ganadorEscogido");
// PRUEBA 2 ///////////////////////////////////////////////////
// Obtener los datos del evento
const eventoGanador = eventos[0].returnValues;
const posicionGanadora = (eventoGanador.posicion);
const saldoGanador = parseInt(saldosAnteriores[posicionGanadora]) + parseInt(saldoInicialContrato);
assert.isTrue(saldosPosteriores[posicionGanadora] == saldoGanador, "No se transfirió el valor al ganador");
})
A diferencia de la primera prueba para evitar doble explicación iniciamos en los saldos:
- Cremos un arreglo para obtener los saldos anteriores de todos los jugadores antes de ejecutar la lotería o función escogerGanador. Esto lo hacemos con un for y luego dentro con la función web3.eth.getBalance(accounts[i]).
- Obtenemos el saldo del contrato igualmente antes de ejecutar la loteria.
- Ejecutamos la lotería.
- Obtenemos los pasos 1 y 2, pero ahora luego de ejecutar la loteria.
Ahora aquí realizamos 2 pruebas. La primera verificando que el evento ganadorEscogido si se haya ejecutado y que si se haya tansferido el saldo.
- Obtenemos todos los eventos emitidos en el contrato y buscamos uno por el nombre de ganadorEscogido.
- Luego verificamos que si se haya emitido este evento con la existencia de eventos. Es decir si es una variable no vacía, entonces si se ejecutó.
Ahora la prueba 2:
- obtenemos la posición ganadora, del evento ganadorEscogido guardado en la variable eventoGanador.
- Encontramos el saldo del ganador con esta posición y el arreglo de los saldos anteriores y el saldo inicial del contrato, sumando ambos.
- Luego usando las aserciones, verificamos que sean iguales tanto el saldo posterior de este jugador, con la sumatoria anterior. si si, entonces se ha pasado la prueba.
Migrando contrato
Una vez finalizado todo entonces solo nos queda hacer test y migrar. Cómo lo hacemos, ejecutando:
truffle test
truffle migrate --reset
Estos dos comandos verificarán que el test sea efectivo y luego reiniciará la migración completa con todo lo del nuevo contrato, cambiando lo de la sesión 1 a esta! y listo ✅
Creando front-end en REACT
Bueno en esta etapa vamos a avanzar explicando cómo creé el front para este proyecto. Entonces iniciamos con las variables de useState:
const [nombre, setNombre] = useState('');
const [estado, setEstado] = useState({
account: '',
contratoEnBlockchain: "",
abiContrato: "",
addressContrato: "",
cantidadJugadores: 0,
todosJugadores: []
});
const [ganador, setGanador] = useState('');
Creamos principalmente 3 las cuales contendrán el nombre, el ganador y un objeto con varios valores del contrato obtenido desde backend. Ya vamos a ver cómo las utilizamos. Ahora importante, dentro de esta siguiente etapa de web3, usaremos useEffect de react. Es decir ambas funciones deben estar dentro de este hook.
Función para conectar metamask
Esta función es la encargada de determinar qué tipo de navegador está interactuando con nuestra aplicación, si tiene o no metamask y luego, obtener la cuenta e información.
async function loadWeb3(){
window.addEventListener('load', async () => {
// FORMA1: Modern dapp browsers... - chrome
if (window.ethereum) {
// crea una nueva conexión a la blockchain. Se pasa como argumento window.ethereum. Luego lo hbilitamos
try {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
setEstado(prevEstado => { return { ...prevEstado, account: accounts[0], } }); // Actualizamos account con el valor obtenido, PERO mantenimiento los otros valores anteriores.
} catch (error) {
if (error.code === 4001) {
// User rejected request
console.log("error 4001");
}
}
}
// FORMA2: Legacy dapp browsers...
else if (window.web3) { window.web3 = new Web3(web3.currentProvider); }
// FORMA3: Non-dapp browsers...
else {console.log('No se detectó un navegador que no es Ethereum. ¡Deberías considerar probar MetaMask!');}
});
}
Esta función la podemos ver en la documentación de metamask pero la he modificado para este proyecto. Prácticamente usamos chrome dentro de la forma 1. Una vez encuentre la conexión, guarda la cuenta en estado.account. Esta será usada más adelante.
La forma 2 y 3 no las he utilizado en producción, pero determinan otros tipos de navegadores. En sí solo debemos obtener esta informción por ahora, que es la cuenta.
Función para obtener la información del contrato en blockchain
Esta función es la encargada de obtener la inforamción del contrato que creamos he implementamos, y tiene varios pasos entonces vamos a revisarlos.
async function loadBlockchainData(){
const conexionWeb3 = new Web3(window.ethereum);
await axios.post("/obtener-contrato")
.then(async function(response){
//console.log(response);
setEstado(prevEstado => {return {...prevEstado, abiContrato: response.data.abi, addressContrato: response.data.addres }} );
const contratoCurso = response.data.contrato;
const networkId = await conexionWeb3.eth.net.getId();
const networkData = contratoCurso.networks[networkId];
if(networkData){
const contratoCursoEnBlockchain = new conexionWeb3.eth.Contract(contratoCurso.abi, networkData.address);
setEstado(prevEstado => {return {...prevEstado, contratoEnBlockchain: contratoCursoEnBlockchain }} );
let cantidadDeJugadores = await contratoCursoEnBlockchain.methods.cantidadDeJugadores().call();
setEstado(prevEstado => {return {...prevEstado, cantidadJugadores: cantidadDeJugadores }} );
let todosJugadores = await contratoCursoEnBlockchain.methods.obtenerJugadores().call(); // 0: direcciones. 1: nombres.
let jugadoresOrganizados= [];
for(let i = 0; i < todosJugadores[0].length; i++){
jugadoresOrganizados.push({
direccion: todosJugadores[0][i],
nombre: todosJugadores[1][i]
});
}
setEstado(prevEstado => {return {...prevEstado, todosJugadores: jugadoresOrganizados }} );
}
else{alert('el contrato contratoCurso no se implementó en la red conectada')}
})
.catch((error) => {console.log(error);})
}
Vamos primero con axios. Axios nos permite crear como lo he usado en otros post, una conexión con backend enviando y obteniendo datos entre sí. Entonces ejecutamos una solicitud que la veremos abajo, y como respuesta guardamos en el estado: abi, addressContrato. A continuación:
- encontramos si existen datos o un contrato sobre la red actual que está interactuando. Es decir si la wallet de metamask está conectada con ganache y esta no tiene una red dentro del contrato que creamos entonces, no hay datos de la red en el contrato, o bien no se ha implementado en esta red. Pero si si , entonces procedemos.
- Con abi y address obtenemos el contrato.
- Guardamos el contrato obtenido en contratoEnBlockchain.
- Obtenemos la cantidad de jugadores y guardamos en cantidadDeJugadores. Esta desde la función cantidadDeJugadores del contrato.
- Obtenemos los dos arreglos que contienen direcciones y nombres, desde la función obtenerJugadores y guardamos en todosJugadores.
- Por último creamos un for que nos permita reorganizar esto en un nuevo arreglo llamado jugadoresOrganizados que contenga en una misma posición el nombre y la dirección del mismo jugador.
- Guardamos el arreglo en todosJugadores.
Método post axios obtener-contrato
Este método lo creo para obtener desde backend el contrato con respecto a la red que deseemos. Entonces guardamos abi y address del contrato solicitado y luego la enviamos en un archivo json. Cabe mencionar que esta etapa es back-end en Node js.
const ContratoCurso = require('../blockchain/abis/ContratoCurso.json');
router.route("/obtener-contrato")
.post(function(req, res){
const ganache = 5777;
const abi = ContratoCurso.abi;
const address = ContratoCurso.networks[ganache].address;
res.json({contrato: ContratoCurso, abi: abi, address: address});
});
Creamos HTML y funciones handle REACT
Ambos pasos son complementarios entonces voy a iniciar de arriba para abajo con las funciones handle. Entonces tenemos 3 funciones, la primera que captura y guarda con setNombre lo que ingresa el input del usuario. La segunda, ejecuta la función del contrato ingresar. La tercera ejecuta la lotería o la función escogerGanador.
function handleChange(event){
setNombre(event.target.value);
}
async function enviarJugador(event){
event.preventDefault();
// dirección, nombre
let cuenta = estado.account;
let valor = Web3.utils.toWei('0.00055', 'ether');
await estado.contratoEnBlockchain.methods.ingresar(nombre).send({from: cuenta, gas: 1000000, value: valor }).once('receipt', (receipt) => {
console.log(receipt);
alert("transacción realizada");
});
}
async function escogerGanador(event){
try{
let cuenta = estado.account;
await estado.contratoEnBlockchain.methods.escogerGanador().send({from: cuenta, gas: 1000000 }).once('receipt', (receipt) => {
console.log(receipt);
let direccionGanador = receipt.events.ganadorEscogido.address;
setGanador(direccionGanador);
console.log(direccionGanador);
alert("la dirección del ganador es: ", direccionGanador);
});
}catch(error) {console.log(error);}
}
Datos importantes de estas 3 funciones:
- Aunque no conocemos el gas que estas gastan (solo lo sabremos luego de ejecutar) determinamos un valor de 1000000 ya que lo requiere la función.
- Asignamos el valor de transferencia para ingresar a la lotería de 0.00055 eth.
- En la función escogerGanador guardamos la dirección del ganador desde el evento ganadorEscogido para poder mostrarlo en front ya que recuerden que una vez finaliza, borra el arreglo jugadores y reinicia.
Ahora podemos revisar el HTML. Aquí podemos encontrar varios puntos clave.
return(
<div>
<div className='container text-center my-5'>
<h1>Lotería</h1>
<p>aquí podrás participar en el juego de la lotería</p>
<h2>Reglas</h2>
<p>
- El costo es de 0.00055 eth. Aproximádamente 2 usd, a la tasa actual.<br/>
- Debes ingresar tu nombre.
</p>
<form className='my-5'>
{/* Nombre */}
<div className="form-floating mb-3">
<input onChange={handleChange} value={nombre} type="text" className="form-control" id="floatingInput" placeholder="nombre" />
<label htmlFor="floatingInput">Tu nombre</label>
</div>
{/* Botón enviar */}
<button onClick={enviarJugador} type="submit" style={{width: "100%"}} className="btn btn-primary">Participar</button>
</form>
<h3>Jugadores de la lotería actual</h3>
<p>Estos son los jugadores que están participando en la lotería actual</p>
<table className='table mb-5'>
<thead>
<tr>
<th scope="col">Nombre</th>
<th scope="col">Dirección</th>
</tr>
</thead>
<tbody id="productList">
{
estado.todosJugadores.map( (jugadorActual, key) => {
return(
<tr key={key}>
{<td>{jugadorActual.nombre}</td>}
{<td>{jugadorActual.direccion}</td>}
</tr>
)
})
}
</tbody>
</table>
{/* Escoger ganador */}
<p className='mt-5'>Esta función solo la puede ejecutar el propietario o administrador del contrato.</p>
<button onClick={escogerGanador} style={{width: "100%"}} className="btn btn-secondary mb-5" >Escoger ganador</button>
{ganador!== '' ? <h3>El ganador es: {ganador}. </h3> : <h3>aún no hay un ganador</h3>}
</div>
</div>
)
- En la lista de jugadores usamos una tabla, donde los datos provienen de estado.todosJugadores, el arreglo con los datos organizados de arriba. En la cual, usamos map para recorrer el arreglo y dividir sus datos.
- Creamos y usamos operador ternary para mostrar el ganador si ya existe uno, desde la variable ganador.
listo! hemos finalizado nuestra sesió 3. De esta forma podemos ver el back-end con el contrato y el front-end con react, capturando y mostrando los datos. ES IMPORANTE saber que debes ver el video y el repositorio para complementar este post. Ya que debes saber donde se ubican los archivos y como implementarlos correctamente.
Video de ayuda
Hice este video para ayudarte en el proceso. Puedes dar like y suscribirte para ayudarme a seguir creciendo en youtube!
Producto sugerido
Si eres como yo que pasa bastatne tiempo frente al compu, puede que la barra de luz te sirva bastante para cuidar tu vista en esas largas jornadas. También te dejo aquí el post para que veas la reseña completa.
Quntis Lámpara para monitor de computadora, barra de luz para monitor de pantalla para el cuidado de los ojos, lámpara de tareas LED de lectura electrónica con atenuación automática, barra de lámpara regulable, control táctil, sin deslumbramiento de pantalla, ahorro de espacio, lámparas de escritorio para oficina en casa
- Sin reflejos de pantalla y sin parpadeo, antiluz azul: la barra de luz para monitor de computadora Quntis tiene un diseño óptico asimétrico único que ilumina solo tu escritorio y teclado mientras garantiza que no se reflejen en la pantalla. Gracias a su avanzado sistema de protección ocular, nuestras lámparas de escritorio para oficina en casa bloquean la luz azul y la radiación óptica, aliviando eficazmente la fatiga ocular para proteger tus ojos. Es la opción ideal para estudiantes, diseñadores de pintura y trabajadores de oficina de negocios.
Conclusión
Esta sesión 3 en definitiva es la más extensa porque hablamos de crear un contrato desde ceros hasta cómo mostrarlo en front-end 100% funcional.