Una guía para desarrollar una aplicación de votación descentralizada de Ethereum

Después de que todo el mercado de criptomonedas superó los 700 mil millones de dólares en capitalización de mercado, el espacio de las criptomonedas se disparó durante estos últimos meses. Pero esto es solo el principio. A medida que los sistemas blockchain continúan evolucionando y escalando, una excelente manera de sumergirse en este nuevo espacio y aprovechar esta tecnología es con aplicaciones descentralizadas, también conocidas como dApps.

CryptoKitties, famoso por su congestión de Ethereum Blockchain, es un gran ejemplo de dApp, que combina de forma única conceptos de gatos criables y coleccionables con blockchain. Este sensacional juego es solo un ejemplo creativo de un número prácticamente ilimitado de oportunidades.

Aunque aparentemente es muy complicado, se han desarrollado ciertos marcos y herramientas para abstraer sus interacciones con la cadena de bloques y los contratos inteligentes. En esta publicación de blog, repasaré una forma de crear una aplicación de votación descentralizada en Ethereum. Repasaré brevemente Ethereum, pero probablemente debería comprenderlo para usar esta guía al máximo. Además, espero que conozcas Javascript.

¿Por qué crear una aplicación de votación descentralizada?

Esencialmente, una excelente aplicación descentralizada que utiliza tecnología blockchain le permite realizar las mismas acciones que haría hoy (como transferir dinero) sin un tercero confiable. Las mejores dApps tienen un caso de uso específico del mundo real que aprovecha las características únicas de blockchain.

En esencia, la cadena de bloques es un libro mayor compartido, programable, criptográficamente seguro y, por lo tanto, confiable que ningún usuario controla y que puede ser inspeccionado por cualquier persona.- Klaus Schwab

Aunque una aplicación de votación puede no ser una gran aplicación para los consumidores, he elegido usarla para esta guía porque los principales problemas que resuelve blockchain (transparencia, seguridad, accesibilidad, audibilidad) son los principales problemas que plagan las elecciones democráticas actuales.

Dado que una cadena de bloques es un registro permanente de transacciones (votos) que se distribuyen, cada voto puede rastrearse de manera irrefutable hasta exactamente cuándo y dónde ocurrió sin revelar la identidad del votante. Además, los votos pasados ​​no se pueden cambiar, mientras que los presentes no se pueden piratear, porque cada transacción es verificada por cada nodo de la red. Y cualquier atacante externo o interno debe tener el control del 51% de los nodos para alterar el registro.

Incluso si el atacante pudo lograr eso mientras ingresaba incorrectamente los votos de los usuarios con sus ID reales bajo el radar, los sistemas de votación de extremo a extremo podrían permitir a los votantes verificar si su voto se ingresó correctamente en el sistema, haciendo que el sistema sea extremadamente seguro.

Componentes principales de Ethereum

Espero que comprenda Blockchain y Ethereum durante el resto de esta guía. Aquí hay una guía increíble al respecto, y he escrito una breve descripción general de los componentes principales que me gustaría que conociera.

  1. Los contratos inteligentes actúan como la lógica y el almacenamiento de back-end. Un contrato está escrito en Solidity, un lenguaje de contrato inteligente, y es una colección de código y datos que residen en una dirección específica en la cadena de bloques Ethereum. Es muy similar a una clase de Programación Orientada a Objetos, donde incluye funciones y variables de estado. Los contratos inteligentes, junto con Blockchain, son la base de todas las aplicaciones descentralizadas. Son, como Blockchain, inmutables y distribuidos, lo que significa que actualizarlos será una molestia si ya están en la red Ethereum. Afortunadamente, aquí hay algunas formas de hacerlo.
  2. La máquina virtual Ethereum (EVM) maneja el estado interno y el cálculo de toda la red Ethereum. Piense en el EVM como una computadora descentralizada masiva que contiene “direcciones” que son capaces de ejecutar código, cambiar datos e interactuar entre sí.
  3. Web3.jses una API de JavaScript que le permite interactuar con Blockchain, lo que incluye realizar transacciones y llamadas a contratos inteligentes. Esta API abstrae la comunicación con los clientes de Ethereum, lo que permite a los desarrolladores centrarse en el contenido de su aplicación. Debe tener una instancia web3 incorporada en su navegador para hacerlo.

