Developing > Skapa en Subgraph

Skapa en Subgraph

Reading time: 43 min

En subgraph extraherar data från en blockchain, bearbetar den och lagrar den så att den kan frågas enkelt via GraphQL.

Definiera en Subgraph

Subgraph-definitionen består av några filer:

In order to use your subgraph on The Graph's decentralized network, you will need to create an API key. It is recommended that you add signal to your subgraph with at least 3,000 GRT.

Before you go into detail about the contents of the manifest file, you need to install the Graph CLI which you will need to build and deploy a subgraph.

Installera Graph CLI

Länk till detta avsnitt

Graph CLI är skrivet i JavaScript, och du måste installera antingen yarn eller npm för att använda det; det antas att du har yarn i det följande.

När du har yarn, installera Graph CLI genom att köra

Installera med yarn:

yarn global add @graphprotocol/graph-cli

Installera med npm:

npm install -g @graphprotocol/graph-cli

Once installed, the graph init command can be used to set up a new subgraph project, either from an existing contract or from an example subgraph. This command can be used to create a subgraph in Subgraph Studio by passing in graph init --product subgraph-studio. If you already have a smart contract deployed to your preferred network, bootstrapping a new subgraph from that contract can be a good way to get started.

Från ett Befintligt kontrakt

Länk till detta avsnitt

Följande kommando skapar en subgraf som indexerar alla händelser i ett befintligt kontrakt. Det försöker hämta kontraktets ABI från Etherscan och faller tillbaka till att begära en lokal filsökväg. Om något av de valfria argumenten saknas tar det dig genom ett interaktivt formulär.

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

<SUBGRAPH_SLUG> är ID för din subgraf i Subgraf Studio, det kan hittas på din subgraf detaljsida.

Från ett Exempel Subgraph

Länk till detta avsnitt

Det andra läget som graph init stöder är att skapa ett nytt projekt från ett exempel på en undergraf. Följande kommando gör detta:

graph init --studio <SUBGRAPH_SLUG>

The example subgraph is based on the Gravity contract by Dani Grant that manages user avatars and emits NewGravatar or UpdateGravatar events whenever avatars are created or updated. The subgraph handles these events by writing Gravatar entities to the Graph Node store and ensuring these are updated according to the events. The following sections will go over the files that make up the subgraph manifest for this example.

Lägg till nya datakällor i en befintlig Subgraf

Länk till detta avsnitt

Från och med v0.31.0 stöder graph-cli att lägga till nya datakällor i en befintlig subgraf genom kommandot graph add.

graph add <address> [<subgraph-manifest default: "./subgraph.yaml">]
Options:
--abi <path> Sökväg till kontraktets ABI (standard: nedladdning från Etherscan)
--contract-name Kontraktets namn (standard: Kontrakt)
--merge-entities Om enheter med samma namn ska slås samman (standard: false)
--network-file <path> Sökväg till konfigurationsfil för nätverk (standard: "./networks.json")

Kommandot add hämtar ABI: en från Etherscan (om inte en ABI-sökväg anges med alternativet --abi) och skapar en ny dataSource på samma sätt som kommandot graph init skapar en dataSource --from-contract, och uppdaterar schemat och mappningarna därefter.

Alternativet --merge-entities identifierar hur utvecklaren vill hantera konflikter med entity- och event-namn:

  • Om true: den nya dataSource ska använda befintliga eventHandlers & entities.
  • Om false: en ny entitet och händelsehanterare ska skapas med ${dataSourceName}{EventName}.

Kontraktsadressen kommer att skrivas till networks.json för den relevanta nätverket.

Obs: När du använder det interaktiva kommandoraden, efter att ha kört graph init framgångsrikt, kommer du att bli ombedd att lägga till en ny dataSource.

Subgrafens manifest

Länk till detta avsnitt

Subgrafens manifest subgraph.yaml definierar de smarta kontrakten som din subgraf indexerar, vilka händelser från dessa kontrakt som ska uppmärksammas och hur man kartlägger händelsedata till entiteter som Graph Node lagrar och tillåter att fråga. Den fullständiga specifikationen för subgrafens manifest finns här.

För exempelsubgrafen är subgraph.yaml:

specVersion: 0.0.4
description: Gravatar for Ethereum
repository: https://github.com/graphprotocol/graph-tooling
schema:
file: ./schema.graphql
indexerHints:
prune: auto
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

De viktiga posterna att uppdatera för manifestet är:

  • specVersion: a semver version that identifies the supported manifest structure and functionality for the subgraph. The latest version is 1.2.0. See specVersion releases section to see more details on features & releases.

  • description: a human-readable description of what the subgraph is. This description is displayed in Graph Explorer when the subgraph is deployed to Subgraph Studio.

  • repository: the URL of the repository where the subgraph manifest can be found. This is also displayed in Graph Explorer.

  • features: en lista över alla använda funktions namn.

  • indexerHints.prune: Defines the retention of historical block data for a subgraph. See prune in indexerHints section.

  • dataSources.source: adressen till det smarta kontraktet som subgrafen hämtar data från, och ABI för det smarta kontraktet att använda. Adressen är valfri; att utelämna den gör det möjligt att indexera matchande händelser från alla kontrakt.

  • dataSources.source.startBlock: det valfria blocknummer som datakällan börjar indexera från. I de flesta fall föreslår vi att du använder det block där kontraktet skapades.

  • 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: de entiteter som datakällan skriver till lagringsplatsen. Schemat för varje entitet definieras i filen schema.graphql.

  • dataSources.mapping.abis: en eller flera namngivna ABI-filer för källkontraktet samt eventuella andra smarta kontrakt som du interagerar med från inom mappningarna.

  • dataSources.mapping.eventHandlers: listar de smarta kontraktshändelser som denna subgraf reagerar på och hanterare i mappningen—./src/mapping.ts i exemplet - som omvandlar dessa händelser till entiteter i lagringsplatsen.

  • dataSources.mapping.callHandlers: listar de smarta kontraktsfunktioner som denna subgraf reagerar på och hanterare i mappningen som omvandlar in- och utdata till funktionsanrop till entiteter i lagringsplatsen.

  • dataSources.mapping.blockHandlers: listar de block som denna subgraf reagerar på och hanterare i mappningen som körs när ett block läggs till i kedjan. Utan ett filter körs blockhanteraren varje block. En valfri anropsfiltrering kan tillhandahållas genom att lägga till en filter-fält med kind: call till hanteraren. Detta körs bara om blocket innehåller minst ett anrop till datakällan.

En enskild subgraf kan indexera data från flera smarta kontrakt. Lägg till en post för varje kontrakt från vilket data behöver indexeras i dataSources-matrisen.

Order of Triggering Handlers

Länk till detta avsnitt

Utlösarna för en datakälla inom ett block ordnas med hjälp av följande process:

  1. Händelse- och anropsutlösare ordnas först efter transaktionsindex inom blocket.
  2. Händelse- och anropsutlösare inom samma transaktion ordnas med hjälp av en konvention: händelseutlösare först, sedan anropsutlösare, varje typ respekterar ordningen de definieras i manifestet.
  3. Blockutlösare körs efter händelse- och anropsutlösare, i den ordning de definieras i manifestet.

