API AssemblyScript
Reading time: 22 min
Remarque : Si vous avez créé un subgraph avant la version graph-cli
/graph-ts
0.22.0
, alors vous utilisez une ancienne version d'AssemblyScript. Il est recommandé de consulter le [Guide de Migration
] (/resources/release-notes/assemblyscript-migration-guide/).
Découvrez quelles APIs intégrées peuvent être utilisées lors de l'écriture des mappages de subgraph. Il existe deux types d'APIs disponibles par défaut :
Vous pouvez également ajouter d'autres bibliothèques comme dépendances, à condition qu'elles soient compatibles avec [AssemblyScript] ().
Étant donné que les mappages de langage sont écrits en AssemblyScript, il est utile de consulter les fonctionnalités de langage et de bibliothèque standard dans le du [wiki AssemblyScript] ().
La bibliothèque @graphprotocol/graph-ts
fournit les API suivantes :
- Une API
ethereum
pour travailler avec les contrats intelligents Ethereum, les événements, les blocs, les transactions et les valeurs Ethereum. - Une API
store
pour charger et enregistrer des entités depuis et vers le magasin Graph Node. - Une API
log
pour enregistrer des messages dans la sortie Graph Node et Graph Explorer. - Une API
ipfs
pour charger des fichiers depuis IPFS. - Une API
json
pour analyser les données JSON. - Une API
crypto
pour utiliser des fonctions cryptographiques. - Primitives de bas niveau pour traduire entre différents systèmes de types tels que Ethereum, JSON, GraphQL et AssemblyScript.
La apiVersion
dans le manifeste du subgraph spécifie la version de l'API de mappage exécutée par Graph Node pour un subgraph donné.
La documentation sur les types de base intégrés dans AssemblyScript se trouve dans le .
Les types additionnels suivants sont fournis par @graphprotocol/graph-ts
.
import { ByteArray } from '@graphprotocol/graph-ts'
ByteArray
représente un tableau de u8
.
Construction
fromI32(x: i32): ByteArray
- Décomposex
en octets.fromHexString(hex: string): ByteArray
- La longueur de la saisie doit être paire. Le préfixe0x
est optionnel.
Conversions de type
toHexString(): string
- Convertit en une chaîne de caractères hexadécimale ayant comme préfixe0x
.toString(): string
- Interprète les octets comme une chaîne UTF-8.- toBase58(): string` - Encode les octets en une chaîne de caractères de type base58.
toU32(): u32
- Interprète les octets comme unu32
en little-endian. Envoie une exception en cas de dépassement.toI32(): i32
- Interprète le tableau d'octets comme uni32
en little-endian. Envoie une exception en cas de dépassement.
Operateurs
equals(y: ByteArray): bool
– peut être écrit commex == y
.concat(other: ByteArray) : ByteArray
- renvoie un nouveauByteArray
constitué dethis
directement suivi parother
concatI32(other: i32) : ByteArray
- retourne un nouveauByteArray
constitué dethis
directement suivi par la représentation en octets deother
import { BigDecimal } from '@graphprotocol/graph-ts'
BigDecimal
est utilisé pour représenter des décimales à précision arbitraire.
Remarque: BigDecimal
est stocké au format , qui supporte 34 chiffres significatifs. Cela rend BigDecimal
inapproprié pour représenter des types à virgule fixe pouvant dépasser 34 chiffres, comme un Solidity ou équivalent.
Construction
constructor(bigInt: BigInt)
– crée unBigDecimal
à partir d'unBigInt
.static fromString(s: string): BigDecimal
– analyse à partir d'une chaîne de caractères décimaux.
Conversions de type
toString(): string
– affiche en une chaîne de caractères décimaux.
Math
plus(y: BigDecimal): BigDecimal
– peut être écrit commex + y
.minus(y: BigDecimal): BigDecimal
– peut être écrit commex - y
.times(y: BigDecimal): BigDecimal
– peut être écrit commex * y
.div(y: BigDecimal): BigDecimal
– peut être écrit commex / y
.equals(y: BigDecimal): bool
– peut être écrit commex == y
.notEqual(y: BigDecimal): bool
– peut être écrit commex != y
.lt(y: BigDecimal): bool
– peut être écrit commex < y
.le(y: BigDecimal): bool
– peut être écrit commex <= y
.gt(y: BigDecimal): bool
– peut être écrit commex > y
.ge(y: BigDecimal): bool
– peut être écrit commex >= y
.neg(): BigDecimal
- peut être écrit comme-x
.
importer { BigInt } depuis '@graphprotocol/graph-ts'
BigInt
est utilisé pour représenter de grands entiers. Cela inclut les valeurs Ethereum de type uint32
à uint256
et int64
àint256
. Tout ce qui est en dessous de uint32
, tel que int32
, uint24
ou int8
est représenté sous forme de i32
.
La classe BigInt
possède l'API suivante :
Construction
-
BigInt.fromI32(x: i32): BigInt
– crée unBigInt
à partir d'uni32
. -
BigInt.fromString(s: string): BigInt
– Analyse unBigInt
à partir d'une chaîne de caractères. -
BigInt.fromUnsignedBytes(x: Bytes): BigInt
– Interprètebytes
comme un entier non signé en little-endian. Si votre saisie est en big-endian, appelez d'abord.reverse()
. -
BigInt.fromSignedBytes(x: Bytes): BigInt
– Interprètebytes
comme un entier signé en little-endian. Si votre saisie est en big-endian, appelez d'abord.reverse()
.Conversions de type
-
x.toHex(): string
– transformeBigInt
en une chaîne de caractères hexadécimaux. -
x.toString(): string
– transformeBigInt
en une chaîne de caractères de nombres décimaux. -
x.toI32(): i32
– renvoie leBigInt
comme uni32
; échoue si la valeur ne rentre pas dans uni32
. Il est conseillé de vérifier d'abordx.isI32()
. -
x.toBigDecimal(): BigDecimal
- convertit en un nombre décimal sans virgule.
Math
x.plus(y: BigInt): BigInt
– peut être écrit commex + y
.x.minus(y: BigInt): BigInt
– peut être écrit commex - y
.x.times(y: BigInt): BigInt
– peut être écrit commex * y
.x.div(y: BigInt): BigInt
– peut être écrit commex / y
.x.mod(y: BigInt): BigInt
– peut être écrit commex % y
.x.equals(y: BigInt): bool
– peut être écrit commex == y
.x.notEqual(y: BigInt): bool
– peut être écrit commex != y
.x.lt(y: BigInt): bool
– peut être écrit commex < y
.x.le(y: BigInt): bool
– peut être écrit commex <= y
.x.gt(y: BigInt): bool
– peut être écrit commex > y
.x.ge(y: BigInt): bool
– peut être écrit commex >= y
.x.neg(): BigInt
– peut être écrit comme-x
.x.divDecimal(y: BigDecimal): BigDecimal
– divise par un nombre décimal, donnant un résultat décimal.x.isZero(): bool
– Est pratique pour vérifier si le nombre est zéro.x.isI32(): bool
– Vérifie si le nombre rentre dans uni32
.x.abs(): BigInt
– Valeur absolue.x.pow(exp: u8): BigInt
– Exponentiation.bitOr(x: BigInt, y: BigInt): BigInt
– peut être écrit commex | y
.bitAnd(x: BigInt, y: BigInt): BigInt
– peut être écrit commex & y
.leftShift(x: BigInt, bits: u8): BigInt
– peut être écrit commex << y
.rightShift(x: BigInt, bits: u8): BigInt
– peut être écrit commex >> y
.
import { TypedMap } from '@graphprotocol/graph-ts'
TypedMap
peut être utilisé pour stocker des paires clé-valeur. Consultez .
La classe TypedMap
possède l'API suivante :
new TypedMap<K, V>()
– crée une carte vide avec des clés de typeK
et des valeurs de typeV
map.set(key: K, value: V): void
– définit la valeur dekey
àvalue
map.getEntry(key: K): TypedMapEntry<K, V> | null
– renvoie la paire clé-valeur pour unekey
ounull
si lakey
n'existe pas dans la cartemap.get(key: K): V | null
– renvoie la valeur pour unekey
ounull
si lakey
n'existe pas dans la cartemap.isSet(key: K): bool
– renvoietrue
si lakey
existe dans la carte etfalse
si ce n'est pas le cas
import { Bytes } from '@graphprotocol/graph-ts'
Bytes
est utilisé pour représenter des tableaux d'octets de longueur arbitraire. Ceci inclut les valeurs Ethereum de type bytes
, bytes32
, etc.
La classe Bytes
hérite de d'AssemblyScript et prend en charge toutes les fonctionnalités de Uint8Array
ainsi que les nouvelles méthodes suivantes :
Construction
fromHexString(hex: string) : Bytes
- Convertit la chaîne de caractèreshex
qui doit comporter un nombre pair de chiffres hexadécimaux en unByteArray
. La chaîne de caractèreshex
peut de façon optionnelle commencer par0x
fromI32(i: i32) : Bytes
- Convertiti
en un tableau de d'octets
Conversions de type
b.toHex()
– renvoie une chaîne de caractères hexadécimale représentant les octets dans le tableaub.toString()
– convertit les octets dans le tableau en une chaîne de caractères unicodeb.toBase58()
– convertit une valeur Ethereum Bytes en codage de type base58 (utilisé pour les hachages IPFS)
Operateurs
b.concat(other: Bytes) : Bytes
- - renvoie un nouveauBytes
constitué dethis
suivi directement deother
b.concatI32(other: i32) : ByteArray
- renvoie un nouveauBytes
constitué dethis
suivi directement de la représentation en octets deother
import { Address } du '@graphprotocol/graph-ts'
Address
hérite de Bytes
pour représenter les valeurs address
d'Ethereum.
Cela ajoute la méthode suivante en plus de l'API Bytes
:
Address.fromString(s: string): Address
– crée uneAddress
à partir d'une chaîne de caractères hexadécimaleAddress.fromBytes(b: Bytes): Address
– crée uneAddress
à partir deb
qui doit avoir exactement 20 octets de long. Passer une valeur avec moins ou plus d'octets entraînera une erreur
import { store } from '@graphprotocol/graph-ts'
L'API store
permet de charger, sauvegarder et supprimer des entités dans et depuis le magasin Graph Node.
Les entités écrites dans le magasin correspondent directement aux types @entity
définis dans le schéma GraphQL du subgraph. Pour faciliter le travail avec ces entités, la commande graph codegen
fournie par génère des classes d'entités, qui sont des sous-classes du type Entity
intégré, avec des accesseurs et des mutateurs pour les champs du schéma ainsi que des méthodes pour charger et sauvegarder ces entités.
Ce qui suit est un modèle courant pour créer des entités à partir d’événements Ethereum.
// Importer la classe Transfer générée à partir de l'ABI ERC20import { Transfer as TransferEvent } from '../generated/ERC20/ERC20'// Importer le type d'entité Transfer généré à partir du schéma GraphQLimport { Transfer } from '../generated/schema'// Gestionnaire d'événement Transferexport function handleTransfer(event: TransferEvent): void {// Créer une entité Transfer, en utilisant le hash de la transaction comme ID de l'entitélet id = event.transaction.hashlet transfer = new Transfer(id)// Définir les propriétés sur l'entité, en utilisant les paramètres de l'événementtransfer.from = event.params.fromtransfer.to = event.params.totransfer.amount = event.params.amount// Sauvegarder l'entité dans le stockagetransfer.save()}
Lorsqu'un événement Transfer
est rencontré lors du traitement de la blockchain, il est transmis au gestionnaire d'événements handleTransfer
en utilisant le type Transfer
généré (ayant pour pseudonyme TransferEvent
ici pour éviter un conflit de nom avec le type d'entité). Ce type permet d'accéder à des données telles que la transaction parente de l'événement et ses paramètres.
Chaque entité doit avoir un ID unique pour éviter les collisions avec d'autres entités. Il est assez courant que les paramètres des événements incluent un identifiant unique pouvant être utilisé.
Remarque : utiliser le hash de la transaction comme ID suppose qu'aucun autre événement dans la même transaction ne crée d'entités avec ce hash comme ID.
Si une entité existe déjà, elle peut être chargée depuis le magasin avec les éléments suivants :
let id = event.transaction.hash // ou selon la manière dont l'ID est construitlet transfer = Transfer.load(id)if (transfer == null) {transfer = new Transfer(id)}// Utiliser l'entité Transfer comme précédemment
Comme l'entité peut ne pas encore exister dans le magasin, la méthode load
renvoie une valeur de type Transfer | null
. Il peut être nécessaire de vérifier le cas null
avant d'utiliser la valeur.
Remarque : Le chargement des entités n'est nécessaire que si les modifications apportées dans le mappage dépendent des données précédentes d'une entité. Consultez la section suivante pour savoir les deux façons de mettre à jour les entités existantes.
Depuis graph-node
v0.31.0, @graphprotocol/graph-ts
v0.30.0 et @graphprotocol/graph-cli
v0.49.0 la méthode loadInBlock
est disponible pour tous les types d'entités.
L'API store facilite la récupération des entités qui ont été créées ou mises à jour dans le bloc actuel. Une situation typique est qu'un gestionnaire crée une transaction à partir d'un événement on-chain, et qu'un gestionnaire ultérieur souhaite accéder à cette transaction si elle existe.
- Dans le cas où la transaction n'existe pas, le subgraph devra interroger la base de données pour découvrir que l'entité n'existe pas. Si l'auteur du subgraph sait déjà que l'entité doit avoir été créée dans le même bloc, utiliser
loadInBlock
évite ce détour par la base de données. - Pour certains subgraphs, ces recherches infructueuses peuvent contribuer de manière significative au temps d'indexation.
let id = event.transaction.hash // ou de toute autre manière dont l'ID est construitlet transfer = Transfer.loadInBlock(id)if (transfer == null) {transfer = new Transfer(id)}// Utiliser l'entité Transfer comme auparavant
Remarque : S'il n'y a pas d'entité créée dans le bloc donné, loadInBlock
renverra null
même s'il y a une entité avec l'ID donné dans le magasin.
Depuis graph-node
v0.31.0, @graphprotocol/graph-ts
v0.31.0 et@graphprotocol/graph-cli
v0.51.0 la méthode loadRelated
est disponible.
Cela permet de charger des champs d'entités dérivés à partir d'un gestionnaire d'événements. Par exemple, étant donné le schéma suivant :
type Token @entity {id: ID!holder: Holder!color: String}type Holder @entity {id: ID!tokens: [Token!]! @derivedFrom(field: "holder")}
Le code suivant chargera l'entité Token
dont l'entité Holder
est dérivée :
let holder = Holder.load('test-id')// Charger les entités Token associées à un détenteur donnélet tokens = holder.tokens.load()
Il existe deux manières de mettre à jour une entité existante :
- Chargez l'entité avec, par exemple,
Transfer.load(id)
, définissez des propriétés sur l'entité, puis.save()
pour la sauvegarder dans le magasin. - Créez simplement l'entité avec, par exemple,
new Transfer(id)
, séfinissez des propriétés sur l'entité, puis.save()
pour la sauvegarder dans le magasin. Si l'entité existe déjà, les modifications y sont fusionnées.
La modification des propriétés est simple dans la plupart des cas, grâce aux paramètres de propriétés générés :
let transfer = new Transfer(id)transfer.from = ...transfer.to = ...transfer.amount = ...
Il est également possible de supprimer des propriétés avec l'une des deux instructions suivantes :
transfer.from.unset()transfer.from = null
Ceci ne fonctionne qu'avec des propriétés optionnelles, c'est-à-dire des propriétés déclarées sans !
dans GraphQL. Deux exemples seraient owner: Bytes
ou amount: BigInt
.
La mise à jour des propriétés de tableau est un peu plus complexe, car obtenir un tableau à partir d'une entité crée une copie de ce tableau. Cela signifie que les propriétés de tableau doivent être définies à nouveau explicitement après la modification du tableau. Ce qui suit suppose que entity
a un champ numbers: [BigInt!]!
.
// Cela ne fonctionnera pasentity.numbers.push(BigInt.fromI32(1))entity.save()// Cela fonctionneralet numbers = entity.numbersnumbers.push(BigInt.fromI32(1))entity.numbers = numbersentity.save()
Il n'y a actuellement aucun moyen de supprimer une entité via les types générés. Au lieu de cela, la suppression d'une entité nécessite de passer le nom du type d'entité et l'ID de l'entité à store.remove
:
import { store } from '@graphprotocol/graph-ts'...let id = event.transaction.hashstore.remove('Transfer', id)
L'API Ethereum donne accès aux contrats intelligents, aux variables d'état public, aux fonctions de contrat, aux événements, aux transactions, aux blocs et aux données d'encodage/décodage Ethereum.
Comme pour les entités, graph codegen
génère des classes pour tous les contrats intelligents et événements utilisés dans un subgraph. Pour cela, les ABIs des contrats doivent faire partie de la source de données dans le manifeste du subgraph. En général, les fichiers ABI sont stockés dans un dossier abis/
.
Avec les classes générées, les conversions entre les types Ethereum et se font en arrière-plan afin que les auteurs de subgraph n'aient pas à s'en soucier.
L’exemple suivant illustre cela. Étant donné un schéma de subgraph comme
type Transfer @entity {id: Bytes!from: Bytes!to: Bytes!amount: BigInt!}
et une signature d'événement Transfer(address,address,uint256)
sur Ethereum, les valeurs from
, to
etamount
de type address
, address
et uint256
sont enverties en Address
et BigInt
, leur permettant d'être passées aux propriétés Bytes!
et BigInt!
de l'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()
Les événements Ethereum passés aux gestionnaires d'événements, comme l'événement Transfer
dans les exemples précédents, fournissent non seulement l'accès aux paramètres de l'événement, mais également à leur transaction parente et au bloc auquel ils appartiennent. Les données suivantes peuvent être obtenues à partir des instances d' event
(ces classes font partie du module ethereum
dans 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}
Le code généré par graph codegen
inclut également des classes pour les contrats intelligents utilisés dans le subgraph. Celles-ci peuvent être utilisées pour accéder aux variables d'état publiques et appeler des fonctions du contrat au bloc actuel.
Un modèle courant consiste à accéder au contrat dont provient un événement. Ceci est réalisé avec le code suivant :
// Importer la classe de contrat générée et la classe d'événement Transfer généréeimport { ERC20Contract, Transfer as TransferEvent } from '../generated/ERC20Contract/ERC20Contract'// Importer la classe d'entité généréeimport { Transfer } from '../generated/schema'export function handleTransfer(event: TransferEvent) {// Lier le contrat à l'adresse qui a émis l'événementlet contract = ERC20Contract.bind(event.address)// Accéder aux variables d'état et aux fonctions en les appelantlet erc20Symbol = contract.symbol()}
Transfer
est remplacé par TransferEvent
ici pour éviter un conflit de nommage avec le type d'entité
Tant que le ERC20Contract
sur Ethereum a une fonction publique en lecture seule appelée symbol
, elle peut être appelée avec .symbol()
. Pour les variables d'état publiques, une méthode du même nom est créée automatiquement.
Tout autre contrat faisant partie du subgraph peut être importé à partir du code généré et peut être lié à une adresse valide.
Si les méthodes en lecture seule de votre contrat peuvent échouer, vous devez gérer cela en appelant la méthode de contrat générée préfixée par try_
.
- Par exemple, le contrat Gravity expose la méthode
gravatarToOwner
. Ce code serait capable de gérer une erreur dans cette méthode :
let gravity = Gravity.bind(event.address)let callResult = gravity.try_gravatarToOwner(gravatar)if (callResult.reverted) {log.info('getGravatar a été annulé', [])} else {let owner = callResult.value}
Remarque : Un Graph Node connecté à un client Geth ou Infura peut ne pas détecter toutes les réversions (reverts). Si vous en dépendez, nous recommandons d'utiliser un Graph Node connecté à un client Parity.
Les données peuvent être encodées et décodées selon le format de codage ABI d'Ethereum en utilisant les fonctions encode
et decode
du module 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)
Pour plus d'informations:
Le solde de jetons natifs d'une adresse peut être récupéré en utilisant le module ethereum
. Cette fonctionnalité est disponible à partir de apiVersion: 0.0.9
définie dans subgraph.yaml
. La fonction getBalance()
récupère le solde de l'adresse spécifiée à la fin du bloc où l'événement est déclenché.
import { ethereum } from '@graphprotocol/graph-ts'let address = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')let balance = ethereum.getBalance(address) // renvoie le solde en BigInt
Vérifier si une adresse est une adresse de contrat intelligent ou une adresse détenue par des personnes (EOA)
Pour vérifier si une adresse est une adresse de contrat intelligent ou une adresse détenue extérieurement (EOA), utilisez la fonction hasCode()
du module ethereum
qui retournera un boolean
. Cette fonctionnalité est disponible à partir de apiVersion: 0.0.9
qui est définie dans subgraph.yaml
.
import { ethereum } from '@graphprotocol/graph-ts'let contractAddr = Address.fromString('0x2E645469f354BB4F5c8a05B3b30A929361cf77eC')let isContract = ethereum.hasCode(contractAddr).inner // renvoie truelet eoa = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')let isContract = ethereum.hasCode(eoa).inner // renvoie false
import { log } from '@graphprotocol/graph-ts'
L'API log
permet aux subgraphs d'enregistrer des informations sur la sortie standard de Graph Node ainsi que sur Graph Explorer. Les messages peuvent être enregistrés en utilisant différents niveaux de journalisation. Une syntaxe de chaîne de caractère de format de base est fournie pour composer des messages de journal à partir de l'argument.
L'API log
inclut les fonctions suivantes :
log.debug(fmt: string, args: Array<string>): void
- enregistre un message de débogage.log.info(fmt: string, args: Array<string>): void
- enregistre un message d'information.log.warning(fmt: string, args: Array<string>): void
- enregistre un avertissement.log.error(fmt: string, args: Array<string>): void
- enregistre un message d'erreur.log.critical(fmt: string, args: Array<string>): void
– enregistre un message critique et met fin au subgraph.
L'API log
prend une chaîne de caractères de format et un tableau de valeurs de chaîne de caractères. Elle remplace ensuite les espaces réservés par les valeurs de chaîne de caractères du tableau. Le premier espace réservé {}
est remplacé par la première valeur du tableau, le second {}
est remplacé par la deuxième valeur, et ainsi de suite.
log.info('Message à afficher : {}, {}, {}', [value.toString(), anotherValue.toString(), 'déjà une chaîne'])
Dans l'exemple ci-dessous, la valeur de chaîne de caractères "A" est passée dans un tableau pour devenir ['A']
avant d'être enregistrée:
let myValue = 'A'export function handleSomeEvent(event: SomeEvent): void {// Affiche : "Ma valeur est : A"log.info('Ma valeur est : {}', [myValue])}
Dans l'exemple ci-dessous, seule la première valeur du tableau d'arguments est journalisée, bien que le tableau contienne trois valeurs.
let myArray = ['A', 'B', 'C']export function handleSomeEvent(event: SomeEvent): void {// Affiche : "Ma valeur est : A" (Même si trois valeurs sont passées à `log.info`)log.info('Ma valeur est : {}', myArray)}
Chaque entrée dans le tableau des arguments nécessite son propre espace réservé {}
dans la chaîne de caractères du message de log. L'exemple ci-dessous contient trois espaces réservés {}
dans le message de log. À cause de cela, toutes les trois valeurs dans myArray
sont enregistrées.
let myArray = ['A', 'B', 'C']export function handleSomeEvent(event: SomeEvent): void {// Affiche : "Ma première valeur est : A, la deuxième valeur est : B, la troisième valeur est : C"log.info('Ma première valeur est : {}, la deuxième valeur est : {}, la troisième valeur est : {}', myArray)}
Pour afficher une valeur spécifique dans le tableau, la valeur indexée doit être fournie.
export function handleSomeEvent(event: SomeEvent): void {// Affiche : "Ma troisième valeur est C"log.info('Ma troisième valeur est : {}', [myArray[2]])}
L'exemple ci-dessous enregistre le numéro de bloc, le hachage de bloc et le hachage de transaction d'un événement :
import { log } from '@graphprotocol/graph-ts'export function handleSomeEvent(event: SomeEvent): void {log.debug('Numéro de bloc : {}, hachage de bloc : {}, hachage de transaction : {}', [event.block.number.toString(), // "47596000"event.block.hash.toHexString(), // "0x..."event.transaction.hash.toHexString(), // "0x..."])}
import { ipfs } from '@graphprotocol/graph-ts'
Les contrats intelligents ancrent occasionnellement des fichiers IPFS sur la blockchain. Cela permet aux mappages d'obtenir les hashs IPFS du contrat et de lire les fichiers correspondants à partir d'IPFS. Les données du fichier seront retournées sous forme de Bytes
, ce qui nécessite généralement un traitement supplémentaire, par exemple avec l'API json
documentée plus loin sur cette page.
Étant donné un hachage ou un chemin IPFS, la lecture d'un fichier depuis IPFS se fait comme suit :
// Mettez ceci à l'intérieur d'un gestionnaire d'événements dans le mappinglet hash = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D'let data = ipfs.cat(hash)// Les chemins comme `QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile`// qui incluent des fichiers dans des répertoires sont également pris en chargelet path = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile'let data = ipfs.cat(path)
Remarque: ipfs.cat
n'est pas déterministe pour le moment. Si le fichier ne peut pas être récupéré sur le réseau IPFS avant l'expiration de la demande, il retournera null
. Pour cette raison, il est toujours utile de vérifier le résultat pour null
.
Il est également possible de traiter des fichiers plus volumineux en streaming avec ipfs.map
. La fonction s'attend à recevoir un hash ou à un chemin pour un fichier IPFS, le nom d'un callback, et des indicateurs pour modifier son comportement :
import { JSONValue, Value } from '@graphprotocol/graph-ts'export function processItem(value: JSONValue, userData: Value): void {// Voir la documentation JSONValue pour les détails sur le traitement// des valeurs JSONlet obj = value.toObject()let id = obj.get('id')let title = obj.get('title')if (!id || !title) {return}// Les callbacks peuvent également créer des entitéslet newItem = new Item(id)newItem.title = title.toString()newItem.parent = userData.toString() // Définit le parent à "parentId"newItem.save()}// Mettez ceci à l'intérieur d'un gestionnaire d'événements dans le mappingipfs.map('Qm...', 'processItem', Value.fromString('parentId'), ['json'])// Alternativement, utilisez `ipfs.mapJSON`ipfs.mapJSON('Qm...', 'processItem', Value.fromString('parentId'))
Le seul indicateur actuellement pris en charge est json
, qui doit être passé à ipfs.map
. Avec l'indicateur json
, le fichier IPFS doit consister en une série de valeurs JSON, une valeur par ligne. L'appel à ipfs.map
lira chaque ligne du fichier, la désérialisera en un JSONValue
et appellera le callback pour chacune d'entre elles. Le callback peut alors utiliser des opérations des entités pour stocker des données à partir du JSONValue
. Les modifications d'entité ne sont enregistrées que lorsque le gestionnaire qui a appelé ipfs.map
se termine avec succès ; en attendant, elles sont conservées en mémoire, et la taille du fichier que ipfs.map
peut traiter est donc limitée.
En cas de succès, ipfs.map
renvoie void
. Si une invocation du callback provoque une erreur, le gestionnaire qui a invoqué ipfs.map
est interrompu et le subgraph marqué comme échoué.
import { crypto } from '@graphprotocol/graph-ts'
L'API crypto
rend des fonctions cryptographiques disponibles pour une utilisation dans les mappages. Actuellement, il n'y en a qu'une seule :
crypto.keccak256(input: ByteArray): ByteArray
import { json, JSONValueKind } from '@graphprotocol/graph-ts'
Les données JSON peuvent être analysées en utilisant l'API json
:
json.fromBytes(data: Bytes): JSONValue
– analyse les données JSON à partir d'un tableauBytes
interprété comme une séquence UTF-8 validejson.try_fromBytes(data: Bytes): Result<JSONValue, boolean>
– version sécurisée dejson.fromBytes
, elle renvoie une variante d'erreur si l'analyse échouejson.fromString(data: string): JSONValue
– analyse les données JSON à partir d'uneString
UTF-8 validejson.try_fromString(data: string): Result<JSONValue, boolean>
– version sécurisée dejson.fromString
, elle renvoie une variante d'erreur si l'analyse échoue
La classe JSONValue
fournit un moyen d'extraire des valeurs d'un document JSON arbitraire. Étant donné que les valeurs JSON peuvent être des booléens, des nombres, des tableaux et plus encore, JSONValue
est accompagné d'une propriété kind
pour vérifier le type d'une valeur :
let value = json.fromBytes(...)if (value.kind == JSONValueKind.BOOL) {...}
De plus, il existe une méthode pour vérifier si la valeur est null
:
value.isNull(): boolean
Lorsque le type d'une valeur est certain, il peut être converti en un n utilisant l'une des méthodes suivantes :
value.toBool(): boolean
value.toI64(): i64
value.toF64(): f64
value.toBigInt(): BigInt
value.toString(): string
value.toArray(): Array<JSONValue>
- (et ensuite convertirJSONValue
avec l'une des 5 méthodes ci-dessus)
Source(s) | Destination | Fonctions de conversion |
---|---|---|
Address | Bytes | aucune |
Address | String | s.toHexString() |
BigDecimal | String | s.toString() |
BigInt | BigDecimal | s.toBigDecimal() |
BigInt | String (hexadecimal) | s.toHexString() or s.toHex() |
BigInt | String (unicode) | s.toString() |
BigInt | i32 | s.toI32() |
Boolean | Boolean | aucune |
Bytes (signé) | BigInt | BigInt.fromSignedBytes(s) |
Bytes (non signé) | BigInt | BigInt.fromUnsignedBytes(s) |
Bytes | String (hexadecimal) | s.toHexString() or 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 | aucune |
int32 | i32 | aucune |
int32 | BigInt | BigInt.fromI32(s) |
uint24 | i32 | aucune |
int64 - int256 | BigInt | aucune |
uint32 - uint256 | BigInt | aucune |
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) |
Vous pouvez inspecter l'adresse du contrat, le réseau et le contexte de la source de données qui a invoqué le gestionnaire grâce un namespace dataSource
:
dataSource.address(): Address
dataSource.network(): string
dataSource.context(): DataSourceContext
La classe de base Entity
et la classe enfant DataSourceContext
disposent d'assistants pour définir et récupérer dynamiquement des champs :
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 section context
de dataSources
vous permet de définir des paires clé-valeur qui sont accessibles dans vos mappages de subgraphs. Les types disponibles sont Bool
, String
, Int
, Int8
, BigDecimal
, Bytes
, List
, et BigInt
.
Voici un exemple YAML illustrant l'utilisation de différents types dans la section 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
: Spécifie une valeur booléenne (true
oufalse
).String
: Spécifie une valeur de type chaîne de caractères.Int
: Spécifie un nombre entier de 32 bits.Int8
: Spécifie un entier de 8 bits.BigDecimal
: Spécifie un nombre décimal. Doit être entre mis guillemets.Bytes
: Spécifie une chaîne de caractères hexadécimale.List
: Spécifie une liste d'éléments. Chaque élément doit spécifier son type et ses données.BigInt
: Spécifie une grande valeur entière. Elle doit être mise entre guillemets en raison de sa grande taille.
Ce contexte est ensuite accessible dans vos fichiers de mappage de subgraphs, permettant des subgraphs plus dynamiques et configurables.