Developing > Создание субграфа

Создание субграфа

Reading time: 32 min

Субграф извлекает данные из блокчейна, обрабатывает их и сохраняет таким образом, чтобы их можно было легко запросить с помощью GraphQL.

Определение субграфа

Определение субграфа состоит из нескольких файлов:

Чтобы использовать свой субграф в децентрализованной сети The Graph, Вам необходимо создать API-ключ. Рекомендуется добавить сигнал в свой субграф как минимум с 10,000 GRT.

Прежде чем Вы перейдете к подробному описанию содержимого файла манифеста, Вам необходимо установитьGraph CLI, который понадобится для создания и развертывания субграфа.

Установка Graph CLI

Ссылка на этот раздел

The Graph CLI написан на JavaScript, и для его использования необходимо установить либо yarn, либо npm; в дальнейшем предполагается, что у Вас есть yarn.

Получив yarn, установите Graph CLI, запустив следующие команды

Установка с помощью yarn:

yarn global add @graphprotocol/graph-cli

Установка с помощью npm:

npm install -g @graphprotocol/graph-cli

После установки команду graph init можно использовать для настройки нового проекта субграфа либо из существующего контракта, либо из примера субграфа. Эту команду можно использовать для создания субграфа в Subgraph Studio, передав в graph init --product subgraph-studio. Если у Вас уже есть смарт-контракт, развернутый в выбранной Вами сети, загрузка нового субграфа из этого контракта может быть хорошим способом начать работу.

Из существующего контракта

Ссылка на этот раздел

Следующая команда создает субграф, который индексирует все события существующего контракта. Он пытается получить ABI контракта из Etherscan и возвращается к запросу пути к локальному файлу. Если какой-либо из необязательных аргументов отсутствует, он проведет Вас через интерактивную форму.

graph init \
--product subgraph-studio
--from-contract <CONTRACT_ADDRESS> \
[--network <ETHEREUM_NETWORK>] \
[--abi <FILE>] \
<SUBGRAPH_SLUG> [<DIRECTORY>]

<SUBGRAPH_SLUG> - это идентификатор Вашего субграфа в Subgraph Studio, его можно найти на странице сведений о субграфе.

Из примера субграфа

Ссылка на этот раздел

Второй режим, который поддерживает graph init, - это создание нового проекта из примера субграфа. Это делает следующая команда:

graph init --studio <SUBGRAPH_SLUG>

Пример субграфа основан на контракте Gravity Дэни Гранта, который управляет пользовательскими аватарами и генерирует события NewGravatar или UpdateGravatar при создании или обновлении аватаров. Субграф обрабатывает эти события, записывая объекты Gravatar в хранилище Graph Node и обеспечивая их обновление в соответствии с событиями. В следующих разделах будут рассмотрены файлы, составляющие манифест субграфа для этого примера.

Добавление новых источников данных к существующему субграфу

Ссылка на этот раздел

Начиная с v0.31.0 graph-cli поддерживает добавление новых источников данных к существующему субграфу с помощью команды graph add.

graph add <address> [<subgraph-manifest default: "./subgraph.yaml">]
Опции:
--abi <path> Путь к контракту ABI (default: download from Etherscan)
--contract-name Имя контракта (default: Contract)
--merge-entities Следует ли объединять объекты с одинаковым именем (default: false)
--network-file <path> Путь к файлу конфигурации сети (default: "./networks.json")

Команда add извлечёт ABI из Etherscan (если путь к ABI не указан с помощью опции --abi) и создаст новый dataSource таким же образом, как graph init создает dataSource --from-contract, соответствующим образом обновляя схему и мэппинги.

Параметр --merge-entities определяет, как разработчик хотел бы обрабатывать конфликты имен entity и event:

  • Если true: новый dataSource должен использовать существующие eventHandlers & entities.
  • Если false: следует создать новую сущность и обработчик событий с помощью ${dataSourceName}{EventName}.

Контракт address будет записан в networks.json для соответствующей сети.

Примечание: При использовании интерактивного интерфейса командной строки после успешного запуска graph init Вам будет предложено добавить новый dataSource.

Манифест субграфа

Ссылка на этот раздел

Манифест субграфа subgraph.yaml определяет смарт-контракты, которые индексирует Ваш субграф, на какие события из этих контрактов следует обращать внимание и как сопоставлять данные событий с объектами, которые хранит и позволяет запрашивать Graph Node. Полную спецификацию манифестов субграфов можно найти здесь.

Для примера субграфа subgraph.yaml:

specVersion: 0.0.4
description: Gravatar for Ethereum
repository: https://github.com/graphprotocol/graph-tooling
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: Gravity
network: mainnet
source:
address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'
abi: Gravity
startBlock: 6175244
endBlock: 7175245
context:
foo:
type: Bool
data: true
bar:
type: String
data: 'bar'
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- Gravatar
abis:
- name: Gravity
file: ./abis/Gravity.json
eventHandlers:
- event: NewGravatar(uint256,address,string,string)
handler: handleNewGravatar
- event: UpdatedGravatar(uint256,address,string,string)
handler: handleUpdatedGravatar
callHandlers:
- function: createGravatar(string,string)
handler: handleCreateGravatar
blockHandlers:
- handler: handleBlock
- handler: handleBlockWithCall
filter:
kind: call
file: ./src/mapping.ts

Важными элементами манифеста, которые необходимо обновить, являются:

  • description: понятное описание того, что представляет собой субграф. Это описание отображается в Graph Explorer при развертывании субграфа в хостинговом сервисе.

  • repository: URL-адрес репозитория, в котором можно найти манифест субграфа. Это также отображается в The Graph Explorer.

  • features: список всех используемых имен функций.

  • dataSources.source: адрес смарт-контракта, источники субграфа и ABI смарт-контракта для использования. Адрес необязателен; отсутствие этого параметра позволяет индексировать совпадающие события из всех контрактов.

  • dataSources.source.startBlock: необязательный номер блока, с которого источник данных начинает индексацию. В большинстве случаев мы предлагаем использовать блок, в котором был создан контракт.

  • dataSources.source.endBlock: необязательный номер блока, индексирование которого прекращается источником данных, включая этот блок. Минимальная требуемая версия спецификации: 0.0.9.

  • dataSources.context: пары «ключ-значение», которые можно использовать внутри мэппингов субграфов. Поддерживает различные типы данных, такие как Bool, String, Int, Int8, BigDecimal, Bytes, List и BigInt. Для каждой переменной нужно указать ее type и data. Эти контекстные переменные затем становятся доступными в файлах мэппинга, предлагая больше настраиваемых параметров для разработки субграфов.

  • dataSources.mapping.entities: объекты, которые источник данных записывает в хранилище. Схема для каждого объекта определена в файле schema.graphql.

  • dataSources.mapping.abis: один или несколько именованных файлов ABI для исходного контракта, а также любых других смарт-контрактов, с которыми Вы взаимодействуете из мэппингов.

  • DataSources.mapping.EventHandlers: перечисляет события смарт—контракта, на которые реагирует этот субграф, и обработчики в мэппинге —./src/mapping.ts в примере - которые преобразуют эти события в объекты в хранилище.

  • DataSources.mapping.callHandlers: перечисляет функции смарт-контракта, на которые реагирует этот субграф, и обработчики в мэппинге, которые преобразуют входные и выходные данные для вызовов функций в объекты в хранилище.

  • dataSources.mapping.blockHandlers: перечисляет блоки, на которые реагирует этот субграф, и обработчики в мэппинг, которые запускаются при добавлении блока в чейн. Без фильтра обработчик блока будет запускаться для каждого блока. Дополнительный фильтр вызовов может быть предоставлен путем добавления в обработчик поля filter с kind: call . Обработчик будет запущен только в том случае, если блок содержит хотя бы один вызов контракта источника данных.