Otras herramientas que usaremos

  1. Trufaes un marco de desarrollo de pruebas popular para Ethereum. Incluye una cadena de bloques de desarrollo, scripts de compilación y migración para implementar su contrato en Blockchain, pruebas de contrato, etc. ¡Facilita el desarrollo!
  2. Truffle Contracts es una abstracción de la API Web3 Javascript, lo que le permite conectarse e interactuar fácilmente con su contrato inteligente.
  3. Metamask trae Ethereum a su navegador. Es una extensión del navegador que proporciona una instancia web3 segura vinculada a su dirección de Ethereum, lo que le permite utilizar aplicaciones descentralizadas. No usaremos Metamask en este tutorial, pero es una forma para que las personas interactúen con su DApp en producción. En su lugar, inyectaremos nuestra propia instancia web3 durante el desarrollo. Para obtener más información, consulte este enlace.

¡Empecemos!

Para simplificar, en realidad no crearemos el sistema de votación completo que describí anteriormente. Para facilitar la explicación, será solo una aplicación de una página donde un usuario puede ingresar su ID y votar por un candidato. También habrá un botón que cuenta y muestra el número de votos por candidato.

De esta forma, podremos enfocar el proceso de creación e interacción con los contratos inteligentes dentro de una aplicación. El código fuente de toda esta aplicación estará en este repositorio y deberá tener Node.js y npm instalados.

Primero, instalemos Truffle a nivel mundial.

npm install -g truffle

Para utilizar los comandos de Truffle, debe ejecutarlos en un proyecto existente.

git clone //github.com/tko22/truffle-webpack-boilerplatecd truffle-webpack-boilerplatenpm install

Este repositorio es sólo un esqueleto de una caja de la trufa, que son boilerplates o aplicaciones de ejemplo que se puede obtener en un solo comando - truffle unbox [box name]. Sin embargo, la caja Truffle con paquete web no está actualizada con las últimas versiones e incluye una aplicación de ejemplo. Por lo tanto, creé este repositorio (el vinculado en las instrucciones anteriores).

2. Estructura del directorio

Su estructura de directorio debe incluir estos:

  • contracts/- Carpeta con todos los Contratos. NO BORRESMigrations.sol
  • migrations/ - Carpeta que contiene archivos de migración, que lo ayudan a implementar sus contratos inteligentes en Blockchain.
  • src/ - contiene los archivos HTML / CSS y Javascript para la aplicación
  • truffle.js - Archivo de configuración de trufas
  • build/- No verá esta carpeta hasta que compile sus contratos. Esta carpeta contiene los artefactos de compilación, ¡así que no modifique ninguno de estos archivos! Los artefactos de construcción describen la función y la arquitectura de su contrato y brindan a Truffle Contracts e información web3 sobre cómo interactuar con su Smart Contract en Blockchain.

1. Escriba sus contratos inteligentes

Suficiente con la configuración y la introducción. ¡Entremos en el código! En primer lugar, escribiremos nuestro Smart Contract, que está escrito en Solidity (los otros lenguajes no son tan populares). Puede parecer aterrador, pero no lo es.

Para cualquier aplicación, desea que sus contratos inteligentes sean lo más simples posible, incluso estúpidamente simples. Recuerde que debe pagar por cada cálculo / transacción que realice, y sus contratos inteligentes estarán en Blockchain para siempre. Entonces, realmente quiere que funcione perfectamente, es decir, cuanto más complejo es, más fácil es cometer un error.

Nuestro contrato incluirá:

  1. Variables de estado : variables que contienen valores que se almacenan permanentemente en Blockchain. Usaremos variables de estado para mantener una lista y número de votantes y candidatos.
  2. Funciones : las funciones son ejecutables de contratos inteligentes. Son lo que llamaremos para interactuar con Blockchain, y tienen diferentes niveles de visibilidad, interna y externamente. Tenga en cuenta que siempre que desee cambiar el valor / estado de una variable, debe ocurrir una transacción, con un costo de Ether. También puede hacer callsa Blockchain, que no le costará ningún Ether porque los cambios que hizo serán destruidos (más sobre esto en la Sección 3 cuando realmente hagamos el transactionsy calls).
  3. Eventos : cada vez que se llama a un evento, el valor que se pasa al evento se registrará en el registro de la transacción. Esto permite que las funciones de devolución de llamada de Javascript o las promesas resueltas vean el valor determinado que deseaba devolver después de una transacción. Esto se debe a que cada vez que realice una transacción, se devolverá un registro de transacciones. Usaremos un evento para registrar el ID del Candidato recién creado, que mostraremos (verifique el primer punto de la Sección 3).
  4. Tipos de estructura : es muy similar a una estructura de C. Las estructuras le permiten contener múltiples variables y son increíbles para cosas con múltiples atributos. Candidatessolo tendrán su nombre y fiesta, pero definitivamente puedes agregarles más atributos.
  5. Mapeos : piense en estos como mapas hash o diccionarios, donde tiene un par clave-valor. Usaremos dos mapeos.

