Desarrollando > Creación de un subgrafo

Creación de un subgrafo

Un subgrafo extrae datos de una blockchain, los procesa y los almacena para que puedan consultarse fácilmente mediante GraphQL.

Definir un Subgrafo

La definición del subgrafo consta de unos cuantos archivos:

Para poder utilizar tu subgrafo en la red descentralizada de The Graph, necesitarás crear una clave API. Se recomienda que añadas señal a tu subgrafo con al menos 10.000 GRT.

Antes de entrar en detalles sobre el contenido del archivo de manifiesto, es necesario instalar el Graph CLI que necesitarás para construir y deployar un subgrafo.

Instalar The Graph CLI

Enlace a esta sección

The Graph CLI está escrito en JavaScript, y tendrás que instalar yarn o npm para utilizarlo; se asume que tienes yarn en lo que sigue.

Una vez que tengas yarn, instala The Graph CLI ejecutando

Instalar con yarn:

yarn global add @graphprotocol/graph-cli

Instalar con npm:

npm install -g @graphprotocol/graph-cli

Una vez instalado, el comando graph init se puede utilizar para configurar un nuevo proyecto de subgrafo, ya sea a partir de un contrato existente o a partir de un subgrafo de ejemplo. Este comando se puede utilizar para crear un subgrafo en Subgraph Studio al pasar graph init --product subgraph-studio. Si ya tienes un contrato inteligente deployado en tu red preferida, crear un nuevo subgrafo a partir de ese contrato puede ser una buena manera de empezar.

Desde un Contrato Existente

Enlace a esta sección

El siguiente comando crea un subgrafo que indexa todos los eventos de un contrato existente. Intenta obtener la ABI del contrato desde Etherscan y vuelve a solicitar una ruta de archivo local. Si falta alguno de los argumentos opcionales, te lleva a través de un formulario interactivo.

graph init \
--product subgraph-studio
--from-contract <CONTRACT_ADDRESS> \
[--network <ETHEREUM_NETWORK>] \
[--abi <FILE>] \
<SUBGRAPH_SLUG> [<DIRECTORY>]

El <SUBGRAPH_SLUG> es el ID de tu subgrafo en Subgraph Studio, y se puede encontrar en la página de detalles de tu subgrafo.

Desde un Subgrafo de Ejemplo

Enlace a esta sección

El segundo modo que admite graph init es la creación de un nuevo proyecto a partir de un subgrafo de ejemplo. El siguiente comando lo hace:

graph init --studio <SUBGRAPH_SLUG>

El subgrafo de ejemplo se basa en el contrato Gravity de Dani Grant que gestiona los avatares de los usuarios y emite NewGravatar o UpdateGravatar cada vez que se crean o actualizan los avatares. El subgrafo maneja estos eventos escribiendo entidades Gravatar en el almacén de Graph Node y asegurándose de que éstas se actualicen según los eventos. Las siguientes secciones repasarán los archivos que componen el manifiesto del subgrafo para este ejemplo.

Añadir nuevas fuentes de datos a un subgrafo existente

Enlace a esta sección

Desde v0.31.0, graph-cli permite añadir nuevos dataSources a un subgrafo existente mediante el comando graph add.

graph add <address> [<subgraph-manifest default: "./subgraph.yaml">]
Options:
--abi <path> Path to the contract ABI (default: download from Etherscan)
--contract-name Name of the contract (default: Contract)
--merge-entities Whether to merge entities with the same name (default: false)
--network-file <path> Networks config file path (default: "./networks.json")

El comando add obtendrá el ABI de Etherscan (a menos que se especifique una ruta ABI con la opción --abi), y creará un nuevo dataSource de la misma manera que el comando graph init crea un dataSource --from-contract, actualizando el esquema y los mappings de manera acorde.

La opción --merge-entities identifica cómo el desarrollador desea manejar los conflictos de nombres de entity y event:

  • Si es true: el nuevo dataSource debe utilizar los eventHandlers& entities existentes.
  • Si es false: se creará una nueva entidad & event handler con ${dataSourceName}{EventName}.

La address del contrato se escribirá en el archivo networks.json para la red correspondiente.

Nota: Cuando se utiliza el cli interactivo, después de ejecutar correctamente graph init, se te pedirá que añadas un nuevo dataSource.

El Manifiesto de Subgrafo

Enlace a esta sección

El manifiesto del subgrafo subgraph.yaml define los contratos inteligentes que indexa tu subgrafo, a qué eventos de estos contratos prestar atención, y cómo mapear los datos de los eventos a las entidades que Graph Node almacena y permite consultar. La especificación completa de los manifiestos de subgrafos puede encontrarse en here.

Para este subgrafo de ejemplo, subgraph.yaml es:

specVersion: 0.0.4
description: Gravatar for Ethereum
repository: https://github.com/graphprotocol/graph-tooling
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: Gravity
network: mainnet
source:
address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'
abi: Gravity
startBlock: 6175244
endBlock: 7175245
context:
foo:
type: Bool
data: true
bar:
type: String
data: 'bar'
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- Gravatar
abis:
- name: Gravity
file: ./abis/Gravity.json
eventHandlers:
- event: NewGravatar(uint256,address,string,string)
handler: handleNewGravatar
- event: UpdatedGravatar(uint256,address,string,string)
handler: handleUpdatedGravatar
callHandlers:
- function: createGravatar(string,string)
handler: handleCreateGravatar
blockHandlers:
- handler: handleBlock
- handler: handleBlockWithCall
filter:
kind: call
file: ./src/mapping.ts

Las entradas importantes a actualizar para el manifiesto son:

  • description: a human-readable description of what the subgraph is. This description is displayed by the Graph Explorer when the subgraph is deployed to the hosted service.

  • repository: la URL del repositorio donde se encuentra el manifiesto del subgrafo. También se muestra en The Graph Explorer.

  • features: una lista de todos los nombres de las feature usadas.

  • dataSources.source: la dirección del contrato inteligente del que procede el subgrafo, y la ABI del contrato inteligente a utilizar. La dirección es opcional; omitirla permite indexar eventos coincidentes de todos los contratos.

  • dataSources.source.startBlock: el número opcional del bloque desde el que la fuente de datos comienza a indexar. En la mayoría de los casos, sugerimos utilizar el bloque en el que se creó el contrato.

  • dataSources.source.endBlock: The optional number of the block that the data source stops indexing at, including that block. Minimum spec version required: 0.0.9.

  • dataSources.context: key-value pairs that can be used within subgraph mappings. Supports various data types like Bool, String, Int, Int8, BigDecimal, Bytes, List, and BigInt. Each variable needs to specify its type and data. These context variables are then accessible in the mapping files, offering more configurable options for subgraph development.

  • dataSources.mapping.entities: las entidades que la fuente de datos escribe en el almacén. El esquema de cada entidad se define en el archivo schema.graphql.

  • dataSources.mapping.abis: uno o más archivos ABI con nombre para el contrato fuente, así como cualquier otro contrato inteligente con el que interactúes desde los mappings.

  • dataSources.mapping.eventHandlers: enumera los eventos de contratos inteligentes a los que reacciona este subgrafo y los handlers en el mapping -./src/mapping.ts en el ejemplo- que transforman estos eventos en entidades en el almacén.

  • dataSources.mapping.callHandlers: enumera las funciones de contrato inteligente a las que reacciona este subgrafo y los handlers en el mapping que transforman las entradas y salidas a las llamadas de función en entidades en el almacén.

  • dataSources.mapping.blockHandlers: enumera los bloques a los que reacciona este subgrafo y los handlers en el mapping que se ejecutan cuando se agrega un bloque a la cadena. Sin un filtro, el handler de bloque se ejecutará cada bloque. Se puede proporcionar un filtro de llamada opcional añadiendo un campo con filter field with kind: call al handler. Este sólo ejecutará el handler si el bloque contiene al menos una llamada al contrat de la fuente de datos.