Dessa ordningsregler kan komma att ändras.

Note: When new dynamic data source are created, the handlers defined for dynamic data sources will only start processing after all existing data source handlers are processed, and will repeat in the same sequence whenever triggered.

Indexed Argument Filters / Topic Filters

Länk till detta avsnitt

Requires: SpecVersion >= 1.2.0

Topic filters, also known as indexed argument filters, are a powerful feature in subgraphs that allow users to precisely filter blockchain events based on the values of their indexed arguments.

  • These filters help isolate specific events of interest from the vast stream of events on the blockchain, allowing subgraphs to operate more efficiently by focusing only on relevant data.

  • This is useful for creating personal subgraphs that track specific addresses and their interactions with various smart contracts on the blockchain.

How Topic Filters Work

Länk till detta avsnitt

When a smart contract emits an event, any arguments that are marked as indexed can be used as filters in a subgraph's manifest. This allows the subgraph to listen selectively for events that match these indexed arguments.

  • The event's first indexed argument corresponds to topic1, the second to topic2, and so on, up to topic3, since the Ethereum Virtual Machine (EVM) allows up to three indexed arguments per event.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Token {
// Event declaration with indexed parameters for addresses
event Transfer(address indexed from, address indexed to, uint256 value);
// Function to simulate transferring tokens
function transfer(address to, uint256 value) public {
// Emitting the Transfer event with from, to, and value
emit Transfer(msg.sender, to, value);
}
}

In this example:

  • The Transfer event is used to log transactions of tokens between addresses.
  • The from and to parameters are indexed, allowing event listeners to filter and monitor transfers involving specific addresses.
  • The transfer function is a simple representation of a token transfer action, emitting the Transfer event whenever it is called.

Configuration in Subgraphs

Länk till detta avsnitt

Topic filters are defined directly within the event handler configuration in the subgraph manifest. Here is how they are configured:

eventHandlers:
- event: SomeEvent(indexed uint256, indexed address, indexed uint256)
handler: handleSomeEvent
topic1: ['0xValue1', '0xValue2']
topic2: ['0xAddress1', '0xAddress2']
topic3: ['0xValue3']

In this setup:

  • topic1 corresponds to the first indexed argument of the event, topic2 to the second, and topic3 to the third.
  • Each topic can have one or more values, and an event is only processed if it matches one of the values in each specified topic.
  • Within a Single Topic: The logic functions as an OR condition. The event will be processed if it matches any one of the listed values in a given topic.
  • Between Different Topics: The logic functions as an AND condition. An event must satisfy all specified conditions across different topics to trigger the associated handler.

Example 1: Tracking Direct Transfers from Address A to Address B

Länk till detta avsnitt
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleDirectedTransfer
topic1: ['0xAddressA'] # Sender Address
topic2: ['0xAddressB'] # Receiver Address

In this configuration:

  • topic1 is configured to filter Transfer events where 0xAddressA is the sender.
  • topic2 is configured to filter Transfer events where 0xAddressB is the receiver.
  • The subgraph will only index transactions that occur directly from 0xAddressA to 0xAddressB.

Example 2: Tracking Transactions in Either Direction Between Two or More Addresses

Länk till detta avsnitt
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransferToOrFrom
topic1: ['0xAddressA', '0xAddressB', '0xAddressC'] # Sender Address
topic2: ['0xAddressB', '0xAddressC'] # Receiver Address

In this configuration:

  • topic1 is configured to filter Transfer events where 0xAddressA, 0xAddressB, 0xAddressC is the sender.
  • topic2 is configured to filter Transfer events where 0xAddressB and 0xAddressC is the receiver.
  • The subgraph will index transactions that occur in either direction between multiple addresses allowing for comprehensive monitoring of interactions involving all addresses.

Declared eth_call

Länk till detta avsnitt

Requires: SpecVersion >= 1.2.0. Currently, eth_calls can only be declared for event handlers.

Declarative eth_calls are a valuable subgraph feature that allows eth_calls to be executed ahead of time, enabling graph-node to execute them in parallel.

This feature does the following:

  • Significantly improves the performance of fetching data from the Ethereum blockchain by reducing the total time for multiple calls and optimizing the subgraph's overall efficiency.
  • Allows faster data fetching, resulting in quicker query responses and a better user experience.
  • Reduces wait times for applications that need to aggregate data from multiple Ethereum calls, making the data retrieval process more efficient.
  • Declarative eth_calls: Ethereum calls that are defined to be executed in parallel rather than sequentially.
  • Parallel Execution: Instead of waiting for one call to finish before starting the next, multiple calls can be initiated simultaneously.
  • Time Efficiency: The total time taken for all the calls changes from the sum of the individual call times (sequential) to the time taken by the longest call (parallel).

Scenario without Declarative eth_calls

Länk till detta avsnitt

Imagine you have a subgraph that needs to make three Ethereum calls to fetch data about a user's transactions, balance, and token holdings.

Traditionally, these calls might be made sequentially:

  1. Call 1 (Transactions): Takes 3 seconds
  2. Call 2 (Balance): Takes 2 seconds
  3. Call 3 (Token Holdings): Takes 4 seconds

Total time taken = 3 + 2 + 4 = 9 seconds

Scenario with Declarative eth_calls

Länk till detta avsnitt

With this feature, you can declare these calls to be executed in parallel:

  1. Call 1 (Transactions): Takes 3 seconds
  2. Call 2 (Balance): Takes 2 seconds
  3. Call 3 (Token Holdings): Takes 4 seconds

Since these calls are executed in parallel, the total time taken is equal to the time taken by the longest call.

Total time taken = max (3, 2, 4) = 4 seconds

  1. Declarative Definition: In the subgraph manifest, you declare the Ethereum calls in a way that indicates they can be executed in parallel.
  2. Parallel Execution Engine: The Graph Node's execution engine recognizes these declarations and runs the calls simultaneously.
  3. Result Aggregation: Once all calls are complete, the results are aggregated and used by the subgraph for further processing.

Example Configuration in Subgraph Manifest

Länk till detta avsnitt

Declared eth_calls can access the event.address of the underlying event as well as all the event.params.

Subgraph.yaml using event.address:

eventHandlers:
event: Swap(indexed address,indexed address,int256,int256,uint160,uint128,int24)
handler: handleSwap
calls:
global0X128: Pool[event.address].feeGrowthGlobal0X128()
global1X128: Pool[event.address].feeGrowthGlobal1X128()

Details for the example above:

  • global0X128 is the declared eth_call.
  • The text before colon(global0X128) is the label for this eth_call which is used when logging errors.
  • The text (Pool[event.address].feeGrowthGlobal0X128()) is the actual eth_call that will be executed, which is in the form of Contract[address].function(arguments)
  • The address and arguments can be replaced with variables that will be available when the handler is executed.

