Skip to main content

TheGraph subgraph GraphQL helper

The Graph The Graph is an indexing protocol for querying networks like Ethereum and IPFS. Anyone can build and publish open APIs, called subgraphs, making data easily accessible.

Using the TheGraphHelper

The TheGraphHelper helper class can be used to query any graphql server by using the appToolkit helpers that is readily provided on all classes

First, let's grab either our PositionFetcher, AppTokenFetcher, BalanceFetcher or TvlFetcher and use this.appToolkit to get the instance of TheGraphHelper

import { Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { ContractPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { MeanFinanceContractFactory } from '../contracts';
import { MEAN_FINANCE_DEFINITION } from '../mean-finance.definition';

const appId = MEAN_FINANCE_DEFINITION.id;
const groupId = MEAN_FINANCE_DEFINITION.groups.dcaPosition.id;
const network = Network.POLYGON_MAINNET;

@Register.ContractPositionFetcher({ appId, groupId, network })
export class PolygonMeanFinanceDcaPositionContractPositionFetcher implements PositionFetcher<ContractPosition> {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(MeanFinanceContractFactory) private readonly meanFinanceContractFactory: MeanFinanceContractFactory,
) { }

async getPositions() {
const graphHelper = this.appToolkit.helpers.theGraphHelper;
return [];
}
}

Create a Graphql query

We'll use this Graphql query to fetch specific data from our subgraph. Create a graphql folder inside your app folder /src/apps/<YOUR APP>/graphql, next we are going to create a file that represents what we are going to fetch. In our case we are going to fetch all DCA positions from Mean Finance so we'll be creating the file /src/apps/<YOUR APP>/graphql/getPositions.ts and we'll also create a type for the return of this query.

import { gql } from 'graphql-request';

type MeanFinancePosition = {
positions: {
id: string;
executedSwaps: string;
user: string;
from: {
address: string;
decimals: string;
name: string;
symbol: string;
};
to: {
address: string;
decimals: string;
name: string;
symbol: string;
};
status: string;
swapInterval: {
id: string;
interval: string;
};
current: {
id: string;
rate: string;
remainingSwaps: string;
remainingLiquidity: string;
idleSwapped: string;
};
}[];
};

export const GET_POSITIONS = gql`
query getPositions {
positions {
id
executedSwaps
user
from {
address: id
decimals
name
symbol
}
to {
address: id
decimals
name
symbol
}
status
swapInterval {
interval
}
current {
rate
remainingSwaps
remainingLiquidity
idleSwapped
}
}
}
`;

Execute the query

From our fetcher we'll use the graphHelper we previously defined to execute they query using requestGraph. This is an async action that will return the data that we requested on our subgraph.

import { Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { ContractPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { MeanFinanceContractFactory } from '../contracts';
import { getPositions, MeanFinancePosition } from '../graphql/getPositions'
import { MEAN_FINANCE_DEFINITION } from '../mean-finance.definition';

const appId = MEAN_FINANCE_DEFINITION.id;
const groupId = MEAN_FINANCE_DEFINITION.groups.dcaPosition.id;
const network = Network.POLYGON_MAINNET;

@Register.ContractPositionFetcher({ appId, groupId, network })
export class PolygonMeanFinanceDcaPositionContractPositionFetcher implements PositionFetcher<ContractPosition> {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(MeanFinanceContractFactory) private readonly meanFinanceContractFactory: MeanFinanceContractFactory,
) { }

async getPositions() {
const graphHelper = this.appToolkit.helpers.theGraphHelper;
const data = graphHelper.requestGraph<MeanFinancePositions>({
endpoint: 'https://api.thegraph.com/subgraphs/name/mean-finance/dca-v2-polygon',
query: getPositions,
})
const positions = data.positions;

return [];
}
}

Execute a query with variables

Lets say that you want your graphql query to just query the positions created by a specific user, or you want to send any other variables to the query, you cansend a variables property to the requestGraph function.

Lets create our query to get positions for a specific user this is going to look exactly like the one before, but we are adding a user parameter.

Now we create a getUserPositions.ts file inside /src/apps/<YOUR APP>/graphql

import { gql } from 'graphql-request';

type MeanFinancePosition = {
positions: {
id: string;
executedSwaps: string;
user: string;
from: {
address: string;
decimals: string;
name: string;
symbol: string;
};
to: {
address: string;
decimals: string;
name: string;
symbol: string;
};
status: string;
swapInterval: {
id: string;
interval: string;
};
current: {
id: string;
rate: string;
remainingSwaps: string;
remainingLiquidity: string;
idleSwapped: string;
};
}[];
};

export const GET_USER_POSITIONS = gql`
query getUserPositions($address: String!) {
positions(
where: {
user: $address,
},
) {
id
executedSwaps
user
from {
address: id
decimals
name
symbol
}
to {
address: id
decimals
name
symbol
}
status
swapInterval {
interval
}
current {
rate
remainingSwaps
remainingLiquidity
idleSwapped
}
}
}
`;

And now from a BalanceFetcher we could just send the user address to the requestGraph function inside the variables property.

import { Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { BalanceFetcher } from '~balance/balance-fetcher.interface';
import { Network } from '~types/network.interface';

import { getUserPositions, MeanFinancePosition } from '../graphql/getUserPositions';
import { MEAN_FINANCE_DEFINITION } from '../mean-finance.definition';

const network = Network.POLYGON_MAINNET;

@Register.BalanceFetcher(MEAN_FINANCE_DEFINITION.id, network)
export class PolygonMeanFinanceBalanceFetcher implements BalanceFetcher {
constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) { }

async getUserPositions(address: string) {
const graphHelper = this.appToolkit.helpers.theGraphHelper;
const data = graphHelper.requestGraph<MeanFinancePositions>({
endpoint: 'https://api.thegraph.com/subgraphs/name/mean-finance/dca-v2-polygon',
query: getPositions,
variables: { address },
})
return data.positions;
}

async getBalances(address: string) {
const positions = await this.getUserPositions(address);
}
}

Fetch an entire collection that is paginated

Graphql allows us to fetch any kind of data, but specifically for Zapper Studio we are mostly going to be fetching collections of data. TheGraph sets a maximum limit of 1000 items per query, this means that we need to go through the list of items with multiple queries in case there is more than 1000 items that you need to fetch.

For this you can use the gqlFetchAll utility from TheGraphHelper it takes the same inputs as requestGraph but it does require an additional property dataToSearch. dataToSearch refers to the name (in your query) of the collection name.

It is required for your query to have defined first and lastId parameters for the query so the helper can actually go through the paginated list. You will need to add a where clause into the collection that you are fetching, setting id_gt: $lastId

Modifying the example before the new query would look like

import { gql } from 'graphql-request';

type MeanFinancePosition = {
positions: {
id: string;
executedSwaps: string;
user: string;
from: {
address: string;
decimals: string;
name: string;
symbol: string;
};
to: {
address: string;
decimals: string;
name: string;
symbol: string;
};
status: string;
swapInterval: {
id: string;
interval: string;
};
current: {
id: string;
rate: string;
remainingSwaps: string;
remainingLiquidity: string;
idleSwapped: string;
};
}[];
};

export const GET_USER_POSITIONS = gql`
query getUserPositions($address: String!, $first: Int, $lastId: String) {
positions(
where: {
user: $address,
id_gt: $lastId,
},
first: $first,
) {
id
executedSwaps
user
from {
address: id
decimals
name
symbol
}
to {
address: id
decimals
name
symbol
}
status
swapInterval {
interval
}
current {
rate
remainingSwaps
remainingLiquidity
idleSwapped
}
}
}
`;

And now from a BalanceFetcher we can query the full list of positions using gqlFetchAll instead of requestGraph passing positions as the `dataToSearch.

import { Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { BalanceFetcher } from '~balance/balance-fetcher.interface';
import { Network } from '~types/network.interface';

import { getUserPositions, MeanFinancePosition } from '../graphql/getUserPositions';
import { MEAN_FINANCE_DEFINITION } from '../mean-finance.definition';

const network = Network.POLYGON_MAINNET;

@Register.BalanceFetcher(MEAN_FINANCE_DEFINITION.id, network)
export class PolygonMeanFinanceBalanceFetcher implements BalanceFetcher {
constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) { }

async getUserPositions(address: string) {
const graphHelper = this.appToolkit.helpers.theGraphHelper;
const data = graphHelper.gqlFetchAll<MeanFinancePositions>({
endpoint: 'https://api.thegraph.com/subgraphs/name/mean-finance/dca-v2-polygon',
query: getPositions,
variables: { address },
dataToSearch: 'positions',
})
return data.positions;
}

async getBalances(address: string) {
const positions = await this.getUserPositions(address);
}
}