21 minutos
API AssemblyScript
Note: If you created a Subgraph prior to graph-cli
/graph-ts
version 0.22.0
, then you’re using an older version of AssemblyScript. It is recommended to review the Migration Guide
.
Learn what built-in APIs can be used when writing Subgraph mappings. There are two kinds of APIs available out of the box:
- A biblioteca do Graph TypeScript (
graph-ts
) - Code generated from Subgraph files by
graph codegen
Você também pode adicionar outras bibliotecas como dependências, contanto que sejam compatíveis com AssemblyScript.
Já que os mapeamentos de linguagem são escritos em AssemblyScript, vale a pena consultar os recursos padrão de linguagem e biblioteca da wiki do AssemblyScript.
Referência da API
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.
Versões
The apiVersion
in the Subgraph manifest specifies the mapping API version which is run by Graph Node for a given Subgraph.
Versão | Notas de atualização |
---|---|
0.0.9 | Adiciona novas funções de host eth_get_balance & hasCode |
0.0.8 | Adiciona validação para existência de campos no schema ao salvar uma entidade. |
0.0.7 | Classes TransactionReceipt e Log adicionadas aos tipos do EthereumCampo Campo receipt adicionado ao objeto Ethereum Event |
0.0.6 | Campo nonce adicionado ao objeto Ethereum TransactionCampo baseFeePerGas adicionado ao objeto Ethereum Block |
0.0.5 | AssemblyScript upgraded to version 0.19.10 (this includes breaking changes, please see the Migration Guide )ethereum.transaction.gasUsed renamed to ethereum.transaction.gasLimit |
0.0.4 | Campo functionSignature adicionado ao objeto Ethereum SmartContractCall |
0.0.3 | Campo from adicionado ao objeto de chamada no EthereumCallethereum.call.address renomeado para ethereum.call.to |
0.0.2 | Campo input adicionado ao objeto Ethereum Transaction |
Tipos Embutidos
A documentação sobre os tipos de base embutidos no AssemblyScript está na wiki do AssemblyScript.
Os seguintes tipos adicionais são fornecidos pelo @graphprotocol/graph-ts
.
ByteArray
1import { 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
BigDecimal
1import { BigDecimal } from '@graphprotocol/graph-ts'
O BigDecimal
é usado para representar decimais de precisão arbitrária.
Nota: Internalmente, o BigDecimal
é armazenado no formato de ponto flutuante IEEE-754 decimal128, 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)[https://docs.soliditylang.org/en/latest/types.html#fixed-point-numbers] 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
.
BigInt
1import { 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
.
TypedMap
1import { TypedMap } from '@graphprotocol/graph-ts'
O TypedMap
pode servir para armazenar pares de chave e valor (key e value ). Confira este exemplo.
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
Bytes
1import { 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 Uint8Array 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
Address
1import { 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
Armazenamento da API
1import { store } from '@graphprotocol/graph-ts'
A API store
permite carregar, salvar e remover entidades do/para o armazenamento do Graph Node.
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 Graph CLI 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.
Como criar entidades
Este é um padrão comum para a criação de entidades de eventos do Ethereum.
1// Importar a classe de evento de transferência gerada da ABI ERC202import { Transfer as TransferEvent } from '../generated/ERC20/ERC20'34// Importar o tipo de entidade de transferência gerado do schema do GraphQL5import { Transfer } from '../generated/schema'67// Handler de evento de transferência8export function handleTransfer(event: TransferEvent): void {9 // Criar uma entidade de Transferência, usando o hash da transação como a ID da entidade10 let id = event.transaction.hash11 let transfer = new Transfer(id)1213 // Determinar propriedades na entidade, usando os parâmetros do evento14 transfer.from = event.params.from15 transfer.to = event.params.to16 transfer.amount = event.params.amount1718 // Salvar a entidade no armazenamento19 transfer.save()20}
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 identificador exclusivo para evitar colisões com outras entidades. É bastante comum que parâmetros de evento incluam um identificador exclusivo que pode ser usado.
Nota: Usar o hash de transação como ID supõe que nenhum outro evento na mesma transação cria entidades com este hash como o ID.
Como carregar entidades a partir do armazenamento
Se uma entidade já existe, ela pode ser carregada do armazenamento com os seguintes comandos:
1let id = event.transaction.hash // ou como a ID for construída2let transfer = Transfer.load(id)3if (transfer == null) {4 transfer = new Transfer(id)5}67// 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.
Como consultar entidades criadas dentro de um bloco
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 a recuperação de entidades que já foram criadas ou atualizadas no bloco atual. Uma situação típica para isso é que um manipulador cria uma transação a partir de algum evento em cadeia, e um handler posterior quer acessar esta transação — se ela existir.
- In the case where the transaction does not exist, the Subgraph will have to go to the database simply to find out that the entity does not exist. If the Subgraph author already knows that the entity must have been created in the same block, using
loadInBlock
avoids this database roundtrip. - For some Subgraphs, these missed lookups can contribute significantly to the indexing time.
1let id = event.transaction.hash // ou como a ID for construída2let transfer = Transfer.load(id)3if (transfer == null) {4 transfer = new Transfer(id)5}67// 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.
Como buscar entidades derivadas
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:
1type Token @entity {2 id: ID!3 holder: Holder!4 color: String5}67type Holder @entity {8 id: ID!9 tokens: [Token!]! @derivedFrom(field: "holder")10}
O código a seguir carregará a entidade Token
de que foi derivada a entidade Holder
:
1let holder = Holder.load('test-id')2// Carrega as entidades de Token associadas com um titular dado3let tokens = holder.tokens.load()
Como atualizar entidades existentes
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:
1let transfer = new Transfer(id)2transfer.from = ...3transfer.to = ...4transfer.amount = ...
Também é possível cancelar propriedades com uma das seguintes instruções:
1transfer.from.unset()2transfer.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!]!
.
1// Isto não funcionará2entity.numbers.push(BigInt.fromI32(1))3entity.save()45// Isto funcionará6let numbers = entity.numbers7numbers.push(BigInt.fromI32(1))8entity.numbers = numbers9entity.save()
Como remover entidades do armazenamento
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
:
1import { store } from '@graphprotocol/graph-ts'2...3let id = event.transaction.hash4store.remove('Transfer', id)
API do Ethereum
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.
Apoio para Tipos no Ethereum
As with entities, graph codegen
generates classes for all smart contracts and events used in a Subgraph. For this, the contract ABIs need to be part of the data source in the Subgraph manifest. Typically, the ABI files are stored in an abis/
folder.
With the generated classes, conversions between Ethereum types and the built-in types take place behind the scenes so that Subgraph authors do not have to worry about them.
The following example illustrates this. Given a Subgraph schema like
1type Transfer @entity {2 id: Bytes!3 from: Bytes!4 to: Bytes!5 amount: BigInt!6}
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
:
1let id = event.transaction.hash2let transfer = new Transfer(id)3transfer.from = event.params.from4transfer.to = event.params.to5transfer.amount = event.params.amount6transfer.save()
Eventos e Dados de Blocos/Transações
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
):
1class Event {2 address: Address3 logIndex: BigInt4 transactionLogIndex: BigInt5 logType: string | null6 block: Block7 transaction: Transaction8 parameters: Array<EventParam>9 receipt: TransactionReceipt | null10}1112class Block {13 hash: Bytes14 parentHash: Bytes15 unclesHash: Bytes16 author: Address17 stateRoot: Bytes18 transactionsRoot: Bytes19 receiptsRoot: Bytes20 number: BigInt21 gasUsed: BigInt22 gasLimit: BigInt23 timestamp: BigInt24 difficulty: BigInt25 totalDifficulty: BigInt26 size: BigInt | null27 baseFeePerGas: BigInt | null28}2930class Transaction {31 hash: Bytes32 index: BigInt33 from: Address34 to: Address | null35 value: BigInt36 gasLimit: BigInt37 gasPrice: BigInt38 input: Bytes39 nonce: BigInt40}4142class TransactionReceipt {43 transactionHash: Bytes44 transactionIndex: BigInt45 blockHash: Bytes46 blockNumber: BigInt47 cumulativeGasUsed: BigInt48 gasUsed: BigInt49 contractAddress: Address50 logs: Array<Log>51 status: BigInt52 root: Bytes53 logsBloom: Bytes54}5556class Log {57 address: Address58 topics: Array<Bytes>59 data: Bytes60 blockHash: Bytes61 blockNumber: Bytes62 transactionHash: Bytes63 transactionIndex: BigInt64 logIndex: BigInt65 transactionLogIndex: BigInt66 logType: string67 removed: bool | null68}
Acesso ao Estado do Contrato Inteligente
The code generated by graph codegen
also includes classes for the smart contracts used in the Subgraph. These can be used to access public state variables and call functions of the contract at the current block.
É comum acessar o contrato de qual origina um evento. Isto é feito com o seguinte código:
1// Importar a classe do contrato gerado e a classe do evento de transferência gerado2import { ERC20Contract, Transfer as TransferEvent } from '../generated/ERC20Contract/ERC20Contract'3// Import the generated entity class4import { Transfer } from '../generated/schema'56export function handleTransfer(event: TransferEvent) {7 // Ligar o contrato ao endereço que emitiu o evento8 let contract = ERC20Contract.bind(event.address)910 // Acessar variáveis e funções de estado fazendo chamadas11 let erc20Symbol = contract.symbol()12}
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.
Any other contract that is part of the Subgraph can be imported from the generated code and can be bound to a valid address.
Como Lidar com Chamadas Revertidas
Se houver reversão dos métodos somente-leitura do seu contrato, cuide disso chamando o método do contrato gerado prefixado com try_
.
- Por exemplo, o contrato da Gravity expõe o método
gravatarToOwner
. Este código poderia manusear uma reversão nesse método:
1let gravity = Gravity.bind(event.address)2let callResult = gravity.try_gravatarToOwner(gravatar)3if (callResult.reverted) {4 log.info('getGravatar reverted', [])5} else {6 let owner = callResult.value7}
Observe que um Graph Node conectado a um cliente Geth ou Infura pode não detetar todas as reversões; se depender disto, recomendamos usar um Graph Node conectado a um cliente Parity.
ABI de Codificação/Decodificação
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
.
1import { Address, BigInt, ethereum } from '@graphprotocol/graph-ts'23let tupleArray: Array<ethereum.Value> = [4 ethereum.Value.fromAddress(Address.fromString('0x0000000000000000000000000000000000000420')),5 ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(62)),6]78let tuple = tupleArray as ethereum.Tuple910let encoded = ethereum.encode(ethereum.Value.fromTuple(tuple))!1112let decoded = ethereum.decode('(address,uint256)', encoded)
Para mais informações:
- ABI Spec
- Codificação e decodificação da biblioteca/CLI do Rust
- Um exemplo mais complexo.
Saldo de um Endereço
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.
1import { ethereum } from '@graphprotocol/graph-ts'23let address = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')4let balance = ethereum.getBalance(address) // retorna o saldo em BigInt
Como Conferir Se Um Endereço é Um Contrato Ou Um EOA
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
.
1import { ethereum } from '@graphprotocol/graph-ts'23let contractAddr = Address.fromString('0x2E645469f354BB4F5c8a05B3b30A929361cf77eC')4let isContract = ethereum.hasCode(contractAddr).inner // returns true56let eoa = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')7let isContract = ethereum.hasCode(eoa).inner // retorna false
API de Logging
1import { 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.
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
– logs a critical message and terminates the 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.
1log.info('Message to be displayed: {}, {}, {}', [value.toString(), anotherValue.toString(), 'already a string'])
Como logar um ou mais valores
Como logar um único valor
No exemplo abaixo, o valor de string “A” é passado a um arranjo para tornar-se ['A']
antes de ser registado no log:
1let myValue = 'A'23export function handleSomeEvent(event: SomeEvent): void {4 // Mostra : "My value is: A"5 log.info('My value is: {}', [myValue])6}
Como logar uma única entrada de um arranjo existente
No exemplo abaixo, só é logado o primeiro valor do arranjo do argumento, apesar de haver três valores no arranjo.
1let myArray = ['A', 'B', 'C']23export function handleSomeEvent(event: SomeEvent): void {4 // Displays : "My value is: A" (Apesar de três valores serem passados ao `log.info`)5 log.info('My value is: {}', myArray)6}
Como logar várias entradas de um arranjo existente
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
.
1let myArray = ['A', 'B', 'C']23export function handleSomeEvent(event: SomeEvent): void {4 // Mostra : "My first value is: A, second value is: B, third value is: C"5 log.info('My first value is: {}, second value is: {}, third value is: {}', myArray)6}
Como logar uma entrada específica de um arranjo existente
Para mostrar um valor específico no arranjo, forneça o valor indexado.
1export function handleSomeEvent(event: SomeEvent): void {2 // Mostra : "My third value is C"3 log.info('My third value is: {}', [myArray[2]])4}
Como logar informações de eventos
O exemplo abaixo loga o número do bloco, hash do bloco e o hash da transação de um evento:
1import { log } from '@graphprotocol/graph-ts'23export function handleSomeEvent(event: SomeEvent): void {4 log.debug('Block number: {}, block hash: {}, transaction hash: {}', [5 event.block.number.toString(), // "47596000"6 event.block.hash.toHexString(), // "0x..."7 event.transaction.hash.toHexString(), // "0x..."8 ])9}
API do IPFS
1import { 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:
1// Coloque isto dentro de um handler de evento no mapeamento2let hash = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D'3let data = ipfs.cat(hash)45// Locais como `QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile`6// que incluem arquivos em diretorias também são apoiados7let path = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile'8let 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:
1import { JSONValue, Value } from '@graphprotocol/graph-ts'23export function processItem(value: JSONValue, userData: Value): void {4 // Ver a documentação do JSONValue para detalhes sobre5 // como lidar com valores JSON6 let obj = value.toObject()7 let id = obj.get('id')8 let title = obj.get('title')910 if (!id || !title) {11 return12 }1314 // Callbacks também podem criar entidades15 let newItem = new Item(id)16 newItem.title = title.toString()17 newitem.parent = userData.toString() // Set parent to "parentId"18 newitem.save()19}2021// Coloque isto dentro de um handler de evento no mapeamento22ipfs.map('Qm...', 'processItem', Value.fromString('parentId'), ['json'])2324// Como alternativa, use `ipfs.mapJSON`25ipfs.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.
On success, ipfs.map
returns void
. If any invocation of the callback causes an error, the handler that invoked ipfs.map
is aborted, and the Subgraph is marked as failed.
API de Criptografia
1import { 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
API JSON
1import { 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:
1let value = json.fromBytes(...)2if (value.kind == JSONValueKind.BOOL) {3 ...4}
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 tipo embutido 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)
Referência de Conversões de Tipos
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) |
Metadados de Fontes de Dados
É 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
Entidade e 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
DataSourceContext no Manifest
The context
section within dataSources
allows you to define key-value pairs that are accessible within your Subgraph mappings. The available types are Bool
, String
, Int
, Int8
, BigDecimal
, Bytes
, List
, and BigInt
.
Aqui está um exemplo de YAML que ilustra o uso de vários tipos na seção context
:
1dataSources:2 - kind: ethereum/contract3 name: ContractName4 network: mainnet5 context:6 bool_example:7 type: Bool8 data: true9 string_example:10 type: String11 data: 'hello'12 int_example:13 type: Int14 data: 4215 int8_example:16 type: Int817 data: 12718 big_decimal_example:19 type: BigDecimal20 data: '10.99'21 bytes_example:22 type: Bytes23 data: '0x68656c6c6f'24 list_example:25 type: List26 data:27 - type: Int28 data: 129 - type: Int30 data: 231 - type: Int32 data: 333 big_int_example:34 type: BigInt35 data: '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.
This context is then accessible in your Subgraph mapping files, enabling more dynamic and configurable Subgraphs.