Один субграф может индексировать данные из нескольких смарт-контрактов. Добавьте в массив dataSources запись для каждого контракта, данные которого нужно проиндексировать.

Триггеры для источника данных внутри блока упорядочиваются с помощью следующего процесса:

  1. Триггеры событий и вызовов сначала упорядочиваются по индексу транзакции внутри блока.
  2. Триггеры событий и вызовов в рамках одной транзакции упорядочиваются по следующему принципу: сначала триггеры событий, затем триггеры вызовов, причем для каждого типа соблюдается тот порядок, в котором они определены в манифесте.
  3. Триггеры блоков запускаются после триггеров событий и вызовов в том порядке, в котором они определены в манифесте.

Эти правила оформления заказа могут быть изменены.

Файл(ы) ABI должен(ы) соответствовать Вашему контракту (контрактам). Существует несколько способов получения файлов ABI:

  • Если Вы создаете свой собственный проект, у Вас, скорее всего, будет доступ к наиболее актуальным ABIS.
  • Если Вы создаете субграф для публичного проекта, Вы можете загрузить этот проект на свой компьютер и получить ABI, используя truffle compile или используя solc для компиляции.
  • Вы также можете найти ABI на Etherscan, но это не всегда надежно, так как загруженный туда ABI может быть устаревшим. Убедитесь, что у Вас есть нужный ABI, в противном случае запуск Вашего субграфа будет неудачным.

Схема для Вашего субграфа находится в файле schema.graphql. Схемы GraphQL определяются с использованием языка определения интерфейса GraphQL. Если Вы никогда ранее не писали схему GraphQL, рекомендуем ознакомиться с этим руководством по системе типов GraphQL. Справочную документацию по схемам GraphQL можно найти в разделе GraphQL API.

Определение Объектов

Ссылка на этот раздел

Прежде чем определять объекты, важно сделать шаг назад и подумать о том, как структурированы и связаны Ваши данные. Все запросы будут выполняться к модели данных, определенной в схеме субграфа, и объектам, проиндексированным этим субграфом. Для этого рекомендуется определить схему субграфа таким образом, чтобы она соответствовала потребностям Вашего децентрализованного приложения. Может быть полезно представить объекты как "объекты, содержащие данные", а не как события или функции.

С помощью The Graph Вы просто определяете типы объектов в schema.graphql, и узел The Graph будет генерировать поля верхнего уровня для запроса отдельных экземпляров и коллекций этого типа объектов. Каждый тип, который должен быть объектом, должен быть аннотирован директивой @entity. По умолчанию объекты изменяемы, что означает, что мэппинги могут загружать существующие объекты, изменять их и сохранять их новую версию. Измененяемость имеет свою цену, и для типов объектов, для которых известно, что они никогда не будут изменены, например, потому что они просто содержат данные, идентично извлеченные из чейна, рекомендуется помечать их как неизменяемые с помощью @entity(immutable: true). Мэппинги могут вносить изменения в неизменяемые объекты до тех пор, пока эти изменения происходят в том же блоке, в котором был создан объект. Неизменяемые объекты гораздо быстрее записываются и запрашиваются, и поэтому их следует использовать каждый раз, когда это возможно.

Удачный пример

Ссылка на этот раздел

Приведенный ниже объект Gravatar структурирован вокруг объекта Gravatar и является хорошим примером того, как объект может быть определен.

type Gravatar @entity(immutable: true) {
id: Bytes!
owner: Bytes
displayName: String
imageUrl: String
accepted: Boolean
}

Неудачный пример

Ссылка на этот раздел

Приведенные ниже примеры объектов GravatarAccepted и GravatarDeclined основаны на событиях. Не рекомендуется сопоставлять события или вызовы функций с объектами 1:1.

type GravatarAccepted @entity {
id: Bytes!
owner: Bytes
displayName: String
imageUrl: String
}
type GravatarDeclined @entity {
id: Bytes!
owner: Bytes
displayName: String
imageUrl: String
}

Дополнительные и обязательные поля

Ссылка на этот раздел

Поля объекта могут быть определены как обязательные или необязательные. Обязательные поля обозначены символом ! в схеме. Если в мэппинге не задано обязательное поле, то при запросе к нему будет выдана эта ошибка:

Null value resolved for non-null field 'name'

У каждого объекта должно быть поле id, у которого должен быть тип Bytes! или String!. Обычно рекомендуется использовать Bytes!, если только id не содержит удобочитаемый текст, поскольку объекты с ! будут записываться и запрашиваться быстрее, чем объекты с String!``id. Поле id служит первичным ключом и должно быть уникальным среди всех объектов одного типа. В силу исторических причин тип ID! также принимается и является синонимом String!.

Для некоторых типов объектов id создается из идентификаторов двух других объектов; этому способствует concat, например, для формирования id let id = left.id.concat(right.id) из идентификаторов left и right. Аналогично этому, чтобы создать идентификатор из идентификатора существующего объекта и счетчика count, можно использовать let id = left.id.concatI32(count). Объединение гарантированно приведёт к созданию уникальных идентификаторов, если длина left одинакова для всех таких объектов, например, потому что left.id является Address.

Встроенные скалярные типы

Ссылка на этот раздел

Поддерживаемые GraphQL скаляры

Ссылка на этот раздел

Мы поддерживаем следующие скаляры в нашем GraphQL API:

ТипОписание
BytesМассив байтов, представленный в виде шестнадцатеричной строки. Обычно используется для хэшей и адресов Ethereum.
StringСкаляр для значений string. Нулевые символы не поддерживаются и автоматически удаляются.
BooleanСкаляр для значений boolean.
IntСпецификация GraphQL определяет Int как имеющий размер 32 байта.
Int88-байтовое целое число со знаком, также называемое 64-битным целым числом со знаком, может хранить значения в диапазоне от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807. Предпочтительно использовать это для представления i64 из Ethereum.
BigIntБольшие целые числа. Используются для типов Ethereum uint32, int64, uint64, ..., uint256. Примечание: Все, что находится ниже uint32, например, int32, uint24 или int8, представлено как i32.
BigDecimalBigDecimal Десятичные дроби высокой точности, представленные в виде значащего числа и экспоненты. Диапазон значений экспоненты от -6143 до +6144. Округляется до 34 значащих цифр.