Subgraph.yaml using event.params

calls:
- ERC20DecimalsToken0: ERC20[event.params.token0].decimals()

SpecVersion Releases

Länk till detta avsnitt
VersionVersionsanteckningar
1.2.0Added support for Indexed Argument Filtering & declared eth_call
1.1.0Supports Timeseries & Aggregations. Added support for type Int8 for id.
1.0.0Supports indexerHints feature to prune subgraphs
0.0.9Supports endBlock feature
0.0.8Added support for polling Block Handlers and Initialisation Handlers.
0.0.7Added support for File Data Sources.
0.0.6Supports fast Proof of Indexing calculation variant.
0.0.5Added support for event handlers having access to transaction receipts.
0.0.4Added support for managing subgraph features.

Hämta ABI: erna

Länk till detta avsnitt

ABI-filerna måste matcha ditt/dina kontrakt. Det finns några olika sätt att få ABI-filer:

  • Om du bygger ditt eget projekt har du förmodligen tillgång till dina senaste ABIs.
  • Om du bygger en subgraf för ett offentligt projekt kan du ladda ner det projektet till din dator och få ABI:n genom att använda truffle compile eller använda solc för att kompilera.
  • Du kan också hitta ABI:n på Etherscan, men detta är inte alltid pålitligt, eftersom ABI:n som laddas upp där kan vara föråldrad. Se till att du har rätt ABI, annars kommer din subgraf att misslyckas när den körs.

GraphQL-schemat

Länk till detta avsnitt

Schemat för din subgraf finns i filen schema.graphql. GraphQL-scheman definieras med hjälp av gränssnittsdefinitionsspråket för GraphQL. Om du aldrig har skrivit ett GraphQL-schema rekommenderas det att du kollar in denna introduktion till GraphQL-typsystemet. Referensdokumentation för GraphQL-scheman finns i avsnittet GraphQL API.

Definition av entiteter

Länk till detta avsnitt

Innan du definierar entiteter är det viktigt att ta ett steg tillbaka och tänka på hur din data är strukturerad och länkad. Alla frågor kommer att göras mot datamodellen som definieras i subgrafens schema och de entiteter som indexerats av subgraf. Därför är det bra att definiera subgrafens schema på ett sätt som matchar din dapp's behov. Det kan vara användbart att tänka på entiteter som "objekt som innehåller data", snarare än som händelser eller funktioner.

Med The Graph definierar du helt enkelt entitetstyper i schema.graphql, och Graph Node kommer att generera toppnivåfält för att fråga enskilda instanser och samlingar av den entitetstypen. Varje typ som ska vara en entitet måste vara annoterad med en @entity-direktiv. Som standard är entiteter muterbara, vilket innebär att mappningar kan ladda befintliga entiteter, ändra dem och lagra en ny version av den entiteten. Mutabilitet har ett pris, och för entitetstyper där det är känt att de aldrig kommer att ändras, till exempel eftersom de helt enkelt innehåller data som extraherats ordagrant från kedjan, rekommenderas att markera dem som omutbara med @entity(immutable: true). Mappningar kan göra ändringar i omutbara entiteter så länge dessa ändringar sker i samma block som entiteten skapades. Omutebara entiteter är mycket snabbare att skriva och att fråga, och bör därför användas när det är möjligt.

Entiteten Gravatar nedan är strukturerad kring ett Gravatar-objekt och är ett bra exempel på hur en entitet kan definieras.

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

Dåligt exempel

Länk till detta avsnitt

Exemplen GravatarAccepted och GravatarDeclined nedan är baserade på händelser. Det rekommenderas inte att mappa händelser eller funktionsanrop till entiteter 1:1.

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

Valfria och obligatoriska fält

Länk till detta avsnitt

Entitetsfält kan definieras som obligatoriska eller valfria. Obligatoriska fält anges med ! i schemat. Om ett obligatoriskt fält inte har angetts i mappningen får du det här felmeddelandet när du frågar efter fältet:

Null value resolved for non-null field 'name'

Varje entitet måste ha ett id-fält, som måste vara av typen Bytes! eller String!. Det rekommenderas generellt att använda Bytes!, om inte id innehåller läsbar text, eftersom entiteter med Bytes!-id kommer att vara snabbare att skriva och fråga än de med ett String! id. id-fältet fungerar som primärnyckel och måste vara unikt bland alla entiteter av samma typ. Av historiska skäl accepteras också typen ID! och är en synonym för String!.

För vissa entitetstyper konstrueras id från id:erna hos två andra entiteter; det är möjligt med concat, t.ex. let id = left.id.concat(right.id) för att bilda id från id:erna hos left och right. På liknande sätt kan för att konstruera ett id från id:et hos en befintlig entitet och en räknare count användas let id = left.id.concatI32(count). Konkatineringen garanterar att producera unika id:er så länge längden av left är densamma för alla sådana entiteter, till exempel eftersom left.id är en Address.

Inbyggda Skalartyper

Länk till detta avsnitt

GraphQL-Stödda Skalartyper

Länk till detta avsnitt

Vi stödjer följande skalartyper i vår GraphQL API:

TypBeskrivning
BytesBytematris, representerad som en hexadecimal sträng. Vanligt används för Ethereum-hashar och adresser.
StringSkalär för string-värden. Nolltecken stöds inte och tas automatiskt bort.
BooleanSkalär för boolean-värden.
IntThe GraphQL spec defines Int to be a signed 32-bit integer.
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.
BigIntStora heltal. Används för Ethereum's uint32, int64, uint64, ..., uint256 typer. Observera: Allt under uint32, som int32, uint24 eller int8 representeras som i32.
BigDecimalBigDecimal Högprecisionsdecimaler representerade som en signifikant och en exponent. Exponentområdet är från −6143 till +6144. Avrundat till 34 signifikanta siffror.
TimestampIt is an i64 value in microseconds. Commonly used for timestamp fields for timeseries and aggregations.

Du kan också skapa enums inom ett schema. Enums har följande syntax:

enum TokenStatus {
OriginalOwner
SecondOwner
ThirdOwner
}

När enumet är definierat i schemat kan du använda enumvärdenas strängrepresentation för att ställa in ett enumfält på en entitet. Till exempel kan du ställa in tokenStatus till SecondOwner genom att först definiera din entitet och sedan ställa in fältet med entity.tokenStatus = "SecondOwner". Exemplet nedan visar hur Token-entiteten skulle se ut med ett enumfält:

Mer detaljer om att skriva enums finns i GraphQL-dokumentationen.

Entitetsrelationer

Länk till detta avsnitt

En entitet kan ha en relation till en eller flera andra entiteter i ditt schema. Dessa relationer kan traverseras i dina frågor. Relationer i The Graph är enriktade. Det är möjligt att simulera dubbelriktade relationer genom att definiera en enriktad relation på antingen den ena "änden" av relationen.

Relationer definieras på entiteter precis som vilket annat fält som helst, förutom att den specificerade typen är en annan entitet.

