API AssemblyScript
Reading time: 21 min
Nota: Se criou um subgraph antes da versão 0.22.0
do graph-cli
/graph-ts
, está a usar uma versão mais antiga do AssemblyScript. Favor conferir o
Esta página documenta quais APIs embutidas podem ser usadas ao escrever mapeamentos de subgraph. Há dois tipos de API disponíveis do início:
Também é possível adicionar outras bibliotecas como dependências, desde que sejam compatíveis com o . Como esta é a linguagem na qual são escritos os mapeamentos, a é uma boa referência para a linguagem e as características comuns das bibliotecas.
A biblioteca @graphprotocol/graph-ts
fornece as seguintes APIs:
- Uma API
ethereum
para trabalhar com contratos inteligentes, eventos, blocos, transações, e valores no Ethereum. - Uma API
store
para carregar e guardar entidades dentro e fora do armazenamento do Graph Node. - Uma API
log
para gravar mensagens ao resultado do Graph Node e ao Graph Explorer. - Uma API
ipfs
para carregar arquivos do IPFS. - Uma API
json
para analisar dados em JSON. - Uma API
crypto
para usar funções criptográficas. - Primitivos de baixo nível para traduzir entre sistemas de tipos diferentes, como Ethereum, JSON, GraphQL e AssemblyScript.
No manifest do subgraph, apiVersion
especifica a versão da API de mapeamento, executada pelo Graph Node para um subgraph.
A documentação sobre os tipos de base embutidos no AssemblyScript está na .
Os seguintes tipos adicionais são fornecidos pelo @graphprotocol/graph-ts
.
import { ByteArray } from '@graphprotocol/graph-ts'
ByteArray
representa um arranjo de u8
.
Construção
fromI32(x: i32): ByteArray
- Decompõe x em bytes.fromHexString(hex: string): ByteArray
— O comprimento da entrada deve ser par. É facultativo prefixar com 0x.
Conversões de tipo
toHexString(): string
— Converte numa cadeia de caracteres hex prefixada com 0x`.toString(): string
— Interpreta os bytes como uma cadeia de caracteres UTF-8.toBase58(): string
— Codifica os bytes como uma cadeia base58.toU32(): u32
— Interpreta os bytes como umu32
little-endian. Não funciona em caso de overflow.toI32(): i32
- Interpreta o arranjo de byte como umi32
little-endian. Não funciona em caso de overflow.
Operadores
equals(y: ByteArray): bool
— pode ser escrito como x == y`.concat(other: ByteArray) : ByteArray
— retorna um novoByteArray
que consiste dethis
diretamente seguido deother
concatI32(other: i32) : ByteArray
— retorna um novoByteArray
que consiste dethis
diretamente seguido pela representação em byte deother
import { BigDecimal } from '@graphprotocol/graph-ts'
O BigDecimal
é usado para representar decimais de precisão arbitrária.
Nota: , o BigDecimal
é armazenado no , que apoia 34 dígitos decimais de significando. Portanto, o BigDecimal
não serve para representar tipos de ponto fixo que possam exceder 34 dígitos, como um (ufixed256x18)[] em Solidity ou equivalente.
Construção
constructor(bigInt: BigInt)
– cria umBigDecimal
a partir de umBigInt
.static fromString(s: string): BigDecimal
— faz uma análise sintática a partir de uma cadeia decimal.
Conversões de tipo
toString(): string
— imprime para uma cadeia decimal.
Matemática
plus(y: BigDecimal): BigDecimal
— pode ser escrito comox + y
.minus(y: BigDecimal): BigDecimal
— pode ser escrito comox - y
.times(y: BigDecimal): BigDecimal
— pode serx * y
.div(y: BigDecimal): BigDecimal
— pode ser escrito comox / y
.equals(y: BigDecimal): bool
— pode serx == y
.notEqual(y: BigDecimal): bool
— pode serx != y
.lt(y: BigDecimal): bool
— pode serx < y
.le(y: BigDecimal): bool
– pode serx <= y
.gt(y: BigDecimal): bool
– pode serx > y
.ge(y: BigDecimal): bool
– pode serx >= y
.neg(): BigDecimal
- pode ser-x
.
import { BigInt } from '@graphprotocol/graph-ts'
O BigInt
é usado para representar números inteiros grandes, inclusive valores em Ethereum de uint32
até uint256
, e int64
até int256
. Tudo abaixo de uint32
, como o int32
, uint24
ou int8
é representado como i32
.
A classe BigInt
tem a seguinte API:
Construção
-
BigInt.fromI32(x: i32): BigInt
– cria umBigInt
de umi32
. -
BigInt.fromString(s: string): BigInt
– Analisa umBigInt
de uma cadeia. -
BigInt.fromUnsignedBytes(x: Bytes): BigInt
— Interpretabytes
como um inteiro little-endian, não assinado. Se a sua entrada for big-endian, chame pelo.reverse()
primeiro. -
BigInt.fromSignedBytes(x: Bytes): BigInt
— Interpretabytes
como um inteiro little-endian, assinado. Se a sua entrada for big-endian, chame pelo.reverse()
primeiro.Conversões de tipo
-
x.toHex(): string
— transforma oBigInt
numa cadeia de caracteres hexadecimais. -
x.toString(): string
— transforma oBigInt
numa cadeia de números decimais. -
x.toI32(): i32
— retorna oBigInt
como umi32
; falha se o valor não couber noi32
. É bom verificar ox.isI32()
primeiro. -
x.toBigDecimal(): BigDecimal
— converte num decimal sem fracionário.
Matemática
x.plus(y: BigInt): BigInt
– pode ser escrito comox + y
.x.minus(y: BigInt): BigInt
– pode ser escrito comox - y
.x.times(y: BigInt): BigInt
– pode ser escrito comox * y
.x.div(y: BigInt): BigInt
– pode ser escrito comox / y
.x.mod(y: BigInt): BigInt
– pode ser escrito comox % y
.x.equals(y: BigInt): bool
– pode ser escrito comox == y
.x.notEqual(y: BigInt): bool
– pode ser escrito comox != y
.x.lt(y: BigInt): bool
– pode ser escrito comox < y
.x.le(y: BigInt): bool
– pode ser escrito comox <= y
.x.gt(y: BigInt): bool
– pode ser escrito comox > y
.x.ge(y: BigInt): bool
– pode ser escrito comox >= y
.x.neg(): BigInt
– pode ser escrito como-x
.x.divDecimal(y: BigDecimal): BigDecimal
– divide por um decimal e dá um resultado decimal.x.isZero(): bool
— Conveniência para conferir se o número é zero.x.isl32(): bool
— Confere se o número cabe em umi32
.x.abs(): BigInt
— Valor absoluto.x.pow(exp: u8): BigInt
— Exponenciação.bitOr(x: BigInt, y: BigInt): BigInt
— pode ser escrito comox | y
.bitAnd(x: BigInt, y: BigInt): BigInt
— pode ser escrito comox & y
.leftShift(x: BigInt, bits: u8): BigInt
– pode ser escrito comox << y
.rightShift(x: BigInt, bits: u8): BigInt
– pode ser escrito comox >> y
.
import { TypedMap } from '@graphprotocol/graph-ts'
O TypedMap
pode servir para armazenar pares de chave e valor (key e value ). Confira .
A classe TypedMap
tem a seguinte API:
new TypedMap<K, V>()
— cria um mapa vazio com chaves do tipoK
e valores do tipoV
map.set(key: K, value: V): void
— coloca o valor dokey
comovalue
map.getEntry(key: K): TypedMapEntry<K, V> | null
— retorna o par de valor-chave para umkey
ounull
se okey
não existir no mapamap.get(key: K): V | null
— retorna o valor para umkey
ounull
se okey
não existir no mapamap.isSet(key: K): bool
— retornatrue
se okey
existir no mapa efalse
se não existir
import { Bytes } from '@graphprotocol/graph-ts'
O Bytes
serve para representar arranjos de comprimento arbitrário de bytes. Isto inclui valores do Ethereum do tipo bytes
, bytes32
, etc.
A classe Bytes
estende o do AssemblyScript. Isto apoia toda a funcionalidade Uint8Array
, além dos novos métodos a seguir:
Construção
fromHexString(hex: string) : Bytes
— Converte a cadeiahex
, que deve consistir de um número par de dígitos hexadecimais para umByteArray
. Opcionalmente, a cadeiahex
pode começar com0x
fromI32(i: i32) : Bytes
- Converte oi
em um arranjo de bytes
Conversões de tipo
b.toHex()
— retorna uma cadeia hexadecimal que representa os bytes no arranjob.toString()
— converte os bytes no arranjo para uma cadeia de caracteres em unicodeb.toBase58()
— transforma um valor de Ethereum Bytes numa codificação base58 (usado para hashes IPFS)
Operadores
b.concat(other: Bytes) : Bytes
- - retorna um novoBytes
que consiste dethis
diretamente seguido porother
b.concatI32(other: i32) : ByteArray
— retorna um novoBytes
que consiste dethis
diretamente seguido pela representação em byte deother
import { Address } from '@graphprotocol/graph-ts'
Address
estende o Bytes
para representar valores de address
do Ethereum.
Ele adiciona o seguinte método em cima da API Bytes
:
Address.fromString(s: string): Address
— cria umAddress
a partir de uma cadeia hexadecimalAddress.fromBytes(b: Bytes): Address
— cria umAddress
a partir dob
, que deve ter o comprimento exato de 20 bytes. Preencher um valor com menos ou mais bytes causará um erro
import { store } from '@graphprotocol/graph-ts'
A API store
permite carregar, salvar e remover entidades do/para o armazenamento do Graph Node.
As entidades escritas no armazenamento mapeam um-por-um com os tipos de @entity
definidos no schema GraphQL do subgraph. Para trabalhar com estas entidades de forma conveniente, o comando graph codegen
fornecido pelo gera classes de entidades, que são subclasses do tipo embutido Entity
, com getters e setters de propriedade para os campos no schema e métodos para carregar e salvar estas entidades.
Este é um padrão comum para a criação de entidades de eventos do Ethereum.
// Importar a classe de evento de transferência gerada da ABI ERC20import { Transfer as TransferEvent } from '../generated/ERC20/ERC20'// Importar o tipo de entidade de transferência gerado do schema do GraphQLimport { Transfer } from '../generated/schema'// Handler de evento de transferênciaexport function handleTransfer(event: TransferEvent): void {// Criar uma entidade de Transferência, usando o hash da transação como a ID da entidadelet id = event.transaction.hashlet transfer = new Transfer(id)// Determinar propriedades na entidade, usando os parâmetros do eventotransfer.from = event.params.fromtransfer.to = event.params.totransfer.amount = event.params.amount// Salvar a entidade no armazenamentotransfer.save()}
Quando um evento Transfer
é encontrado durante o processamento da chain, ele é passado para o handler de evento handleTransfer
com o tipo Transfer
gerado (apelidado de TransferEvent
aqui, para evitar confusões com o tipo de entidade). Este tipo permite o acesso a dados como a transação parente do evento e seus parâmetros.
Cada entidade deve ter um ID única para evitar colisões com outras entidades. É bem comum que parâmetros de eventos incluam um identificador único a ser usado. Nota: usar o mesmo hash de transação como ID presume que nenhum outro evento na mesma transação criará entidades a usar este hash como o ID.
Se uma entidade já existe, ela pode ser carregada do armazenamento com os seguintes comandos:
let id = event.transaction.hash // ou como a ID for construídalet transfer = Transfer.load(id)if (transfer == null) {transfer = new Transfer(id)}// Use a entidade Transfer como antes
Como a entidade pode ainda não existir no armazenamento, o método load
retorna um valor de tipo Transfer | null
. Portanto, é bom prestar atenção ao caso null
antes de usar o valor.
Nota: Só é necessário carregar entidades se as mudanças feitas no mapeamento dependem dos dados anteriores de uma entidade. Veja a próxima seção para ver as duas maneiras de atualizar entidades existentes.
Desde o graph-node
v0.31.0, o @graphprotocol/graph-ts
v0.30.0 e o @graphprotocol/graph-cli v0.49.0
, o método loadInBlock
está disponível em todos os tipos de entidade.
A API do armazenamento facilita o resgate de entidades que foram criadas ou atualizadas no bloco atual. Um caso comum: um handler cria uma Transação de algum evento on-chain, e um handler seguinte quer acessar esta transação caso ela exista. Se a transação não existe, o subgraph deve acessar o banco de dados para descobrir que a entidade não existe; se o autor do subgraph já souber que a entidade deve ter sido criada no mesmo bloco, o uso do loadInBlock evita esta volta pelo banco de dados. Para alguns subgraphs, estas consultas perdidas podem contribuir muito para o tempo de indexação.
let id = event.transaction.hash // ou como a ID for construídalet transfer = Transfer.load(id)if (transfer == null) {transfer = new Transfer(id)}// Use a entidade Transfer como antes
Nota: se não houver nenhuma entidade criada no bloco dado, o loadInBlock
retornará um null
mesmo se houver uma entidade com o ID dado no armazenamento.
A partir da versão 0.31.0 do graph-node
, @graphprotocol/graph-ts
v0.31.0 e a versão 0.51.0 do @graphprotocol/graph-cli
, está disponível o método loadRelated
.
Isto permite o carregamento de campos de entidade derivada a partir de um event handler. Por exemplo, considerando o schema a seguir:
type Token @entity {id: ID!holder: Holder!color: String}type Holder @entity {id: ID!tokens: [Token!]! @derivedFrom(field: "holder")}
O código a seguir carregará a entidade Token
de que foi derivada a entidade Holder
:
let holder = Holder.load('test-id')// Carrega as entidades de Token associadas com um titular dadolet tokens = holder.tokens.load()
Há duas maneiras de atualizar uma entidade existente:
- Carregar a entidade com, por ex.,
Transfer.load(id)
, determinar as propriedades da entidade, e então usar o.save()
para colocá-la no armazenamento. - Simplesmente criar a entidade com, por ex.,
new Transfer(id)
, determinar as propriedades da entidade, e depois colocá-la no armazenamento com.save()
. Se a entidade já existir, as mudanças serão integradas a ela.
Geralmente é simples mudar propriedades, graças aos setters de propriedade gerados:
let transfer = new Transfer(id)transfer.from = ...transfer.to = ...transfer.amount = ...
Também é possível cancelar propriedades com uma das seguintes instruções:
transfer.from.unset()transfer.from = null
Isto só funciona com propriedades opcionais; por ex., propriedades declaradas sem um !
no GraphQL. Dois exemplos seriam owner: Bytes
ou amount: BigInt
.
Atualizar propriedades de arranjos é um processo um pouco mais envolvido, pois pegar um arranjo de uma entidade cria uma cópia deste mesmo arranjo. Isto significa que as propriedades de arranjos devem ser impostas explicitamente após mudar um arranjo. O seguinte assume que o entity
tem um campo numbers: [BigInt!]!
.
// Isto não funcionaráentity.numbers.push(BigInt.fromI32(1))entity.save()// Isto funcionarálet numbers = entity.numbersnumbers.push(BigInt.fromI32(1))entity.numbers = numbersentity.save()
Atualmente, não há como remover uma entidade através dos tipos gerados. Em vez disto, o processo requer a passagem do ID da entidade e do nome do tipo da mesma ao store.remove
:
import { store } from '@graphprotocol/graph-ts'...let id = event.transaction.hashstore.remove('Transfer', id)
A API do Ethereum fornece acesso a contratos inteligentes, variáveis de estado público, funções de contrato, eventos, transações, blocos e a codificação/decodificação de dados no Ethereum.
Assim como em entidades, o graph codegen
gera classes para todos os contratos inteligentes e eventos usados em um subgraph. Para isto, as ABIs dos contratos devem ser parte da fonte de dados no manifest do subgraph. Tipicamente, os arquivos da ABI são armazenados em uma pasta abis/
.
Com as classes geradas, conversões entre tipos no Ethereum e os acontecem em segundo plano para que os autores de subgraphs não precisem se preocupar com elas.
Veja um exemplo a seguir. Considerando um schema de subgraph como
type Transfer @entity {id: Bytes!from: Bytes!to: Bytes!amount: BigInt!}
e uma assinatura de evento Transfer(address,address,uint256)
na Ethereum, os valores from
, to
e amount
do tipo address
, address
and uint256
são convertidos para Address
and BigInt
, o que permite que sejam passados para as propriedades Bytes!
and BigInt!
da entidade Transfer
:
let id = event.transaction.hashlet transfer = new Transfer(id)transfer.from = event.params.fromtransfer.to = event.params.totransfer.amount = event.params.amounttransfer.save()
Eventos de Ethereum passados para handlers de eventos, como o evento Transfer
nos exemplos anteriores, não só permitem acessar os parâmetros de evento, mas também sua transação parente e o bloco de qual fazem parte. Os seguintes dados podem ser obtidos de instâncias event
(estas classes são parte do módulo ethereum
no 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}
O código gerado pelo graph codegen
também inclui classes para os contratos inteligentes usados no subgraph. Estes servem para acessar variáveis de estado público e funções de chamada do contrato no bloco atual.
É comum acessar o contrato de qual origina um evento. Isto é feito com o seguinte código:
// Importar a classe do contrato gerado e a classe do evento de transferência geradoimport { ERC20Contract, Transfer as TransferEvent } from '../generated/ERC20Contract/ERC20Contract'// Import the generated entity classimport { Transfer } from '../generated/schema'export function handleTransfer(event: TransferEvent) {// Ligar o contrato ao endereço que emitiu o eventolet contract = ERC20Contract.bind(event.address)// Acessar variáveis e funções de estado fazendo chamadaslet erc20Symbol = contract.symbol()}
O Transfer
é apelidado de TransferEvent
aqui para evitar confusões de nomenclatura com o tipo da entidade
Enquanto o ERC20Contract
no Ethereum tiver uma função pública de apenas-leitura chamada symbol
, ele pode ser chamado com o .symbol()
. Para variáveis de estado público, um método com o mesmo nome é criado automaticamente.
Qualquer outro contrato que seja parte do subgraph pode ser importado do código gerado e ligado a um endereço válido.
Se os métodos de apenas-leitura do seu contrato forem revertidos, chame o método do contrato gerado prefixado com try_
. Por exemplo, o contrato do Gravity expõe o método gravatarToOwner
. Este código poderia lidar com uma reversão naquele método:
let gravity = Gravity.bind(event.address)let callResult = gravity.try_gravatarToOwner(gravatar)if (callResult.reverted) {log.info('getGravatar reverted', [])} else {let owner = callResult.value}
Note que um Graph Node conectado a um cliente Geth ou Infura pode não detectar todas as reversões; se depender disto, recomendamos usar um Graph Node conectado a um cliente Parity.
Dados podem ser codificados e decodificados conforme o formato de codificação da ABI do Ethereum, através das funções encode
e decode
no módulo 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)
Para mais informações:
O saldo de token nativo de um endereço pode ser resgatado com o módulo ethereum
. Este recurso está disponível a partir do apiVersion: 0.0.9
, definido no subgraph.yaml
. O getBalance()
resgata o saldo do endereço especificado como o do fim do bloco em que o evento é adicionado.
import { ethereum } from '@graphprotocol/graph-ts'let address = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')let balance = ethereum.getBalance(address) // retorna o saldo em BigInt
Para conferir se um endereço é um de contrato inteligente ou um endereço titulado externamente (sigla em português para EOA), use a função hasCode()
do módulo ethereum
que retornará boolean
. Este recurso está disponível a partir do apiVersion: 0.0.9
, definido no 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 // retorna false
import { log } from '@graphprotocol/graph-ts'
A API log
permite que os subgraphs gravem informações à saída padrão do Graph Node, assim como ao Graph Explorer. Mensagens podem ser gravadas com níveis diferentes de log. É fornecida uma sintaxe básica de formatação de strings para compor mensagens de log do argumento.
A API log
inclui as seguintes funções:
log.debug(fmt: string, args: Array<string>): void
— loga uma mensagem de debug.log.info(fmt: string, args: Array<string>): void
- loga uma mensagem de debug.log.warning(fmt: string, args: Array<string>): void
- loga um aviso.log.error(fmt: string, args: Array<string>): void
- loga uma mensagem de erro.log.critical(fmt: string, args: Array<string>): void
– loga uma mensagem crítica e encerra o subgraph.
A API log
toma um string de formato e um arranjo de valores de string. Ele então substitui os temporários com os valores de strings do arranjo. O primeiro {}
temporário é substituído pelo primeiro valor no arranjo, o segundo {}
temporário é substituído pelo segundo valor, e assim por diante.
log.info('Message to be displayed: {}, {}, {}', [value.toString(), anotherValue.toString(), 'already a string'])
No exemplo abaixo, o valor de string "A" é passado a um arranjo para tornar-se ['A']
antes de ser registado no log:
let myValue = 'A'export function handleSomeEvent(event: SomeEvent): void {// Mostra : "My value is: A"log.info('My value is: {}', [myValue])}
No exemplo abaixo, só é logado o primeiro valor do arranjo do argumento, apesar de haver três valores no arranjo.
let myArray = ['A', 'B', 'C']export function handleSomeEvent(event: SomeEvent): void {// Displays : "My value is: A" (Apesar de três valores serem passados ao `log.info`)log.info('My value is: {}', myArray)}
Cada entrada no arranjo dos argumentos exige o seu próprio {}
no string de mensagens de log. O exemplo abaixo contém três {}
temporários na mensagem de log. Por causa disto, são registados todos os três valores no myArray
.
let myArray = ['A', 'B', 'C']export function handleSomeEvent(event: SomeEvent): void {// Mostra : "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)}
Para mostrar um valor específico no arranjo, forneça o valor indexado.
export function handleSomeEvent(event: SomeEvent): void {// Mostra : "My third value is C"log.info('My third value is: {}', [myArray[2]])}
O exemplo abaixo loga o número do bloco, hash do bloco e o hash da transação de um 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'
Contratos inteligentes ocasionalmente ancoram arquivos IPFS on-chain. Assim, os mapeamentos obtém os hashes IPFS do contrato e lêem os arquivos correspondentes do IPFS. Os dados dos arquivos serão retornados como Bytes
, o que costuma exigir mais processamento; por ex., com a API json
documentada mais abaixo nesta página.
Considerando um hash ou local IPFS, um arquivo do IPFS é lido da seguinte maneira:
// Coloque isto dentro de um handler de evento no mapeamentolet hash = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D'let data = ipfs.cat(hash)// Locais como `QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile`// que incluem arquivos em diretorias também são apoiadoslet path = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile'let data = ipfs.cat(path)
Nota: O ipfs.cat
não é determinístico no momento. Se o arquivo não puder ser retirado sobre a rede IPFS antes do tempo do pedido acabar, ele retornará um null
. Com isto, sempre vale a pena procurar o null
no resultado.
Também é possível processar arquivos maiores em streaming com o ipfs.map
. A função espera o hash ou local de um arquivo IPFS, o nome de um callback, e flags para modificar o seu comportamento:
import { JSONValue, Value } from '@graphprotocol/graph-ts'export function processItem(value: JSONValue, userData: Value): void {// Ver a documentação do JSONValue para detalhes sobre// como lidar com valores JSONlet obj = value.toObject()let id = obj.get('id')let title = obj.get('title')if (!id || !title) {return}// Callbacks também podem criar entidadeslet newItem = new Item(id)newItem.title = title.toString()newitem.parent = userData.toString() // Set parent to "parentId"newitem.save()}// Coloque isto dentro de um handler de evento no mapeamentoipfs.map('Qm...', 'processItem', Value.fromString('parentId'), ['json'])// Como alternativa, use `ipfs.mapJSON`ipfs.mapJSON('Qm...', 'processItem', Value.fromString('parentId'))
O único flag atualmente apoiado é o json
, que deve ser passado ao ipfs.map
. Com o flag json
, o arquivo IPFS deve consistir de uma série de valores JSON, com um valor por linha. Chamar ipfs.map
, irá ler cada linha no arquivo, desserializá-lo em um JSONValue
, e chamar o callback para cada linha. O callback pode então armazenar dados do JSONValue
com operações de entidade. As mudanças na entidade só serão armazenadas quando o handler que chamou o ipfs.map
concluir com sucesso; enquanto isso, elas ficam na memória, e o tamanho do arquivo que o ipfs.map
pode processar é então limitado.
Em caso de sucesso, o ipfs.map
retorna void
. Se qualquer invocação do callback causar um erro, o handler que invocou o ipfs.map
é abortado, e o subgraph é marcado como falho.
import { crypto } from '@graphprotocol/graph-ts'
A API crypto
disponibiliza funções criptográficas para uso em mapeamentos. No momento, apenas um está disponível:
crypto.keccak256(input: ByteArray): ByteArray
import { json, JSONValueKind } from '@graphprotocol/graph-ts'
Dados em JSON podem ser analisados com a API json
:
json.fromBytes(data: Bytes): JSONValue
— analisa dados JSON de um arranjoBytes
interpretado como uma sequência válida de UTF-8json.try_fromBytes(data: Bytes): Result<JSONValue, boolean>
— versão segura dojson.fromBytes
, retorna um erro se houver falha no parsingjson.fromString(data: string): JSONValue
— faz parsing de dados JSON de um String em UTF-8 válido`json.try_fromString(data: string): Result<JSONValue, boolean>
– versão segura dojson.fromString
, retorna um erro se houver falha no parsing
A classe JSONValue
fornece uma maneira de retirar valores de um documento JSON arbitrário. Como valores JSON podem ser booleans, números, arranjos e mais, o JSONValue
vem com uma propriedade kind
para conferir o tipo de um valor:
let value = json.fromBytes(...)if (value.kind == JSONValueKind.BOOL) {...}
Além disso, há um método para conferir se o valor é null
:
value.isNull(): boolean
Quando o tipo de um valor é confirmado, ele pode ser convertido num usando um dos seguintes métodos:
value.toBool(): boolean
value.toI64(): i64
value.toF64(): f64
value.toBigInt(): BigInt
value.toString(): string
value.toArray(): Array<JSONValue>
- (e depois converter oJSONValue
com um dos 5 métodos acima)
Fonte(s) | Destino | Função de conversão |
---|---|---|
Address | Bytes | nenhum |
Address | String | s.toHexString() |
BigDecimal | String | s.toString() |
BigInt | BigDecimal | s.toBigDecimal() |
BigInt | String (hexadecimal) | s.toHexString() ou s.toHex() |
BigInt | String (unicode) | s.toString() |
BigInt | i32 | s.toI32() |
Boolean | Boolean | nenhum |
Bytes (assinado) | BigInt | BigInt.fromSignedBytes(s) |
Bytes (não assinado) | BigInt | BigInt.fromUnsignedBytes(s) |
Bytes | String (hexadecimal) | s.toHexString() ou 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 | nenhum |
int32 | i32 | nenhum |
int32 | BigInt | BigInt.fromI32(s) |
uint24 | i32 | nenhum |
int64 - int256 | BigInt | nenhum |
uint32 - uint256 | BigInt | nenhum |
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) |
É possível inspecionar o endereço do contrato, a rede, e o contexto das fontes de dados que invocaram o handler através do namespace dataSource
:
dataSource.address(): Address
dataSource.network(): string
dataSource.context(): DataSourceContext
A classe base Entity
e a subclasse DataSourceContext
têm helpers para determinar e retornar campos de forma dinâmica:
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
A seção context
dentro do dataSources
lhe permite definir pares key-value acessíveis dentro dos seus mapeamentos de subgraph. Os tipos disponíveis são Bool
, String
, Int
, Int8
, BigDecimal
, Bytes
, List
, e BigInt
.
Aqui está um exemplo de YAML que ilustra o uso de vários tipos na seção 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
: Especifica um valor Boolean (true
oufalse
).String
: Especifica um valor String.Int
: Especifica um integral de 32 bits.Int8
: Especifica um integral de 8 bits.BigDecimal
: Especifica um número decimal. Deve ser citado.Bytes
: Especifica um string hexadecimal.List
: Especifica uma lista de itens. Cada item deve especificar o seu tipo e dados.BigInt
: Especifica um valor integral largo. É necessário citar este devido ao seu grande tamanho.
Este contexto, então, pode ser acessado nos seus arquivos de mapeamento de subgraph, o que resulta em subgraphs mais dinâmicos e configuráveis.