The Graph: corrección de las consultas de datos de Web3
Esta vez, analizaremos más de cerca The Graph, que esencialmente se convirtió en parte de la pila estándar para el desarrollo de dapps el año pasado. Veamos primero cómo haríamos las cosas de la manera tradicional...
Sin The Graph...
Veamos un ejemplo sencillo a fines ilustrativos. A todos nos gustan los juegos, así que imagine un juego simple en el que los usuarios hacen apuestas:
1pragma solidity 0.7.1;23contract Game {4 uint256 totalGamesPlayerWon = 0;5 uint256 totalGamesPlayerLost = 0;6 event BetPlaced(address player, uint256 value, bool hasWon);78 function placeBet() external payable {9 bool hasWon = evaluateBetForPlayer(msg.sender);1011 if (hasWon) {12 (bool success, ) = msg.sender.call{ value: msg.value * 2 }('');13 require(success, "Error en la transferencia");14 totalGamesPlayerWon++;15 } else {16 totalGamesPlayerLost++;17 }1819 emit BetPlaced(msg.sender, msg.value, hasWon);20 }21}Mostrar todoAhora, digamos que en nuestra dapp queremos mostrar el total de apuestas, el total de partidas perdidas/ganadas y también actualizarlo cada vez que alguien vuelva a jugar. El enfoque sería el siguiente:
- Obtener
totalGamesPlayerWon. - Obtener
totalGamesPlayerLost. - Suscribirse a los eventos
BetPlaced.
Podemos escuchar el evento en Web3opens in a new tab como se muestra a la derecha, pero requiere gestionar bastantes casos.
1GameContract.events.BetPlaced({2 fromBlock: 03}, function(error, event) { console.log(event); })4.on('data', function(event) {5 // evento disparado6})7.on('changed', function(event) {8 // el evento fue eliminado de nuevo9})10.on('error', function(error, receipt) {11 // tx rechazada12});Mostrar todoAhora bien, esto sigue estando bien para nuestro sencillo ejemplo. Pero digamos que ahora queremos mostrar la cantidad de apuestas perdidas/ganadas solo para el jugador actual. Bueno, no tenemos suerte. Sería mejor desplegar un nuevo contrato que almacene esos valores y los recupere. Ahora imagine un contrato inteligente y una dapp mucho más complicados; las cosas se pueden descontrolar rápidamente.
Puede ver que esto no es óptimo:
- No funciona para contratos ya desplegados.
- Costos de gas adicionales por almacenar esos valores.
- Se requiere otra llamada para obtener los datos de un nodo de Ethereum.
Veamos ahora una mejor solución.
Permítame presentarle GraphQL
Primero, hablemos de GraphQL, diseñado e implementado originalmente por Facebook. Puede que esté familiarizado con el modelo de API REST tradicional. Ahora imagine que, en su lugar, pudiera escribir una consulta para obtener exactamente los datos que desea:
Las dos imágenes capturan muy bien la esencia de GraphQL. Con la consulta de la derecha podemos definir exactamente qué datos queremos, así que ahí recibimos todo en una única solicitud y nada más que exactamente lo que necesitamos. Un servidor de GraphQL se encarga de obtener todos los datos necesarios, por lo que es increíblemente fácil de usar para el consumidor del frontend. Esta es una buena explicaciónopens in a new tab de cómo gestiona exactamente el servidor una consulta, por si le interesa.
Ahora, con ese conocimiento, vayamos finalmente al campo de la cadena de bloques y The Graph.
¿Qué es The Graph?
Una cadena de bloques es una base de datos descentralizada, pero, a diferencia de lo habitual, no tenemos un lenguaje de consulta para esta base de datos. Las soluciones para recuperar datos son complejas o completamente imposibles. The Graph es un protocolo descentralizado para indexar y consultar datos de la cadena de bloques. Y, como habrá adivinado, utiliza GraphQL como lenguaje de consulta.
Los ejemplos son siempre la mejor manera de entender algo, así que utilicemos The Graph para nuestro ejemplo de GameContract.
Cómo crear un subgrafo
La definición de cómo indexar datos se denomina subgrafo. Requiere tres componentes:
- Manifiesto (
subgraph.yaml) - Esquema (
schema.graphql) - Mapeo (
mapping.ts)
Manifiesto (subgraph.yaml)
El manifiesto es nuestro archivo de configuración y define:
- qué contratos inteligentes se deben indexar (dirección, red, ABI...)
- qué eventos escuchar
- otros aspectos a escuchar, como llamadas a funciones o bloques
- las funciones de mapeo a las que se llama (véase
mapping.tsmás abajo)
Aquí puede definir múltiples contratos y manejadores (handlers). Una configuración típica tendría una carpeta de subgrafos dentro del proyecto de Hardhat con su propio repositorio. Así, puede hacer referencia fácilmente al ABI.
Por comodidad, también puede que quiera utilizar una herramienta de plantillas como mustache. A continuación, cree un subgraph.template.yaml e inserte las direcciones basándose en los despliegues más recientes. Para ver un ejemplo de configuración más avanzada, consulte, por ejemplo, el repositorio del subgrafo de Aaveopens in a new tab.
Y la documentación completa se puede consultar aquíopens in a new tab.
1specVersion: 0.0.12description: Realizando apuestas en Ethereum3repository: - Enlace de GitHub -4schema:5 file: ./schema.graphql6dataSources:7 - kind: ethereum/contract8 name: GameContract9 network: mainnet10 source:11 address: '0x2E6454...cf77eC'12 abi: GameContract13 startBlock: 617524414 mapping:15 kind: ethereum/events16 apiVersion: 0.0.117 language: wasm/assemblyscript18 entities:19 - GameContract20 abis:21 - name: GameContract22 file: ../build/contracts/GameContract.json23 eventHandlers:24 - event: PlacedBet(address,uint256,bool)25 handler: handleNewBet26 file: ./src/mapping.tsMostrar todoEsquema (schema.graphql)
El esquema es la definición de datos de GraphQL. Le permitirá definir qué entidades existen y sus tipos. Los tipos admitidos de The Graph son:
- Bytes
- ID
- String
- Boolean
- Int
- BigInt
- BigDecimal
También puede utilizar entidades como tipo para definir relaciones. En nuestro ejemplo, definimos una relación de uno a muchos (1-to-many) de jugador a apuestas. El ! significa que el valor no puede estar vacío. La documentación completa se puede consultar aquíopens in a new tab.
1type Bet @entity {2 id: ID!3 player: Player!4 playerHasWon: Boolean!5 time: Int!6}78type Player @entity {9 id: ID!10 totalPlayedCount: Int11 hasWonCount: Int12 hasLostCount: Int13 bets: [Bet]!14}Mostrar todoMapeo (mapping.ts)
El archivo de mapeo de The Graph define nuestras funciones que transforman los eventos entrantes en entidades. Está escrito en AssemblyScript, un subconjunto de TypeScript. Esto significa que puede ser compilado en WASM (WebAssembly) para una ejecución más eficiente y portátil del mapeo.
Tendrá que definir cada función nombrada en el archivo subgraph.yaml, por lo que en nuestro caso solo necesitamos una: handleNewBet. Primero, intentamos cargar la entidad Player desde la dirección del remitente como id. Si no existe, crearemos una nueva entidad y la llenaremos con valores iniciales.
A continuación, creamos una nueva entidad Bet. El id para esto será event.transaction.hash.toHex() + "-" + event.logIndex.toString(), lo que garantiza que el valor sea siempre único. Usar solo el hash no es suficiente, ya que alguien podría estar llamando a la función placeBet varias veces en una transacción a través de un contrato inteligente.
Por último, podemos actualizar la entidad Player con todos los datos. No se pueden añadir elementos directamente a los arrays, sino que deben actualizarse como se muestra aquí. Utilizamos el id para hacer referencia a la apuesta. Y se requiere .save() al final para almacenar una entidad.
La documentación completa se puede consultar aquí: https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappingsopens in a new tab. También puede añadir resultados de registro al archivo de mapeo, consulte aquíopens in a new tab.
1import { Bet, Player } from "../generated/schema"2import { PlacedBet } from "../generated/GameContract/GameContract"34export function handleNewBet(event: PlacedBet): void {5 let player = Player.load(event.transaction.from.toHex())67 if (player == null) {8 // se crea si no existe todavía9 player = new Player(event.transaction.from.toHex())10 player.bets = new Array<string>(0)11 player.totalPlayedCount = 012 player.hasWonCount = 013 player.hasLostCount = 014 }1516 let bet = new Bet(17 event.transaction.hash.toHex() + "-" + event.logIndex.toString()18 )19 bet.player = player.id20 bet.playerHasWon = event.params.hasWon21 bet.time = event.block.timestamp22 bet.save()2324 player.totalPlayedCount++25 if (event.params.hasWon) {26 player.hasWonCount++27 } else {28 player.hasLostCount++29 }3031 // actualizar el array de esta forma32 let bets = player.bets33 bets.push(bet.id)34 player.bets = bets3536 player.save()37}Mostrar todoUso en el frontend
Usando algo como Apollo Boost, puede integrar de forma sencilla The Graph en su dapp de React (o Apollo-Vue). Especialmente al usar hooks de React y Apollo, obtener datos es tan simple como escribir una única consulta de GraphQL en su componente. Una configuración típica podría ser la siguiente:
1// Ver todos los subgrafos: https://thegraph.com/explorer/2const client = new ApolloClient({3 uri: "{{ subgraphUrl }}",4})56ReactDOM.render(7 <ApolloProvider client={client}>8 <App />9 </ApolloProvider>,10 document.getElementById("root")11)Mostrar todoY ahora podemos escribir, por ejemplo, una consulta como esta. Esto obtendrá:
- cuántas veces ganó el usuario actual
- cuántas veces perdió el usuario actual
- una lista de marcas de tiempo con todas sus apuestas anteriores
Todo en una sola solicitud al servidor de GraphQL.
1const myGraphQlQuery = gql`2 players(where: { id: $currentUser }) {3 totalPlayedCount4 hasWonCount5 hasLostCount6 bets {7 time8 }9 }10`1112const { loading, error, data } = useQuery(myGraphQlQuery)1314React.useEffect(() => {15 if (!loading && !error && data) {16 console.log({ data })17 }18}, [loading, error, data])Mostrar todoPero nos estaría faltando una última pieza del rompecabezas y es el servidor. Puede ejecutarlo usted mismo o usar el servicio alojado.
El servidor de The Graph
Graph Explorer: el servicio alojado
La forma más fácil es utilizar el servicio alojado. Siga las instrucciones que aparecen aquíopens in a new tab para desplegar un subgrafo. Para muchos proyectos, puede encontrar subgrafos existentes en el exploradoropens in a new tab.
Ejecutar su propio nodo
Como alternativa, puede ejecutar su propio nodo. Documentación aquíopens in a new tab. Una razón para hacer esto podría ser usar una red no admitida por el servicio alojado. Las redes compatibles actualmente se pueden encontrar aquíopens in a new tab.
El futuro descentralizado
GraphQL también admite flujos (streams) para nuevos eventos entrantes. Son compatibles con The Graph a través de Substreamsopens in a new tab, que actualmente se encuentran en beta abierta.
En 2021opens in a new tab, The Graph comenzó su transición a una red de indexación descentralizada. Puede leer más sobre la arquitectura de esta red de indexación descentralizada aquíopens in a new tab.
Dos aspectos clave son:
- Los usuarios pagan a los indexadores por las consultas.
- Los indexadores apuestan Graph Tokens (GRT).
Última actualización de la página: 24 de junio de 2025