Un único subgrafo puede indexar datos de múltiples contratos inteligentes. Añade una entrada por cada contrato del que haya que indexar datos a la array dataSources.

Las triggers de una fuente de datos dentro de un bloque se ordenan mediante el siguiente proceso:

  1. Las triggers de eventos y calls se ordenan primero por el índice de la transacción dentro del bloque.
  2. Los triggers de eventos y calls dentro de la misma transacción se ordenan siguiendo una convención: primero los triggers de eventos y luego los de calls, respetando cada tipo el orden en que se definen en el manifiesto.
  3. Las triggers de bloques se ejecutan después de las triggers de eventos y calls, en el orden en que están definidos en el manifiesto.

Estas normas de orden están sujetas a cambios.

Obtención de ABIs

Enlace a esta sección

Los archivos ABI deben coincidir con tu(s) contrato(s). Hay varias formas de obtener archivos ABI:

  • Si estás construyendo tu propio proyecto, es probable que tengas acceso a tus ABIs más actuales.
  • Si estás construyendo un subgrafo para un proyecto público, puedes descargar ese proyecto en tu computadora y obtener la ABI utilizando truffle compile o usando solc para compilar.
  • También puedes encontrar la ABI en Etherscan, pero no siempre es fiable, ya que la ABI que se sube allí puede estar desactualizada. Asegúrate de que tienes la ABI correcta, de lo contrario la ejecución de tu subgrafo fallará.

El Esquema GraphQL

Enlace a esta sección

El esquema para tu subgrafo está en el archivo schema.graphql. Los esquemas GraphQL se definen utilizando el lenguaje de definición de interfaces GraphQL. Si nunca has escrito un esquema GraphQL, te recomendamos que le eches un vistazo a este manual sobre el sistema de tipos GraphQL. La documentación de referencia para los esquemas GraphQL se puede encontrar en la sección GraphQL API.

Definir Entidades

Enlace a esta sección

Antes de definir las entidades, es importante dar un paso atrás y pensar en cómo están estructurados y vinculados los datos. Todas las consultas se harán contra el modelo de datos definido en el esquema del subgrafo y las entidades indexadas por el subgrafo. Debido a esto, es bueno definir el esquema del subgrafo de una manera que coincida con las necesidades de tu dapp. Puede ser útil imaginar las entidades como "objetos que contienen datos", más que como eventos o funciones.

Con The Graph, simplemente defines los tipos de entidad en schema.graphql y Graph Node generará campos de nivel superior para consultar instancias individuales y colecciones de ese tipo de entidad. Cada tipo que deba ser una entidad debe ser anotado con una directiva @entity. Por defecto, las entidades son mutables, lo que significa que los mappings pueden cargar entidades existentes, modificarlas y almacenar una nueva versión de esa entidad. La mutabilidad tiene un precio, y para los tipos de entidades que se sabe que nunca se modificarán, por ejemplo, porque simplemente contienen datos extraídos textualmente de la cadena, se recomienda marcarlas como inmutables con @entity(immutable: true). Los mappings pueden realizar cambios en las entidades inmutables siempre que esos cambios se produzcan en el mismo bloque en el que se creó la entidad. Las entidades inmutables son mucho más rápidas de escribir y de consultar, por lo que deberían utilizarse siempre que sea posible.

Un buen ejemplo

Enlace a esta sección

La entidad Gravatar que aparece a continuación está estructurada en torno a un objeto Gravatar y es un buen ejemplo de cómo podría definirse una entidad.

type Gravatar @entity(immutable: true) {
id: Bytes!
owner: Bytes
displayName: String
imageUrl: String
accepted: Boolean
}

Un mal ejemplo

Enlace a esta sección

El ejemplo las entidades GravatarAccepted y GravatarDeclined que aparecen a continuación se basan en eventos. No se recomienda asignar eventos o calls a funciones a entidades 1:1.

type GravatarAccepted @entity {
id: Bytes!
owner: Bytes
displayName: String
imageUrl: String
}
type GravatarDeclined @entity {
id: Bytes!
owner: Bytes
displayName: String
imageUrl: String
}

Campos opcionales y obligatorios

Enlace a esta sección

Los campos de la entidad pueden definirse como obligatorios u opcionales. Los campos obligatorios se indican con el ! en el esquema. Si un campo obligatorio no está establecido en el mapping, recibirá este error al consultar el campo:

Null value resolved for non-null field 'name'

Cada entidad debe tener un campo id, que debe ser de tipo Bytes! o String!. Por lo general, se recomienda utilizar Bytes!, a menos que el id contenga texto legible por humanos, ya que las entidades con ids de Bytes! serán más rápidas de escribir y consultar que las que tienen un id de String! El campo id sirve como clave primaria y debe ser único entre todas las entidades del mismo tipo. Por razones históricas, el tipo ID! también se acepta y es un sinónimo de String!.

Para algunos tipos de entidad, el id se construye a partir de los id de otras dos entidades; esto es posible utilizando concat, por ejemplo, let id = left.id.concat(right.id) para formar el id a partir de los id de left y right. Del mismo modo, para construir un id a partir del id de una entidad existente y un contador, se puede utilizar count y let id = left.id.concatI32(count). Se garantiza que la concatenación produzca id's únicos siempre que la longitud de left sea la misma para todas esas entidades, por ejemplo, porque left.id es una Address.

Tipos de Scalars incorporados

Enlace a esta sección

GraphQL admite Scalars

Enlace a esta sección

Admitimos los siguientes escalares en nuestra API GraphQL:

TipoDescripción
BytesByte array, representado como un string hexadecimal. Comúnmente utilizado para los hashes y direcciones de Ethereum.
StringEscalar para valores string. Los caracteres nulos no son compatibles y se eliminan automáticamente.
BooleanEscalar para valores boolean.
IntLa especificación GraphQL define Int con un tamaño de 32 bytes.
Int8An 8-byte signed integer, also known as a 64-bit signed integer, can store values in the range from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. Prefer using this to represent i64 from ethereum.
BigIntNúmeros enteros grandes. Se utiliza para los tipos uint32, int64, uint64, ..., uint256 de Ethereum. Nota: Todo por debajo de uint32, como int32, uint24 o int8 se representa como i32.
BigDecimalBigDecimal Decimales de alta precisión representados como un signo y un exponente. El rango de exponentes va de -6143 a +6144. Redondeado a 34 dígitos significativos.

También puedes crear enums dentro de un esquema. Los enums tienen la siguiente sintaxis:

enum TokenStatus {
OriginalOwner
SecondOwner
ThirdOwner
}

Una vez definido el enum en el esquema, puedes utilizar la representación del string del valor del enum para establecer un campo enum en una entidad. Por ejemplo, puedes establecer el tokenStatus a SecondOwner definiendo primero tu entidad y posteriormente estableciendo el campo con entity.tokenStatus = "SecondOwner. El siguiente ejemplo muestra el aspecto de la entidad Token con un campo enum:

Puedes encontrar más detalles sobre la escritura de enums en la GraphQL documentation.

Relaciones entre Entidades

Enlace a esta sección

Una entidad puede tener una relación con otra u otras entidades de su esquema. Estas relaciones pueden ser recorridas en sus consultas. Las relaciones en The Graph son unidireccionales. Es posible simular relaciones bidireccionales definiendo una relación unidireccional en cada "extremo" de la relación.

Las relaciones se definen en las entidades como cualquier otro campo, salvo que el tipo especificado es el de otra entidad.

Relaciones Uno a Uno

Enlace a esta sección

Define un tipo de entidad Transaction con una relación opcional de uno a uno con un tipo de entidad TransactionReceipt:

type Transaction @entity(immutable: true) {
id: Bytes!
transactionReceipt: TransactionReceipt
}
type TransactionReceipt @entity(immutable: true) {
id: Bytes!
transaction: Transaction
}

Relaciones one-to-many

Enlace a esta sección

Define un tipo de entidad TokenBalance con una relación requerida de uno a varios con un tipo de entidad Token:

type Token @entity(immutable: true) {
id: Bytes!
}
type TokenBalance @entity {
id: Bytes!
amount: Int!
token: Token!
}

Búsquedas Inversas

Enlace a esta sección

Se pueden definir búsquedas inversas en una entidad a través del campo @derivedFrom. Esto crea un campo virtual en la entidad que puede ser consultado pero que no puede ser establecido manualmente a través de la API de mapping. Más bien, se deriva de la relación definida en la otra entidad. Para este tipo de relaciones, rara vez tiene sentido almacenar ambos lados de la relación, y tanto la indexación como el rendimiento de la consulta serán mejores cuando sólo se almacene un lado y el otro se derive.

En el caso de las relaciones one-to-many, la relación debe almacenarse siempre en el lado "one", y el lado "many" debe derivarse siempre. Almacenar la relación de esta manera, en lugar de almacenar una array de entidades en el lado "many", resultará en un rendimiento dramáticamente mejor tanto para la indexación como para la consulta del subgrafo. En general, debe evitarse, en la medida de lo posible, el almacenamiento de arrays de entidades.

Podemos hacer que los balances de un token sean accesibles desde el token derivando un campo tokenBalances:

type Token @entity(immutable: true) {
id: Bytes!
tokenBalances: [TokenBalance!]! @derivedFrom(field: "token")
}
type TokenBalance @entity {
id: Bytes!
amount: Int!
token: Token!
}

Relaciones de many-to-many

Enlace a esta sección

Para las relaciones de many-to-many, como los usuarios pueden pertenecer a cualquier número de organizaciones, la forma más directa, pero generalmente no la más eficaz, de modelar la relación es en un array en cada una de las dos entidades implicadas. Si la relación es simétrica, sólo es necesario almacenar un lado de la relación y el otro puede derivarse.

Define una búsqueda inversa desde un tipo de entidad User a un tipo de entidad Organization. En el ejemplo siguiente, esto se consigue buscando el atributo members desde la entidad Organization. En las consultas, el campo organizations en User se resolverá buscando todas las entidades de Organization que incluyan el ID del usuario.

type Organization @entity {
id: Bytes!
name: String!
members: [User!]!
}
type User @entity {
id: Bytes!
name: String!
organizations: [Organization!]! @derivedFrom(field: "members")
}

Una forma más eficaz de almacenar esta relación es a través de una tabla de mapping que tiene una entrada para cada par User / Organization con un esquema como

type Organization @entity {
id: Bytes!
name: String!
members: [UserOrganization!]! @derivedFrom(field: "organization")
}
type User @entity {
id: Bytes!
name: String!
organizations: [UserOrganization!] @derivedFrom(field: "user")
}
type UserOrganization @entity {
id: Bytes! # Set to `user.id.concat(organization.id)`
user: User!
organization: Organization!
}

Este enfoque requiere que las consultas desciendan a un nivel adicional para recuperar, por ejemplo, las organizaciones para los usuarios:

query usersWithOrganizations {
users {
organizations {
# this is a UserOrganization entity
organization {
name
}
}
}
}

Esta forma más elaborada de almacenar las relaciones many-to-many se traducirá en menos datos almacenados para el subgrafo y, por tanto, en un subgrafo que suele ser mucho más rápido de indexar y consultar.

Agregar comentarios al esquema

Enlace a esta sección

Según la especificación GraphQL, se pueden añadir comentarios por encima de los atributos de entidad del esquema utilizando comillas dobles "". Esto se ilustra en el siguiente ejemplo:

type MyFirstEntity @entity {
"unique identifier and primary key of the entity"
id: Bytes!
address: Bytes!
}

Definición de campos de búsqueda de texto completo

Enlace a esta sección

Las consultas de búsqueda de texto completo filtran y clasifican las entidades basándose en una entrada de búsqueda de texto. Las consultas de texto completo pueden devolver coincidencias de palabras similares procesando el texto de la consulta en stems antes de compararlo con los datos del texto indexado.

La definición de una consulta de texto completo incluye el nombre de la consulta, el diccionario lingüístico utilizado para procesar los campos de texto, el algoritmo de clasificación utilizado para ordenar los resultados y los campos incluidos en la búsqueda. Cada consulta de texto completo puede abarcar varios campos, pero todos los campos incluidos deben ser de un solo tipo de entidad.

Para agregar una consulta de texto completo, incluye un tipo _Schema_ con una directiva de texto completo en el esquema GraphQL.

type _Schema_
@fulltext(
name: "bandSearch"
language: en
algorithm: rank
include: [{ entity: "Band", fields: [{ name: "name" }, { name: "description" }, { name: "bio" }] }]
)
type Band @entity {
id: Bytes!
name: String!
description: String!
bio: String
wallet: Address
labels: [Label!]!
discography: [Album!]!
members: [Musician!]!
}