Hay un par de tipos más que no se enumeran aquí, pero algunos de ellos son un poco más complicados. Estos cinco abarcan muchas de las estructuras que generalmente utilizará un contrato inteligente. Estos tipos se explican con más profundidad aquí.

Como referencia, aquí está el código del Smart Contract. Tenga en cuenta que este archivo debería llamarse, Voting.solpero quería que la esencia de Github tuviera estilo, así que le di una .jsextensión. Al igual que el resto de esta guía, proporcionaré comentarios dentro del código que explicarán lo que está haciendo, y luego explicaré el panorama general mientras señalo ciertas advertencias y lógica.

Básicamente, tenemos dos estructuras (tipos que contienen múltiples variables) que describen un votante y un candidato. Con Structs, podemos asignarles múltiples propiedades, como correos electrónicos, direcciones, etc.

Para realizar un seguimiento de los votantes y los candidatos, los colocamos en asignaciones separadas donde se indexan en números enteros. El índice / clave de un candidato o votante (llamémoslo ID) es la única forma de que las funciones accedan a ellos .

También realizamos un seguimiento del número de votantes y candidatos, lo que nos ayudará a indexarlos. Además, no se olvide del evento en la línea 8, que registrará la identificación del candidato cuando se agregue. Este evento será utilizado por nuestra interfaz, ya que necesitamos realizar un seguimiento de la identificación de un candidato para poder votar por un candidato.

  1. Lo sé, al contrario de lo que dije antes acerca de hacer que los contratos sean súper simples, hice este contrato un poco más complicado en comparación con lo que realmente hace esta aplicación. Sin embargo, hice esto para que fuera mucho más fácil para ustedes hacer ediciones y agregar funciones a esta aplicación después (más sobre eso al final). Si desea hacer una aplicación de votación aún más simple, el contrato inteligente podría funcionar en menos de 15 líneas de código.
  2. Tenga en cuenta que las variables de estado numCandidatesy numVotersno se declaran públicas. De forma predeterminada, estas variables tienen una visibilidad de internal, lo que significa que solo se puede acceder a ellas directamente mediante el contrato actual o los contratos derivados (no se preocupe por eso, no lo usaremos).
  3. Estamos usando 32bytespara cadenas en lugar de usar el stringtipo. Nuestro EVM tiene un tamaño de palabra de 32 bytes, por lo que está "optimizado" para tratar datos en trozos de 32 bytes. (Los compiladores, como Solidity, tienen que trabajar más y generar más código de bytes cuando los datos no están en trozos de 32 bytes, lo que efectivamente conduce a un mayor costo de gas).
  4. Cuando un usuario vota, Voterse crea una nueva estructura y se agrega a la asignación. Para contar el número de votos que tiene un determinado candidato, debe recorrer todos los Votantes y contar el número de votos. Los candidatos operan con el mismo comportamiento. Por lo tanto, estas asignaciones mantendrán el historial de todos los candidatos y votantes.

2. Cree una instancia de web3 y contratos

Con nuestro Smart Contract completado, ahora necesitamos ejecutar nuestra blockchain de prueba e implementar este contrato en Blockchain. También necesitaremos una forma de hablar con él, que será a través de web3.js.

Antes de comenzar nuestra cadena de bloques de prueba, debemos crear un archivo llamado 2_deploy_contracts.jsdentro de la carpeta /contractsque le indique que incluya su Contrato de votación inteligente cuando realice la migración.

Para iniciar el desarrollo de la cadena de bloques Ethereum, vaya a su línea de comando y ejecute:

truffle develop

Esto vivirá en su línea de comando. Dado que Solidity es un lenguaje compilado, debemos compilarlo primero en bytecode para que se ejecute el EVM.

