22 minutes
API AssemblyScript
Note : 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
.
Découvrez les API intégrées qui peuvent être utilisées lors de l’écriture de mappages de subgraphs. Deux types d’API sont disponibles sont disponibles nativement :
- La Bibliothèque TypeScript de The Graph (
graph-ts
) - Code généré à partir des fichiers de subgraphs par
graph codegen
Vous pouvez également ajouter d’autres bibliothèques comme dépendances, à condition qu’elles soient compatibles avec [AssemblyScript] (https://github.com/AssemblyScript/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] (https://github.com/AssemblyScript/assemblyscript/wiki).
Référence API
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.
Versions
La apiVersion
dans le manifeste du subgraph spécifie la version de l’API de mappage qui est exécutée par Graph Node pour un subgraph donné.
Version | Notes de version |
---|---|
0.0.9 | Ajout de nouvelles fonctions hôtes eth_get_balance & hasCode |
0.0.8 | Ajout de la validation pour l’existence des champs dans le schéma lors de l’enregistrement d’une entité. |
0.0.7 | Ajout des classes TransactionReceipt et Log aux types EthereumAjout du champ receipt à l’objet Ethereum Event |
0.0.6 | Ajout du champ nonce à l’objet Ethereum TransactionAjout de baseFeePerGas à l’objet Ethereum Block |
0.0.5 | AssemblyScript mis à jour vers la version 0.19.10 (cela inclut des changements de rupture, veuillez consulter le Guide de migration )ethereum.transaction.gasUsed renommé en ethereum.transaction.gasLimit |
0.0.4 | Ajout du champ functionSignature à l’objet Ethereum SmartContractCall |
0.0.3 | Ajout du champ from à l’objet Ethereum Callethereum.call.address renommé en ethereum.call.to |
0.0.2 | Ajout du champ input à l’objet Ethereum Transaction |
Types intégrés
La documentation sur les types de base intégrés dans AssemblyScript se trouve dans le wiki AssemblyScript.
Les types additionnels suivants sont fournis par @graphprotocol/graph-ts
.
ByteArray
1import { 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
BigDecimal
1import { BigDecimal } from '@graphprotocol/graph-ts'
BigDecimal
est utilisé pour représenter des décimales à précision arbitraire.
Remarque: En interne BigDecimal
est stocké au format IEEE-754 décimal128 à virgule flottante, qui supporte 34 chiffres significatifs. Cela rend BigDecimal
inapproprié pour représenter des types à virgule fixe pouvant dépasser 34 chiffres, comme un Solidity ufixed256x18
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
.
BigInt
1importer { 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
.
TypedMap
1import { TypedMap } from '@graphprotocol/graph-ts'
TypedMap
peut être utilisé pour stocker des paires clé-valeur. Consultez cet exemple.
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
Octets
1import { 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 Uint8Array 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
Addresse
1import { 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
Store API
1import { 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 store correspondent 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 Graph CLI génère des classes d’entités, qui sont des sous-classes du type intégré Entity
, avec des getters et des setters de propriétés pour les champs du schéma ainsi que des méthodes pour charger et sauvegarder ces entités.
Création d’entités
Ce qui suit est un modèle courant pour créer des entités à partir d’événements Ethereum.
1// Importer la classe Transfer générée à partir de l'ABI ERC202import { Transfer as TransferEvent } from '../generated/ERC20/ERC20'34// Importer le type d'entité Transfer généré à partir du schéma GraphQL5import { Transfer } from '../generated/schema'67// Gestionnaire d'événement Transfer8export function handleTransfer(event: TransferEvent): void {9 // Créer une entité Transfer, en utilisant le hash de la transaction comme ID de l'entité10 let id = event.transaction.hash11 let transfer = new Transfer(id)1213 // Définir les propriétés sur l'entité, en utilisant les paramètres de l'événement14 transfer.from = event.params.from15 transfer.to = event.params.to16 transfer.amount = event.params.amount1718 // Sauvegarder l'entité dans le stockage19 transfer.save()20}
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.
Chargement d’entités depuis le magasin
Si une entité existe déjà, elle peut être chargée depuis le magasin avec les éléments suivants :
1let id = event.transaction.hash // ou selon la manière dont l'ID est construit2let transfer = Transfer.load(id)3if (transfer == null) {4 transfer = new Transfer(id)5}67// 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.
Recherche d’entités créées dans un bloc
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 de store facilite la récupération des entités créées ou mises à jour dans le bloc actuel. Une situation typique pour cela est qu’un gestionnaire crée une transaction à partir d’un événement onchain 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 aller dans la base de données simplement pour découvrir que l’entité n’existe pas. Si l’auteur du subgraph sait déjà que l’entité a dû être créée dans le même bloc, l’utilisation de
loadInBlock
évite cet aller-retour dans la base de données. - Pour certains subgraphs, ces recherches manquées peuvent contribuer de manière significative au temps d’indexation.
1let id = event.transaction.hash // ou de toute autre manière dont l'ID est construit2let transfer = Transfer.loadInBlock(id)3if (transfer == null) {4 transfer = new Transfer(id)5}67// 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.
Recherche d’entités dérivées
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 :
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}
Le code suivant chargera l’entité Token
dont l’entité Holder
est dérivée :
1let holder = Holder.load('test-id')2// Charger les entités Token associées à un détenteur donné3let tokens = holder.tokens.load()
Mise à jour des entités existantes
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 :
1let transfer = new Transfer(id)2transfer.from = ...3transfer.to = ...4transfer.amount = ...
Il est également possible de supprimer des propriétés avec l’une des deux instructions suivantes :
1transfer.from.unset()2transfer.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!]!
.
1// Cela ne fonctionnera pas2entity.numbers.push(BigInt.fromI32(1))3entity.save()45// Cela fonctionnera6let numbers = entity.numbers7numbers.push(BigInt.fromI32(1))8entity.numbers = numbers9entity.save()
Supprimer des entités du magasin
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
:
1import { store } from '@graphprotocol/graph-ts'2...3let id = event.transaction.hash4store.remove('Transfer', id)
Ethereum API
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.
Prise en charge des types Ethereum
Comme pour les entités, graph codegen
génère des classes pour tous les contrats intelligents et les événements utilisés dans un subgraph. Pour cela, les ABI des contrats doivent faire partie de la source de données dans le manifeste du subgraph. Typiquement, les fichiers ABI sont stockés dans un dossier abis/
.
Avec les classes générées, les conversions entre les types Ethereum et les types intégrés ont lieu en coulisses, de sorte que les auteurs de subgraphs n’ont pas à s’en préoccuper.
L’exemple suivant l’illustre. Étant donné un schéma de Subgraphs tel que
1type Transfer @entity {2 id: Bytes!3 from: Bytes!4 to: Bytes!5 amount: BigInt!6}
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
:
1let id = event.transaction.hash2let transfer = new Transfer(id)3transfer.from = event.params.from4transfer.to = event.params.to5transfer.amount = event.params.amount6transfer.save()
Événements et données de bloc/transaction
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
):
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}
Accès à l’état du contrat intelligent
Le code généré par graph codegen
comprend é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 les fonctions du contrat dans le bloc actuel.
Un modèle courant consiste à accéder au contrat dont provient un événement. Ceci est réalisé avec le code suivant :
1// Importer la classe de contrat générée et la classe d'événement Transfer générée2import { ERC20Contract, Transfer as TransferEvent } from '../generated/ERC20Contract/ERC20Contract'3// Importer la classe d'entité générée4import { Transfer } from '../generated/schema'56export function handleTransfer(event: TransferEvent) {7 // Lier le contrat à l'adresse qui a émis l'événement8 let contract = ERC20Contract.bind(event.address)910 // Accéder aux variables d'état et aux fonctions en les appelant11 let erc20Symbol = contract.symbol()12}
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.
Gestion des appels retournés
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 :
1let gravity = Gravity.bind(event.address)2let callResult = gravity.try_gravatarToOwner(gravatar)3if (callResult.reverted) {4 log.info('getGravatar a été annulé', [])5} else {6 let owner = callResult.value7}
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.
Encodage/décodage ABI
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
.
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)
Pour plus d’informations:
- Spécifications ABI
- Encodage/décodage [bibliothèque Rust/CLI] (https://github.com/rust-ethereum/ethabi)
- Exemple plus complexe.
Solde d’une adresse
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é.
1import { ethereum } from '@graphprotocol/graph-ts'23let address = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')4let 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
.
1import { ethereum } from '@graphprotocol/graph-ts'23let contractAddr = Address.fromString('0x2E645469f354BB4F5c8a05B3b30A929361cf77eC')4let isContract = ethereum.hasCode(contractAddr).inner // renvoie true56let eoa = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')7let isContract = ethereum.hasCode(eoa).inner // renvoie false
Logging API
1import { log } from '@graphprotocol/graph-ts'
L’API log
permet aux subgraphs de consigner des informations sur la sortie standard de Graph Node ainsi que sur Graph Explorer. Les messages peuvent être enregistrés à différents niveaux. Une syntaxe de chaîne de caractère de format de base est fournie pour composer les messages de journal à partir d’un 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.
1log.info('Message à afficher : {}, {}, {}', [value.toString(), anotherValue.toString(), 'déjà une chaîne'])
Enregistrer une ou plusieurs valeurs
Enregistrer une seule valeur
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:
1let myValue = 'A'23export function handleSomeEvent(event: SomeEvent): void {4 // Affiche : "Ma valeur est : A"5 log.info('Ma valeur est : {}', [myValue])6}
Journalisation d’une seule entrée à partir d’un tableau existant
Dans l’exemple ci-dessous, seule la première valeur du tableau d’arguments est journalisée, bien que le tableau contienne trois valeurs.
1let myArray = ['A', 'B', 'C']23export function handleSomeEvent(event: SomeEvent): void {4 // Affiche : "Ma valeur est : A" (Même si trois valeurs sont passées à `log.info`)5 log.info('Ma valeur est : {}', myArray)6}
Journalisation de plusieurs entrées d’un tableau existant
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.
1let myArray = ['A', 'B', 'C']23export function handleSomeEvent(event: SomeEvent): void {4 // Affiche : "Ma première valeur est : A, la deuxième valeur est : B, la troisième valeur est : C"5 log.info('Ma première valeur est : {}, la deuxième valeur est : {}, la troisième valeur est : {}', myArray)6}
Enregistrer une entrée spécifique à partir d’un tableau existant
Pour afficher une valeur spécifique dans le tableau, la valeur indexée doit être fournie.
1export function handleSomeEvent(event: SomeEvent): void {2 // Affiche : "Ma troisième valeur est C"3 log.info('Ma troisième valeur est : {}', [myArray[2]])4}
Journalisation des informations sur les événements
L’exemple ci-dessous enregistre le numéro de bloc, le hachage de bloc et le hachage de transaction d’un événement :
1import { log } from '@graphprotocol/graph-ts'23export function handleSomeEvent(event: SomeEvent): void {4 log.debug('Numéro de bloc : {}, hachage de bloc : {}, hachage de transaction : {}', [5 event.block.number.toString(), // "47596000"6 event.block.hash.toHexString(), // "0x..."7 event.transaction.hash.toHexString(), // "0x..."8 ])9}
IPFS API
1import { ipfs } from '@graphprotocol/graph-ts'
Les contrats intelligents ancrent parfois des fichiers IPFS onchain. Cela permet aux mappages d’obtenir les hashs IPFS du contrat et de lire les fichiers correspondants depuis IPFS. Les données du fichier seront renvoyé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 :
1// Mettez ceci à l'intérieur d'un gestionnaire d'événements dans le mapping2let hash = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D'3let data = ipfs.cat(hash)45// Les chemins comme `QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile`6// qui incluent des fichiers dans des répertoires sont également pris en charge7let path = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile'8let 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 :
1import { JSONValue, Value } from '@graphprotocol/graph-ts'23export function processItem(value: JSONValue, userData: Value): void {4 // Voir la documentation JSONValue pour les détails sur le traitement5 // des valeurs JSON6 let obj = value.toObject()7 let id = obj.get('id')8 let title = obj.get('title')910 if (!id || !title) {11 return12 }1314 // Les callbacks peuvent également créer des entités15 let newItem = new Item(id)16 newItem.title = title.toString()17 newItem.parent = userData.toString() // Définit le parent à "parentId"18 newItem.save()19}2021// Mettez ceci à l'intérieur d'un gestionnaire d'événements dans le mapping22ipfs.map('Qm...', 'processItem', Value.fromString('parentId'), ['json'])2324// Alternativement, utilisez `ipfs.mapJSON`25ipfs.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 est marqué comme ayant échoué.
Crypto API
1import { 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
JSON API
1import { 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 :
1let value = json.fromBytes(...)2if (value.kind == JSONValueKind.BOOL) {3 ...4}
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 type intégré 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)
Référence des conversions de types
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) |
Métadonnées de la source de données
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
Entité et 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
DataSourceContext in Manifest
La section context
de dataSources
vous permet de définir des paires clé-valeur 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
:
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
: 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 Subgraph, ce qui permet de créer des Subgraphs plus dynamiques et configurables.