El ejemplo campo bandSearch se puede utilizar en las consultas para filtrar las entidades Band con base en los documentos de texto en los campos name, description, y bio. Ve a GraphQL API - Queries para ver una descripción de la API de búsqueda de texto completo y más ejemplos de uso.

query {
bandSearch(text: "breaks & electro & detroit") {
id
name
description
wallet
}
}

Feature Management: Desde specVersion 0.0.4 y en adelante, fullTextSearch se debe declarar bajo la sección features en el manifiesto del subgrafo.

Idiomas admitidos

Enlace a esta sección

La elección de un idioma diferente tendrá un efecto definitivo, aunque a veces sutil, en la API de búsqueda de texto completo. Los campos cubiertos por un campo de consulta de texto completo se examinan en el contexto de la lengua elegida, por lo que los lexemas producidos por las consultas de análisis y búsqueda varían de un idioma a otro. Por ejemplo: al utilizar el diccionario turco compatible, "token" se convierte en "toke", mientras que el diccionario inglés lo convierte en "token".

Diccionarios de idiomas admitidos:

CódigoDiccionario
simpleGeneral
daDanés
nlHolandés
enInglés
fiFinlandés
frFrancés
deAlemán
huHúngaro
itItaliano
noNoruego
ptPortugués
roRumano
ruRuso
esEspañol
svSueco
trTurco

Algoritmos de Clasificación

Enlace a esta sección

Algoritmos admitidos para ordenar los resultados:

AlgoritmosDescripción
rangoUsa la calidad de coincidencia (0-1) de la consulta de texto completo para ordenar los resultados.
rango de proximidadSimilar al rango, pero también incluye la proximidad de los matches.

Escribir Mappings

Enlace a esta sección

Los mapeos toman datos de una fuente particular y los transforman en entidades que están definidas dentro de su esquema. Los mapeos se escriben en un subconjunto de TypeScript llamado AssemblyScript que puede compilarse a WASM (WebAssembly). AssemblyScript es más estricto que TypeScript normal, pero proporciona una sintaxis familiar.

Para cada handler de eventos que se define en subgraph.yaml bajo mapping.eventHandlers, crea una función exportada del mismo nombre. Cada handler debe aceptar un único parámetro llamado event con un tipo correspondiente al nombre del evento que se está manejando.

En el subgrafo de ejemplo, src/mapping.ts contiene handlers para los eventos NewGravatar y UpdatedGravatar:

import { NewGravatar, UpdatedGravatar } from '../generated/Gravity/Gravity'
import { Gravatar } from '../generated/schema'
export function handleNewGravatar(event: NewGravatar): void {
let gravatar = new Gravatar(event.params.id)
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.imageUrl = event.params.imageUrl
gravatar.save()
}
export function handleUpdatedGravatar(event: UpdatedGravatar): void {
let id = event.params.id
let gravatar = Gravatar.load(id)
if (gravatar == null) {
gravatar = new Gravatar(id)
}
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.imageUrl = event.params.imageUrl
gravatar.save()
}

El primer handler toma un evento NewGravatar y crea una nueva entidad Gravatar con new Gravatar(event.params.id.toHex()), poblando los campos de la entidad usando los parámetros correspondientes del evento. Esta instancia de entidad está representada por la variable gravatar, con un valor de id de event.params.id.toHex().

El segundo handler intenta cargar el Gravatar existente desde el almacén de The Graph Node. Si aún no existe, se crea bajo demanda. A continuación, la entidad se actualiza para que coincida con los nuevos parámetros del evento, antes de volver a guardarla en el almacén mediante gravatar.save().

ID recomendados para la creación de nuevas entidades

Enlace a esta sección

Cada entidad tiene que tener un id que es único entre todas las entidades del mismo tipo. El valor del id de una entidad se establece cuando se crea la entidad. A continuación se muestran algunos valores de id recomendados a tener en cuenta al crear nuevas entidades. NOTA: El valor de id debe ser un string.

  • event.params.id.toHex()
  • event.transaction.from.toHex()
  • event.transaction.hash.toHex() + "-" + event.logIndex.toString()

We provide the Graph Typescript Library which contains utilities for interacting with the Graph Node store and conveniences for handling smart contract data and entities. You can use this library in your mappings by importing @graphprotocol/graph-ts in mapping.ts.

Generación de código

Enlace a esta sección

Para que trabajar con contratos inteligentes, eventos y entidades sea fácil y seguro desde el punto de vista de los tipos, Graph CLI puede generar tipos AssemblyScript a partir del esquema GraphQL del subgrafo y de las ABIs de los contratos incluidas en las fuentes de datos.

Esto se hace con

graph codegen [--output-dir <OUTPUT_DIR>] [<MANIFEST>]

pero en la mayoría de los casos, los subgrafos ya están preconfigurados a través de package.json para permitirte simplemente ejecutar uno de los siguientes para lograr lo mismo:

# Yarn
yarn codegen
# NPM
npm run codegen

Esto generará una clase AssemblyScript para cada contrato inteligente en los archivos ABI mencionados en subgraph.yaml, permitiéndote vincular estos contratos a direcciones específicas en los mappings y llamar a métodos de contrato de sólo lectura contra el bloque que se está procesando. También generará una clase para cada evento del contrato para facilitar el acceso a los parámetros del evento, así como el bloque y la transacción que originó el evento. Todos estos tipos se escriben en <OUTPUT_DIR>/<DATA_SOURCE_NAME>/<ABI_NAME>.ts. En el subgrafo de ejemplo, esto sería generated/Gravity/Gravity.ts, permitiendo mappings con los que importar estos tipos.

import {
// The contract class:
Gravity,
// The events classes:
NewGravatar,
UpdatedGravatar,
} from '../generated/Gravity/Gravity'

Además, se genera una clase para cada tipo de entidad en el esquema GraphQL del subgrafo. Estas clases proporcionan una carga de entidades segura, acceso de lectura y escritura a los campos de la entidad, así como un método save() para escribir entidades en el almacén. Todas las clases de entidades se escriben en <OUTPUT_DIR>/schema.ts, permitiendo mappings con los que importarlos

import { Gravatar } from '../generated/schema'

Nota: La generación de código debe realizarse de nuevo después de cada cambio en el esquema GraphQL o en las ABIs incluidas en el manifiesto. También debe realizarse al menos una vez antes de construir o deployar el subgrafo.

La generación de código no comprueba tu código de mapping en src/mapping.ts. Si quieres comprobarlo antes de intentar deployar tu subgrafo en the Graph Explorer, puedes ejecutar yarn build y corregir cualquier error de sintaxis que el compilador de TypeScript pueda encontrar.

Plantillas para fuentes de datos

Enlace a esta sección