compile

Debería ver una build/carpeta dentro de su directorio ahora. Esta carpeta contiene los artefactos de construcción, que son fundamentales para el funcionamiento interno de Truffle, ¡así que no los toques!

A continuación, debemos migrar el contrato. Migrations es un script de Truffle que le ayuda a modificar el estado del contrato de su aplicación a medida que se desarrolla. Recuerde que su contrato se implementa en una determinada dirección en Blockchain, por lo que cada vez que realice cambios, su contrato se ubicará en una dirección diferente. Las migraciones lo ayudan a hacer esto y también lo ayudan a mover datos.

migrate

¡Felicidades! Su contrato inteligente ahora está en Blockchain para siempre. Bueno en realidad no…. porque se truffle developactualiza cada vez que lo detienes.

Si desea tener una cadena de bloques persistente, considere Ganache, que también es desarrollada por Truffle. Si está usando Ganache, no necesitará llamar truffle develop. En su lugar, ejecutará truffle compiley truffle migrate. Para comprender lo que realmente se necesita para implementar un contrato sin Truffle, consulte esta publicación de blog.

Una vez que hayamos implementado el contrato inteligente en Blockchain, tendremos que configurar una instancia web3.0 con Javascript en el navegador cada vez que se inicie la aplicación. Por lo tanto, el siguiente fragmento de código se colocará al final de js/app.js. Tenga en cuenta que estamos usando la versión 0.20.1 de web3.0.

Realmente no tiene que preocuparse demasiado si no comprende este código. Solo sepa que esto se ejecutará cuando se inicie la aplicación y verificará si ya hay una instancia web3 (Metamask) en su navegador. Si no lo hay, simplemente crearemos uno con el que hable localhost:9545, que es la cadena de bloques de desarrollo de Truffle.

Si está utilizando Ganache, debe cambiar el puerto a 7545. Una vez que se crea una instancia, llamaremos a la startfunción (la definiré en la siguiente sección).

3. Agregar funcionalidad

Lo último que tendremos que hacer es escribir la interfaz de la aplicación. Esto involucra lo esencial para cualquier aplicación web: HTML, CSS y Javascript (ya hemos escrito un poco de Javascript con la creación de una instancia web3). Primero, creemos nuestro archivo HTML.

Esta es una página muy simple, con un formulario de entrada para la identificación de usuario y botones para votar y contar votos. Cuando se haga clic en esos botones, llamarán a funciones específicas que votan y encontrarán el número de votos para los candidatos.

Hay tres importantes elementos div sin embargo, con los identificadores: candidate-box, msgy vote-boxque cabe en casillas de verificación para cada candidato, un mensaje y el número de votos, respectivamente. También importamos JQuery, Bootstrap y app.js.

Ahora, solo necesitamos interactuar con el Contrato e implementar las funciones para votar y contar el número de votos de cada candidato. JQuery manipulará el DOM, y usaremos Promesas cuando realicemos transacciones o llamadas a Blockchain. A continuación se muestra el código de app.js.

Tenga en cuenta que el código que proporcioné en el paso anterior para crear una instancia web3 también está aquí. Primero, importamos las bibliotecas y el paquete web necesarios, incluidos los contratos web3 y Truffle. Usaremos Truffle Contracts, que está construido sobre web3 para interactuar con Blockchain.

Para usarlo, tomaremos los artefactos de construcción que se construyeron automáticamente cuando compilamos el contrato inteligente de votación y los usaremos para crear el Contrato de trufa. Finalmente, configuramos las funciones en la variable global windowpara iniciar la aplicación, votar por un candidato y encontrar el número de votos.

Para interactuar realmente con Blockchain, debemos crear una instancia del Contrato de trufa utilizando la deployedfunción. Esto, a su vez, devolverá una promesa con la instancia como valor de retorno que utilizará para llamar a funciones desde el contrato inteligente.

Hay dos formas de interactuar con esas funciones: transacciones y llamadas. Una transacción es una operación de escritura, y será transmitida a toda la red y procesada por los mineros (y por lo tanto, cuesta Ether). Debe realizar una transacción si está cambiando una variable de estado, ya que cambiará el estado de la cadena de bloques.