En-till-en-relationer

Länk till detta avsnitt

Definiera en entitetstyp Transaction med en valfri en-till-en-relation till en entitetstyp TransactionReceipt:

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

En-till-många-relationer

Länk till detta avsnitt

Definiera en entitetstyp TokenBalance med ett obligatoriskt en-till-many förhållande med en entitetstyp Token:

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

Omvända sökningar

Länk till detta avsnitt

Omvända sökningar kan definieras på en entitet genom fältet @derivedFrom. Det skapar ett virtuellt fält på entiteten som kan frågas, men som inte kan ställas in manuellt via mappings API. Istället härleds det från den relation som är definierad på den andra entiteten. För sådana relationer är det sällan meningsfullt att lagra båda sidor av relationen, och både indexering och frågeprestanda blir bättre när bara en sida lagras och den andra härleds.

För en-till-många-relationer bör relationen alltid lagras på 'en'-sidan, och 'många'-sidan bör alltid härledas. Att lagra relationen på detta sätt, istället för att lagra en array av entiteter på 'många'-sidan, kommer att resultera i dramatiskt bättre prestanda både för indexering och för frågning av subgraphen. Generellt sett bör lagring av arrayer av entiteter undvikas så mycket som är praktiskt möjligt.

Vi kan göra balanserna för en token åtkomliga från token genom att härleda ett fält tokenBalances:

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

Många-till-många-relationer

Länk till detta avsnitt

För många-till-många-relationer, som till exempel användare som var och en kan tillhöra ett antal organisationer, är det mest raka, men generellt sett inte den mest prestanda-optimerade, sättet att modellera relationen som en array i vardera av de två entiteter som är involverade. Om relationen är symmetrisk behöver bara ena sidan av relationen lagras och den andra sidan kan härledas.

Definiera en omvänd sökning från en entitet av typen Användare till en entitet av typen Organisation. I exemplet nedan uppnås detta genom att söka upp attributet medlemmar inom entiteten Organisation. I frågor kommer fältet organisationerAnvändare att lösas genom att hitta alla Organisations-entiteter som inkluderar användarens ID.

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

Ett mer effektivt sätt att lagra denna relation är genom en mappningstabell som har en post för varje User / Organization-par med ett schema som

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!
}

Detta tillvägagångssätt kräver att frågorna går ner till ytterligare en nivå för att hämta t. ex. organisationer för användare:

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

Detta mer avancerade sätt att lagra många-till-många-relationer kommer att leda till att mindre data lagras för subgrafen, och därför till en subgraf som ofta är dramatiskt snabbare att indexera och att fråga.

Lägga till kommentarer i schemat

Länk till detta avsnitt

As per GraphQL spec, comments can be added above schema entity attributes using the hash symble #. This is illustrated in the example below:

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

Definiera fält för fulltextsökning

Länk till detta avsnitt

Fulltextsökningar filtrerar och rangordnar entiteter baserat på en textinmatning för sökning. Fulltextförfrågningar kan returnera träffar för liknande ord genom att bearbeta söktexten till stammar innan de jämförs med den indexerade textdata.

En fulltextförfrågningsdefinition inkluderar förfrågningsnamnet, ordboken som används för att bearbeta textfälten, rangordningsalgoritmen som används för att ordna resultaten och fälten som ingår i sökningen. Varje fulltextförfrågan kan omfatta flera fält, men alla inkluderade fält måste vara från en enda entitetstyp.

För att lägga till en fulltextförfrågan inkludera en typ _Schema_ med en fulltextdirektiv i GraphQL-schemat.

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!]!
}

Exempelfältet bandSearch kan användas i frågor för att filtrera Band-entiteter baserat på textdokumenten i fälten name, description och bio. Gå till GraphQL API - Frågor för en beskrivning av API:et för fulltextsökning och fler exempel på användning.

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

Funktionshantering: Från specVersion 0.0.4 och framåt måste fullTextSearch deklareras under avsnittet features i subgraph-manifestet.

Stödda språk

Länk till detta avsnitt

Att välja ett annat språk kommer att ha en definitiv, om än ibland subtil, effekt på fulltext-sök-API:en. Fält som omfattas av en fulltextförfrågningsfunktion granskas i kontexten av det valda språket, så lexem som produceras av analys och sökfrågor varierar från språk till språk. Till exempel: när det används det stödda turkiska ordboken "token" så avstamsas det till "toke", medan engelska ordboken självklart avstammar det till "token".

Stödda språkordböcker:

KodOrdbok
enkelAllmän
daDanska
nlHolländska
enEngelska
fiFinska
frFranska
deTyska
huUngerska
itItalienska
noNorska
ptPortugisiska
roRumänska
ruRyska
esSpanska
svSvenska
trTurkiska

Rankningsalgoritmer

Länk till detta avsnitt

Stödda algoritmer för att ordna resultat:

AlgoritmBeskrivning
rankAnvänd matchningskvaliteten (0-1) från fulltextförfrågan för att ordna resultaten.
proximityRankLiknande rank, men inkluderar också närheten av träffarna.

Skriv Mappningar

Länk till detta avsnitt

Mappningar tar data från en specifik källa och omvandlar den till entiteter som är definierade i din schema. Mappningar skrivs i en delmängd av TypeScript som kallas AssemblyScript som kan kompileras till WASM (WebAssembly). AssemblyScript är strängare än vanlig TypeScript, men erbjuder en bekant syntax.

För varje händelsehanterare som är definierad i subgraph.yaml under mapping.eventHandlers, skapa en exporterad funktion med samma namn. Varje hanterare måste acceptera en enda parameter med namnet event med en typ som motsvarar namnet på händelsen som hanteras.

I det här exempelsubgraphet innehåller src/mapping.ts hanterare för händelserna NewGravatar och 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();
}

Den första hanteraren tar en NewGravatar-händelse och skapar en ny Gravatar-entitet med new Gravatar(event.params.id.toHex()), fyller i entitetsfälten med hjälp av motsvarande händelseparametrar. Denna entitetsinstans representeras av variabeln gravatar, med ett id-värde av event.params.id.toHex().

Den andra hanteraren försöker ladda den befintliga Gravatar från Graph Node-lagringen. Om den inte finns ännu skapas den på begäran. Entiteten uppdateras sedan för att matcha de nya händelseparametrarna innan den sparas tillbaka till lagringen med gravatar.save().

Rekommenderade ID:n för att skapa nya entiteter

Länk till detta avsnitt

It is highly recommended to use Bytes as the type for id fields, and only use String for attributes that truly contain human-readable text, like the name of a token. Below are some recommended id values to consider when creating new entities.

  • transfer.id = event.transaction.hash

  • let id = event.transaction.hash.concatI32(event.logIndex.toI32())

  • For entities that store aggregated data, for e.g, daily trade volumes, the id usually contains the day number. Here, using a Bytes as the id is beneficial. Determining the id would look like

let dayID = event.block.timestamp.toI32() / 86400
let id = Bytes.fromI32(dayID)
  • Convert constant addresses to Bytes.

