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.
La definición del subgrafo consta de unos cuantos archivos:
-
subgraph.yaml
: un archivo YAML que contiene el manifiesto del subgrafo -
schema.graphql
: un esquema GraphQL que define qué datos se almacenan para su subgrafo, y cómo consultarlos a través de GraphQL -
AssemblyScript Mappings
: AssemblyScript codigo que traduce de los datos del evento a las entidades definidas en su esquema (por ejemplomapping.ts
en este tutorial)
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.
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.
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.
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.
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 nuevodataSource
debe utilizar loseventHandlers
&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 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.4description: Gravatar for Ethereumrepository: https://github.com/graphprotocol/graph-toolingschema:file: ./schema.graphqldataSources:- kind: ethereum/contractname: Gravitynetwork: mainnetsource:address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'abi: GravitystartBlock: 6175244endBlock: 7175245context:foo:type: Booldata: truebar:type: Stringdata: 'bar'mapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptentities:- Gravatarabis:- name: Gravityfile: ./abis/Gravity.jsoneventHandlers:- event: NewGravatar(uint256,address,string,string)handler: handleNewGravatar- event: UpdatedGravatar(uint256,address,string,string)handler: handleUpdatedGravatarcallHandlers:- function: createGravatar(string,string)handler: handleCreateGravatarblockHandlers:- handler: handleBlock- handler: handleBlockWithCallfilter:kind: callfile: ./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 likeBool
,String
,Int
,Int8
,BigDecimal
,Bytes
,List
, andBigInt
. Each variable needs to specify itstype
anddata
. 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 confilter
field withkind: 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:
- Las triggers de eventos y calls se ordenan primero por el índice de la transacción dentro del bloque.
- 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.
- 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.
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 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.
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.
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: BytesdisplayName: StringimageUrl: Stringaccepted: Boolean}
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: BytesdisplayName: StringimageUrl: String}type GravatarDeclined @entity {id: Bytes!owner: BytesdisplayName: StringimageUrl: String}
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
.
Admitimos los siguientes escalares en nuestra API GraphQL:
Tipo | Descripción |
---|---|
Bytes | Byte array, representado como un string hexadecimal. Comúnmente utilizado para los hashes y direcciones de Ethereum. |
String | Escalar para valores string . Los caracteres nulos no son compatibles y se eliminan automáticamente. |
Boolean | Escalar para valores boolean . |
Int | La especificación GraphQL define Int con un tamaño de 32 bytes. |
Int8 | An 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. |
BigInt | Nú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 . |
BigDecimal | BigDecimal 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 {OriginalOwnerSecondOwnerThirdOwner}
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.
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.
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}
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!}
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!}
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 entityorganization {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.
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!}
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: enalgorithm: rankinclude: [{ entity: "Band", fields: [{ name: "name" }, { name: "description" }, { name: "bio" }] }])type Band @entity {id: Bytes!name: String!description: String!bio: Stringwallet: Addresslabels: [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") {idnamedescriptionwallet}}
Feature Management: Desde specVersion
0.0.4
y en adelante, fullTextSearch
se debe declarar bajo la sección features
en el manifiesto del subgrafo.
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ódigo | Diccionario |
---|---|
simple | General |
da | Danés |
nl | Holandés |
en | Inglés |
fi | Finlandés |
fr | Francés |
de | Alemán |
hu | Húngaro |
it | Italiano |
no | Noruego |
pt | Portugués |
ro | Rumano |
ru | Ruso |
es | Español |
sv | Sueco |
tr | Turco |
Algoritmos admitidos para ordenar los resultados:
Algoritmos | Descripción |
---|---|
rango | Usa la calidad de coincidencia (0-1) de la consulta de texto completo para ordenar los resultados. |
rango de proximidad | Similar al rango, pero también incluye la proximidad de los matches. |
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.ownergravatar.displayName = event.params.displayNamegravatar.imageUrl = event.params.imageUrlgravatar.save()}export function handleUpdatedGravatar(event: UpdatedGravatar): void {let id = event.params.idlet gravatar = Gravatar.load(id)if (gravatar == null) {gravatar = new Gravatar(id)}gravatar.owner = event.params.ownergravatar.displayName = event.params.displayNamegravatar.imageUrl = event.params.imageUrlgravatar.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()
.
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
.
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:
# Yarnyarn codegen# NPMnpm 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.
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.
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/contractname: Factorynetwork: mainnetsource:address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'abi: Factorymapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptfile: ./src/mappings/factory.tsentities:- Directoryabis:- name: Factoryfile: ./abis/factory.jsoneventHandlers:- event: NewExchange(address,address)handler: handleNewExchange
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/contractname: Factory# ... other source fields for the main contract ...templates:- name: Exchangekind: ethereum/contractnetwork: mainnetsource:abi: Exchangemapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptfile: ./src/mappings/exchange.tsentities:- Exchangeabis:- name: Exchangefile: ./abis/exchange.jsoneventHandlers:- 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
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 contractExchange.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.
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.
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/contractname: ExampleSourcenetwork: mainnetsource:address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'abi: ExampleContractstartBlock: 6627917mapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptfile: ./src/mappings/factory.tsentities:- Userabis:- name: ExampleContractfile: ./abis/ExampleContract.jsoneventHandlers:- event: NewEvent(address,address)handler: handleNewEvent
Nota: El bloque de creación del contrato se puede buscar rápidamente en Etherscan:
- Busca el contrato introduciendo su dirección en la barra de búsqueda.
- Haz clic en el hash de la transacción de creación en la sección
Contract Creator
. - Carga la página de detalles de la transacción, donde encontrarás el bloque inicial de ese contrato.
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.
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/contractname: Gravitynetwork: mainnetsource:address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'abi: Gravitymapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptentities:- Gravatar- Transactionabis:- name: Gravityfile: ./abis/Gravity.jsoncallHandlers:- 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.
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.hashlet transaction = new Transaction(id)transaction.displayName = call.inputs._displayNametransaction.imageUrl = call.inputs._imageUrltransaction.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
.
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.
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/contractname: Gravitynetwork: devsource:address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'abi: Gravitymapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptentities:- Gravatar- Transactionabis:- name: Gravityfile: ./abis/Gravity.jsonblockHandlers:- handler: handleBlock- handler: handleBlockWithCallToContractfilter:kind: call
Requires specVersion
>= 0.0.8
Note: Polling filters are only available on dataSources of kind: ethereum
.
blockHandlers:- handler: handleBlockfilter:kind: pollingevery: 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: handleOncefilter: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()}
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.hashlet entity = new Block(id)entity.save()}
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.
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: handleNewGravatarreceipt: 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.
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ística | Nombre |
---|---|
Errores no fatales | nonFatalErrors |
Full-text Search | fullTextSearch |
Grafting | grafting |
IPFS on Ethereum Contracts | ipfsOnEthereumContracts 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.4description: Gravatar for Ethereumfeatures:- fullTextSearch- nonFatalErrorsdataSources: ...
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.
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.
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.4description: Gravatar for Ethereumfeatures:- 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"}]
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 subgraphblock: 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 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
Las fuentes de datos de archivos requieren graph-ts >=0.29.0 y graph-cli >=0.33.1
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: BigIntowner: User!}
Nueva, entidad dividida:
type Token @entity {id: ID!tokenID: BigInt!tokenURI: String!ipfsURI: TokenMetadataupdatedAtTimestamp: BigIntowner: 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.
Esta es la fuente de datos que se generará cuando se identifique un archivo de interés.
templates:- name: TokenMetadatakind: file/ipfsmapping:apiVersion: 0.0.7language: wasm/assemblyscriptfile: ./src/mapping.tshandler: handleMetadataentities:- TokenMetadataabis:- name: Tokenfile: ./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.
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()}}
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.tokenIdtoken.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 = tokenIpfsHashTokenMetadataTemplate.create(tokenIpfsHash)}token.updatedAtTimestamp = event.block.timestamptoken.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!
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.
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"
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.