Una llamada es una operación de lectura que simula una transacción, pero descarta el cambio de estado. Por lo tanto, no le costará a Ether. Esto es excelente para llamar a funciones de captador (consulte las cuatro funciones de captador que escribimos anteriormente en nuestro contrato inteligente).

Para realizar una transacción con Truffle Contracts, escriba instance.functionName(param1, param2), con instancela instancia que devolvió la deployedfunción (consulte la línea 36 para ver un ejemplo). Esta transacción devolverá una promesa con los datos de la transacción como valor de retorno. Por lo tanto, si devuelve un valor en su función de contrato inteligente pero realiza una transacción con esa misma función, no devolverá ese valor.

Es por eso que tenemos un evento que registrará lo que usted quiera que escriba en los datos de la transacción que serán devueltos. En el caso de las líneas 36 a 37, realizamos una transacción para agregar un candidato. Cuando resolvemos la promesa, tenemos los datos de la transacción en formato result.

Para obtener el candidateIDque registra con el evento AddedCandidate()(comprobar el contrato inteligente para ver que 0), tenemos que ir a través de los registros y recuperar de esta manera: result.logs[0].args.candidateID.

Para ver realmente lo que está sucediendo, use las herramientas de desarrollo de Chrome para imprimir resulty revisar su estructura de result.

Para realizar una llamada, debe escribir instance.functionName.call(param1,param2). Sin embargo, si una función tiene la palabra clave view, Truffle Contracts creará automáticamente una llamada y, por lo tanto, no es necesario agregar el .call.

Es por eso que nuestras funciones getter tienen la viewpalabra clave. A diferencia de realizar una transacción, la promesa devuelta de una llamada tendrá un valor de retorno de lo que sea devuelto por la función de contrato inteligente.

Ahora explicaré las 3 funciones brevemente, pero esto debería ser muy familiar si ha creado aplicaciones que recuperan / cambian datos de un almacén de datos y manipula el DOM en consecuencia. Piense en Blockchain como su base de datos y Truffle Contracts como la API para obtener datos de su base de datos.

App.start ()

Esta función se llama inmediatamente después de que creamos una instancia web3. Para que Truffle Contracts funcione, debemos configurar el proveedor en la instancia web3 creada y establecer valores predeterminados (como qué cuenta está usando y la cantidad de gas que desea pagar para realizar una transacción).

Como estamos en modo de desarrollo, podemos usar cualquier cantidad de gas y cualquier cuenta. Durante la producción, tomaríamos la cuenta proporcionada por MetaMask y trataríamos de calcular la menor cantidad de gas que podría usar, ya que en realidad es dinero real.

Con todo configurado, ahora mostraremos las casillas de verificación de cada candidato para que el usuario vote. Para ello, debemos crear una instancia del contrato y obtener la información del Candidato. Si no hay candidatos, los crearemos. Para que un usuario vote por un candidato, debemos proporcionar la identificación de ese determinado candidato. Por lo tanto, hacemos que cada elemento de la casilla de verificación tenga un id(atributo de elemento HTML) del ID del candidato. Además, agregaremos el número de candidatos a una variable global numOfCandidates, que usaremos en App.findNumOfVotes(). JQuery se usa para agregar cada casilla de verificación y su nombre de candidato a .candidate-box.

App.vote ()

Esta función votará por un determinado candidato según la casilla de verificación en la que se haga clic y su idatributo.

Uno, comprobaremos si el usuario ha introducido su ID de usuario, que es su identificación. Si no lo hicieron, mostramos un mensaje indicándoles que lo hagan.

Dos, verificaremos si el usuario está votando por un candidato, verificando si hay al menos una casilla de verificación que está marcada. Si no se hizo clic en ninguna de las casillas de verificación, también mostraremos un mensaje que les indica que voten por un candidato. Si se hace clic en una de las casillas de verificación, tomaremos el idatributo de esa casilla de verificación, que también es la identificación del candidato vinculado, y la usaremos para votar por el candidato.

Una vez que se haya completado la transacción, resolveremos la promesa devuelta y mostraremos un mensaje de "Votado".

App.findNumOfVotes ()

Esta última función encontrará el número de votos de cada candidato y los mostrará. Examinaremos a los candidatos y llamaremos a dos funciones de contrato inteligente, getCandidatey totalVotes. Resolveremos esas promesas y crearemos un elemento HTML para ese determinado candidato.

¡Ahora, inicie la aplicación y lo verá //localhost:8080/!