const id = Bytes.fromHexString('0xdead...beef')

There is a Graph Typescript Library which contains utilities for interacting with the Graph Node store and conveniences for handling smart contract data and entities. It can be imported into mapping.ts from @graphprotocol/graph-ts.

Handling of entities with identical IDs

Länk till detta avsnitt

When creating and saving a new entity, if an entity with the same ID already exists, the properties of the new entity are always preferred during the merge process. This means that the existing entity will be updated with the values from the new entity.

If a null value is intentionally set for a field in the new entity with the same ID, the existing entity will be updated with the null value.

If no value is set for a field in the new entity with the same ID, the field will result in null as well.

För att göra det enkelt och typsäkert att arbeta med smarta kontrakt, händelser och entiteter kan Graph CLI generera AssemblyScript-typer från subgrafens GraphQL-schema och kontrakts-ABIn som ingår i datakällorna.

Detta görs med

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

men i de flesta fall är undergrafer redan förkonfigurerade via package.json så att du helt enkelt kan köra en av följande för att uppnå samma sak:

# Yarn
yarn codegen
# NPM
npm run codegen

Detta genererar en AssemblyScript-klass för varje smart kontrakt i ABI-filerna som nämns i subgraph.yaml, så att du kan binda dessa kontrakt till specifika adresser i mappningarna och anropa skrivskyddade kontraktsmetoder mot det block som bearbetas. Den kommer också att generera en klass för varje kontraktshändelse för att ge enkel åtkomst till händelseparametrar, samt blocket och transaktionen som händelsen härstammar från. Alla dessa typer skrivs till <OUTPUT_DIR>/<DATA_SOURCE_NAME>/<ABI_NAME>.ts. I undergrafen i exemplet skulle detta vara generated/Gravity/Gravity.ts, vilket gör att mappningar kan importera dessa typer med.

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

Utöver detta genereras en klass för varje entitetstyp i subgrafens GraphQL-schema. Dessa klasser tillhandahåller typsäker entitetsladdning, läs- och skrivåtkomst till entitetsfält samt en save()-metod för att skriva entiteter till lagret. Alla entitetsklasser skrivs till <OUTPUT_DIR>/schema.ts, vilket gör att mappningar kan importera dem med

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

Observera: Kodgenerering måste utföras igen efter varje ändring av GraphQL-schemat eller ABIn som ingår i manifestet. Det måste också utföras minst en gång innan du bygger eller distribuerar subgrafet.

Code generation does not check your mapping code in src/mapping.ts. If you want to check that before trying to deploy your subgraph to Graph Explorer, you can run yarn build and fix any syntax errors that the TypeScript compiler might find.

Datakällmallar

Länk till detta avsnitt

En vanlig mönster i EVM-kompatibla smarta kontrakt är användningen av register- eller fabrikskontrakt, där ett kontrakt skapar, hanterar eller hänvisar till ett godtyckligt antal andra kontrakt som var och en har sin egen stat och händelser.

Adresserna till dessa underkontrakt kan eller kanske inte vara kända på förhand, och många av dessa kontrakt kan skapas och/eller läggas till över tid. Det är därför, i sådana fall, som det är omöjligt att definiera en enda datakälla eller ett fast antal datakällor och en mer dynamisk metod behövs: datakällmallar.

Datakälla för huvudkontraktet

Länk till detta avsnitt

Först definierar du en vanlig datakälla för huvudkontraktet. Snutten nedan visar ett förenklat exempel på en datakälla för Uniswap utbytesfabrikskontrakt. Observera NewExchange(address,address) händelsehanteraren. Denna händelse emitteras när en ny utbyteskontrakt skapas på kedjan av fabrikskontraktet.

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

Datakällmallar för dynamiskt skapade kontrakt

Länk till detta avsnitt

Sedan lägger du till datakällmallar i manifestet. Dessa är identiska med vanliga datakällor, förutom att de saknar en fördefinierad avtalsadress under source. Vanligtvis definierar du en mall för varje typ av underkontrakt som hanteras eller refereras till av det överordnade kontraktet.

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

Instansiering av en mall för datakälla

Länk till detta avsnitt

I det sista steget uppdaterar du mappningen av huvudkontraktet för att skapa en dynamisk datakällinstans från en av mallarna. I det här exemplet ändrar du mappningen av huvudkontraktet för att importera mallen Exchange och anropar metoden Exchange.create(address) för att börja indexera det nya växlingskontraktet.

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)
}

** Notera:** En ny datakälla bearbetar endast anrop och händelser för det block där den skapades och alla efterföljande block, men bearbetar inte historiska data, dvs. data som finns i tidigare block.

Om tidigare block innehåller data som är relevanta för den nya datakällan, är det bäst att indexera dessa data genom att läsa kontraktets aktuella status och skapa enheter som representerar denna status vid den tidpunkt då den nya datakällan skapas.

Kontext för datakälla

Länk till detta avsnitt

Datakällans kontext gör det möjligt att skicka extra konfiguration när en mall instansieras. I vårt exempel kan vi säga att börser är associerade med ett visst handelspar, vilket ingår i händelsen NewExchange. Den informationen kan skickas till den instansierade datakällan, så här:

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)
}

Inuti en mappning av mallen Exchange kan kontexten sedan nås:

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

Det finns sättare och hämtare som setString och getString för alla värdestyper.

startBlock är en valfri inställning som låter dig definiera från vilken block i kedjan datakällan ska börja indexera. Genom att ställa in startblocket kan datakällan hoppa över potentiellt miljontals block som är irrelevanta. Vanligtvis kommer en subgrafutvecklare att ställa in startBlock till blocket där datakällans smarta kontrakt skapades.

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

Observera: Blocket där kontraktet skapades kan snabbt sökas upp på Etherscan:

  1. Sök efter kontraktet genom att ange dess adress i sökfältet.
  2. Klicka på transaktionshashen för skapandet i avsnittet Kontraktsskapare.
  3. Ladda sidan med transaktionsdetaljer där du hittar startblocket för det kontraktet.

The indexerHints setting in a subgraph's manifest provides directives for indexers on processing and managing a subgraph. It influences operational decisions across data handling, indexing strategies, and optimizations. Presently, it features the prune option for managing historical data retention or pruning.

This feature is available from specVersion: 1.0.0

indexerHints.prune: Defines the retention of historical block data for a subgraph. Options include:

  1. "never": No pruning of historical data; retains the entire history.
  2. "auto": Retains the minimum necessary history as set by the indexer, optimizing query performance.
  3. A specific number: Sets a custom limit on the number of historical blocks to retain.
indexerHints:
prune: auto

The term "history" in this context of subgraphs is about storing data that reflects the old states of mutable entities.

History as of a given block is required for:

  • Time travel queries, which enable querying the past states of these entities at specific blocks throughout the subgraph's history
  • Using the subgraph as a graft base in another subgraph, at that block
  • Rewinding the subgraph back to that block

If historical data as of the block has been pruned, the above capabilities will not be available.