Перечисления

Ссылка на этот раздел

Вы также можете создавать перечисления внутри схемы. Перечисления имеют следующий синтаксис:

enum TokenStatus {
OriginalOwner
SecondOwner
ThirdOwner
}

Как только перечисление определено в схеме, Вы можете использовать строковое представление значения перечисления, чтобы задать поле перечисления для объекта. Например, Вы можете установить для tokenStatus значение SecondOwner, сначала определив свой объект, а затем установив в поле entity.tokenStatus = "SecondOwner". Приведенный ниже пример демонстрирует, как будет выглядеть объект Token с полем enum:

Более подробную информацию о написании перечислений можно найти в Документации по GraphQL.

Связи объектов

Ссылка на этот раздел

Объект может иметь связь с одним или несколькими другими объектами в Вашей схеме. Эти связи могут быть использованы в Ваших запросах. Связи в The Graph являются однонаправленными. Можно смоделировать двунаправленные связи, определив однонаправленную связь на любом "конце" связи.

Связи определяются для объектов точно так же, как и для любого другого поля, за исключением того, что в качестве типа указывается тип другого объекта.

Связи "Один к одному"

Ссылка на этот раздел

Определите тип объекта Transaction с необязательной связью "один к одному" с типом объекта transactionReceipt:

type Transaction @entity(immutable: true) {
id: Bytes!
transactionReceipt: TransactionReceipt
}
type TransactionReceipt @entity(immutable: true) {
id: Bytes!
transaction: Transaction
}

Связи "Один ко многим"

Ссылка на этот раздел

Определите тип объекта TokenBalance с обязательной связью "один ко многим" с типом объекта Token:

type Token @entity(immutable: true) {
id: Bytes!
}
type TokenBalance @entity {
id: Bytes!
amount: Int!
token: Token!
}

Обратные запросы

Ссылка на этот раздел

Обратные зпросы могут быть определены для объекта с помощью поля @derivedFrom. При этом в объекте создается виртуальное поле, которое можно запрашивать, но нельзя задать вручную через API мэппингов. Скорее, оно вытекает из отношений, определенных для другого объекта. Для таких отношений редко имеет смысл сохранять обе стороны связи, а производительность как индексирования, так и запросов будет выше, когда сохраняется только одна сторона, а другая является производной.

Для связей "один ко многим" связь всегда должна храниться на стороне "один", а сторона "многие" всегда должна быть производной. Такое сохранение связи, вместо хранения массива объектов на стороне "многие", приведет к значительному повышению производительности как при индексации, так и при запросах к субграфам. В общем, следует избегать хранения массивов объектов настолько, насколько это возможно.

Мы можем сделать балансы для токена доступными из самого токена, создав поле tokenBalances:

type Token @entity(immutable: true) {
id: Bytes!
tokenBalances: [TokenBalance!]! @derivedFrom(field: "token")
}
type TokenBalance @entity {
id: Bytes!
amount: Int!
token: Token!
}

Связи "Многие ко многим"

Ссылка на этот раздел

Для связей "многие ко многим", таких, например, как пользователи, каждый из которых может принадлежать к любому числу организаций, наиболее простым, но, как правило, не самым производительным способом моделирования связей является создание массива в каждом из двух задействованных объектов. Если связь симметрична, то необходимо сохранить только одну сторону связи, а другая сторона может быть выведена.

Определите обратный запрос от типа объекта User к типу объекта Organization. В приведенном ниже примере это достигается путем поиска атрибута members внутри объекта Organization. В запросах поле organizations в User будет разрешено путем поиска всех объектов Organization, включающих идентификатор пользователя.

type Organization @entity {
id: Bytes!
name: String!
members: [User!]!
}
type User @entity {
id: Bytes!
name: String!
organizations: [Organization!]! @derivedFrom(field: "members")
}

Более эффективный способ сохранить эту взаимосвязь - с помощью таблицы мэппинга, которая содержит по одной записи для каждой пары User / Organization со схемой, подобной

type Organization @entity {
id: Bytes!
name: String!
members: [UserOrganization!]! @derivedFrom(field: "organization")
}
type User @entity {
id: Bytes!
name: String!
organizations: [UserOrganization!] @derivedFrom(field: "user")
}
type UserOrganization @entity {
id: Bytes! # Set to `user.id.concat(organization.id)`
user: User!
organization: Organization!
}

Этот подход требует, чтобы запросы опускались на один дополнительный уровень для получения, например, сведений об организациях для пользователей:

query usersWithOrganizations {
users {
organizations {
# this is a UserOrganization entity
organization {
name
}
}
}
}

Такой более сложный способ хранения связей "многие ко многим" приведет к уменьшению объема хранимых данных для субграфа и, следовательно, к тому, что субграф будет значительно быстрее индексироваться и запрашиваться.

Добавление комментариев к схеме

Ссылка на этот раздел

Согласно спецификации GraphQL, комментарии можно добавлять над атрибутами объекта схемы с использованием двойных кавычек "". Это проиллюстрировано в примере ниже:

type MyFirstEntity @entity {
"unique identifier and primary key of the entity"
id: Bytes!
address: Bytes!
}

Определение полей полнотекстового поиска

Ссылка на этот раздел

Полнотекстовые поисковые запросы фильтруют и ранжируют объекты на основе введенных данных текстового запроса. Полнотекстовые запросы способны возвращать совпадения по схожим словам путем обработки текста запроса в виде строк перед сравнением с индексированными текстовыми данными.

Определение полнотекстового запроса включает в себя название запроса, словарь языка, используемый для обработки текстовых полей, алгоритм ранжирования, используемый для упорядочивания результатов, и поля, включенные в поиск. Каждый полнотекстовый запрос может охватывать несколько полей, но все включенные поля должны относиться к одному типу объекта.

Чтобы добавить полнотекстовый запрос, включите тип _Schema_ с полнотекстовой директивой в схему GraphQL.

type _Schema_
@fulltext(
name: "bandSearch"
language: en
algorithm: rank
include: [{ entity: "Band", fields: [{ name: "name" }, { name: "description" }, { name: "bio" }] }]
)
type Band @entity {
id: Bytes!
name: String!
description: String!
bio: String
wallet: Address
labels: [Label!]!
discography: [Album!]!
members: [Musician!]!
}

Поле example bandSearch можно использовать в запросах для фильтрации объектов Band на основе текстовых документов в name, description и bio.> поля. Перейдите к GraphQL API - запросы для описания API полнотекстового поиска и дополнительных примеров использования.

query {
bandSearch(text: "breaks & electro & detroit") {
id
name
description
wallet
}
}