Un patrón común en los contratos inteligentes de Ethereum es el uso de contratos de registro o fábrica, donde un contrato crea, gestiona o hace referencia a un número arbitrario de otros contratos que tienen cada uno su propio estado y eventos.

Las direcciones de estos subcontratos pueden o no ser conocidas de antemano y muchos de estos contratos pueden ser creados y/o añadidos con el tiempo. Es por eso que, en tales casos, es imposible definir una sola fuente de datos o un número fijo de fuentes de datos y se necesita un enfoque más dinámico: plantillas de fuente de datos.

Fuente de Datos para el Contrato Principal

Enlace a esta sección

En primer lugar, define una fuente de datos regular para el contrato principal. El siguiente fragmento muestra un ejemplo simplificado de fuente de datos para el contrato generador del exchange Uniswap. Nota el handler NewExchange(address,address) del evento. Esto se emite cuando el contrato de fábrica crea un nuevo contrato de exchange en la cadena.

dataSources:
- kind: ethereum/contract
name: Factory
network: mainnet
source:
address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'
abi: Factory
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
file: ./src/mappings/factory.ts
entities:
- Directory
abis:
- name: Factory
file: ./abis/factory.json
eventHandlers:
- event: NewExchange(address,address)
handler: handleNewExchange

Plantillas de fuentes de datos para contratos creados dinámicamente

Enlace a esta sección

A continuación, añade plantillas de origen de datos al manifiesto. Son idénticas a las fuentes de datos normales, salvo que carecen de una dirección de contrato predefinida en source. Normalmente, defines un modelo para cada tipo de subcontrato gestionado o referenciado por el contrato principal.

dataSources:
- kind: ethereum/contract
name: Factory
# ... other source fields for the main contract ...
templates:
- name: Exchange
kind: ethereum/contract
network: mainnet
source:
abi: Exchange
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
file: ./src/mappings/exchange.ts
entities:
- Exchange
abis:
- name: Exchange
file: ./abis/exchange.json
eventHandlers:
- event: TokenPurchase(address,uint256,uint256)
handler: handleTokenPurchase
- event: EthPurchase(address,uint256,uint256)
handler: handleEthPurchase
- event: AddLiquidity(address,uint256,uint256)
handler: handleAddLiquidity
- event: RemoveLiquidity(address,uint256,uint256)
handler: handleRemoveLiquidity

Instanciación de una plantilla de fuente de datos

Enlace a esta sección

En el último paso, actualiza el mapping del contrato principal para crear una instancia de fuente de datos dinámica a partir de una de las plantillas. En este ejemplo, cambiarías el mapping del contrato principal para importar la plantilla Exchange y llamaría al método Exchange.create(address) en él para empezar a indexar el nuevo contrato de exchange.

import { Exchange } from '../generated/templates'
export function handleNewExchange(event: NewExchange): void {
// Start indexing the exchange; `event.params.exchange` is the
// address of the new exchange contract
Exchange.create(event.params.exchange)
}

Nota: Un nuevo origen de datos sólo procesará las llamadas y los eventos del bloque en el que fue creado y todos los bloques siguientes, pero no procesará los datos históricos, es decir, los datos que están contenidos en bloques anteriores.

Si los bloques anteriores contienen datos relevantes para la nueva fuente de datos, lo mejor es indexar esos datos leyendo el estado actual del contrato y creando entidades que representen ese estado en el momento de crear la nueva fuente de datos.

Contexto de la fuente de datos

Enlace a esta sección

Los contextos de fuentes de datos permiten pasar una configuración extra al instanciar una plantilla. En nuestro ejemplo, digamos que los exchanges se asocian a un par de trading concreto, que se incluye en el evento NewExchange. Esa información se puede pasar a la fuente de datos instanciada, así:

import { Exchange } from '../generated/templates'
export function handleNewExchange(event: NewExchange): void {
let context = new DataSourceContext()
context.setString('tradingPair', event.params.tradingPair)
Exchange.createWithContext(event.params.exchange, context)
}

Dentro de un mapping de la plantilla Exchange, se puede acceder al contexto:

import { dataSource } from '@graphprotocol/graph-ts'
let context = dataSource.context()
let tradingPair = context.getString('tradingPair')

Hay setters y getters como setString and getString para todos los tipos de valores.

Bloques iniciales

Enlace a esta sección

El startBlock es un ajuste opcional que permite definir a partir de qué bloque de la cadena comenzará a indexar la fuente de datos. Establecer el bloque inicial permite a la fuente de datos omitir potencialmente millones de bloques que son irrelevantes. Normalmente, un desarrollador de subgrafos establecerá startBlock al bloque en el que se creó el contrato inteligente de la fuente de datos.

dataSources:
- kind: ethereum/contract
name: ExampleSource
network: mainnet
source:
address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'
abi: ExampleContract
startBlock: 6627917
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
file: ./src/mappings/factory.ts
entities:
- User
abis:
- name: ExampleContract
file: ./abis/ExampleContract.json
eventHandlers:
- event: NewEvent(address,address)
handler: handleNewEvent

Nota: El bloque de creación del contrato se puede buscar rápidamente en Etherscan:

  1. Busca el contrato introduciendo su dirección en la barra de búsqueda.
  2. Haz clic en el hash de la transacción de creación en la sección Contract Creator.
  3. Carga la página de detalles de la transacción, donde encontrarás el bloque inicial de ese contrato.

Call Handlers

Enlace a esta sección

Aunque los eventos proporcionan una forma eficaz de recoger los cambios relevantes en el estado de un contrato, muchos contratos evitan generar registros para optimizar los costos de gas. En estos casos, un subgrafo puede suscribirse a las calls realizadas al contrato de la fuente de datos. Esto se consigue definiendo los call handlers que hacen referencia a la firma de la función y al handler de mapping que procesará las calls a esta función. Para procesar estas calls, el handler de mapping recibirá un ethereum.Call como argumento con las entradas y salidas tipificadas de la call. Las calls realizadas en cualquier profundidad de la cadena de calls de una transacción activarán el mapping, permitiendo capturar la actividad con el contrato de origen de datos a través de los contratos proxy.

Los call handlers solo se activarán en uno de estos dos casos: cuando la función especificada sea llamada por una cuenta distinta del propio contrato o cuando esté marcada como externa en Solidity y sea llamada como parte de otra función en el mismo contrato.

Nota: Los call handlers dependen actualmente de la API de rastreo Parity. Ciertas redes, como BNB chain y Arbitrum, no soportan esta API. Si un subgrafo que indexa una de estas redes contiene uno o más call handlers, no comenzará la sincronización. En su lugar, los developers de subgrafos deberían utilizar handlers de eventos. Estos son mucho más eficaces que los handlers de llamadas, y están soportados en todas las redes evm.

Definición de un Call Handler

Enlace a esta sección

Para definir un call handler en su manifiesto simplemente añade una array callHandlers bajo la fuente de datos a la que deseas suscribirte.