Using "auto" is generally recommended as it maximizes query performance and is sufficient for most users who do not require access to extensive historical data.

For subgraphs leveraging time travel queries, it's advisable to either set a specific number of blocks for historical data retention or use prune: never to keep all historical entity states. Below are examples of how to configure both options in your subgraph's settings:

To retain a specific amount of historical data:

indexerHints:
prune: 1000 # Replace 1000 with the desired number of blocks to retain

To preserve the complete history of entity states:

indexerHints:
prune: never

You can check the earliest block (with historical state) for a given subgraph by querying the Indexing Status API:

{
indexingStatuses(subgraphs: ["Qm..."]) {
subgraph
synced
health
chains {
earliestBlock {
number
}
latestBlock {
number
}
chainHeadBlock { number }
}
}
}

Note that the earliestBlock is the earliest block with historical data, which will be more recent than the startBlock specified in the manifest, if the subgraph has been pruned.

Event Handlers

Länk till detta avsnitt

Event handlers in a subgraph react to specific events emitted by smart contracts on the blockchain and trigger handlers defined in the subgraph's manifest. This enables subgraphs to process and store event data according to defined logic.

Defining an Event Handler

Länk till detta avsnitt

An event handler is declared within a data source in the subgraph's YAML configuration. It specifies which events to listen for and the corresponding function to execute when those events are detected.

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
eventHandlers:
- event: Approval(address,address,uint256)
handler: handleApproval
- event: Transfer(address,address,uint256)
handler: handleTransfer
topic1: ['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', '0xc8dA6BF26964aF9D7eEd9e03E53415D37aA96325'] # Optional topic filter which filters only events with the specified topic.

Anropsbehandlare

Länk till detta avsnitt

Medan händelser ger ett effektivt sätt att samla in relevanta ändringar av ett kontrakts tillstånd, undviker många kontrakt att generera loggar för att optimera gasavgifterna. I dessa fall kan en subgraf prenumerera på anrop som görs till datakällans kontrakt. Detta uppnås genom att definiera anropsbehandlare som refererar till funktions signaturen och hanteraren som kommer att bearbeta anrop till denna funktion. För att bearbeta dessa anrop kommer hanteraren att ta emot ett ethereum.Call som ett argument med de typade in- och utdata från anropet. Anrop som görs på vilken djupnivå som helst i en transaktions anropskedja kommer att utlösa kartläggningen, vilket gör det möjligt att fånga aktivitet med datakällan genom proxykontrakt.

Anropsbehandlare utlöses endast i ett av två fall: när den specificerade funktionen anropas av ett konto som inte är kontraktet självt eller när den är markerad som extern i Solidity och anropas som en del av en annan funktion i samma kontrakt.

Observera: Anropsbehandlare är för närvarande beroende av Paritys spårnings-API. Vissa nätverk, som BNB-kedjan och Arbitrum, stöder inte denna API. Om en subgraf som indexerar ett av dessa nätverk innehåller en eller flera anropsbehandlare kommer den inte att börja synkroniseras. Subgrafutvecklare bör istället använda händelsehanterare. Dessa är mycket mer prestandaoptimerade än anropsbehandlare och stöds på alla evm-nätverk.

Definiera en Anropsbehandlare

Länk till detta avsnitt

För att definiera en anropsbehandlare i din manifest, lägg helt enkelt till en callHandlers-array under den datakälla du vill prenumerera på.

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

function är den normaliserade funktions signaturen för att filtrera anrop efter. Egenskapen handler är namnet på funktionen i din kartläggning som du vill utföra när målfunktionen anropas i datakällans kontrakt.

Kartläggningsfunktion

Länk till detta avsnitt

Varje anropsbehandlare tar en enda parameter med en typ som motsvarar namnet på den kallade funktionen. I det ovanstående exempelsubgrafet innehåller kartläggningen en hanterare för när funktionen createGravatar anropas och tar emot en CreateGravatarCall-parameter som ett argument:

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()
}

Funktionen handleCreateGravatar tar emot ett nytt CreateGravatarCall, som är en underklass av ethereum.Call, tillhandahållen av @graphprotocol/graph-ts, som inkluderar de typade in- och utmatningarna från anropet. Typen CreateGravatarCall genereras för dig när du kör graph codegen.

Blockbehandlare

Länk till detta avsnitt

Förutom att prenumerera på kontrakts händelser eller funktionsanrop kan en subgraf vilja uppdatera sina data när nya block läggs till i kedjan. För att uppnå detta kan en subgraf köra en funktion efter varje block eller efter block som matchar en fördefinierad filter.

Stödda filter

Länk till detta avsnitt
filter:
kind: call

Den definierade hanteraren kommer att anropas en gång för varje block som innehåller ett anrop till det kontrakt (datakälla) som hanteraren är definierad under.

Observera: call-filtret är för närvarande beroende av Parity-tracing-API: et. Vissa nätverk, som BNB-kedjan och Arbitrum, stöder inte detta API. Om en subgraf som indexerar ett av dessa nätverk innehåller en eller flera blockhanterare med ett call-filter, kommer den inte att börja synkronisera.

Avsaknaden av ett filter för en blockhanterare kommer att säkerställa att hanteraren kallas för varje block. En datakälla kan endast innehålla en blockhanterare för varje filttyp.

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

Undersökningsfilter

Länk till detta avsnitt

Requires specVersion >= 0.0.8

Observera: Undersökningsfilter är endast tillgängliga på datakällor av typen kind: ethereum.

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

Den definierade hanteraren kommer att kallas en gång för varje n block, där n är värdet som anges i fältet every. Denna konfiguration möjliggör för delgrafer att utföra specifika operationer med regelbundna blockintervall.

En Gång Filter

Länk till detta avsnitt

Requires specVersion >= 0.0.8

Observera: En gång-filtrar är endast tillgängliga på datakällor av typen kind: ethereum.

blockHandlers:
- handler: handleOnce
filter:
kind: once

Den definierade hanteraren med filtret once kommer att anropas endast en gång innan alla andra hanterare körs. Denna konfiguration gör det möjligt för subgrafen att använda hanteraren som en initialiseringshanterare, som utför specifika uppgifter i början av indexeringen.

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

Kartläggningsfunktion

Länk till detta avsnitt

Mappningsfunktionen tar emot ett ethereum.Block som sitt enda argument. Liksom mappningsfunktioner för händelser kan denna funktion komma åt befintliga subgrafiska enheter i lagret, anropa smarta kontrakt och skapa eller uppdatera enheter.

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

Anonyma händelser

Länk till detta avsnitt

Om du behöver behandla anonyma händelser i Solidity kan du göra det genom att ange händelsens ämne 0, som i exemplet:

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

En händelse utlöses endast när både signaturen och topic0 matchar. Som standard är topic0 lika med hashtillståndet för händelsesignaturen.

Transaktionskvitton i Händelsehanterare

Länk till detta avsnitt