Управление функциями: Начиная с specVersion 0.0.4 и далее, fullTextSearch должно быть объявлено в разделе features в манифесте субграфа.

Поддерживаемые языки

Ссылка на этот раздел

Выбор другого языка окажет решающее, хотя иногда и неуловимое влияние на API полнотекстового поиска. Поля, охватываемые полем полнотекстового запроса, рассматриваются в контексте выбранного языка, поэтому лексемы, полученные в результате анализа и поисковых запросов, варьируются от языка к языку. Например: при использовании поддерживаемого турецкого словаря "token" переводится как "toke", в то время как, конечно, словарь английского языка переводит его в "token".

Поддерживаемые языковые словари:

КодСловарь
простойОбщий
daДатский
nlГолландский
enАнглийский
fiФинский
frФранцузский
deНемецкий
huВенгерский
itИтальянский
noНорвежский
ptПортугальский
roРумынский
ruРусский
esИспанский
svШведский
trТурецкий

Алгоритмы ранжирования

Ссылка на этот раздел

Поддерживаемые алгоритмы для упорядочивания результатов:

АлгоритмОписание
rankИспользуйте качество соответствия (0-1) полнотекстового запроса, чтобы упорядочить результаты.
proximityRankАналогично рангу, но также включает в себя близость совпадений.

Написание мэппингов

Ссылка на этот раздел

Мэппинги берут данные из определенного источника и преобразуют их в объекты, которые определены в Вашей схеме. Мэппинги записываются в подмножестве TypeScript, которое называется AssemblyScript, и которое может быть скомпилировано в WASM (WebAssembly). AssemblyScript более строг, чем обычный TypeScript, но при этом предоставляет знакомый синтаксис.

Для каждого обработчика событий, определенного в subgraph.yaml в разделе mapping.EventHandlers, создайте экспортируемую функцию с тем же именем. Каждый обработчик должен принимать один параметр с именем event с типом, соответствующим имени обрабатываемого события.

В примере субграф src/mapping.ts содержит обработчики для событий NewGravatar и UpdatedGravatar:

import { NewGravatar, UpdatedGravatar } from '../generated/Gravity/Gravity'
import { Gravatar } from '../generated/schema'
export function handleNewGravatar(event: NewGravatar): void {
let gravatar = new Gravatar(event.params.id)
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.imageUrl = event.params.imageUrl
gravatar.save()
}
export function handleUpdatedGravatar(event: UpdatedGravatar): void {
let id = event.params.id
let gravatar = Gravatar.load(id)
if (gravatar == null) {
gravatar = new Gravatar(id)
}
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.imageUrl = event.params.imageUrl
gravatar.save()
}

Первый обработчик принимает событие NewGravatar и создает новый объект Gravatar с помощью new Gravatar(event.params.id.toHex()), заполняя поля объекта, используя соответствующие параметры события. Этот экземпляр объекта представлен переменной gravatar со значением идентификатора event.params.id.toHex().

Второй обработчик пытается загрузить существующий Gravatar из хранилища узлов The Graph. Если его еще нет, он создается по требованию. Затем объект обновляется в соответствии с новыми параметрами события, прежде чем он будет сохранен обратно в хранилище с помощью gravatar.save().

Рекомендуемые идентификаторы для создания новых объектов

Ссылка на этот раздел

Каждый объект должен иметь id, который является уникальным среди всех объектов одного типа. Значение объекта id устанавливается при создании этого объекта. Ниже приведены некоторые рекомендуемые значения id, которые следует учитывать при создании новых объектов. ПРИМЕЧАНИЕ: Значение id должно быть string.

  • event.params.id.toHex()
  • event.transaction.from.toHex()
  • event.transaction.hash.toHex() + "-" + event.logIndex.toString()

Мы предоставляем Библиотеку Graph Typescript, которая содержит утилиты для взаимодействия с хранилищем узлов The Graph и удобства для обработки данных смарт-контрактов и объектов. Вы можете использовать эту библиотеку в своих мэппингах, импортировав @graphprotocol/graph-ts в mapping.ts.

Генерация кода

Ссылка на этот раздел

Для упрощения и обеспечения безопасности типов при работе со смарт-контрактами, событиями и сущностями Graph CLI может генерировать типы AssemblyScript на основе схемы GraphQL субграфа и ABI контрактов, включенных в источники данных.

Это делается с помощью

graph codegen [--output-dir <OUTPUT_DIR>] [<MANIFEST>]

но в большинстве случаев субграфы уже предварительно сконфигурированы с помощью package.json, что позволяет вам просто запустить одно из следующих действий для достижения того же результата:

# Yarn
yarn codegen
# NPM
npm run codegen

Это сгенерирует класс AssemblyScript для каждого смарт-контракта в файлах ABI, упомянутых в subgraph.yaml, позволяя вам привязывать эти контракты к определенным адресам в мэппигах и вызывать контрактные методы, доступные только для чтения, для обрабатываемого блока. Кроме того, для каждого события контракта генерируется класс, обеспечивающий удобный доступ к параметрам события, а также к блоку и транзакции, от которых произошло событие. Все эти типы записываются в <OUTPUT_DIR>/<DATA_SOURCE_NAME>/<ABI_NAME>.ts. В примере субграфа это будет generated/Gravity/Gravity.ts, позволяющий импортировать эти типы с помощью мэппинга.

import {
// The contract class:
Gravity,
// The events classes:
NewGravatar,
UpdatedGravatar,
} from '../generated/Gravity/Gravity'

В дополнение к этому, для каждого типа сущности в схеме subgraphs GraphQL генерируется по одному классу. Эти классы обеспечивают безопасную для типов загрузку сущностей, доступ к полям сущностей на чтение и запись, а также метод save() для записи сущностей в хранилище. Все классы сущностей записываются в <OUTPUT_DIR>/schema.ts, что позволяет мэппингам импортировать с их помощью

import { Gravatar } from '../generated/schema'

Примечание: Генерация кода должна выполняться повторно после каждого изменения схемы GraphQL или ABIS, включенного в манифест. Это также должно быть выполнено по крайней мере один раз перед сборкой или развертыванием субграфа.

Генерация кода не проверяет ваш мэппинг код в src/mapping.ts. Если вы хотите проверить это, прежде чем пытаться развернуть свой субграф в Graph Explorer, вы можете запустить yarn build и исправить любые синтаксические ошибки, которые может обнаружить компилятор TypeScript.

Шаблоны источников данных

Ссылка на этот раздел

Распространенным шаблоном в смарт-контрактах, совместимых с EVM, является использование реестровых или заводских контрактов, когда один контракт создает, управляет или ссылается на произвольное количество других контрактов, каждый из которых имеет свое собственное состояние и события.

Адреса этих субконтрактов могут быть известны или не известны заранее, и многие из этих контрактов могут быть созданы и/или добавлены с течением времени. Поэтому в таких случаях определение одного источника данных или фиксированного количества источников данных невозможно и необходим более динамичный подход: data source templates.

