AssemblyScript API
Reading time: 18 min
Примечание: Если Вы создали субграф до версии graph-cli
/graph-ts
0.22.0, значит, Вы используете более старую версию AssemblyScript. Рекомендуется ознакомиться с [
Руководством по миграции`](/resources/release-notes/assemblyscript-migration-guide/).
Узнайте, какие встроенные API можно использовать при написании мэппингов субграфов. По умолчанию доступны два типа API:
Вы также можете добавлять другие библиотеки в качестве зависимостей, если они совместимы с .
Поскольку мэппинги языков написаны на AssemblyScript, полезно ознакомиться с функциями языка и стандартной библиотеки на .
Библиотека @graphprotocol/graph-ts
предоставляет следующие API:
- API
ethereum
для работы со смарт-контактами Ethereum, событиями, блоками, транзакциями и значениями Ethereum. - API
store
для загрузки и сохранения объектов из хранилища the Graph Node и в него. - API
log
для регистрации сообщений в выходных данных the Graph Node и Graph Explorer. - API
ipfs
для загрузки файлов из IPFS. - API
json
для выполнения разбора данных в формате JSON. - API
crypto
для использования криптографических функций. - Низкоуровневые примитивы для перевода между системами различных типов, таких как Ethereum, JSON, GraphQL и AssemblyScript.
apiVersion
в манифесте субграфа указывает версию мэппинга API, которая запускается посредством Graph Node для данного субграфа.
Документацию по базовым типам, встроенным в AssemblyScript, можно найти в .
Дополнительные типы, предоставляемые @graphprotocol/graph-ts
.
import { ByteArray } from '@graphprotocol/graph-ts'
ByteArray
представляет собой массив u8
.
Конструкция
fromI32(x: i32): ByteArray
— Разбиваетx
на байты.fromHexString(hex: string): ByteArray
- Длина ввода должна быть четной. Префикс0x
необязателен.
Преобразования типов
toHexString(): string
- Преобразуется в шестнадцатеричную строку с префиксом0x
.toString(): string
- Интерпретирует байты как строку UTF-8.to Base 58(): string
- Кодирует байты в строку base58.to U32(): u32
- Интерпретирует байты как little-endianu32
. Выбрасывает в случае переполнения.to I32(): i32
- Интерпретирует массив байтов как little-endiani32
. Выбрасывает в случае переполнения.
Операторы
equals(y: ByteArray): bool
– может быть записано какx == y
.concat(other: ByteArray) : ByteArray
- возвращает новыйByteArray
, состоящий изthis
, за которым непосредственно следуетother
concatI32(other: i32) : ByteArray
- возвращает новыйByteArray
, состоящий изthis
, за которым непосредственно следует байтовое представлениеother
import { BigDecimal } from '@graphprotocol/graph-ts'
BigDecimal
используется для представления десятичных знаков произвольной точности.
Примечание: BigDecimal
хранится в , который поддерживает 34 десятичных знака после запятой. Это делает BigDecimal
непригодным для представления типов с фиксированной точкой, которые могут охватывать более 34 знаков, таких как Solidity или его эквивалентов.
Конструкция
constructor(bigInt: BigInt)
— создаетBigDecimal
изBigInt
.static fromString(s: string): BigDecimal
– выполняет синтаксический разбор из десятичной строки.
Преобразования типов
toString(): string
– выводит в виде десятичной строки.
Математика
plus(y: BigDecimal): BigDecimal
– может быть записано какx + y
.minus(y: BigDecimal): BigDecimal
– может быть записано какx - y
.times(y: BigDecimal): BigDecimal
– может быть записано какx * y
.div(y: BigDecimal): BigDecimal
– может быть записано какx / y
.equals(y: BigDecimal): bool
– может быть записано какx == y
.notEqual(y: BigDecimal): bool
– может быть записано какx != y
.lt(y: BigDecimal): bool
– может быть записано какx < y
.le(y: BigDecimal): bool
– может быть записано какx <= y
.gt(y: BigDecimal): bool
– может быть записано какx > y
.ge(y: BigDecimal): bool
– может быть записано какx >= y
.neg(): BigDecimal
- может быть записано как-x
.
import { BigInt } from '@graphprotocol/graph-ts'
BigInt
используется для представления больших целых чисел. Сюда входят значения Ethereum типа от uint32
до uint256
и от int64
до int256
. Все, что находится ниже uint32
, например int32
, uint24
или int8
, представлено как i32
.
Класс BigInt
имеет следующий API:
Конструкция
-
BigInt.fromI32(x: i32): BigInt
– создаетBigInt
изi32
. -
BigInt.fromString(s:string): BigInt
– Выполняет разборBigInt
из строки. -
BigInt.fromUnsignedBytes(x: Bytes): BigInt
— Интерпретируетbytes
беззнаковое целое число в формате little-endian (младшие байты идут первыми). Если Ваши входные данные в формате big-endian (старшие байты идут первыми), сначала вызовите.reverse()
. -
BigInt.fromSignedBytes(x: Bytes): BigInt
— Интерпретируетbytes
как знаковое целое число в формате little-endian (младшие байты идут первыми). Если Ваши входные данные в формате big-endian (старшие байты идут первыми), сначала вызовите.reverse()
.Преобразования типов
-
x.toHex(): string
– преобразуетBigInt
в строку шестнадцатеричных символов. -
x.toString(): string
– преобразуетBigInt
в строку десятичных чисел. -
x.toI32(): i32
– возвращаетBigInt
в видеi32
; завершается с ошибкой, если значение не соответствуетi32
. Рекомендуется сначала проверитьx.is I32()
. -
x.to BigDecimal(): BigDecimal
- преобразует в десятичное число без дробной части.
Математика
x.plus(y: BigInt): BigInt
– может быть записано какx + y
.x.minus(y: BigInt): BigInt
– может быть записано какx - y
.x.times(y: BigInt): BigInt
– может быть записано какx * y
.x.div(y: BigInt): BigInt
– может быть записано какx / y
.x.mod(y: BigInt): BigInt
– может быть записано какx % y
.x.equals(y: BigInt): bool
– может быть записано какx == y
.x.notEqual(y: BigInt): bool
– может быть записано какx != y
.x.lt(y: BigInt): bool
– может быть записано какx < y
.x.le(y: BigInt): bool
– может быть записано какx <= y
.x.gt(y: BigInt): bool
– может быть записано какx > y
.x.ge(y: BigInt): bool
– может быть записано какx >= y
.x.neg(): BigInt
– может быть записано как-x
.x.divDecimal(y: BigDecimal): BigDecimal
– делит на десятичное число, что дает десятичный результат.x.isZero(): bool
– Удобство для проверки, равно ли число нулю.x.isI32(): bool
– Проверяет, соответствует ли числоi32
.x.abs(): BigInt
– Абсолютное значение.x.pow(exp: u8): BigInt
– Возведение в степень.bitOr(x: BigInt, y: BigInt): BigInt
– может быть записан какx | y
.bitAnd(x: BigInt, y: BigInt): BigInt
– может быть записан какx & y
.leftShift(x: BigInt, bits: u8): BigInt
– может быть записан какx << y
.rightShift(x: BigInt, bits: u8): BigInt
– может быть записан какx >> y
.
import { TypedMap } from '@graphprotocol/graph-ts'
TypedMap
можно использовать для хранения пар ключ-значение. Смотрите .
Класс TypedMap
имеет следующий API:
new TypedMap<K, V>()
создает пустую карту с ключами типаK
и значениями типаV
map.set(key: K, value: V): void
– устанавливает значениеkey
вvalue
map.getEntry(key: K): TypedMapEntry<K, V> | null
– возвращает пару ключ-значение дляkey
илиnull
, еслиkey
не существует на картеmap.get(key: K): V | null
– возвращает значение дляkey
илиnull
, еслиkey
не существует на картеmap.isSet(key: K): bool
– возвращаетtrue
, еслиkey
существует на карте, иfalse
, если его нет
import { Bytes } from '@graphprotocol/graph-ts'
Bytes
используется для представления массивов байтов произвольной длины. Сюда входят значения Ethereum типа bytes
, bytes32
и т. д.
Класс Bytes
расширяет [Uint8Array] из AssemblyScript () и поддерживает все функциональные возможности Uint8Array
, а также следующие новые методы:
Конструкция
fromHexString(hex: string) : Bytes
- Преобразует строкуhex
, которая должна состоять из четного числа шестнадцатеричных цифр, вByteArray
. Строкаhex
может опционально начинаться с0x
fromI32(i: i32) : Bytes
- Преобразовываетi
в массив байтов
Преобразования типов
b.toHex()
– возвращает шестнадцатеричную строку, представляющую байты в массивеb.toString()
– преобразует байты в массиве в строку символов Unicodeb.toBase58()
– преобразует значение EthereumBytes
в кодировкуbase58
(используется для хэшей IPFS)
Операторы
b.concat(other: Bytes) : Bytes
- - возвращает новыеBytes
, состоящие изthis
, за которым непосредственно следуетother
b.concatI32(other: i32) : ByteArray
- возвращает новыеBytes
, состоящие изthis
, за которым непосредственно следует байтовое представлениеother
import { Address } from '@graphprotocol/graph-ts'
Address
расширяет Bytes
для представления значений Ethereum address
.
Поверх Bytes
API добавляется следующий метод:
Address.fromString(s: string): Address
– создаетAddress
из шестнадцатеричной строкиAddress.fromBytes(b: Bytes): Address
— создайтеAddress
изb
длиной ровно 20 байт. Передача значения с меньшим или большим количеством байт приведет к ошибке
import { store } from '@graphprotocol/graph-ts'
API store
позволяет загружать, сохранять и удалять объекты из хранилища the Graph Node и в него.
Объекты, записанные в хранилище карты, сопоставляются один к одному с типами @entity
, определенными в схеме субграфов GraphQL. Чтобы сделать работу с этими объектами удобной, команда graph codegen
, предоставляемая генерирует классы объектов, которые являются подклассами встроенного типа Entity
, с геттерами и сеттерами свойств для полей в схеме, а также методами загрузки и сохранения этих объектов.
Ниже приведен общий шаблон для создания объектов из событий Ethereum.
// Импорт класса событий Transfer, сгенерированного из ERC20 ABIimport { Transfer as TransferEvent } from '../generated/ERC20/ERC20'// Импорт типа объекта Transfer, сгенерированного из схемы GraphQLimport { Transfer } from '../generated/schema'событие// Обработчик события передачиэкспортирует функцию handleTransfer(event: TransferEvent): void {// Создание объекта Transfer, с использованием хеша транзакции в качестве идентификатора объектаlet id = event.transaction.hashlet transfer = new Transfer(id)// Установка свойства объекта, с использованием параметров событияtransfer.from = event.params.fromtransfer.to = event.params.totransfer.amount = event.params.amount// Сохранение объекта в хранилищеtransfer.save()}
Когда при обработке чейна возникает событие Transfer
, оно передается обработчику события handleTransfer
, используя сгенерированный тип Transfer
(здесь используется псевдоним TransferEvent
, чтобы избежать конфликта наименования с типом объекта). Этот тип позволяет получить доступ к таким данным, как материнская транзакция события и ее параметры.
Каждый объект должен иметь уникальный идентификатор, чтобы избежать конфликтов с другими объектами. Параметры событий довольно часто включают уникальный идентификатор, который можно использовать.
Примечание: Использование хэша транзакции в качестве идентификатора предполагает, что никакие другие события в той же транзакции не создают объекты с этим хэшем в качестве идентификатора.
Если объект уже существует, его можно загрузить из хранилища следующим образом:
let id = event.transaction.hash // или некоторым образом создается идентификаторlet transfer = Transfer.load(id)if (transfer == null) {transfer = new Transfer(id)}// Используйте объект Transfer, как и раньше
Поскольку объект может еще не существовать в хранилище, метод load
возвращает значение типа Transfer | null
. Таким образом, перед использованием значения может потребоваться проверка на наличие null
.
Примечание: Загрузка объектов необходима только в том случае, если изменения, внесенные в мэппинг, зависят от предыдущих данных объекта. В следующем разделе описаны два способа обновления существующих объектов.
Начиная с graph-node
v0.31.0, @graphprotocol/graph-ts
v0.30.0 и @graphprotocol/graph-cli
v0.49.0 метод loadInBlock
доступен для всех типов объектов.
API хранилища облегчает извлечение объектов, которые были созданы или обновлены в текущем блоке. Типичная ситуация: один обработчик создает транзакцию из какого-то события в он-чейне, а следующий обработчик хочет получить доступ к этой транзакции, если она существует.
- В случае, если транзакция не существует, субграф должен будет обратиться к базе данных просто для того, чтобы узнать, что объект не существует. Если автор субграфа уже знает, что объект должен быть создан в том же блоке, использование
loadInBlock
позволяет избежать этого обращения к базе данных. - Для некоторых субграфов эти пропущенные поиски могут существенно увеличить время индексации.
let id = event.transaction.hash // или некоторым образом создается идентификаторlet transfer = Transfer.loadInBlock(id)if (transfer == null) {transfer = new Transfer(id)}// Используйте объект Transfer, как и раньше
Примечание: Если в данном блоке не создан объект, loadInBlock
вернет null
, даже если в хранилище есть объект с данным идентификатором.
Начиная с graph-node
v0.31.0, @graphprotocol/graph-ts
v0.31.0 и @graphprotocol/graph-cli
v0.51.0 метод loadRelated
доступен.
Это позволяет загружать поля производных объектов из обработчика событий. Например, учитывая следующую схему:
type Token @entity {id: ID!holder: Holder!color: String}type Holder @entity {id: ID!tokens: [Token!]! @derivedFrom(field: "holder")}
Следующий код загрузит объект Token
, из которого был получен объект Holder
:
let holder = Holder.load('test-id')// Загрузите объекты токена, связанные с данным держателемlet tokens = holder.tokens.load()
Существует два способа обновить существующий объект:
- Загрузите объект, например, с помощью
Transfer.load(id)
, установите свойства объекта, затем с помощью.save()
верните его обратно в хранилище. - Просто создайте объект, например, с помощью
new Transfer(id)
, установите свойства объекта, затем с помощью.save()
сохраните его в хранилище. Если объект уже существует, изменения будут объединены с ним.
Изменение свойств в большинстве случаев не вызывает затруднений благодаря сгенерированным установщикам свойств:
let transfer = new Transfer(id)transfer.from = ...transfer.to = ...transfer.amount = ...
Также можно сбросить свойства с помощью одной из следующих двух инструкций:
transfer.from.unset()transfer.from = null
Это работает только с необязательными свойствами, то есть свойствами, объявленными без !
в GraphQL. Два примера: owner: Bytes
или amount: BigInt
.
Обновление свойств массива немного сложнее, поскольку получение массива из объекта создает копию этого массива. Это означает, что после изменения массива необходимо снова точно задать его свойства. В следующем примере предполагается, что entity
имеет поле numbers: [BigInt!]!
.
// Это не сработаетentity.numbers.push(BigInt.fromI32(1))entity.save()// Это сработаетlet numbers = entity.numbersnumbers.push(BigInt.fromI32(1))entity.numbers = numbersentity.save()
В настоящее время нет способа удалить объект с помощью сгенерированных типов. Вместо этого для удаления объекта требуется передать имя типа объекта и идентификатор объекта в store.remove
:
import { store } from '@graphprotocol/graph-ts'...let id = event.transaction.hashstore.remove('Transfer', id)
Ethereum API предоставляет доступ к смарт-контрактам, общедоступным переменным состояния, функциям контрактов, событиям, транзакциям, блокам и кодированию/декодированию данных Ethereum.
Как и в случае с объектами, graph codegen
генерирует классы для всех смарт-контрактов и событий, используемых в субграфе. Для этого ABI контракта должны быть частью источника данных в манифесте субграфа. Как правило, файлы ABI хранятся в папке abis/
.
С помощью сгенерированных классов преобразования между типами Ethereum и [встроенными типами] (#built-in-types) происходят за кулисами, так что авторам субграфов не нужно беспокоиться о них.
Следующий пример иллюстрирует это. С учётом схемы субграфа, такой как
type Transfer @entity {id: Bytes!from: Bytes!to: Bytes!amount: BigInt!}
и сигнатура события Transfer(address,address,uint256)
на Ethereum, значения from
, to
и amount
типа address
, address
и uint256
преобразуются в Address
и BigInt
, что позволяет передавать их в свойства Bytes!
и BigInt!
объекта Transfer
:
let id = event.transaction.hashlet transfer = new Transfer(id)transfer.from = event.params.fromtransfer.to = event.params.totransfer.amount = event.params.amounttransfer.save()
События Ethereum, передаваемые обработчикам событий, такие как событие Transfer
в предыдущих примерах, предоставляют доступ не только к параметрам события, но и к их материнской транзакции и блоку, частью которого они являются. Следующие данные могут быть получены из экземпляров event
(эти классы являются частью модуля ethereum
в 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}
Код, сгенерированный с помощью graph codegen
, также включает классы для смарт-контрактов, используемых в субграфе. Они могут быть использованы для доступа к общедоступным переменным состояния и вызова функций контракта в текущем блоке.
Распространенным шаблоном является доступ к контракту, из которого исходит событие. Это достигается с помощью следующего кода:
// Импорт сгенерированного класса контракта и сгенерированного класса события Transferimport { ERC20Contract, Transfer as TransferEvent } from '../generated/ERC20Contract/ERC20Contract'// Импорт созданного класса объектаimport { Transfer } from '../generated/schema'export function handleTransfer(event: TransferEvent) {// Привязка контракта к адресу, сгенерировавшему событиеlet contract = ERC20Contract.bind(event.address)// Доступ к переменным состояния и функциям путем их вызовапусть erc20Symbol = контракт.символ()}
Transfer
связывается с TransferEvent
, чтобы избежать конфликта наименований с типом объекта
Пока ERC20Contract
в Ethereum имеет общедоступную функцию только для чтения, называемую symbol
, ее можно вызвать с помощью .symbol()
. Для общедоступных переменных состояния автоматически создается метод с таким же именем.
Любой другой контракт, который является частью субграфа, может быть импортирован из сгенерированного кода и привязан к действительному адресу.
Если методы Вашего контракта, доступные только для чтения, могут вернуться к предыдущему состоянию, то Вам следует решить эту проблему, вызвав сгенерированный метод контракта с префиксом try_
.
- Например, контракт Gravity предоставляет метод
gravatarToOwner
. Этот код сможет выполнить возврат в этом методе:
let gravity = Gravity.bind(event.address)let callResult = gravity.try_gravatarToOwner(gravatar)if (callResult.reverted) {log.info('getGravatar reverted', [])} else {let owner = callResult.value}
Примечание: Graph Node, подключенный к клиенту Geth или Infura, может обнаружить не все откаты. Если Вы полагаетесь на это, мы рекомендуем использовать Graph Node, подключенный к клиенту Parity.
Данные могут быть закодированы и декодированы в соответствии с форматом кодирования ABI Ethereum с использованием функций encode
и decode
в модуле 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)
Для получения дополнительной информации:
Баланс нативного токена адреса можно получить с помощью модуля ethereum
. Эта функция доступна начиная с apiVersion: 0.0.9
, которая определена subgraph.yaml
. getBalance()
извлекает баланс указанного адреса на конец блока, в котором инициировано событие.
import { ethereum } from '@graphprotocol/graph-ts'let address = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')let balance = ethereum.getBalance(address) // возвращает баланс в BigInt
Чтобы проверить, является ли адрес адресом смарт-контракта или внешним адресом (EOA), используйте функцию hasCode()
из модуля ethereum
, которая вернет boolean
. Эта функция доступна начиная с apiVersion: 0.0.9
, которая определена subgraph.yaml
.
import { ethereum } from '@graphprotocol/graph-ts'let contractAddr = Address.fromString('0x2E645469f354BB4F5c8a05B3b30A929361cf77eC')let isContract = ethereum.hasCode(contractAddr).inner // возвращает ложное значениеlet eoa = Address.fromString('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')let isContract = ethereum.hasCode(eoa).inner // возвращает ложное значение
import { log } from '@graphprotocol/graph-ts'
API log
позволяет субграфам записывать информацию в стандартный вывод Graph Node, а также в Graph Explorer. Сообщения могут быть зарегистрированы с использованием различных уровней ведения лога. Для составления сообщений лога из аргумента предусмотрен синтаксис строки базового формата.
API log
включает в себя следующие функции:
log.debug(fmt: string, args: Array<string>): void
- регистрирует сообщение об отладке.log.info (fmt: string, args: Array<string>): void
- регистрирует информационное сообщение.log.warning(fmt: string, args: Array<string>): void
- регистрирует предупреждение.log.error(fmt: string, args: Array<string>): void
- регистрирует сообщение об ошибке.log.critical(fmt: string, args: Array<string>): void
– регистрирует критическое сообщение и завершает работу субграфа.
API log
принимает строку формата и массив строковых значений. Затем он заменяет заполнители строковыми значениями из массива. Первый {}
заполнитель заменяется первым значением в массиве, второй {}
заполнитель заменяется вторым значением и так далее.
log.info('Message to be displayed: {}, {}, {}', [value.toString(), anotherValue.toString(), 'already a string'])
В приведенном ниже примере строковое значение "A" передается в массив, чтобы стать ['A']
перед тем как будет записано в лог:
let myValue = 'A'export function handleSomeEvent(event: SomeEvent): void {// Отображает: "My value is: A"log.info('My value is: {}', [myValue])}
В приведенном ниже примере регистрируется только первое значение массива аргументов, несмотря на то, что массив содержит три значения.
let myArray = ['A', 'B', 'C']export function handleSomeEvent(event: SomeEvent): void {// Отображает : "My value is: A" (Несмотря на то, что в `log.info` передаются три значения)log.info('My value is: {}', myArray)}
Для каждой записи в массиве arguments требуется свой собственный заполнитель {}
в строке сообщения лога. В приведенном ниже примере в сообщении лога содержатся три заполнителя {}
. По этой причине в myArray
регистрируются все три значения.
let myArray = ['A', 'B', 'C']export function handleSomeEvent(event: SomeEvent): void {// Отображает : "My first value is: A, second value is: B, third value is: C"log.info('My first value is: {}, second value is: {}, third value is: {}', myArray)}
Чтобы отобразить определенное значение в массиве, необходимо указать индексированное значение.
export function handleSomeEvent(event: SomeEvent): void {// Отображает : "My third value is C"log.info('My third value is: {}', [myArray[2]])}
В приведенном ниже примере регистрируется номер блока, хэш блока и хэш транзакции из события:
import { log } from '@graphprotocol/graph-ts'export function handleSomeEvent(event: SomeEvent): void {log.debug('Block number: {}, block hash: {}, transaction hash: {}', [event.block.number.toString(), // "47596000"event.block.hash.toHexString(), // "0x..."event.transaction.hash.toHexString(), // "0x..."])}
import { ipfs } from '@graphprotocol/graph-ts'
Смарт-контракты иногда привязывают файлы IPFS к чейну. Это позволяет мэппингам получать хэши IPFS из контракта и считывать соответствующие файлы из IPFS. Данные файла будут возвращены в виде Bytes
, что обычно требует дальнейшей обработки, например, с помощью json
API, описанного далее на этой странице.
При наличии хеша или пути IPFS чтение файла из IPFS выполняется следующим образом:
// Поместите это в обработчик события в мэппингеlet hash = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D'let data = ipfs.cat(hash)// Пути, подобные `QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile`,// которые включают файлы в директориях, также поддерживаютсяlet path = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/Makefile'let data = ipfs.cat(path)
Примечание: pfs.cat
на данный момент не является детерминированным. Если файл не может быть получен по сети IPFS до истечения времени ожидания запроса, он вернет null
. В связи с этим всегда стоит проверять результат на наличие null
.
С помощью ipfs.map
можно также обрабатывать файлы большего размера в потоковом режиме. Функция ожидает, что хэш или путь к файлу IPFS, имя обратного вызова и флаги изменят его поведение:
import { JSONValue, Value } from '@graphprotocol/graph-ts'export function processItem(value: JSONValue, userData: Value): void {// Смотрите документацию по JsonValue для получения подробной информации о работе// со значениями JSONlet obj = value.toObject()let id = obj.get('id')let title = obj.get('title')if (!id || !title) {return}// Обратные вызовы также могут создавать объектыlet newItem = new Item(id)newItem.title = title.toString()newitem.parent = userData.toString() // Установите для родителя значение "parentId"newitem.save()}// Поместите это внутри обработчика событий в мэппингеipfs.map('Qm...', 'processItem', Value.fromString('parentId'), ['json'])// В качестве альтернативы, используйте `ipfs.mapJSON`ipfs.mapJSON('Qm...', 'processItem', Value.fromString('parentId'))
Единственным поддерживаемым в настоящее время флагом является json
, который должен быть передан в ipfs.map
. С флагом json
файл IPFS должен состоять из серии значений JSON, по одному значению в строке. Вызов ipfs.map
прочитает каждую строку в файле, десериализует ее в JSONValue
и совершит обратный вызов для каждой из них. Затем обратный вызов может использовать операции с объектами для хранения данных из JSONValue
. Изменения объекта сохраняются только после успешного завершения обработчика, вызвавшего ipfs.map
; в то же время они хранятся в памяти, и поэтому размер файла, который может обработать ipfs.map
, ограничен.
При успешном завершении ipfs.map
возвращает void
. Если какое-либо совершение обратного вызова приводит к ошибке, обработчик, вызвавший ipfs.map
, прерывается, а субграф помечается как давший сбой.
import { crypto } from '@graphprotocol/graph-ts'
API crypto
делает криптографические функции доступными для использования в мэппингах. На данный момент есть только один:
crypto.keccak256(input: ByteArray): ByteArray
import { json, JSONValueKind } from '@graphprotocol/graph-ts'
Данные JSON могут быть разобраны с помощью json
API:
json.fromBytes(data: Bytes): JSONValue
– выполняет разбор данных JSON из массиваBytes
, интерпретируемого как допустимая последовательность UTF-8json.try_fromBytes(data: Bytes): Result<JSONValue, boolean>
– безопасная версияjson.fromBytes
, возвращает вариант ошибки, если выполнение разбора не удалосьjson.fromString(data: string): JSONValue
– выполняет разбор данных JSON из допустимой UTF-8String
json.try_fromString(data: string): Result<JSONValue, boolean>
– безопасная версияjson.fromString
, возвращает вариант ошибки, если выполнение разбора не удалось
Класс JSONValue
предоставляет способ извлечения значений из произвольного документа JSON. Поскольку значениями JSON могут быть логические значения, числа, массивы и многое другое, JSONValue
поставляется со свойством kind
для проверки типа значения:
let value = json.fromBytes(...)if (value.kind == JSONValueKind.BOOL) {...}
Кроме того, существует способ проверить, является ли значение null
:
value.isNull(): boolean
Когда тип значения определен, его можно преобразовать во , используя один из следующих методов:
value.toBool(): boolean
value.toI64(): i64
value.toF64(): f64
value.toBigInt(): BigInt
value.toString(): string
value.toArray(): Array<JSONValue>
- (а затем преобразоватьJSONValue
одним из 5 методов, описанных выше)
Источник(и) | Место назначения | Функция преобразования |
---|---|---|
Address | Bytes | отсутствует |
Address | String | s.toHexString() |
BigDecimal | String | s.toString() |
BigInt | BigDecimal | s.toBigDecimal() |
BigInt | String (hexadecimal) | s.toHexString() или s.toHex() |
BigInt | String (unicode) | s.toString() |
BigInt | i32 | s.toI32() |
Boolean | Boolean | отсутствует |
Bytes (signed) | BigInt | BigInt.fromSignedBytes(s) |
Bytes (unsigned) | BigInt | BigInt.fromUnsignedBytes(s) |
Bytes | String (hexadecimal) | s.toHexString() или 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 | отсутствует |
int32 | i32 | отсутствует |
int32 | BigInt | BigInt.fromI32(s) |
uint24 | i32 | отсутствует |
int64 - int256 | BigInt | отсутствует |
uint32 - uint256 | BigInt | отсутствует |
JSON | boolean | s.toBool() |
JSON | i64 | s.toU64() |
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) |
Вы можете проверить адрес контракта, сеть и контекст источника данных, который вызвал обработчик, через пространство имен DataSource
:
dataSource.address(): Address
dataSource.network(): string
dataSource.context(): DataSourceContext
Базовый класс Entity
и дочерний класс DataSourceContext
имеют помощников для динамической установки и получения полей:
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
Раздел context
в dataSources
позволяет Вам определять пары ключ-значение, которые доступны в Ваших мэппингах субграфа. Доступные типы: Bool
, String
, Int
, Int8
, BigDecimal
, Bytes
, List
и BigInt
.
Ниже приведен пример YAML, иллюстрирующий использование различных типов в разделе 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
: Определяет логическое значение (true
илиfalse
).String
: Определяет строковое значение.Int
: Определяет 32-разрядное целое число.Int8
: Определяет 8-разрядное целое число.BigDecimal
: Определяет десятичное число. Необходимо заключить в кавычки.Bytes
: Определяет шестнадцатеричную строку.List
: Определяет список элементов. Для каждого элемента необходимо указать его тип и данные.BigInt
: Определяет большое целочисленное значение. Необходимо заключить в кавычки из-за большого размера.
Затем этот контекст становится доступным в Ваших мэппинговых файлах субграфов, что позволяет сделать субграфы более динамичными и настраиваемыми.