Från och med specVersion 0.0.5 och apiVersion 0.0.7 kan händelsehanterare få tillgång till kvittot för den transaktion som emitterade dem.

För att göra detta måste händelsehanterare deklareras i delgrafmanifestet med den nya nyckeln receipt: true, vilket är valfritt och som standard är falskt.

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

Inuti hanterarfunktionen kan kvittot nås i fältet Event.receipt. När nyckeln receipt är inställd som false eller utelämnad i manifestet, kommer istället ett null-värde att returneras.

Experimentella funktioner

Länk till detta avsnitt

Från och med specVersion 0.0.4 måste delgrafsfunktioner deklareras explicit i avsnittet features högst upp i manifestfilen, med deras camelCase-namn, som listas i tabellen nedan:

FunktionNamn
Icke dödliga felnonFatalErrors
FulltextssökningnonFatalErrors
Ympninggrafting

Till exempel, om en delgraf använder funktionerna Fulltextssökning och Icke dödliga fel, ska fältet features i manifestet vara:

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

Observera att att använda en funktion utan att deklarera den kommer att resultera i en valideringsfel under delgrafens distribution, men inga fel uppstår om en funktion deklareras men inte används.

Timeseries and Aggregations

Länk till detta avsnitt

Timeseries and aggregations enable your subgraph to track statistics like daily average price, hourly total transfers, etc.

This feature introduces two new types of subgraph entity. Timeseries entities record data points with timestamps. Aggregation entities perform pre-declared calculations on the Timeseries data points on an hourly or daily basis, then store the results for easy access via GraphQL.

Example Schema

Länk till detta avsnitt
type Data @entity(timeseries: true) {
id: Int8!
timestamp: Timestamp!
price: BigDecimal!
}
type Stats @aggregation(intervals: ["hour", "day"], source: "Data") {
id: Int8!
timestamp: Timestamp!
sum: BigDecimal! @aggregate(fn: "sum", arg: "price")
}

Defining Timeseries and Aggregations

Länk till detta avsnitt

Timeseries entities are defined with @entity(timeseries: true) in schema.graphql. Every timeseries entity must have a unique ID of the int8 type, a timestamp of the Timestamp type, and include data that will be used for calculation by aggregation entities. These Timeseries entities can be saved in regular trigger handlers, and act as the “raw data” for the Aggregation entities.

Aggregation entities are defined with @aggregation in schema.graphql. Every aggregation entity defines the source from which it will gather data (which must be a Timeseries entity), sets the intervals (e.g., hour, day), and specifies the aggregation function it will use (e.g., sum, count, min, max, first, last). Aggregation entities are automatically calculated on the basis of the specified source at the end of the required interval.

Available Aggregation Intervals

Länk till detta avsnitt
  • hour: sets the timeseries period every hour, on the hour.
  • day: sets the timeseries period every day, starting and ending at 00:00.

Available Aggregation Functions

Länk till detta avsnitt
  • sum: Total of all values.
  • count: Number of values.
  • min: Minimum value.
  • max: Maximum value.
  • first: First value in the period.
  • last: Last value in the period.

Example Aggregations Query

Länk till detta avsnitt
{
stats(interval: "hour", where: { timestamp_gt: 1704085200 }) {
id
timestamp
sum
}
}

Note:

To use Timeseries and Aggregations, a subgraph must have a spec version ≥1.1.0. Note that this feature might undergo significant changes that could affect backward compatibility.

Read more about Timeseries and Aggregations.

Icke dödliga fel

Länk till detta avsnitt

Indexeringsfel på redan synkroniserade delgrafer kommer, som standard, att få delgrafen att misslyckas och sluta synkronisera. Delgrafer kan istället konfigureras för att fortsätta synkroniseringen i närvaro av fel, genom att ignorera ändringarna som orsakades av hanteraren som provocerade felet. Det ger delgrafsförfattare tid att korrigera sina delgrafer medan förfrågningar fortsätter att behandlas mot det senaste blocket, även om resultaten kan vara inkonsekventa på grund av felet som orsakade felet. Observera att vissa fel alltid är dödliga. För att vara icke-dödliga måste felet vara känt för att vara deterministiskt.

Observera: The Graph Nätverk stöder ännu inte icke-dödliga fel, och utvecklare bör inte distribuera delgrafer med den funktionaliteten till nätverket via Studio.

Aktivering av icke-dödliga fel kräver att följande funktionsflagga sätts i delgrafens manifest:

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

Frågan måste också välja att fråga efter data med potentiella inkonsekvenser genom argumentet subgraphError. Det rekommenderas också att fråga _meta för att kontrollera om subgrafen har hoppat över fel, som i exemplet:

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

Om subgrafen stöter på ett fel returnerar frågan både data och ett graphql-fel med meddelandet "indexing_error", som i detta exempelsvar:

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

Ympning på befintliga delgrafer

Länk till detta avsnitt

Observera: Det rekommenderas inte att använda ympning vid initial uppgradering till The Graph Nätverk. Läs mer här.

När en delgraf först distribueras börjar den indexera händelser från genesisblocket på den motsvarande kedjan (eller på startBlock som är definierat för varje datakälla). I vissa situationer kan det vara fördelaktigt att återanvända data från en befintlig delgraf och börja indexera vid en mycket senare block. Denna indexeringsläge kallas Ympning. Ympning är exempelvis användbart under utvecklingen för att snabbt komma förbi enkla fel i mappningarna eller tillfälligt få en befintlig delgraf att fungera igen efter att den har misslyckats.

En delgraf ympas på en grunddelgraf när delgrafmanifestet i subgraph.yaml innehåller en graft-block högst upp:

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

När en delgraf vars manifest innehåller en graft-sektion distribueras kommer Graph Node att kopiera data från den base delgrafen upp till och inklusive det angivna block och sedan fortsätta indexera den nya delgrafen från det blocket. Basdelgrafen måste finnas på målnoden Graph Node och måste ha indexerats upp till minst det angivna blocket. På grund av denna begränsning bör ympning endast användas under utveckling eller i en nödsituation för att snabba upp produktionen av en motsvarande icke-ympad delgraf.

Eftersom ympning kopierar data istället för att indexera basdata går det mycket snabbare att få delgrafen till det önskade blocket än att indexera från början, även om den initiala datorkopieringen fortfarande kan ta flera timmar för mycket stora delgrafer. Medan den ympade delgrafen initialiseras kommer Graph Node att logga information om de entitetstyper som redan har kopierats.

Den ympade delgrafen kan använda ett GraphQL-schema som inte är identiskt med basdelgrafens, men bara kompatibelt med den. Det måste vara ett giltigt delgrafschema i sig själv, men kan avvika från basdelgrafens schema på följande sätt:

  • Den lägger till eller tar bort entitetstyper
  • Den tar bort attribut från entitetstyper
  • Den lägger till nollställbara attribut till entitetstyper
  • Den gör icke-nollställbara attribut till nollställbara attribut
  • Den lägger till värden till enum
  • Den lägger till eller tar bort gränssnitt
  • Den ändrar vilka entitetstyper som ett gränssnitt är implementerat för

