🧮Tutorial: Querying Liquidations

This small tutorial outlines the process that a developer might take if they wanted to query some piece of data indexed by the subgraph.

For this example we'll be querying a list of liquidations and ordering them by date (with the latest liquidations showing first), but this process is a generic workflow that can apply to basically any data you may want to query.

First Steps

First, we need to visit https://api.thegraph.com/subgraphs/name/moonwell-fi/moonwell-moonbeam/graphql which will look like this (note you may have previously-cached queries displayed).

From there, pop out the "Explorer" tool, giving you a view like this

On the right, if you go down the list you can see both liquidationEvent and liquidationEvents. The difference between the two is that you can use liquidationEvent to search for a particular event *by ID*, while liquidationEvents gives you the ability to query *all* events in the database.

For our usecase, since we're looking for *all* liquidations ordered by date, we'll be using liquidationEvents. Clicking on that expands the object's fields, and pre-populates the query for you.

First Query

Great, so we have a list of IDs, and if you look closely at them, they are actually in the format ${TX_HASH}-${transactionLogIndex}. You can see how those IDs are constructed in the subgraph processing logic itself here - https://github.com/moonwell-fi/moonwell-subgraph/blob/ea823807632e689a93cdd5a5752b7fb078b9453c/src/ctoken.ts#L298-L301.

So if we wanted to look at the first liquidation returned on moonscan, we'd copy the first half of the ID and paste it into moonscan like this - https://moonscan.io/tx/0x0244be9be1fbae918f52f3cea51b3d377bb85fef814c7e0a42a9c57ec1f187de

Using other tools can be incredibly helpful to validate what you think you're seeing, other contextual data that's not captured by the subgraph, etc.

More Data

Back to the task at hand, what other data would we like instead of just the ID? Here are our options:

Ignoring the top part in bold, we see:

  • amount

  • blockNumber

  • blockTime

  • cTokenSymbol

  • from

  • to

  • underlyingRepayAmount

  • underlyingSymbol

So let's check all of those and click go again and see what happens:

Much more useful data comes back this time!

Controlling the query

Now that we can go find the fields we want, we also want to filter things, order things, etc. This can be done using the fields that are bold. Some are complicated/nested queries (like block, while others are simple, like orderBy).

Since we're trying to get a list of liquidations ordered by when they happened, the orderBy field is the one we're looking for. Clicking that, we get a list of fields we can order our query by. Since we're interested in historical time-based ordering, either blockNumber or blockTime should work for our needs.

By selecting things on the left, the query is auto-updated to order things by block number now.

Re-running our query, we see that now things are sorted by blockTime!

Multiple filters

One small issue though, this shows us *all* liquidations since the start of the protocol, in order. We could paginate through the results (more on this later), but a better solution would be to add another filter to the query, specifically a orderDirection, which defaults to ascending.

Following the same process as before, we'll add in the orderDirection parameter, and click on desc to signify that we want results ordered by block number, but with the largest block number first (i.e. latest events first).

Running our revised query indeed gives us our list of liquidations by block number with the latest ones being first. Feel free to experiment with other filters, things like where are extremely powerful and let you write queries that pare down the entire dataset to just what you're looking for.

Extra: Querying more than 100 results

This query works great! However, for performance reasons the maximum returned results for any given query is 100 items. What if we wanted to go get the prior 250 liquidations that have happened? Or the entire list? That's where the skip parameter comes in.

For demonstration purposes I'll be using skip: 1 but in the real world you'd likely use skip: 100 and recursively query the data store, breaking when you receive a page of less than 100 results.

As with the other queries by clicking "skip" on the left, and putting 1 in the field it'll auto-update our query, and when we click "execute" the first result will be skipped, showing us items 2-101 instead of the default 1-100.

Last updated