Источник данных для основного контракта

Ссылка на этот раздел

Сначала вы определяете обычный источник данных для основного контракта. Во фрагменте ниже показан упрощенный пример источника данных для контракта фабрики обмена Uniswap. Обратите внимание на обработчик события New Exchange(address,address). Этот сигнал выдается, когда новый контракт обмена создается в цепочке с помощью заводского контракта.

dataSources:
- kind: ethereum/contract
name: Factory
network: mainnet
source:
address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'
abi: Factory
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
file: ./src/mappings/factory.ts
entities:
- Directory
abis:
- name: Factory
file: ./abis/factory.json
eventHandlers:
- event: NewExchange(address,address)
handler: handleNewExchange

Шаблоны источников данных для динамически создаваемых контрактов

Ссылка на этот раздел

Затем вы добавляете data source templates в манифест. Они идентичны обычным источникам данных, за исключением того, что в них отсутствует предопределенный адрес контракта в source. Как правило, вы определяете один шаблон для каждого типа субконтракта, управляемого родительским контрактом, или на который ссылается родительский контракт.

dataSources:
- kind: ethereum/contract
name: Factory
# ... other source fields for the main contract ...
templates:
- name: Exchange
kind: ethereum/contract
network: mainnet
source:
abi: Exchange
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
file: ./src/mappings/exchange.ts
entities:
- Exchange
abis:
- name: Exchange
file: ./abis/exchange.json
eventHandlers:
- event: TokenPurchase(address,uint256,uint256)
handler: handleTokenPurchase
- event: EthPurchase(address,uint256,uint256)
handler: handleEthPurchase
- event: AddLiquidity(address,uint256,uint256)
handler: handleAddLiquidity
- event: RemoveLiquidity(address,uint256,uint256)
handler: handleRemoveLiquidity

Создание шаблона источника данных

Ссылка на этот раздел

На заключительном шаге вы обновляете мэппинг основного контракта, чтобы создать экземпляр динамического источника данных из одного из шаблонов. В данном примере в отображение основного контракта импортируется шаблон Exchange и вызывается метод Exchange.create(address), чтобы начать индексирование нового контракта обмена.

import { Exchange } from '../generated/templates'
export function handleNewExchange(event: NewExchange): void {
// Start indexing the exchange; `event.params.exchange` is the
// address of the new exchange contract
Exchange.create(event.params.exchange)
}

Примечание: Новый источник данных будет обрабатывать только вызовы и события для блока, в котором он был создан, и всех последующих блоков, но не будет обрабатывать исторические данные, т.е. данные, которые содержатся в предыдущих блоках.

Если предыдущие блоки содержат данные, относящиеся к новому источнику данных, лучше всего проиндексировать эти данные, считывая текущее состояние контракта и создавая сущности, представляющие это состояние на момент создания нового источника данных.

Контекст источника данных

Ссылка на этот раздел

Контексты источника данных позволяют передавать дополнительную конфигурацию при создании экземпляра шаблона. В нашем примере предположим, что биржи связаны с определенной торговой парой, которая включена в событие newExchange. Эта информация может быть передана в созданный экземпляр источника данных, например, следующим образом:

import { Exchange } from '../generated/templates'
export function handleNewExchange(event: NewExchange): void {
let context = new DataSourceContext()
context.setString('tradingPair', event.params.tradingPair)
Exchange.createWithContext(event.params.exchange, context)
}

Внутри мэппинга шаблона Exchange затем можно получить доступ к контексту:

import { dataSource } from '@graphprotocol/graph-ts'
let context = dataSource.context()
let tradingPair = context.getString('tradingPair')

Существуют установщики и получатели, такие как setString и getString для всех типов значений.

Стартовые блоки

Ссылка на этот раздел

startBlock - это необязательный параметр, который позволяет вам определить, с какого блока в цепочке источник данных начнет индексацию. Установка начального блока позволяет источнику данных пропускать потенциально миллионы блоков, которые не имеют отношения к делу. Как правило, разработчик субграфа устанавливает startBlock в блок, в котором был создан смарт-контракт источника данных.

dataSources:
- kind: ethereum/contract
name: ExampleSource
network: mainnet
source:
address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'
abi: ExampleContract
startBlock: 6627917
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
file: ./src/mappings/factory.ts
entities:
- User
abis:
- name: ExampleContract
file: ./abis/ExampleContract.json
eventHandlers:
- event: NewEvent(address,address)
handler: handleNewEvent

Примечание: Блок создания контракта можно быстро найти в Etherscan:

  1. Найдите контракт, введя его адрес в строке поиска.
  2. Нажмите на хэш транзакции создания в разделе Contract Creator.
  3. Загрузите страницу сведений о транзакции, где вы найдете начальный блок для этого контракта.

Обработчики вызовов

Ссылка на этот раздел

В то время как события обеспечивают эффективный способ сбора соответствующих изменений в состоянии контракта, многие контракты избегают создания журналов для оптимизации затрат на газ. В этих случаях субграф может подписываться на обращения к контракту источника данных. Это достигается путем определения обработчиков вызовов, ссылающихся на сигнатуру функции, и обработчика мэппинга, который будет обрабатывать вызовы этой функции. Чтобы обработать эти вызовы, обработчик мэппинга получит ethereum.Call в качестве аргумента, содержащего типизированные входы и выходы вызова. Вызовы, выполненные на любой глубине цепочки вызовов транзакции, запускают мэппинг, позволяя фиксировать действия с контрактом источника данных через прокси-контракты.

Обработчики вызовов срабатывают только в одном из двух случаев: когда указанная функция вызывается учетной записью, отличной от самого контракта, или когда она помечена как внешняя в Solidity и вызывается как часть другой функции в том же контракте.

Примечание: Обработчики вызовов в настоящее время зависят от Parity tracing API. Некоторые сети, такие как BNB chain и Arbitrium, не поддерживают этот API. Если субграф, индексирующий одну из этих сетей, содержит один или несколько обработчиков вызовов, синхронизация не начнется. Разработчикам субграфов следует вместо этого использовать обработчики событий. Они гораздо более производительны, чем обработчики вызовов, и поддерживаются в каждой сети evm.

Определение обработчика вызова

Ссылка на этот раздел

Чтобы определить обработчик вызовов в вашем манифесте, просто добавьте массив callHandlers под источником данных, на который вы хотели бы подписаться.

dataSources:
- kind: ethereum/contract
name: Gravity
network: mainnet
source:
address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'
abi: Gravity
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- Gravatar
- Transaction
abis:
- name: Gravity
file: ./abis/Gravity.json
callHandlers:
- function: createGravatar(string,string)
handler: handleCreateGravatar

function - это нормализованная сигнатура функции, по которой можно фильтровать вызовы. Свойство handler - это имя функции в вашем мэппинге, которую вы хотели бы выполнить при вызове целевой функции в контракте источника данных.