npm run dev

Recursos

Lo sé, es mucho ... Es posible que tenga este artículo abierto por un tiempo mientras desarrolla lentamente esta aplicación y comprende realmente lo que está sucediendo. ¡Pero eso es aprender! Complemente esta guía con toda la documentación de Ethereum, Truffle y lo que he proporcionado a continuación. Intenté tocar muchos de los puntos clave de este artículo, pero es solo una breve descripción general y estos recursos ayudarán mucho.

  • Todo sobre la solidez y los contratos inteligentes , me refiero a todo
  • Todo sobre Truffle
  • Documentos de contratos de trufa
  • API de JavaScript de Web3- Será genial saberlo y hacer referencia a esto, pero Truffle Contracts abstrae muchas partes de este
  • Patrones de DApp útiles
  • Ethereum Docs : mire a la barra lateral y hay muchas cosas
  • Explicación del código de CryptoKitties : el escritor repasa las partes importantes del contrato inteligente de CryptoKitties
  • Mejores prácticas de contratos inteligentes- una lectura obligada

Conclusión

La creación de aplicaciones en Ethereum es bastante similar a una aplicación normal que llama a un servicio de backend. La parte más difícil es redactar un contrato inteligente sólido y completo. Espero que esta guía le haya ayudado a comprender el conocimiento básico de las aplicaciones descentralizadas y Ethereum y le ayude a poner en marcha su interés en desarrollarlas.

Si desea aprovechar lo que hemos construido, aquí tiene algunas ideas. De hecho, escribí el contrato inteligente de tal manera que se implementa fácilmente con todo lo que le he dado en esta guía.

  • Muestra la fiesta de cada candidato. Ya tenemos el partido de un candidato cuando nos postulamos getCandidate(id).
  • Compruebe si la ID introducida por el usuario es única.
  • Pregunte y almacene más información sobre un Usuario, como su fecha de nacimiento y su domicilio.
  • Agregue una opción para ver si una persona con una identificación específica ha votado o no. Crearía un nuevo formulario para ingresar una ID, que luego buscaría ese determinado usuario en la cadena de bloques.
  • Escriba una nueva función de contrato inteligente que cuente los votos de AMBOS candidatos a la vez. Actualmente, tenemos que hacer dos convocatorias separadas para dos candidatos, lo que requiere que el contrato recorra todos los Usuarios dos veces.
  • Permitir que se agreguen nuevos candidatos. Esto significa agregar un nuevo formulario para agregar candidatos, pero también cambiar un poco la forma en que mostramos y votamos a los candidatos en la interfaz.
  • Requiere que los usuarios tengan una dirección Ethereum para votar. Mi lógica para no incluir las direcciones de los usuarios es porque no se esperaría que los votantes tuvieran Ethereum para participar en este proceso de votación. Sin embargo, muchas DApps requerirán que los usuarios tengan una dirección Ethereum.

Además, aquí hay algunos consejos que podrían evitar que ocurran algunos obstáculos:

  • Verifique dos o tres veces las funciones de su contrato inteligente cuando esté sucediendo algo extraño. Pasé un par de horas en un error para descubrir que devolvía el valor incorrecto en una de mis funciones.
  • Verifique si su URL y puerto son correctos cuando se conecta a su blockchain de desarrollo. Recuerda: 7545es para truffle developy 9545es para Ganache. Estos son valores predeterminados, por lo que si no puede conectarse a su blockchain, es posible que los haya cambiado.
  • No repasé esto porque esta guía habría sido demasiado larga y probablemente haga otra publicación sobre esto, ¡pero debería probar sus contratos! Ayudará mucho.
  • Si no está familiarizado con las promesas, analice cómo funcionan y cómo usarlas. Truffle Contracts usa promesas y la versión beta de web3 también respaldará las promesas. Pueden, si los hace mal, estropear muchos de los datos que está recuperando.

¡Felicidades por trabajar hacia una Internet descentralizada y segura - Web 3.0!

¡Espero que haya disfrutado leyendo esta guía tanto como yo disfruté escribiéndola! Si tiene alguna idea o comentario, no dude en dejar un comentario a continuación o enviarme un correo electrónico a [email protected] o enviarme un tweet (recientemente he creado uno). ¡Siéntete libre de usar mi código y compartirlo con tus amigos!