API AssemblyScript
Reading time: 21 min
Nota: se si hai creato un subgraph prima di graph-cli
/graph-ts
versione 0.22.0
, stai usando una versione precedente di AssemblyScript. Consigliamo di dare un'occhiata alla
Questa pagina documenta quali API integrate possono essere utilizzate per scrivere mappature di subgraph. Sono disponibili due tipi di API:
È anche possibile aggiungere altre librerie come dipendenze, purché siano compatibili con . Poiché questo è il linguaggio in cui sono scritte le mappature, il wiki è una buona fonte per le caratteristiche del linguaggio e delle librerie standard.
La libreria @graphprotocol/graph-ts
fornisce le seguenti API:
- Un'API
ethereum
per lavorare con gli smart contract di Ethereum, gli eventi, i blocchi, le transazioni e i valori di Ethereum. - Un'API
store
per caricare e salvare entità da e verso il Graph Node store. - A
log
API to log messages to the Graph Node output and Graph Explorer. - Un'API
ipfs
per caricare i file da IPFS. - Un'API
json
per analizzare i dati JSON. - Un'API
crypto
per utilizzare le funzioni crittografiche. - Primitive di basso livello per tradurre tra diversi sistemi di tipi come Ethereum, JSON, GraphQL e AssemblyScript.
La apiVersion
nel manifest del subgraph specifica la versione dell'API di mappatura che viene eseguita da the Graph Node per un dato subgraph.
Documentation on the base types built into AssemblyScript can be found in the .
I seguenti tipi aggiuntivi sono forniti da @graphprotocol/graph-ts
.
importare { ByteArray } da '@graphprotocol/graph-ts'
ByteArray
rappresenta un array di u8
.
Construction
fromI32(x: i32): ByteArray
- Decomponex
in byte.fromHexString(hex: string): ByteArray
- La lunghezza dell'input deve essere pari. Il prefisso0x
è facoltativo.
Type conversions
toHexString(): string
- Converte in una stringa esadecimale con prefisso0x
.toString(): string
- Interpreta i byte come una string UTF-8.toBase58(): string
- Codifica i byte in una stringa base58.toU32(): u32
- Interpreta i byte come unu32
little-endian. Viene lanciata in caso di overflow.toI32(): i32
- Interpreta l'array di byte come uni32
little-endian. Viene lanciata in caso di overflow.
Operators
- equals(y: ByteArray): bool
- può essere scritto come
x == y`. concat(other: ByteArray) : ByteArray
- restituisce un nuovoByteArray
costituito dathis
direttamente seguito daother
concatI32(other: i32) : ByteArray
- restituisce un nuovoByteArray
costituito dathis
direttamente seguito dalla rappresentazione in byte diother
import { BigDecimal } from '@graphprotocol/graph-ts'
BigDecimal
è usato per rappresentare decimali di precisione arbitraria.
Note: BigDecimal
is stored in , which supports 34 decimal digits of significand. This makes BigDecimal
unsuitable for representing fixed-point types that can span wider than 34 digits, such as a Solidity or equivalent.
Construction
constructor(bigInt: BigInt)
- crea unBigDecimal
da unBigInt
.static fromString(s: string): BigDecimal
- analizza una stringa decimale.
Type conversions
toString(): string
- stampa una stringa decimale.
Math
plus(y: BigDecimal): BigDecimal
- può essere scritto comex + y
.minus(y: BigDecimal): BigDecimal
- può essere scritto comex - y
.times(y: BigDecimal): BigDecimal
- può essere scritto comex * y
.div(y: BigDecimal): BigDecimal
- può essere scritto comex / y
.equals(y: BigDecimal): bool
- può essere scritto comex == y
.notEqual(y: BigDecimal): bool
- può essere scritto comex != y
.lt(y: BigDecimal): bool
– può essere scritto comex < y
.le(y: BigDecimal): bool
– può essere scritto comex <= y
.gt(y: BigDecimal): bool
– può essere scritto comex > y
.ge(y: BigDecimal): bool
– può essere scritto comex >= y
.neg(): BigDecimal
- può essere scritto come-x
.
import { BigInt } from '@graphprotocol/graph-ts'
BigInt
è usato per rappresentare i grandi numeri interi. Questo include i valori Ethereum di tipo da uint32
a uint256
e da int64
a int256
. Tutto ciò che è inferiore a uint32
, come int32
, uint24
o int8
è rappresentato come i32
.
La classe BigInt
ha la seguente API:
Construction
-
BigInt.fromI32(x: i32): BigInt
- crea unBigInt
da uni32
. -
BigInt.fromString(s: string): BigInt
- Analizza unBigInt
da una stringa. -
BigInt.fromUnsignedBytes(x: Bytes): BigInt
- Interpretabytes
come un intero senza segno, little-endian. Se l'input è big-endian, chiamare prima.reverse()
. -
BigInt.fromSignedBytes(x: Bytes): BigInt
- Interpretabytes
come un intero firmato, little-endian. Se l'input è big-endian, chiamare prima.reverse()
.Type conversions
-
x.toHex(): string
- trasformaBigInt
in una stringa di caratteri esadecimali. -
x.toString(): string
- trasformaBigInt
in una stringa di numeri decimali. -
x.toI32(): i32
- restituisce ilBigInt
comei32
; fallisce se il valore non entra ini32
. È una buona idea controllare primax.isI32()
. -
x.toBigDecimal(): BigDecimal
- converte in un decimale senza parti frazionarie.
Math
x.plus(y: BigInt): BigInt
- può essere scritto comex + y
.x.minus(y: BigInt): BigInt
- può essere scritto comex - y
.x.times(y: BigInt): BigInt
- può essere scritto comex * y
.x.div(y: BigInt): BigInt
- può essere scritto comex / y
.x.mod(y: BigInt): BigInt
- può essere scritto comex % y
.x.equals(y: BigInt): bool
- può essere scritto comex == y
.x.notEqual(y: BigInt): bool
- può essere scritto comex != y
.x.lt(y: BigInt): bool
- può essere scritto comex < y
.x.le(y: BigInt): bool
- può essere scritto comex <= y
.x.gt(y: BigInt): bool
- può essere scritto comex > y
.x.ge(y: BigInt): bool
- può essere scritto comex >= y
.x.neg(): BigInt
- può essere scritto come-x
.x.divDecimal(y: BigDecimal): BigDecimal
- divide per un decimale, dando un risultato decimale.x.isZero(): bool
- Conviene prima verificare se il numero è zero.x.isI32(): bool
- Controlla se il numero è compreso in uni32
.x.abs(): BigInt
- Valore assoluto.x.pow(exp: u8): BigInt
- Esponenziale.bitOr(x: BigInt, y: BigInt): BigInt
- può essere scritto comex | y
.bitAnd(x: BigInt, y: BigInt): BigInt
- può essere scritto comex & y
.leftShift(x: BigInt, bits: u8): BigInt
- può essere scritto comex << y
.rightShift(x: BigInt, bits: u8): BigInt
- può essere scritto comex >> y
.
import { TypedMap } from '@graphprotocol/graph-ts'
TypedMap
può essere usato per memorizzare coppie key-value. Vedere [questo esempio] ().
La classe TypedMap
ha la seguente API:
new TypedMap<K, V>()
- crea una mappa vuota con chiavi di tipoK
e valori di tipoV
map.set(chiave: K, valore: V): void
- imposta il valore dichiave
suvalore
map.getEntry(key: K): TypedMapEntry<K, V> | null
- restituisce la coppia key-value per unakey
onull
se lakey
non esiste nella mappamap.get(chiave: K): V | null
- restituisce il valore di unachiave
onull
se lachiave
non esiste nella mappamap.isSet(key: K): bool
- restituiscetrue
se lakey
esiste nella mappa efalse
se non esiste
import { Bytes } from '@graphprotocol/graph-ts'
Bytes
è usato per rappresentare array di byte di lunghezza arbitraria. Questo include i valori Ethereum di tipo bytes
, bytes32
, ecc.
La classe Bytes
estende di AssemblyScript e supporta tutte le funzionalità di Uint8Array
, oltre ai seguenti nuovi metodi:
Construction
fromHexString(hex: string) : Bytes
- Converte la stringahex
, che deve essere composta da un numero pari di cifre esadecimali, in unByteArray
. La stringahex
può facoltativamente iniziare con0x
fromI32(i: i32) : Bytes
- Convertei
in un array di byte
Type conversions
b.toHex()
- restituisce una stringa esadecimale che rappresenta i byte nell'arrayb.toString()
- converte i byte dell'array in una stringa di caratteri unicodeb.toBase58()
- trasforma un valore Ethereum Bytes in una codifica base58 (usata per gli hash IPFS)
Operators
b.concat(other: Bytes) : Bytes
- - restituisce nuoviBytes
costituiti dathis
direttamente seguito daother
b.concatI32(other: i32) : ByteArray
- restituisce nuoviByte
costituiti dathis
direttamente seguito dalla rappresentazione in byte diother
import { Address } from '@graphprotocol/graph-ts'
Address
estende Bytes
per rappresentare i valori di address
di Ethereum.
Aggiunge il seguente metodo all'API Bytes
:
Address.fromString(s: string): Address
- crea unAddress
a partire da una stringa esadecimaleAddress.fromBytes(b: Bytes): Address
- crea unAddress
dab
che deve essere lungo esattamente 20 byte. Se si passa un valore con un numero di byte inferiore o superiore, risulterà in un errore
import { store } from '@graphprotocol/graph-ts'
L'API store
consente di caricare, salvare e rimuovere entità da e verso il Graph Node store.
Entities written to the store map one-to-one to the @entity
types defined in the subgraph's GraphQL schema. To make working with these entities convenient, the graph codegen
command provided by the generates entity classes, which are subclasses of the built-in Entity
type, with property getters and setters for the fields in the schema as well as methods to load and save these entities.
Quello che segue è un modello comune per la creazione di entità a partire da eventi Ethereum.
// Import the Transfer event class generated from the ERC20 ABIimport { Transfer as TransferEvent } from '../generated/ERC20/ERC20'// Import the Transfer entity type generated from the GraphQL schemaimport { Transfer } from '../generated/schema'// Transfer event handlerexport function handleTransfer(event: TransferEvent): void {// Create a Transfer entity, using the transaction hash as the entity IDlet id = event.transaction.hashlet transfer = new Transfer(id)// Set properties on the entity, using the event parameterstransfer.from = event.params.fromtransfer.to = event.params.totransfer.amount = event.params.amount// Save the entity to the storetransfer.save()}
Quando un evento Transfer
viene incontrato durante l'elaborazione della chain, viene passato al gestore dell'evento handleTransfer
usando il tipo Transfer
generato (qui alias TransferEvent
per evitare un conflitto di nomi con il tipo di entità). Questo tipo consente di accedere a dati quali la transazione genitore dell'evento e i suoi parametri.
Ogni entità deve avere un ID univoco per evitare collisioni con altre entità. È abbastanza comune che i parametri degli eventi includano un identificatore unico che può essere utilizzato. Nota: l'uso dell'hash della transazione come ID presuppone che nessun altro evento della stessa transazione crei entità con questo hash come ID.
Se un'entità esiste già, può essere caricata dall'archivio con la seguente procedura:
let id = event.transaction.hash // or however the ID is constructedlet transfer = Transfer.load(id)if (transfer == null) {transfer = new Transfer(id)}// Use the Transfer entity as before
Poiché l'entità potrebbe non esistere ancora nel negozio, il metodo load
restituisce un valore di tipo Transfer | null
. Potrebbe quindi essere necessario verificare il caso null
prima di utilizzare il valore.
**Nota: ** Il caricamento delle entità è necessario solo se le modifiche apportate alla mappatura dipendono dai dati precedenti di un'entità. Vedere la sezione successiva per i due modi di aggiornare le entità esistenti.
A partire da graph-node
v0.31.0, @graphprotocol/graph-ts
v0.30.0 e @graphprotocol/graph-cli
v0.49.0 il metodo loadInBlock
è disponibile per tutti i tipi di entità.
L'API Store facilita il recupero delle entità create o aggiornate nel blocco corrente. Una situazione tipica è quella in cui un gestore crea una transazione da qualche evento sulla catena e un gestore successivo vuole accedere a questa transazione, se esiste. Nel caso in cui la transazione non esista, il subgraph dovrà andare nel database solo per scoprire che l'entità non esiste; se l'autore del subgraph sa già che l'entità deve essere stata creata nello stesso blocco, l'uso di loadInBlock evita questo viaggio nel database. Per alcuni subgraph, queste ricerche mancate possono contribuire in modo significativo al tempo di indicizzazione.
let id = event.transaction.hash // or however the ID is constructedlet transfer = Transfer.loadInBlock(id)if (transfer == null) {transfer = new Transfer(id)}// Use the Transfer entity as before
Nota: Se non esiste un'entità creata nel blocco dato, loadInBlock
restituirà null
anche se esiste un'entità con l'ID dato nel negozio.
A partire da graph-node
v0.31.0, @graphprotocol/graph-ts
v0.31.0 e @graphprotocol/graph-cli
v0.51.0 è disponibile il metodo loadRelated
.
Ciò consente di caricare i campi entità derivati da un gestore di eventi. Per esempio, dato il seguente schema:
type Token @entity {id: ID!holder: Holder!color: String}type Holder @entity {id: ID!tokens: [Token!]! @derivedFrom(field: "holder")}
Il codice seguente carica l'entità Token
da cui è derivata l'entità Holder
:
let holder = Holder.load('test-id')// Load the Token entities associated with a given holderlet tokens = holder.tokens.load()
Esistono due modi per aggiornare un'entità esistente:
- Caricare l'entità con, ad esempio,
Transfer.load(id)
, impostare le proprietà sull'entità, quindi.save()
riportarla in archivio. - È sufficiente creare l'entità con, ad esempio,
new Transfer(id)
, impostare le proprietà sull'entità e quindi.save()
nel negozio. Se l'entità esiste già, le modifiche vengono unite ad essa.
La modifica delle proprietà è semplice nella maggior parte dei casi, grazie ai setter di proprietà generati:
let transfer = new Transfer(id)transfer.from = ...transfer.to = ...transfer.amount = ...
È anche possibile disattivare le proprietà con una delle due istruzioni seguenti:
transfer.from.unset()transfer.from = null
Questo funziona solo con le proprietà opzionali, cioè quelle dichiarate senza un !
in GraphQL. Due esempi potrebbero essere owner: Bytes
o amount: BigInt
.
L'aggiornamento delle proprietà degli array è un po' più complicato, poiché l'ottenimento di un array da un'entità crea una copia di tale array. Ciò significa che le proprietà dell'array devono essere impostate di nuovo in modo esplicito dopo aver modificato l'array. Quanto segue presuppone che l'entità abbia un campo numbers: [BigInt!]!
.
// This won't workentity.numbers.push(BigInt.fromI32(1))entity.save()// This will worklet numbers = entity.numbersnumbers.push(BigInt.fromI32(1))entity.numbers = numbersentity.save()
Attualmente non c'è modo di rimuovere un'entità tramite i tipi generati. Per rimuovere un'entità è necessario passare il nome del tipo di entità e l'ID dell'entità a store.remove
:
import { store } from '@graphprotocol/graph-ts'...let id = event.transaction.hashstore.remove('Transfer', id)
L'API di Ethereum fornisce l'accesso agli smart contract, alle variabili di stato pubbliche, alle funzioni dei contratti, agli eventi, alle transazioni, ai blocchi e alla codifica/decodifica dei dati di Ethereum.
Come per le entità, graph codegen
genera classi per tutti gli smart contract e gli eventi utilizzati in un subgraph. Per questo, gli ABI dei contratti devono far parte dell'origine dati nel manifest del subgraph. In genere, i file ABI sono memorizzati in una cartella abis/
.
Con le classi generate, le conversioni tra i tipi di Ethereum e i avvengono dietro le quinte, in modo che gli autori dei subgraph non debbano preoccuparsene.
L'esempio seguente lo illustra. Dato uno schema di subgraph come
type Transfer @entity {id: Bytes!from: Bytes!to: Bytes!amount: BigInt!}
e una firma di evento Transfer(address,address,uint256)
su Ethereum, i valori from
, to
e amount
di tipo address
, address
e uint256
sono convertiti in Address
e BigInt
, consentendo di passarli alle proprietà Bytes!
e BigInt!
dell'entità Transfer
:
let id = event.transaction.hashlet transfer = new Transfer(id)transfer.from = event.params.fromtransfer.to = event.params.totransfer.amount = event.params.amounttransfer.save()
Gli eventi di Ethereum passati ai gestori di eventi, come l'evento Transfer
negli esempi precedenti, non solo forniscono accesso ai parametri dell'evento, ma anche alla transazione genitore e al blocco di cui fanno parte. I seguenti dati possono essere ottenuti dalle istanze di event
(queste classi fanno parte del modulo ethereum
di graph-ts
):
class Event {address: AddresslogIndex: BigInttransactionLogIndex: BigIntlogType: string | nullblock: Blocktransaction: Transactionparameters: Array<EventParam>receipt: TransactionReceipt | null}class Block {hash: BytesparentHash: BytesunclesHash: Bytesauthor: AddressstateRoot: BytestransactionsRoot: BytesreceiptsRoot: Bytesnumber: BigIntgasUsed: BigIntgasLimit: BigInttimestamp: BigIntdifficulty: BigInttotalDifficulty: BigIntsize: BigInt | nullbaseFeePerGas: BigInt | null}class Transaction {hash: Bytesindex: BigIntfrom: Addressto: Address | nullvalue: BigIntgasLimit: BigIntgasPrice: BigIntinput: Bytesnonce: BigInt}class TransactionReceipt {transactionHash: BytestransactionIndex: BigIntblockHash: BytesblockNumber: BigIntcumulativeGasUsed: BigIntgasUsed: BigIntcontractAddress: Addresslogs: Array<Log>status: BigIntroot: ByteslogsBloom: Bytes}class Log {address: Addresstopics: Array<Bytes>data: BytesblockHash: BytesblockNumber: BytestransactionHash: BytestransactionIndex: BigIntlogIndex: BigInttransactionLogIndex: BigIntlogType: stringremoved: bool | null}
Il codice generato da graph codegen
include anche classi per gli smart contract utilizzati nel subgraph. Queste possono essere utilizzate per accedere alle variabili di stato pubbliche e per chiamare le funzioni del contratto nel blocco corrente.
Un modello comune è quello di accedere al contratto da cui proviene un evento. Questo si ottiene con il seguente codice:
// Import the generated contract class and generated Transfer event classimport { ERC20Contract, Transfer as TransferEvent } from '../generated/ERC20Contract/ERC20Contract'// Import the generated entity classimport { Transfer } from '../generated/schema'export function handleTransfer(event: TransferEvent) {// Bind the contract to the address that emitted the eventlet contract = ERC20Contract.bind(event.address)// Access state variables and functions by calling themlet erc20Symbol = contract.symbol()}
Transfer
è alias di TransferEvent
qui per evitare un conflitto di nomi con il tipo di entità
Finché il ERC20Contract
su Ethereum ha una funzione pubblica di sola lettura chiamata symbol
, questa può essere chiamata con .symbol()
. Per le variabili di stato pubbliche viene creato automaticamente un metodo con lo stesso nome.
Qualsiasi altro contratto che faccia parte del subgraph può essere importato dal codice generato e può essere legato a un indirizzo valido.
Se i metodi di sola lettura del contratto possono essere annullati, si deve gestire la situazione chiamando il metodo del contratto generato con il prefisso try_
. Per esempio, il contratto Gravity espone il metodo gravatarToOwner
. Questo codice sarebbe in grado di gestire un revert in quel metodo:
let gravity = Gravity.bind(event.address)let callResult = gravity.try_gravatarToOwner(gravatar)if (callResult.reverted) {log.info('getGravatar reverted', [])} else {let owner = callResult.value}
Si noti che un Graph node collegato a un client Geth o Infura potrebbe non rilevare tutti i reverts; se si fa affidamento su questo si consiglia di utilizzare un Graph node collegato a un client Parity.
I dati possono essere codificati e decodificati secondo il formato di codifica ABI di Ethereum utilizzando le funzioni encode
e decode
del modulo ethereum
.
import { Address, BigInt, ethereum } from '@graphprotocol/graph-ts'let tupleArray: Array<ethereum.Value> = [ethereum.Value.fromAddress(Address.fromString('0x0000000000000000000000000000000000000420')),ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(62)),]let tuple = tupleArray as ethereum.Tuplelet encoded = ethereum.encode(ethereum.Value.fromTuple(tuple))!let decoded = ethereum.decode('(address,uint256)', encoded)
Per maggiori informazioni:
The native token balance of an address can be retrieved using the ethereum
module. This feature is available from apiVersion: 0.0.9
which is defined subgraph.yaml
. The getBalance()
retrieves the balance of the specified address as of the end of the block in which the event is triggered.
import { ethereum } from '@graphprotocol/graph-ts'let address = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')let balance = ethereum.getBalance(address) // returns balance in BigInt
To check whether an address is a smart contract address or an externally owned address (EOA), use the hasCode()
function from the ethereum
module which will return boolean
. This feature is available from apiVersion: 0.0.9
which is defined subgraph.yaml
.
import { ethereum } from '@graphprotocol/graph-ts'let contractAddr = Address.fromString('0x2E645469f354BB4F5c8a05B3b30A929361cf77eC')let isContract = ethereum.hasCode(contractAddr).inner // returns truelet eoa = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')let isContract = ethereum.hasCode(eoa).inner // returns false
import { log } from '@graphprotocol/graph-ts'
The log
API allows subgraphs to log information to the Graph Node standard output as well as Graph Explorer. Messages can be logged using different log levels. A basic format string syntax is provided to compose log messages from argument.
L'API log
include le seguenti funzioni:
log.debug(fmt: string, args: Array<string>): void
- registra un messaggio di debug.log.info(fmt: string, args: Array<string>): void
- registra un messaggio informativo.log.warning(fmt: string, args: Array<string>): void
- registra un avviso.log.error(fmt: string, args: Array<string>): void
- registra un messaggio di errore.log.critical(fmt: string, args: Array<string>): void
- registra un messaggio critico and termina il subgraph.
L'API log
accetta una stringa di formato e un array di valori stringa. Quindi sostituisce i segnaposto con i valori stringa dell'array. Il primo segnaposto {}
viene sostituito dal primo valore dell'array, il secondo segnaposto {}
viene sostituito dal secondo valore e così via.
log.info('Message to be displayed: {}, {}, {}', [value.toString(), anotherValue.toString(), 'already a string'])
Nell'esempio seguente, il valore stringa "A" viene passato in un array per diventare ['A']
prima di essere registrato:
let myValue = 'A'export function handleSomeEvent(event: SomeEvent): void {// Displays : "My value is: A"log.info('My value is: {}', [myValue])}
Nell'esempio seguente, viene registrato solo il primo valore dell'array di argomenti, nonostante l'array contenga tre valori.
let myArray = ['A', 'B', 'C']export function handleSomeEvent(event: SomeEvent): void {// Displays : "My value is: A" (Even though three values are passed to `log.info`)log.info('My value is: {}', myArray)}
Ogni voce dell'array di argomenti richiede il proprio segnaposto {}
nella stringa del messaggio di log. L'esempio seguente contiene tre segnaposto {}
nel messaggio di log. Per questo motivo, tutti e tre i valori di myArray
vengono registrati.
let myArray = ['A', 'B', 'C']export function handleSomeEvent(event: SomeEvent): void {// Displays : "My first value is: A, second value is: B, third value is: C"log.info('My first value is: {}, second value is: {}, third value is: {}', myArray)}
Per visualizzare un valore specifico della matrice, è necessario fornire il valore indicizzato.
export function handleSomeEvent(event: SomeEvent): void {// Displays : "My third value is C"log.info('My third value is: {}', [myArray[2]])}
L'esempio seguente registra il numero di blocco, l'hash del blocco e l'hash della transazione di un evento:
import { log } from '@graphprotocol/graph-ts'export function handleSomeEvent(event: SomeEvent): void {log.debug('Block number: {}, block hash: {}, transaction hash: {}', [event.block.number.toString(), // "47596000"event.block.hash.toHexString(), // "0x..."event.transaction.hash.toHexString(), // "0x..."])}
import { ipfs } from '@graphprotocol/graph-ts'
Gli smart contract di tanto in tanto ancorano i file IPFS sulla chain. Ciò consente alle mappature di ottenere gli hash IPFS dal contratto e di leggere i file corrispondenti da IPFS. I dati del file saranno restituiti come Byte
, che di solito richiedono un'ulteriore elaborazione, ad esempio con l'API json
documentata più avanti in questa pagina.
Dato un hash o un percorso IPFS, la lettura di un file da IPFS avviene come segue:
// Put this inside an event handler in the mappinglet hash = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D'let data = ipfs.cat(hash)// Paths like `QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile`// that include files in directories are also supportedlet path = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile'let data = ipfs.cat(path)
Nota: ipfs.cat
non è deterministico al momento. Se il file non può essere recuperato attraverso la rete IPFS prima che la richiesta si esaurisca, restituirà null
. Per questo motivo, vale sempre la pena di controllare che il risultato non sia null
.
È anche possibile elaborare file di dimensioni maggiori in modo streaming con ipfs.map
. La funzione si aspetta l'hash o il percorso di un file IPFS, il nome di un callback e i flag per modificare il suo comportamento:
import { JSONValue, Value } from '@graphprotocol/graph-ts'export function processItem(value: JSONValue, userData: Value): void {// See the JSONValue documentation for details on dealing// with JSON valueslet obj = value.toObject()let id = obj.get('id')let title = obj.get('title')if (!id || !title) {return}// Callbacks can also created entitieslet newItem = new Item(id)newItem.title = title.toString()newitem.parent = userData.toString() // Set parent to "parentId"newitem.save()}// Put this inside an event handler in the mappingipfs.map('Qm...', 'processItem', Value.fromString('parentId'), ['json'])// Alternatively, use `ipfs.mapJSON`ipfs.mapJSON('Qm...', 'processItem', Value.fromString('parentId'))
L'unico flag attualmente supportato è json
, che deve essere passato a ipfs.map
. Con il flag json
, il file IPFS deve essere costituito da una serie di valori JSON, un valore per riga. La chiamata a ipfs.map
leggerà ogni riga del file, la deserializzerà in un JSONValue
e chiamerà il callback per ognuno di essi. Il callback può quindi utilizzare le operazioni sulle entità per memorizzare i dati dal JSONValue
. Le modifiche alle entità vengono memorizzate solo quando il gestore che ha chiamato ipfs.map
termina con successo; nel frattempo, vengono mantenute in memoria e la dimensione del file che ipfs.map
può elaborare è quindi limitata.
In caso di successo, ipfs.map
restituisce void
. Se una qualsiasi invocazione del callback causa un errore, il gestore che ha invocato ipfs.map
viene interrotto e il subgraph viene contrassegnato come fallito.
import { crypto } from '@graphprotocol/graph-ts'
L'API crypto
rende disponibili funzioni crittografiche da usare nelle mappature. Al momento, ce n'è solo una:
crypto.keccak256(input: ByteArray): ByteArray
import { json, JSONValueKind } from '@graphprotocol/graph-ts'
I dati JSON possono essere analizzati utilizzando l'API json
:
json.fromBytes(data: Bytes): JSONValue
- analizza i dati JSON da un array diBytes
interpretati come una sequenza UTF-8 validajson.try_fromBytes(data: Bytes): Result<JSONValue, boolean>
- versione sicura dijson.fromBytes
, restituisce una variante di errore se il parsing è fallitojson.fromString(data: string): JSONValue
- analizza i dati JSON da unastring
UTF-8 validajson.try_fromString(data: string): Result<JSONValue, boolean>
- versione sicura dijson.fromString
, restituisce una variante di errore se il parsing è fallito
La classe JSONValue
fornisce un modo per estrarre valori da un documento JSON arbitrario. Poiché i valori JSON possono essere booleani, numeri, array e altro, JSONValue
è dotato di una proprietà kind
per verificare il tipo di valore:
let value = json.fromBytes(...)if (value.kind == JSONValueKind.BOOL) {...}
Inoltre, esiste un metodo per verificare se il valore è null
:
value.isNull(): boolean
Quando il tipo di un valore è certo, può essere convertito in un usando uno dei seguenti metodi:
value.toBool(): boolean
value.toI64(): i64
value.toF64(): f64
value.toBigInt(): BigInt
value.toString(): string
value.toArray(): Array<JSONValue>
- (e poi convertireJSONValue
con uno dei 5 metodi precedenti)
Fonte(i) | Destinazione | Funzione di conversione |
---|---|---|
Address | Bytes | none |
Address | String | s.toHexString() |
BigDecimal | String | s.toString() |
BigInt | BigDecimal | s.toBigDecimal() |
BigInt | String (hexadecimal) | s.toHexString() o s.toHex() |
BigInt | String (unicode) | s.toString() |
BigInt | i32 | s.toI32() |
Boolean | Boolean | none |
Bytes (signed) | BigInt | BigInt.fromSignedBytes(s) |
Bytes (unsigned) | BigInt | BigInt.fromUnsignedBytes(s) |
Bytes | String (hexadecimal) | s.toHexString() o s.toHex() |
Bytes | String (unicode) | s.toString() |
Bytes | String (base58) | s.toBase58() |
Bytes | i32 | s.toI32() |
Bytes | u32 | s.toU32() |
Bytes | JSON | json.fromBytes(s) |
int8 | i32 | none |
int32 | i32 | none |
int32 | BigInt | BigInt.fromI32(s) |
uint24 | i32 | none |
int64 - int256 | BigInt | none |
uint32 - uint256 | BigInt | none |
JSON | boolean | s.toBool() |
JSON | i64 | s.toI64() |
JSON | u64 | s.toU64() |
JSON | f64 | s.toF64() |
JSON | BigInt | s.toBigInt() |
JSON | string | s.toString() |
JSON | Array | s.toArray() |
JSON | Object | s.toObject() |
String | Address | Address.fromString(s) |
Bytes | Address | Address.fromBytes(s) |
String | BigInt | BigInt.fromString(s) |
String | BigDecimal | BigDecimal.fromString(s) |
String (hexadecimal) | Bytes | ByteArray.fromHexString(s) |
String (UTF-8) | Bytes | ByteArray.fromUTF8(s) |
È possibile ispezionare l'indirizzo del contratto, la rete e il contesto dell'origine dati che ha invocato il gestore attraverso lo spazio dei nomi dataSource
:
dataSource.address(): Address
dataSource.network(): string
dataSource.context(): DataSourceContext
La classe base Entity
e la classe figlia DataSourceContext
hanno degli helper per impostare e ottenere dinamicamente i campi:
setString(key: string, value: string): void
setI32(key: string, value: i32): void
setBigInt(key: string, value: BigInt): void
setBytes(key: string, value: Bytes): void
setBoolean(key: string, value: bool): void
setBigDecimal(key, value: BigDecimal): void
getString(key: string): string
getI32(key: string): i32
getBigInt(key: string): BigInt
getBytes(key: string): Bytes
getBoolean(key: string): boolean
getBigDecimal(key: string): BigDecimal
La sezione contesto
all'interno di dataSources
consente di definire coppie chiave-valore accessibili nelle mappature dei subgraph. I tipi disponibili sono Bool
, String
, Int
, Int8
, BigDecimal
, Bytes
, List
e BigInt
.
Ecco un esempio YAML che illustra l'uso di vari tipi nella sezione context
:
dataSources:- kind: ethereum/contractname: ContractNamenetwork: mainnetcontext:bool_example:type: Booldata: truestring_example:type: Stringdata: 'hello'int_example:type: Intdata: 42int8_example:type: Int8data: 127big_decimal_example:type: BigDecimaldata: '10.99'bytes_example:type: Bytesdata: '0x68656c6c6f'list_example:type: Listdata:- type: Intdata: 1- type: Intdata: 2- type: Intdata: 3big_int_example:type: BigIntdata: '1000000000000000000000000'
Bool
: Specifica un valore booleano (true
ofalse
).String
: Specifica un valore string.Int
: Specifica un numero intero a 32 bit.Int8
: Specifica un numero intero a 8 bit.BigDecimal
: Specifica un numero decimale. Deve essere quotato.Bytes
: Specifica una string esadecimale.List
: Specifica un elenco di elementi. Ogni elemento deve specificare il suo tipo e i suoi dati.BigInt
: Specifica un valore intero di grandi dimensioni. Deve essere quotato a causa delle sue grandi dimensioni.
Questo contesto è quindi accessibile nei file di mappatura dei subgraph, consentendo di ottenere subgraph più dinamici e configurabili.