Функция мэппинга

Ссылка на этот раздел

Каждый обработчик вызова принимает один параметр, тип которого соответствует имени вызываемой функции. В приведенном выше примере субграфа мэппинг содержит обработчик для случаев, когда вызывается функция createGravatar и получает параметр CreateGravatarCall в качестве аргумента:

import { CreateGravatarCall } from '../generated/Gravity/Gravity'
import { Transaction } from '../generated/schema'
export function handleCreateGravatar(call: CreateGravatarCall): void {
let id = call.transaction.hash
let transaction = new Transaction(id)
transaction.displayName = call.inputs._displayName
transaction.imageUrl = call.inputs._imageUrl
transaction.save()
}

Функция handleCreateGravatar принимает новый CreateGravatarCall, который является подклассом ethereum.Call, предоставляемый @graphprotocol/graph-ts, который включает в себя введенные входы и выходы о звонке. Тип CreateGravatarCall генерируется для вас при запуске graph codegen.

Обработчики блоков

Ссылка на этот раздел

В дополнение к подписке на события контракта или вызовы функций, субграф может захотеть обновлять свои данные по мере добавления новых блоков в цепочку. Чтобы достичь этого, субграф может запускать функцию после каждого блока или после блоков, которые соответствуют предопределенному фильтру.

Поддерживаемые фильтры

Ссылка на этот раздел

Фильтр вызовов

Ссылка на этот раздел
filter:
kind: call

Определенный обработчик будет вызван один раз для каждого блока, содержащего обращение к контракту (источнику данных), в соответствии с которым определен обработчик.

Примечание: Фильтр call в настоящее время зависит от Parity tracing API. Некоторые сети, такие как BNB chain и Arbitrium, не поддерживают этот API. Если субграф, индексирующий одну из этих сетей, содержит один или несколько обработчиков блоков с фильтром call, синхронизация не начнется.

Отсутствие фильтра для обработчика блоков гарантирует, что обработчик вызывается для каждого блока. Источник данных может содержать только один обработчик блоков для каждого типа фильтра.

dataSources:
- kind: ethereum/contract
name: Gravity
network: dev
source:
address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'
abi: Gravity
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- Gravatar
- Transaction
abis:
- name: Gravity
file: ./abis/Gravity.json
blockHandlers:
- handler: handleBlock
- handler: handleBlockWithCallToContract
filter:
kind: call

Фильтр опроса

Ссылка на этот раздел

Requires specVersion >= 0.0.8

Примечание. Фильтры опроса доступны только для источников данных kind: ethereum.

blockHandlers:
- handler: handleBlock
filter:
kind: polling
every: 10

Определенный обработчик будет вызываться один раз для каждого блока n, где n — это значение, указанное в поле every. Эта конфигурация позволяет субграфу выполнять определенные операции через регулярные интервалы блоков.

Однократный фильтр

Ссылка на этот раздел

Requires specVersion >= 0.0.8

Примечание. Однократные фильтры доступны только для источников данных kind: ethereum.

blockHandlers:
- handler: handleOnce
filter:
kind: once

Определенный обработчик с однократным фильтром будет вызываться только один раз перед запуском всех остальных обработчиков. Эта конфигурация позволяет субграфу использовать обработчик в качестве обработчика инициализации, выполняя определенные задачи в начале индексирования.

export function handleOnce(block: ethereum.Block): void {
let data = new InitialData(Bytes.fromUTF8('initial'))
data.data = 'Setup data here'
data.save()
}

Функция мэппинга

Ссылка на этот раздел

Функция мэппинга получит ethereum.Block в качестве своего единственного аргумента. Подобно функциям мэппинга событий, эта функция может получать доступ к существующим в хранилище сущностям субграфа, вызывать смарт-контракты и создавать или обновлять сущности.

import { ethereum } from '@graphprotocol/graph-ts'
export function handleBlock(block: ethereum.Block): void {
let id = block.hash
let entity = new Block(id)
entity.save()
}

Анонимные события

Ссылка на этот раздел

Если вам нужно обрабатывать анонимные события в Solidity, это можно сделать, указав тему события 0, как показано в примере:

eventHandlers:
- event: LogNote(bytes4,address,bytes32,bytes32,uint256,bytes)
topic0: '0x644843f351d3fba4abcd60109eaff9f54bac8fb8ccf0bab941009c21df21cf31'
handler: handleGive

Событие будет запущено только в том случае, если подпись и тема 0 совпадают. По умолчанию topic0 равен хэшу сигнатуры события.

Квитанции о транзакциях в обработчиках событий

Ссылка на этот раздел

Начиная с specVersion 0.0.5 и apiVersion 0.0.7 обработчики событий могут иметь доступ к квитанции для транзакции, которая их отправила.

Для этого обработчики событий должны быть объявлены в манифесте субграфа с новым ключом receipt: true, который является необязательным и по умолчанию имеет значение false.

eventHandlers:
- event: NewGravatar(uint256,address,string,string)
handler: handleNewGravatar
receipt: true

Внутри функции обработчика доступ к квитанции можно получить в поле Event.receipt. Если для ключа receipt установлено значениеfalse или оно опущено в манифесте, вместо него будет возвращено значение null.

Экспериментальные возможности

Ссылка на этот раздел

Начиная с specVersion 0.0.4, объекты субграфа должны быть явно объявлены в разделе features на верхнем уровне файла манифеста, используя их имя camelCase, как указано в таблице ниже:

ВозможностьИмя
Нефатальные ошибкиnonFatalErrors
Полнотекстовый поискfullTextSearch
Графтингgrafting
IPFS на контрактах EthereumipfsOnEthereumContracts или nonDeterministicIpfs

Например, если в субграфе используются функции Full-Text Search и Non-fatal Errors, поле features в манифесте должно быть:

specVersion: 0.0.4
description: Gravatar for Ethereum
features:
- fullTextSearch
- nonFatalErrors
dataSources: ...

Обратите внимание, что использование функции без ее объявления приведет к ** validation error ** во время развертывания субграфа, но никаких ошибок не возникнет, если функция объявлена, но не используется.

IPFS на контрактах Ethereum

Ссылка на этот раздел

Распространенным вариантом сочетания IPFS с Ethereum является хранение данных в IPFS, которые было бы слишком дорого поддерживать в цепочке, и ссылка на хэш IPFS в контрактах Ethereum.

Учитывая такие хэши IPFS, субграфы могут считывать соответствующие файлы из IPFS, используя ipfs.cat и ipfs.map. Для надежного выполнения этой задачи необходимо, чтобы эти файлы были привязаны к узлу IPFS с высокой доступностью, так чтобы Узел IPFS размещенный сервис мог найти их при индексировании.

Примечание: Сеть The Graph пока не поддерживает ipfs.cat и ipfs.map, и разработчикам не следует разворачивать субграфы, использующие эту функциональность, в сети через Studio.

