Un blog descentralizado con React, Gatsby y Wavelet

Compartiré algunas ideas sobre mi experiencia de escribir mi primer aplicación descentralizada (dApp).

El dApp consiste en un blog simple con un paywall de un clic.

Cualquiera puede publicar una publicación que implica pagar una tarifa al propietario del blog (cuenta responsable de implementar el contrato inteligente). Para recuperarse y obtener ganancias, los editores pueden agregar una tarifa para que cualquiera pueda ver su contenido.

Puede obtener el proyecto completo de Github: https://github.com/claudiucelfilip/reveal

O puede probarlo aquí: https://wavelet-reveal.netlify.com

El dApp se escribe usando Reaccionar y Gatsby y usa un WebAssembly Smart Contract que se ejecuta en Wavelet.

Wavelet es un libro abierto para escribir una aplicación WebAssembly escalable, crítica y descentralizada (https://wavelet.perlin.net/).

Wavelet, con sus más de 31,240 transacciones de pago por segundo, logra retrasos de 2 a 4 segundos de acción a transacción y luego reacción.

También corre WebAssembly contratos inteligentes, permitiéndole escribirlos en cualquier idioma usted quiere. Fui con Rust porque era más fácil con la ayuda de smart-contract-rs (https://github.com/perlin-network/smart-contract-rs).

Todo el concepto del paywall de un clic se basa en procesamiento rápido de transacciones. Las plataformas de clásicas, como Ethereum, requieren demasiado tiempo (por orden de minutos), lo cual es inaceptable. No podemos hacer que los usuarios miren cargar la animación durante minutos. Confía en mí, simplemente no lo hará.

Con Wavelet, por otro lado, de 2 a 4 segundos está perfectamente bien para este tipo de funcionalidad y a los editores no les importará que sus publicaciones sean casi públicas. instantáneamente.

Gatsby es un generador de sitio estático ultrarrápido para Reaccionar. Le permite crear una aplicación React y tener una representación del lado del servidor de una sola vez.

Esto elimina cualquier Ansiedad SEO y mejora mucho rendimiento de carga sirviendo directamente contenido prestado previamente. Cualquier contenido no esencial y funcionalidad adicional se pueden cargar después de la carga inicial.

Dado que este fue mi primer intento de desarrollar una dApp, comencé con un tutorial y el mejor que encontré fue este Chat descentralizado usando JavaScript y óxido.

Me mostró todo lo que necesitaba, desde instalar Rust hasta poner en funcionamiento una aplicación React y comunicarme con Smart Contract.

No revisaré todo el código, pero quiero mencionar algunos desafíos que encontré en el camino y algunas cosas que (finalmente) me di cuenta.

Smart Contract en la integración de RustReact AppGatsby

Aunque tenía algún tipo de imagen en mi cabeza sobre Rust y la construcción de Web Assembly, el lenguaje todavía me parecía extraño. Así que necesitaba un curso intensivo y encontré uno aquí: https://www.snoyman.com/blog/2018/10/introducing-rust-crash-course.

No lo revisé todo, pero con algunos conocimientos básicos de referencia y propiedad más algunos otros ejemplos de https://github.com/perlin-network/smart-contract-rs, estaba listo para obtener la codificación.

Smart-contract-rs también proporciona macros y macros auxiliares de Rust que permiten que su código se ejecute en la red Wavelet. De lo contrario, tendría que hacer algo de magia seria para que las cosas funcionen. Es el pegamento que une su contrato inteligente a Wavelet y lo ayuda a manejar los entresijos de sus funciones.

BESO

En mi primer intento, me encontré tratando de arreglar los problemas del compilador en cascada mientras escribía de forma orientada a objetos (clases para todo, publicaciones, blogs, etiquetas, etc.). Terminé lidiando con problemas de referencia de por vida y tuve que elegir: mejorar en Rust o hacerlo más simple.

Como estaba ansioso por poner en marcha algo, decidí simplificar y tener una sola clase principal (estructura con implementación). Esto también coincidió más con los otros ejemplos de los contratos inteligentes.

Aquí hay un fragmento con una parte del Contrato inteligente:

todas las funciones deben tener el mismo contrato y devolver Ok (()) para informar un error, use Errar y la cadena ingresada se enviará a través del socket pollTransactionUpdate como una transacción rechazada. Los parámetros de red (cantidad, remitente, etc.) son leídos por el asistente de contrato inteligente-rs y se adjuntan al argumento de parámetros para obtener los parámetros de la función, necesita usar params.read () y especificar el tipo de variable. El orden de las llamadas es importante. Las funciones no devuelven nada relevante. Puedes usar Iniciar sesión o depurar para enviar valores de vuelta al cliente

El código es dinero

Cada línea de código es ejecutado, contado y gravado y siempre debes tener eso en mente. ¡Si no!

Empecé a pensar en el dinero cuando comencé a escribir la funcionalidad pay_post (). Inicialmente, quería reenviar automáticamente los PERL al publicador y luego habilitar el contenido para el lector. Esto dio lugar a la situación por la que el usuario había pagado:

un arbitrario cuota para ver el contenido; establecido por el editor para obtener ganancias cuota para ejecutar la transacción inteligente contractextra matrícula para enviar PERL al editor.

Desafortunadamente (para el lector), no hay forma de evitar los dos primeros, pero creo que los editores deberían pagar el último. Es lo menos que pueden hacer. Además, existe un intervalo de tiempo adicional para esa transacción hasta que se pueda abrir el muro de pago; sin mencionar que no tenía idea de cómo manejar el código asíncrono en Rust. (supongo que debería haber pasado más tiempo en ese curso intensivo)

Para salir de este lío, me inspiré en el Simbólico y Transferencia de regreso ejemplos de https://github.com/perlin-network/smart-contract-rs/blob/master/examples y agregué un saldos Propiedad HashMap al blog El contrato inteligente ahora recibe los PERL de cada lector y realiza un seguimiento de cuánto se le debe a cada editor. Luego, pueden retirar sus ganancias (si las hubiera) cuando lo consideren conveniente.

Tanteo

Después de pagar por el contenido oculto, el usuario generoso tiene la oportunidad de evaluar el contenido (le guste o no). Me gusta cuenta como 1, No me gusta cuenta como -1. los suma de estos valores representará el clasificación de la publicación.

Para tener en cuenta la antigüedad de la publicación, utilicé un algoritmo de puntuación simple de Hacker News (https://medium.com/hacking-and-gonzo/how-hacker-news-ranking-algorithm-works-1d9b0cf2c08d). Lleva la calificación y el tiempo desde que se creó la publicación y devuelve un Puntuación. Cada solicitud de get_posts requerirá la hora actual del cliente y proporcionará la lista de publicaciones, con puntajes actualizados.

Puede estar pensando, ¿vale la pena el costo del código de este algoritmo de puntuación? Bueno, buscar contenido realmente no altera el estado; llamar a la instancia local de WebAssembly es gratis.

Mente = soplado

Una realización importante para mí fue que cualquier acción que altere el estado es asíncrono y requerirá un cuota, ya que se resolverá en Wavelet. Sin embargo, los resultados actualizarán la memoria de VM y cualquier solicitud de recuperación de contenido será gratis y instantáneo (No se necesita async).

Ah, y no sé si lo notó, pero no hay bases de datos, almacenamiento en caché ni ninguna solicitud de AJAX para obtener las publicaciones. Más sobre esto en el Sección React App.

Etiquetado

No quería perder mucho tiempo con las categorías de publicaciones. La solución más simple era agregar cadenas de etiquetas a cada publicación con tokens separados por "|".

Lente y contratos inteligentes

Una vez que obtuve algo compilado con éxito, lo subí a https://lens.perlin.net/#/contracts y comencé a ajustar mis parámetros.

También cloné los repositorios de lentes (https://github.com/perlin-network/lens) y wavelet (https://github.com/perlin-network/wavelet) para tener más control sobre la red. Con él, podría tener una cantidad increíble de PERL y sentirme como el 1% superior.

Utilicé la herramienta de Ejecutor de funciones proporcionada por Lens para enviar parámetros y los contratos inteligentes ¡depurar! macro para imprimir la salida.

Decidí que la aplicación cliente necesitaba las siguientes páginas:

Una página de listado donde las publicaciones se ordenan por una puntuación basada en Me gusta y tiempo. Página de detalles del poste con una sección pública y una privada que solo se completa después de hacer clic en el botón Pagar. Además, el usuario puede hacer Me gusta o No me gusta el artículo y contribuir con la puntuación general de la Publicación. Crear una página de Publicación donde cualquiera pueda escribir su Publicación. Contará con un editor de texto simple y rico para permitir encabezados y formato de texto. También debe permitir contenido incrustado personalizado para un mayor valor. Página de equilibrio donde el editor puede ver y retirar los PERL que han ganado para su página de contenido de inicio de sesión para permitir el acceso a las páginas Balance y Crear

Para la interfaz de usuario, comencé de manera simple con una aplicación create-react. Para facilitar las cosas, incluí un wavelet-client (https://github.com/perlin-network/wavelet-client-js) para conectarse a la red Wavelet y llamar a funciones en el Smart Contract.

Sincronización vs Asíncrono

Una instancia ejecutable del contrato inteligente se carga inicialmente en la memoria a través de la máquina virtual WebAssembly de los navegadores. Cada contrato llamada cambiar el estado (equivalente a POST / PUT / DELETE) desencadenará una transacción dentro de Wavelet y, si se aplica, actualizará la memoria de VM con el nuevo estado.

Para reaccionar a los cambios, utilicé el pollTransactionUpdate función: inicia un canal WebSocket y escucha un tipo de evento "aplicado" o "rechazado". Envolví esto en un fácil de usar listenForApplied y agregado a cada llamada asincrónica.

Para obtener el nuevo estado (equivalente a GET), solo necesita usar el prueba funcionen en el wavelet-client que obtendrá el estado actual de la instancia local del contrato inteligente.

Aquí hay un ejemplo con una sincronización getPost () y async payPost () de la página Detalles de la publicación.

Nuevamente, no hay bases de datos, no hay servidores de ida y vuelta. Dulce.

Listado de cuadrícula

Las publicaciones están ordenadas por puntuación, pero no quería una lista. Sentí que una grilla tipo periódico sería lo mejor. Los cuadros de texto se deben estirar según las longitudes de Título de publicación y Extracto y se deben organizar para que quepan en la pantalla la mayor cantidad posible. Afortunadamente, también hay una biblioteca JavasScript para esto; Shuffle JS (https://vestride.github.io/Shuffle/).

Solo tuve que mover mi código de filtrado de Post a Shuffle JS y se estableció el Listado (con animaciones y todo). Dulce.

No hay forma de evitarlo, el SEO es importante y, aunque Google Bot puede rastrear sitios dinámicos, siempre hay otras plataformas que pueden o no ser tan capaces. El contenido prestado por el servidor siempre es una apuesta segura.

También obtienes un aumento de rendimiento cuando tu contenido ya está generado y listo para ser enviado al navegador. Aquí es donde Gatsby entra en juego la integración.

Para recopilar las publicaciones de Wavelet, creé un complemento de origen que utiliza la biblioteca wavelet-client-js para cargar el contrato inteligente y obtener todas las publicaciones.

Un problema difícil de resolver fue manejar la situación en la que no hay contenido, lo que causa múltiples errores de GraphQL y rompe la compilación.

Cuando el complemento recibe contenido, crea algunos campos (allRevealPost y RevealPost). Si no hay contenido, faltan estos campos y las consultas fallan. En algunos casos, se pueden detectar los errores, como createPages en gatsby-node.js pero algunos no pueden (o al menos no sé cómo): como listQuery del componente IndexPage. O eso pensé.

Resultó que no pude detectar el error de GraphQL en ningún lado, por lo que la única solución era agregar manualmente los campos al esquema a través de createSchemaCustomization funcionar dentro del código del complemento.

Cambios de flujo

En la aplicación React inicial, el usuario se vio obligado a iniciar sesión antes de poder visitar cualquier página. Esto no funcionará ahora porque no hay estado en un sitio generado por Gatsby, y los bots deberían poder rastrear el sitio. Para este tipo de situación, Gatsby recomienda crear un Aplicación Híbrida, en el que una parte del sitio es solo un cliente (https://www.gatsbyjs.org/docs/building-apps-with-gatsby).

De esta forma, las páginas de inicio de sesión, listado y detalles son públicas y se muestran en el servidor, mientras que las páginas Crear y Equilibrar son solo para clientes y requieren una billetera.

No todo el código JS se crea igual …

… algunos no pueden ejecutarse en el servidor.

Por ejemplo, Pluma y Shuffle JS Las bibliotecas solo pueden ejecutarse en el cliente cuando acceden a un DOM. No es necesario molestar al servidor con tales asuntos.

Para que las cosas funcionen, el código del cliente debe ejecutarse después del montaje del componente (dentro del gancho useEffect o el método clásico componentDidMount).

Además, dado que el complemento de origen ya ha obtenido las Publicaciones en el momento de la compilación, no era necesario volver a ejecutar wavelet-client-js en el lado del servidor. Así que moví las llamadas a cualquier recurso relacionado con SmartContract para que suceda solo después del montaje del componente.

Hubo algunas situaciones en las que necesitaba verificar si el código debería ejecutarse dependiendo del entorno.

Una de esas situaciones estaba en la página de listado: el diseño debe evitar que su componente secundario Home llame getPosts funcionar antes en eso. Para garantizar que esto nunca suceda, se representa un componente LoadingSpinner en lugar de Home, hasta que Smart Contract init () haya finalizado. Esta fue una buena solución para el navegador, pero evitó que el servidor mostrara las Publicaciones. Una solución fue verificar la variable de la ventana y solo renderizar la animación de carga en el lado del cliente.

Despliegue

Es importante saber qué ofrece Gatsby y qué no puede ofrecer. No puede manejar contenido / estado dinámico. Es rápido y compatible con SEO, pero debes a mano reconstruirlo para tener el contenido más reciente.

En mi caso, quería que el editor (y todos los demás) pudieran ver su publicación recién publicada en la página de listado sin tener que esperar a otra versión de Gatsby. Para que esto suceda, el código del lado del cliente siempre realizará una segunda búsqueda con el contenido más reciente y se superpondrá sobre la página generada por el servidor. Una vez finalizada la reconstrucción, el servidor puede proporcionar cosas nuevas a cualquier bot curioso.

El sitio se implementa en Netlify (https://www.netlify.com/) con un WebHook para activar compilaciones repetidamente. La idea era hacer que una función AWS Lambda llamara a un enlace cada 30 minutos, lo que debería reconstruir el sitio.

Para la función Lambda, utilicé algún nodo con un enfoque https más detallado. No podría molestarme en agregar las dependencias de NPM a la función lambda.

Al igual que con cada gran dApp, nunca te quedas sin cosas que hacer. Algunos de ellos son:

Archivado de publicaciones: dado que el Contrato inteligente almacena todas las publicaciones, será demasiado grande. Lo mejor sería imponer una tarifa de publicación en función de la disponibilidad. Las publicaciones deben eliminarse / archivarse cuando expire su tiempo. Mejorar el editor Quill: los editores deben poder insertar cualquier cosa como un iframe. El uso de contenido incrustado podría ser una solución fácil a las tarifas de contratos inteligentes. API para aplicaciones de terceros: Esto permitiría que cualquier CMS o sitio use fácilmente el paywall de un solo clic. Agregar imagen: los editores podrían cargar cualquier imagen o miniatura a las estimaciones de costos de la red IPFSadd: actualmente, publicar un artículo es como disparar en la oscuridad, no sabes cuánto cuesta hasta después de enviarlo. Debe haber un contador de palabras y una forma de obtener un costo estimado.

No creo que pueda hacerlo solo, por lo que cualquier contribución es bienvenida.

Ahora, algunas de estas cosas pueden ser evidentes para cualquiera que sepa algo sobre el desarrollo de dApps, Smart Contracts y Gatsby. Supongo que solo quería aprender algunos de los conceptos de la manera difícil. Con suerte, si eres como yo y has leído esto, podrás esquivar algunos de estos desafíos.

Espero que esto pueda ayudar con algunas de las partes más difíciles de escribir su primer dApp con React, Gatsby y Wavelet. Además, no puedo esperar para ver lo que las personas pueden crear en esta nueva red Wavelet.