Funktionshantering: grafting måste deklareras under features i delgrafens manifest.

IPFS/Arweave File Data Sources

Länk till detta avsnitt

Filbaserade datakällor är en ny delgrafsfunktion för att få tillgång till data utanför kedjan under indexering på ett robust, utökat sätt. Filbaserade datakällor stödjer hämtning av filer från IPFS och från Arweave.

Detta lägger också grunden för deterministisk indexering av data utanför kedjan, samt möjligheten att introducera godtycklig data som hämtas via HTTP.

Rather than fetching files "in line" during handler execution, this introduces templates which can be spawned as new data sources for a given file identifier. These new data sources fetch the files, retrying if they are unsuccessful, running a dedicated handler when the file is found.

This is similar to the existing data source templates, which are used to dynamically create new chain-based data sources.

Detta ersätter den befintliga ipfs.cat API

Uppgraderingsguide

Länk till detta avsnitt

Uppdatera graph-ts och graph-cli

Länk till detta avsnitt

Filbaserade datakällor kräver graph-ts >=0.29.0 och graph-cli >=0.33.1

Lägg till en ny entitetstyp som kommer att uppdateras när filer hittas

Länk till detta avsnitt

Filbaserade datakällor kan inte komma åt eller uppdatera kedjebaserade entiteter, utan måste uppdatera filspecifika entiteter.

Detta kan innebära att fält från befintliga entiteter separeras i separata entiteter som är kopplade ihop.

Ursprunglig kombinerad entitet:

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!
}

Ny, delad enhet:

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!
}

Om relationen är 1:1 mellan föräldraentiteten och den resulterande filbaserade datakälla entiteten är det enklaste mönstret att länka föräldraentiteten till en resulterande filbaserad entitet genom att använda IPFS CID som söknyckel. Kontakta oss på Discord om du har svårt att modellera dina nya filbaserade entiteter!

You can use nested filters to filter parent entities on the basis of these nested entities.

Lägg till en ny mallbaserad datakälla med kind: file/ipfs eller kind: file/arweave

Länk till detta avsnitt

Detta är datakällan som skapas när en intressant fil identifieras.

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

För närvarande krävs abis, även om det inte är möjligt att anropa kontrakt från filbaserade datakällor

The file data source must specifically mention all the entity types which it will interact with under entities. See limitations for more details.

Skapa en ny hanterare för att bearbeta filer

Länk till detta avsnitt

This handler should accept one Bytes parameter, which will be the contents of the file, when it is found, which can then be processed. This will often be a JSON file, which can be processed with graph-ts helpers (documentation).

CID för filen som en läsbar sträng kan nås via dataSource enligt följande:

const cid = dataSource.stringParam()

Exempel på hanterare:

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()
}
}

Skapa filbaserade datakällor vid behov

Länk till detta avsnitt

Nu kan du skapa filbaserade datakällor under utförandet av kedjebaserade hanterare:

  • Importera mallen från den automatiskt genererade templates
  • anropa TemplateName.create(cid: string) från en mappning, där cid är en giltig innehållsidentifierare för IPFS eller Arweave

För IPFS stöder Graph Node v0 och v1 innehållsidentifierare, och innehållsidentifierare med kataloger (t.ex. 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 Irys (previously Bundlr), and Graph Node can also fetch files based on Irys manifests.

Exempel:

import { TokenMetadata as TokenMetadataTemplate } from '../generated/templates'
const ipfshash = 'QmaXzZhcYnsisuue5WRdQDH6FDvqkLQX1NckLqBYeYYEfm'
//Denna exempelkod är för en undergraf för kryptosamverkan. Ovanstående ipfs-hash är en katalog med tokenmetadata för alla kryptosamverkande NFT:er.
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
//Detta skapar en sökväg till metadata för en enskild Crypto coven NFT. Den konkaterar katalogen med "/" + filnamn + ".json"
token.ipfsURI = tokenIpfsHash
TokenMetadataTemplate.create(tokenIpfsHash)
}
token.updatedAtTimestamp = event.block.timestamp
token.owner = event.params.to.toHexString()
token.save()
}

Detta kommer att skapa en ny filbaserad datakälla som kommer att övervaka Graph Nodes konfigurerade IPFS- eller Arweave-slutpunkt och försöka igen om den inte hittas. När filen hittas kommer filbaserad datakälla hanteraren att köras.

I det här exemplet används CID som koppling mellan förälderentiteten Token och den resulterande entiteten TokenMetadata.

Tidigare är detta det punkt där en delgrafutvecklare skulle ha anropat ipfs.cat(CID) för att hämta filen

Grattis, du använder filbaserade datakällor!

Distribuera dina delgrafer

Länk till detta avsnitt

Du kan nu bygga och distribuera dina delgrafer till en Graph Node >=v0.30.0-rc.0.

Begränsningar

Länk till detta avsnitt

Filbaserade datakällahanterare och entiteter är isolerade från andra delgrafentiteter, vilket säkerställer att de är deterministiska när de körs och att ingen förorening av kedjebaserade datakällor sker. För att vara specifik:

  • Entiteter skapade av Filbaserade datakällor är oföränderliga och kan inte uppdateras
  • Filbaserade datakällahanterare kan inte komma åt entiteter från andra filbaserade datakällor
  • Entiteter associerade med filbaserade datakällor kan inte nås av kedjebaserade hanterare

Även om denna begränsning inte bör vara problematisk för de flesta användningsfall kan den införa komplexitet för vissa. Var god kontakta oss via Discord om du har problem med att modellera din data baserad på fil i en delgraf!

Dessutom är det inte möjligt att skapa datakällor från en filbaserad datakälla, vare sig det är en datakälla på kedjan eller en annan filbaserad datakälla. Denna begränsning kan komma att hävas i framtiden.

Om du länkar NFT-metadata till motsvarande token, använd metadata IPFS-hash för att referera till en Metadata-entitet från Token-entiteten. Spara Metadata-entiteten med IPFS-hash som ID.

You can use DataSource context when creating File Data Sources to pass extra information which will be available to the File Data Source handler.

Om du har entiteter som uppdateras flera gånger, skapa unika filbaserade entiteter med IPFS-hash & entitets-ID, och referera till dem med hjälp av ett härlett fält i kedjebaserade entiteten.

Vi arbetar med att förbättra rekommendationen ovan så att förfrågningar endast returnerar den "senaste" versionen

Kända problem

Länk till detta avsnitt

Filbaserade datakällor kräver för närvarande ABIs, även om ABIs inte används (issue). Ett arbetsområde är att lägga till en ABI.

Handlers for File Data Sources cannot be in files which import eth_call contract bindings, failing with "unknown import: ethereum::ethereum.call has not been defined" (issue). Workaround is to create file data source handlers in a dedicated file.

Crypto Coven Migration av undergrafer

GIP Filbaserade datakällor

Redigera sida

Tidigare
Stödda Nätverk
Nästa
API för AssemblyScript
Redigera sida