Управление функционалом: ipfsOnEthereumContracts должны быть объявлены в разделе features в манифесте субграфа. Для цепочек, отличных от EVM, псевдоним nonDeterministicIpfs также может использоваться для той же цели.

При запуске локального узла The Graph необходимо установить переменную окружения GRAPH_ALLOW_NON_DETERMINISTIC_IPFS, чтобы индексировать ce, графы, используя эту экспериментальную функциональность.

Нефатальные ошибки

Ссылка на этот раздел

Ошибки индексирования в уже синхронизированных субграфах по умолчанию приведут к сбою субграфа и прекращению синхронизации. В качестве альтернативы субграфы можно настроить на продолжение синхронизации при наличии ошибок, игнорируя изменения, внесенные обработчиком, который спровоцировал ошибку. Это дает авторам субграфов время на исправление своих субграфов, в то время как запросы продолжают выполняться к последнему блоку, хотя результаты могут быть несовместимыми из-за ошибки, вызвавшей ошибку. Обратите внимание, что некоторые ошибки по-прежнему всегда являются фатальными. Чтобы быть не фатальной, ошибка должна быть заведомо детерминированной.

Примечание: Сеть The Graph пока не поддерживает нефатальные ошибки, и разработчикам не следует развертывать субграфы, использующие эту функциональность, в сети через Studio.

Для включения нефатальных ошибок необходимо установить в манифесте субграфа следующий флаг возможности:

specVersion: 0.0.4
description: Gravatar for Ethereum
features:
- nonFatalErrors
...

Запрос также должен разрешить запрос данных с потенциальными несоответствиями с помощью аргумента subgraphError. Также рекомендуется запросить _meta, для проверки того, что субграф пропустил ошибки, как в примере:

foos(first: 100, subgraphError: allow) {
id
}
_meta {
hasIndexingErrors
}

Если субграф обнаруживает ошибку, этот запрос вернет как данные, так и graphqlerror с сообщением "indexing_error", как в данном примере ответа:

"data": {
"foos": [
{
"id": "0xdead"
}
],
"_meta": {
"hasIndexingErrors": true
}
},
"errors": [
{
"message": "indexing_error"
}
]

Графтинг на существующие субграфы

Ссылка на этот раздел

Примечание: не рекомендуется использовать графтинг при первоначальном переходе на сеть The Graph. Подробнее здесь.

Когда субграф развертывается впервые, он начинает индексировать события в блоке genesis соответствующей цепочки (или в startBlock, определенном для каждого источника данных). В некоторых обстоятельствах полезно повторно использовать данные из существующего субграфа и начинать индексацию с гораздо более позднего блока. Этот режим индексации называется Grafting. Графтинг, например, полезен во время разработки, чтобы быстро устранить простые ошибки в отображениях или временно возобновить работу существующего субграфа после его сбоя.

Субграф графтится к базовому субграфу, когда манифест субграфа в subgraph.yaml содержит блок graft на верхнем уровне:

description: ...
graft:
base: Qm... # Subgraph ID of base subgraph
block: 7345624 # Block number

Когда развертывается субграф, манифест которого содержит блок graft, узел The Graph скопирует данные базового ce, графа вплоть до указанного block включительно, а затем продолжит индексирование нового субграфа начиная с этого блока. Базовый субграф должен существовать на целевом экземпляре узла The Graph и должен быть проиндексирован по крайней мере до заданного блока. Из-за этого ограничения графтинг следует использовать только в процессе разработки или в экстренных случаях, чтобы ускорить создание эквивалентного графтового субграфа.

Поскольку графтинг копирует, а не индексирует базовые данные, гораздо быстрее перенести субграф в нужный блок, чем индексировать с нуля, хотя для очень больших субграфов копирование исходных данных может занять несколько часов. Пока графтовый субграф инициализируется, узел The Graph будет регистрировать информацию о типах сущностей, которые уже были скопированы.

Графтовый субграф может использовать схему GraphQL, которая не идентична схеме базового субграфа, а просто совместима с ней. Она сама по себе должна быть допустимой схемой субграфа, но может отличаться от схемы базового субграфа следующими способами:

  • Она добавляет или удаляет типы сущностей
  • Она удаляет атрибуты из типов сущностей
  • Она добавляет в типы сущностей атрибуты с возможностью обнуления
  • Она превращает ненулевые атрибуты в нулевые
  • Она добавляет значения в перечисления
  • Она добавляет или удаляет интерфейсы
  • Она изменяется в зависимости от того, для каких типов сущностей реализован тот или иной интерфейс

Управление функционалом: grafting должен быть объявлен в разделе features в манифесте субграфа.

Источники файловых данных

Ссылка на этот раздел

Источники файловых данных — это новая функциональность субграфа для надежного и расширяемого доступа к данным вне цепочки во время индексации. Источники данных файлов поддерживают получение файлов из IPFS и Arweave.

Это также закладывает основу для детерминированного индексирования данных вне сети, а также потенциального введения произвольных данных из HTTP-источников.

Вместо извлечения файлов «в очереди» во время выполнения обработчика вводятся шаблоны, которые могут создаваться как новые источники данных для заданного идентификатора файла. Эти новые источники данных извлекают файлы, повторяя попытки, если они не увенчались успехом, запуская специальный обработчик, когда файл найден.

Это похоже на существующие шаблоны источников данных, которые используются для динамического создания новых источников данных на чейн-основе.

Это заменяет существующий API ipfs.cat

Руководство по обновлению

Ссылка на этот раздел

Обновите graph-ts и graph-cli

Ссылка на этот раздел

Для файловых источников данных требуется graph-ts >=0.29.0 и graph-cli >=0.33.1

Добавить новый тип сущности, который будет обновляться при обнаружении файлов

Ссылка на этот раздел

Источники файловых данных не могут получать доступ к сущностям на чейн-основе или обновлять их, но должны обновлять сущности, специфичные для файлов.

Это может означать разделение полей существующих сущностей на отдельные сущности, связанные между собой.

Первоначальная объединенная сущность:

type Token @entity {
id: ID!
tokenID: BigInt!
tokenURI: String!
externalURL: String!
ipfsURI: String!
image: String!
name: String!
description: String!
type: String!
updatedAtTimestamp: BigInt
owner: User!
}

Новая разделенная сущность:

type Token @entity {
id: ID!
tokenID: BigInt!
tokenURI: String!
ipfsURI: TokenMetadata
updatedAtTimestamp: BigInt
owner: String!
}
type TokenMetadata @entity {
id: ID!
image: String!
externalURL: String!
name: String!
description: String!
}

Если между родительской сущностью и результирующей сущностью-источником данных существует связь1:1, то наиболее простым вариантом будет связать родительскую сущность с результирующей файловой сущностью, используя в качестве поиска IPFS CID. Свяжитесь с нами в Discord, если у вас возникли трудности с моделированием новых сущностей на основе файлов!