dataSources:
- kind: ethereum/contract
name: Gravity
network: mainnet
source:
address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'
abi: Gravity
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- Gravatar
- Transaction
abis:
- name: Gravity
file: ./abis/Gravity.json
callHandlers:
- function: createGravatar(string,string)
handler: handleCreateGravatar

La function es la firma de la función normalizada por la que se filtran las llamadas. La propiedad handler es el nombre de la función de tu mapping que quieres ejecutar cuando se llame a la función de destino en el contrato de origen de datos.

Función mapeo

Enlace a esta sección

Cada call handler toma un solo parámetro que tiene un tipo correspondiente al nombre de la función llamada. En el subgrafo de ejemplo anterior, el mapping contiene un handler para cuando se llama a la función createGravatar y recibe un parámetro CreateGravatarCall como argumento:

import { CreateGravatarCall } from '../generated/Gravity/Gravity'
import { Transaction } from '../generated/schema'
export function handleCreateGravatar(call: CreateGravatarCall): void {
let id = call.transaction.hash
let transaction = new Transaction(id)
transaction.displayName = call.inputs._displayName
transaction.imageUrl = call.inputs._imageUrl
transaction.save()
}

La función handleCreateGravatar toma una nueva CreateGravatarCall que es una subclase de ethereum.Call, proporcionada por @graphprotocol/graph-ts, que incluye las entradas y salidas tipificadas del call. El tipo CreateGravatarCall se genera por ti cuando ejecutas graph codegen.

Handlers de bloques

Enlace a esta sección

Además de suscribirse a eventos del contracto o calls de funciones, un subgrafo puede querer actualizar sus datos a medida que se añaden nuevos bloques a la cadena. Para ello, un subgrafo puede ejecutar una función después de cada bloque o después de los bloques que coincidan con un filtro predefinido.

Filtros admitidos

Enlace a esta sección
filter:
kind: call

El handler definido será llamado una vez por cada bloque que contenga una llamada al contrato (fuente de datos) bajo el cual está definido el handler.

Nota: El filtro call depende actualmente de la API de rastreo Parity. Ciertas redes, como la cadena BNB y Arbitrum, no soportan esta API. Si un subgrafo que indexa una de estas redes contiene uno o más handlers de bloque con un filtro de call, no comenzará la sincronización.

La ausencia de un filtro para un handler de bloque asegurará que el handler sea llamado en cada bloque. Una fuente de datos solo puede contener un handler de bloque para cada tipo de filtro.

dataSources:
- kind: ethereum/contract
name: Gravity
network: dev
source:
address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'
abi: Gravity
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- Gravatar
- Transaction
abis:
- name: Gravity
file: ./abis/Gravity.json
blockHandlers:
- handler: handleBlock
- handler: handleBlockWithCallToContract
filter:
kind: call

Polling Filter

Enlace a esta sección

Requires specVersion >= 0.0.8

Note: Polling filters are only available on dataSources of kind: ethereum.

blockHandlers:
- handler: handleBlock
filter:
kind: polling
every: 10

The defined handler will be called once for every n blocks, where n is the value provided in the every field. This configuration allows the subgraph to perform specific operations at regular block intervals.

Requires specVersion >= 0.0.8

Note: Once filters are only available on dataSources of kind: ethereum.

blockHandlers:
- handler: handleOnce
filter:
kind: once

The defined handler with the once filter will be called only once before all other handlers run. This configuration allows the subgraph to use the handler as an initialization handler, performing specific tasks at the start of indexing.

export function handleOnce(block: ethereum.Block): void {
let data = new InitialData(Bytes.fromUTF8('initial'))
data.data = 'Setup data here'
data.save()
}

Función mapeo

Enlace a esta sección

La función de mapeo recibirá un ethereum.Block como único argumento. Al igual que las funciones de mapping para eventos, esta función puede acceder a entidades de subgrafos existentes en el almacén, llamar a contratos inteligentes y crear o actualizar entidades.

import { ethereum } from '@graphprotocol/graph-ts'
export function handleBlock(block: ethereum.Block): void {
let id = block.hash
let entity = new Block(id)
entity.save()
}

Eventos anónimos

Enlace a esta sección

Si necesitas procesar eventos anónimos en Solidity, puedes hacerlo proporcionando el tema 0 del evento, como en el ejemplo:

eventHandlers:
- event: LogNote(bytes4,address,bytes32,bytes32,uint256,bytes)
topic0: '0x644843f351d3fba4abcd60109eaff9f54bac8fb8ccf0bab941009c21df21cf31'
handler: handleGive

Un evento solo se activará cuando la firma y el tema 0 coincidan. Por defecto, topic0 es igual al hash de la firma del evento.

Recepción de transacciones en Event Handlers

Enlace a esta sección

A partir de specVersion 0.0.5 y apiVersion 0.0.7, los handlers de eventos pueden tener acceso al recibo de la transacción que los emitió.

Para ello, los handlers de eventos deben declararse en el manifiesto del subgrafo con la nueva clave receipt: true, que es opcional y por defecto es false.

eventHandlers:
- event: NewGravatar(uint256,address,string,string)
handler: handleNewGravatar
receipt: true

Dentro de la función handler, se puede acceder al recibo en el campo Event.receipt. Cuando la clave receipt se establece en false o se omite en el manifiesto, se devolverá un valor null en su lugar.

Características experimentales

Enlace a esta sección

A partir de specVersion 0.0.4, los features de los subgrafos deben declararse explícitamente en la sección de features del nivel superior del archivo de manifiesto, utilizando su nombre en camelCase, como se indica en la tabla siguiente:

CaracterísticaNombre
Errores no fatalesnonFatalErrors
Full-text SearchfullTextSearch
Graftinggrafting
IPFS on Ethereum ContractsipfsOnEthereumContracts o nonDeterministicIpfs

Por ejemplo, si un subgrafo utiliza las características Full-Text Search y Non-fatal Errors, el campo features del manifiesto debería ser:

specVersion: 0.0.4
description: Gravatar for Ethereum
features:
- fullTextSearch
- nonFatalErrors
dataSources: ...

Ten en cuenta que el uso de una característica sin declararla incurrirá en un error de validación durante el deploy del subgrafo, pero no se producirá ningún error si se declara una característica pero no se utiliza.

IPFS on Ethereum Contracts

Enlace a esta sección

Un caso de uso común para combinar IPFS con Ethereum es almacenar datos en IPFS que serían demasiado costosos de mantener on-chain, y hacer referencia al hash de IPFS en los contratos de Ethereum.

Dados estos hashes IPFS, los subgrafos pueden leer los archivos correspondientes desde IPFS usando ipfs.cat e ipfs.map. Para hacer esto de forma fiable, se requiere que estos archivos estén anclados a un nodo IPFS con alta disponibilidad, de modo que el nodo IPFS del Servicio Alojado pueda encontrarlos durante la indexación.

