Build
After having prepared Schema and Manifest file, let's dive into the build process
Prepare phase
Before building the subgraph, you need to run the codegen process
make prepare
Prepared files will be generated and place inside generated
folder. It contains:
abi_helpers
: utils functions to moving forward with the logic fastereventhandlers
: Defines interfaces for handling blockchain eventsmappings
: Defines mapping functions for each processing events defined in Manifest filemigration
: Defines migration process for the subgraph, based on the schemamodels
: Defines models for the subgraph, based on the schemaquery
: Defines GraphQL queries for the subgraphrouters
: Defines the route for the handlers
Run make system-migrate-up
and make generated-migrate-up
to apply the migration to database
Code generation also prepare basic query, and is placed in db/grapqldb
LayerG crawler value highly customizable crawler. Given the generated files, you can still customize the crawler to your needs, or optimize the query for better performance.
Build phase
For handlers, you need to place them in package handlers
Here is an example of a handler:
type TransferHandler struct {
*BaseHandler
}
func NewTransferHandler(queries *db.Queries, gqlQueries *graphqldb.Queries, chainID int32, logger *zap.SugaredLogger) *TransferHandler {
return &TransferHandler{
BaseHandler: &BaseHandler{
Queries: queries,
GQL: gqlQueries,
ChainID: chainID,
Logger: logger,
},
}
}
func (h *TransferHandler) HandleTransfer(ctx context.Context, event *eventhandlers.Transfer) error {
// Handle ERC721 transfer
fromUser, err := h.GQL.GetOrCreateUser(ctx, event.From.Hex())
if err != nil {
h.Logger.Errorw("Failed to ensure 'from' user exists",
"err", err,
"address", event.From.Hex(),
)
return fmt.Errorf("failed to ensure 'from' user exists: %w", err)
}
h.AddOperation("User", fromUser, event.Raw.BlockHash.Hex(), event.Raw.BlockNumber)
toUser, err := h.GQL.GetOrCreateUser(ctx, event.To.Hex())
if err != nil {
h.Logger.Errorw("Failed to ensure 'to' user exists",
"err", err,
"address", event.To.Hex(),
)
return fmt.Errorf("failed to ensure 'to' user exists: %w", err)
}
h.AddOperation("User", toUser, event.Raw.BlockHash.Hex(), event.Raw.BlockNumber)
tokenID := event.TokenId.String()
// Create or update item
item, err := h.GQL.GetItemByTokenId(ctx, tokenID)
if err != nil {
// Create new item if it doesn't exist
item, err = h.GQL.CreateItem(ctx, graphqldb.CreateItemParams{
ID: uuid.New().String(),
TokenID: tokenID,
TokenUri: "",
Standard: "ERC721",
})
if err != nil {
return fmt.Errorf("failed to create item: %w", err)
}
}
// Update balances
if event.From.Hex() != "0x0000000000000000000000000000000000000000" {
// Remove balance from sender
_, err = h.GQL.UpsertBalance(ctx, graphqldb.UpsertBalanceParams{
ID: uuid.New().String(),
ItemID: item.ID,
OwnerID: fromUser.ID,
Value: "0",
UpdatedAt: fmt.Sprintf("%d", event.Raw.BlockNumber),
Contract: event.Raw.Address.Hex(),
})
if err != nil {
return fmt.Errorf("failed to update sender balance: %w", err)
}
}
// Add balance to receiver
_, err = h.GQL.UpsertBalance(ctx, graphqldb.UpsertBalanceParams{
ID: uuid.New().String(),
ItemID: item.ID,
OwnerID: toUser.ID,
Value: "1",
UpdatedAt: fmt.Sprintf("%d", event.Raw.BlockNumber),
Contract: event.Raw.Address.Hex(),
})
if err != nil {
return fmt.Errorf("failed to update 721 receiver balance: %w", err)
}
h.Logger.Infow("Transfer event processed",
"tokenId", tokenID,
"from", fromUser.ID,
"to", toUser.ID,
"contract", event.Raw.Address.Hex(),
"tx", event.Raw.TxHash.Hex(),
)
if err := h.SubmitToDA(); err != nil {
h.Logger.Errorw("Failed to submit to DA", "error", err)
}
return nil
}
Run
Run make run
to run the subgraph
Run make query
to run the query server for the subgraph