Вы можете использовать вложенные фильтры для фильтрации родительских объектов на основе этих вложенных сущностей.

Добавьте новый шаблонный источник данных с помощью kind: file/ipfs или kind: file/arweave.

Ссылка на этот раздел

Это источник данных, который будет создан при обнаружении интересующего файла.

templates:
- name: TokenMetadata
kind: file/ipfs
mapping:
apiVersion: 0.0.7
language: wasm/assemblyscript
file: ./src/mapping.ts
handler: handleMetadata
entities:
- TokenMetadata
abis:
- name: Token
file: ./abis/Token.json

В настоящее время требуется abis хотя невозможно вызывать контракты из файловых источников данных

В файле-источнике данных должны быть конкретно указаны все типы сущностей, с которыми он будет взаимодействовать в рамках entities. Дополнительные сведения см. в разделе ограничения.

Создание нового обработчика для обработки файлов

Ссылка на этот раздел

Этот обработчик должен принимать один параметр Bytes, который будет содержимым файла, когда он будет найден, который затем можно будет обработать. Часто это файл JSON, который можно обработать с помощью помощников graph-ts (документация).

Доступ к CID файла в виде читаемой строки можно получить через dataSource следующим образом:

const cid = dataSource.stringParam()

Пример обработчика:

import { json, Bytes, dataSource } from '@graphprotocol/graph-ts'
import { TokenMetadata } from '../generated/schema'
export function handleMetadata(content: Bytes): void {
let tokenMetadata = new TokenMetadata(dataSource.stringParam())
const value = json.fromBytes(content).toObject()
if (value) {
const image = value.get('image')
const name = value.get('name')
const description = value.get('description')
const externalURL = value.get('external_url')
if (name && image && description && externalURL) {
tokenMetadata.name = name.toString()
tokenMetadata.image = image.toString()
tokenMetadata.externalURL = externalURL.toString()
tokenMetadata.description = description.toString()
}
tokenMetadata.save()
}
}

Размножение файловых источников данных при необходимости

Ссылка на этот раздел

Теперь вы можете создавать файловые источники данных во время выполнения обработчиков на чейн-основе:

  • Импортируйте шаблон из автоматически созданных templates
  • вызовите TemplateName.create(cid: string) из мэппинга, где cid является действительным идентификатором контента для IPFS или Arweave

Для IPFS Нода Graph Node поддерживает идентификаторы контента v0 и v1, а также идентификаторы контента с каталогами (например, bafyreighykzv2we26wfrbzkcdw37sbrby4upq7ae3aqobbq7i4er3tnxci). /metadata.json).

For Arweave, as of version 0.33.0 Graph Node can fetch files stored on Arweave based on their transaction ID from an Arweave gateway (example file). Arweave supports transactions uploaded via Bundlr, and Graph Node can also fetch files based on Bundlr manifests.

Пример:

import { TokenMetadata as TokenMetadataTemplate } from '../generated/templates'
const ipfshash = 'QmaXzZhcYnsisuue5WRdQDH6FDvqkLQX1NckLqBYeYYEfm'
//Этот пример кода предназначен для сборщика субграфа Crypto. Приведенный выше хеш ipfs представляет собой каталог с метаданными токена для всех NFT криптоковена.
export function handleTransfer(event: TransferEvent): void {
let token = Token.load(event.params.tokenId.toString())
if (!token) {
token = new Token(event.params.tokenId.toString())
token.tokenID = event.params.tokenId
token.tokenURI = '/' + event.params.tokenId.toString() + '.json'
const tokenIpfsHash = ipfshash + token.tokenURI
//Это создает путь к метаданным для одного сборщика NFT Crypto. Он объединяет каталог с "/" + filename + ".json"
token.ipfsURI = tokenIpfsHash
TokenMetadataTemplate.create(tokenIpfsHash)
}
token.updatedAtTimestamp = event.block.timestamp
token.owner = event.params.to.toHexString()
token.save()
}

Это создаст новый источник данных файла, который будет опрашивать настроенную конечную точку IPFS или Arweave Graph Node, повторяя попытку, если она не найдена. Когда файл будет найден, будет выполнен обработчик источника данных файла.

В этом примере CID используется для поиска между родительской сущностью Token и результирующей сущностью TokenMetadata.

Раньше это была точка, в которой разработчик субграфа вызывал ipfs.cat(CID) для извлечения файла

Поздравляем, вы используете файловые источники данных!

Развертывание субграфов

Ссылка на этот раздел

Теперь вы можете build (построить) и deploy (развернуть) свой субграф на любом узле The Graph >=v0.30.0-rc.0.

Обработчики и сущности файловых источников данных изолированы от других сущностей субграфа, что гарантирует их детерминированность при выполнении и исключает загрязнение источников данных на чейн-основе. В частности:

  • Сущности, созданные с помощью файловых источников данных, неизменяемы и не могут быть обновлены
  • Обработчики файловых источников данных не могут получить доступ к сущностям из других файловых источников данных
  • Объекты, связанные с источниками данных файлов, не могут быть доступны обработчикам на чейн-основе

Хотя это ограничение не должно вызывать проблем в большинстве случаев, для некоторых оно может вызвать сложности. Если у вас возникли проблемы с моделированием ваших файловых данных в субграфе, свяжитесь с нами через Discord!

Кроме того, невозможно создать источники данных из файлового источника данных, будь то источник данных onchain или другой файловый источник данных. Это ограничение может быть снято в будущем.

Лучшие практики

Ссылка на этот раздел

Если вы связываете метаданные NFT с соответствующими токенами, используйте хэш IPFS метаданных для ссылки на сущность Metadata из сущности Token. Сохраните сущность Metadata, используя хэш IPFS в качестве идентификатора.

Вы можете использовать Контекст источника данных при создании файловых источников данных для передачи дополнительной информации, которая будет доступна обработчику файлового источника данных.

Если у вас есть сущности, которые обновляются несколько раз, создайте уникальные сущности на основе файлов, используя хэш IPFS и идентификатор сущности, и ссылайтесь на них, используя производное поле в сущности на чейн-основе.

Мы работаем над улучшением приведенной выше рекомендации, поэтому запросы возвращают только "самую последнюю" версию

Известные проблемы

Ссылка на этот раздел

Файловые источники данных в настоящее время требуют ABI, даже если ABI не используются (проблема). Обходным решением является добавление любого ABI.

Обработчики для файловых источников данных не могут находиться в файлах, которые импортируют привязки контракта eth_call, с ошибкой "unknown import: ethereum::ethereum.call has not been defined" (проблема). Обходным решением является создание обработчиков файловых источников данных в специальном файле.

Миграция субграфа Crypto Coven

Источники данных GIP-файла

Редактировать страницу

Предыдущий
Поддерживаемые сети
Следующий
AssemblyScript API
Редактировать страницу