Nota: The Graph Network todavía no admite ipfs.cat y ipfs.map, y los desarrolladores no deben deployar subgrafos que utilicen esa funcionalidad en la red a través de Studio.

La Gestión de Features: ipfsOnEthereumContracts debe declararse en features en el manifiesto del subgrafo. Para cadenas que no sean EVM, el alias nonDeterministicIpfs también puede utilizarse con el mismo fin.

Cuando se ejecuta un Graph Node local, debe establecerse la variable de entorno GRAPH_ALLOW_NON_DETERMINISTIC_IPFS para indexar subgrafos utilizando esta funcionalidad experimental.

Errores no fatales

Enlace a esta sección

Los errores de indexación en subgrafos ya sincronizados provocarán, por defecto, que el subgrafo falle y deje de sincronizarse. Los subgrafos pueden ser configurados de manera alternativa para continuar la sincronización en presencia de errores, ignorando los cambios realizados por el handler que provocó el error. Esto da a los autores de los subgrafos tiempo para corregir sus subgrafos mientras las consultas continúan siendo servidas contra el último bloque, aunque los resultados serán posiblemente inconsistentes debido al bug que provocó el error. Nótese que algunos errores siguen siendo siempre fatales, para que el error no sea fatal debe saberse que es deterministico.

Nota: The Graph Network todavía no admite errores no fatales, y los developers no deben deployar subgrafos que utilicen esa funcionalidad en la red a través de Studio.

Para activar los errores no fatales es necesario establecer el siguiente indicador en el manifiesto del subgrafo:

specVersion: 0.0.4
description: Gravatar for Ethereum
features:
- nonFatalErrors
...

La consulta también debe optar por consultar los datos con posibles incoherencias a través del argumento subgraphError. También se recomienda consultar _meta para comprobar si el subgrafo ha saltado por encima de los errores, como en el ejemplo:

foos(first: 100, subgraphError: allow) {
id
}
_meta {
hasIndexingErrors
}

Si el subgrafo encuentra un error esa consulta devolverá tanto los datos como un error de graphql con el mensaje "indexing_error", como en este ejemplo de respuesta:

"data": {
"foos": [
{
"id": "0xdead"
}
],
"_meta": {
"hasIndexingErrors": true
}
},
"errors": [
{
"message": "indexing_error"
}
]

Grafting sobre subgrafos existentes

Enlace a esta sección

Note: it is not recommended to use grafting when initially upgrading to The Graph Network. Learn more here.

Cuando un subgrafo es deployado por primera vez, comienza a indexar eventos en el bloque génesis de la cadena correspondiente (o en el startBlock definido con cada fuente de datos) En algunas circunstancias, es beneficioso reutilizar los datos de un subgrafo existente y comenzar a indexar en un bloque mucho más tardío. Este modo de indexación se denomina Grafting. El Grafting es, por ejemplo, útil durante el desarrollo para superar rápidamente errores simples en los mappings, o para hacer funcionar temporalmente un subgrafo existente después de que haya fallado.

Un subgrafo se graftea en un subgrafo base cuando el manifiesto de subgrafo subgraph.yaml contiene un bloque graft en el nivel superior:

description: ...
graft:
base: Qm... # Subgraph ID of base subgraph
block: 7345624 # Block number

Cuando se realiza el deploy de un subgrafo cuyo manifiesto contiene un bloque graft, Graph Node copiará los datos del subgrafo base hasta el block dado, inclusive, y luego continuará indexando el nuevo subgrafo a partir de ese bloque. El subgrafo base debe existir en la instancia de Graph Node de destino y debe haber indexado hasta al menos el bloque dado. Debido a esta restricción, el grafting sólo debería utilizarse durante el desarrollo o durante una emergencia para acelerar la producción de un subgrafo equivalente no grafted (injertado).

Debido a que el grafting copia en lugar de indexar los datos base, es mucho más rápido llevar el subgrafo al bloque deseado que indexar desde cero, aunque la copia inicial de los datos aún puede llevar varias horas para subgrafos muy grandes. Mientras se inicializa el subgrafo grafted, Graph Node registrará información sobre los tipos de entidad que ya han sido copiados.

El subgrafo grafteado puede utilizar un esquema GraphQL que no es idéntico al del subgrafo base, sino simplemente compatible con él. Tiene que ser un esquema de subgrafo válido por sí mismo, pero puede diferir del esquema del subgrafo base de las siguientes maneras:

  • Agrega o elimina tipos de entidades
  • Elimina los atributos de los tipos de entidad
  • Agrega atributos anulables a los tipos de entidad
  • Convierte los atributos no anulables en atributos anulables
  • Añade valores a los enums
  • Agrega o elimina interfaces
  • Cambia para qué tipos de entidades se implementa una interfaz

La gestión de características: grafting se declara en features en el manifiesto del subgrafo.

File Data Source

Enlace a esta sección

File data sources are a new subgraph functionality for accessing off-chain data during indexing in a robust, extendable way. File data sources support fetching files from IPFS and from Arweave.

Esto también establece las bases para la indexación determinista de datos off-chain, así como la posible introducción de datos arbitrarios procedentes de HTTP.

En lugar de buscar los archivos "en línea" durante la ejecución del handler, se introducen plantillas que pueden generarse como nuevas fuentes de datos para un identificador de archivo determinado. Estas nuevas fuentes de datos buscan los archivos, reintentando si no tienen éxito, ejecutando un handler dedicado cuando se encuentra el archivo.

Esto es similar a las plantillas de fuentes de datos existentes, que se utilizan para crear dinámicamente nuevas fuentes de datos basadas en cadenas.

Esto sustituye a la API ipfs.cat existente

Upgrade guide

Enlace a esta sección

Actualización de graph-ts y graph-cli

Enlace a esta sección

Las fuentes de datos de archivos requieren graph-ts >=0.29.0 y graph-cli >=0.33.1

Añadir un nuevo tipo de entidad que se actualizará cuando se encuentren archivos

Enlace a esta sección

Las fuentes de datos de archivos no pueden acceder a entidades basadas en cadenas ni actualizarlas, pero deben actualizar entidades específicas de archivos.

Esto puede significar dividir campos de entidades existentes en entidades separadas, vinculadas entre sí.

Entidad combinada original:

type Token @entity {
id: ID!
tokenID: BigInt!
tokenURI: String!
externalURL: String!
ipfsURI: String!
image: String!
name: String!
description: String!
type: String!
updatedAtTimestamp: BigInt
owner: User!
}

Nueva, entidad dividida:

type Token @entity {
id: ID!
tokenID: BigInt!
tokenURI: String!
ipfsURI: TokenMetadata
updatedAtTimestamp: BigInt
owner: String!
}
type TokenMetadata @entity {
id: ID!
image: String!
externalURL: String!
name: String!
description: String!
}

Si la relación es 1:1 entre la entidad padre y la entidad fuente de datos de archivo resultante, el patrón más sencillo es vincular la entidad padre a una entidad de archivo resultante utilizando el CID IPFS como búsqueda. Pónte en contacto con nosotros en Discord si tienes dificultades para modelar tus nuevas entidades basadas en archivos!

Puedes utilizar nested filters para filtrar entidades padre en función de estas entidades nested.

Add a new templated data source with kind: file/ipfs or kind: file/arweave

Enlace a esta sección

Esta es la fuente de datos que se generará cuando se identifique un archivo de interés.

templates:
- name: TokenMetadata
kind: file/ipfs
mapping:
apiVersion: 0.0.7
language: wasm/assemblyscript
file: ./src/mapping.ts
handler: handleMetadata
entities:
- TokenMetadata
abis:
- name: Token
file: ./abis/Token.json

Actualmente se requieren abis, aunque no es posible llamar a los contratos desde las fuentes de datos de los archivos

La fuente de datos de archivos debe mencionar específicamente todos los tipos de entidad con los que interactuará en entities. Consulta las limitaciones para obtener más información.

Crear un nuevo handler para procesar archivos

Enlace a esta sección

Este handler debe aceptar un parámetro Bytes, que será el contenido del archivo, cuando se encuentra, que luego puede ser procesado. Esto a menudo será un archivo JSON, que puede ser procesado con graph-ts helpers (documentación).

Se puede acceder al CID del archivo como un string legible a través del dataSource de la siguiente manera:

const cid = dataSource.stringParam()

Ejemplo de handler:

import { json, Bytes, dataSource } from '@graphprotocol/graph-ts'
import { TokenMetadata } from '../generated/schema'
export function handleMetadata(content: Bytes): void {
let tokenMetadata = new TokenMetadata(dataSource.stringParam())
const value = json.fromBytes(content).toObject()
if (value) {
const image = value.get('image')
const name = value.get('name')
const description = value.get('description')
const externalURL = value.get('external_url')
if (name && image && description && externalURL) {
tokenMetadata.name = name.toString()
tokenMetadata.image = image.toString()
tokenMetadata.externalURL = externalURL.toString()
tokenMetadata.description = description.toString()
}
tokenMetadata.save()
}
}

Generar fuentes de datos de archivos cuando sea necesario

Enlace a esta sección

Ahora puedes crear fuentes de datos de archivos durante la ejecución de handlers basados en cadenas:

  • Importar la plantilla desde los templates autogenerados
  • call TemplateName.create(cid: string) from within a mapping, where the cid is a valid content identifier for IPFS or Arweave

For IPFS, Graph Node supports v0 and v1 content identifiers, and content identifers with directories (e.g. bafyreighykzv2we26wfrbzkcdw37sbrby4upq7ae3aqobbq7i4er3tnxci/metadata.json).

For Arweave, as of version 0.33.0 Graph Node can fetch files stored on Arweave based on their transaction ID from an Arweave gateway (example file). Arweave supports transactions uploaded via Bundlr, and Graph Node can also fetch files based on Bundlr manifests.

Ejemplo:

import { TokenMetadata as TokenMetadataTemplate } from '../generated/templates'
const ipfshash = 'QmaXzZhcYnsisuue5WRdQDH6FDvqkLQX1NckLqBYeYYEfm'
//This example code is for a Crypto coven subgraph. The above ipfs hash is a directory with token metadata for all crypto coven NFTs.
export function handleTransfer(event: TransferEvent): void {
let token = Token.load(event.params.tokenId.toString())
if (!token) {
token = new Token(event.params.tokenId.toString())
token.tokenID = event.params.tokenId
token.tokenURI = '/' + event.params.tokenId.toString() + '.json'
const tokenIpfsHash = ipfshash + token.tokenURI
//This creates a path to the metadata for a single Crypto coven NFT. It concats the directory with "/" + filename + ".json"
token.ipfsURI = tokenIpfsHash
TokenMetadataTemplate.create(tokenIpfsHash)
}
token.updatedAtTimestamp = event.block.timestamp
token.owner = event.params.to.toHexString()
token.save()
}

This will create a new file data source, which will poll Graph Node's configured IPFS or Arweave endpoint, retrying if it is not found. When the file is found, the file data source handler will be executed.

En este ejemplo se utiliza el CID como búsqueda entre la entidad Token principal y la entidad TokenMetadata resultante.

Anteriormente, este es el punto en el que un developer de subgrafos habría llamado a ipfs.cat(CID) para obtener el archivo

¡Felicitaciones, estás utilizando fuentes de datos de archivos!

Deploy de tus subgrafos

Enlace a esta sección

Ya puedes build y deploy tu subgrafo en cualquier Graph Node >=v0.30.0-rc.0.

Los handlers y entidades de fuentes de datos de archivos están aislados de otras entidades del subgrafo, asegurando que son deterministas cuando se ejecutan, y asegurando que no se contaminan las fuentes de datos basadas en cadenas. En concreto:

  • Las entidades creadas por File Data Sources son inmutables y no pueden actualizarse
  • Los handlers de File Data Source no pueden acceder a entidades de otras fuentes de datos de archivos
  • Los handlers basados en cadenas no pueden acceder a las entidades asociadas a File Data Sources

Aunque esta restricción no debería ser problemática para la mayoría de los casos de uso, puede introducir complejidad para algunos. Si tienes problemas para modelar tus datos basados en archivos en un subgrafo, ponte en contacto con nosotros a través de Discord!

Además, no es posible crear fuentes de datos a partir de una File Data Source, ya sea una fuente de datos on-chain u otra File Data Source. Es posible que esta restricción se elimine en el futuro.

Mejores Prácticas

Enlace a esta sección

Si estás vinculando metadatos NFT a los tokens correspondientes, utiliza el hash IPFS de los metadatos para hacer referencia a una entidad Metadata desde la entidad Token. Guarda la entidad de metadatos utilizando el hash IPFS como ID.

Puedes utilizar DataSource context cuando creas File Data Sources para pasar información extra que estará disponible para el handler del File Data Source.

Si tienes entidades que se actualizan varias veces, crea entidades únicas basadas en archivos utilizando el hash IPFS & el ID de la entidad, y haz referencia a ellas utilizando un campo derivado en la entidad basada en cadena.

Estamos trabajando para mejorar la recomendación anterior, de modo que las consultas sólo devuelvan la versión "más reciente"

Problemas conocidos

Enlace a esta sección

File Data Sources requieren actualmente ABIs, aunque no se utilicen ABIs (problema). La solución es añadir cualquier ABI.

Los handlers para File Data Sources no pueden estar en archivos que importan enlaces de contrato eth_call, fallando con "unknown import: ethereum::ethereum.call has not been defined" (problema). La solución es crear handlers de File Data Sources en un archivo dedicado.

Migración de subgrafo Crypto Coven

GIP File Data Sources

Editar página

Anterior
Redes admitidas
Siguiente
AssemblyScript API
Editar página