Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Error queries on subgraphs #936

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 90 additions & 63 deletions webapp/utils/subgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ type Schema = {
variables?: Record<string, string | number>
}

type SuccessResponse<T> = { data: T }
type ErrorResponse = { errors: { message: string }[] }
type GraphResponse<T> = SuccessResponse<T> | ErrorResponse

const getSubgraphUrl = function ({
chainId,
subgraphIds,
Expand Down Expand Up @@ -50,8 +54,6 @@ const request = <TResponse, TSchema extends Schema = Schema>(
method: 'POST',
}) satisfies Promise<TResponse>

type GraphResponse<T> = { data: T }

const getTunnelSubgraphUrl = function (chainId: Chain['id']) {
/**
* Use this to override the full url - for example, when using subgraph studio
Expand Down Expand Up @@ -81,6 +83,22 @@ const getTunnelSubgraphUrl = function (chainId: Chain['id']) {
})
}

/**
* Helper function to check for errors in GraphQL responses
* @param response The GraphQL response to check
* @throws Error if the response contains errors
*/
function checkGraphQLErrors<T>(
response: GraphResponse<T>,
): asserts response is SuccessResponse<T> {
// Check if response has errors
if ('errors' in response && response.errors.length > 0) {
// Extract error messages and join them
const errorMessages = response.errors.map(e => e.message).join(', ')
throw new Error(`GraphQL Error: ${errorMessages}`)
}
}

/**
* Retrieves the Last indexed block by the subgraph for the given chain.
* @param chainId Id of the chain whose subgraph is going to be queried.
Expand All @@ -97,15 +115,13 @@ export const getLastIndexedBlock = function (chainId: Chain['id']) {
}
}`,
}
return fetch(url, {
body: JSON.stringify(schema),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
}).then(
(r: GraphResponse<{ _meta: { block: { number: number } } }>) =>
r.data._meta.block.number,
return request<GraphResponse<{ _meta: { block: { number: number } } }>>(
url,
schema,
).then(
response => (
checkGraphQLErrors(response), response.data._meta.block.number
),
)
}

Expand Down Expand Up @@ -174,21 +190,24 @@ export const getBtcWithdrawals = function ({
variables: { address, fromBlock, limit, orderBy, orderDirection, skip },
}

return request<GetBtcWithdrawalsQueryResponse>(url, schema).then(({ data }) =>
data.btcWithdrawals.map(d => ({
// The Subgraph lowercases all the addresses when saving, so better convert them
// into checksum format to avoid errors when trying to get balances or other operations.
// GraphQL also converts BigInt as strings
...d,
blockNumber: Number(d.blockNumber),
// @ts-expect-error OP-SDK does not properly type addresses as Address
from: toChecksum(d.from),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l1Token: toChecksum(d.l1Token),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l2Token: toChecksum(d.l2Token),
timestamp: Number(d.timestamp),
})),
return request<GetBtcWithdrawalsQueryResponse>(url, schema).then(
response => (
checkGraphQLErrors(response),
response.data.btcWithdrawals.map(d => ({
// The Subgraph lowercases all the addresses when saving, so better convert them
// into checksum format to avoid errors when trying to get balances or other operations.
// GraphQL also converts BigInt as strings
...d,
blockNumber: Number(d.blockNumber),
// @ts-expect-error OP-SDK does not properly type addresses as Address
from: toChecksum(d.from),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l1Token: toChecksum(d.l1Token),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l2Token: toChecksum(d.l2Token),
timestamp: Number(d.timestamp),
}))
),
) satisfies Promise<ToBtcWithdrawOperation[]>
}

Expand Down Expand Up @@ -250,23 +269,26 @@ export const getEvmDeposits = function ({
variables: { address, fromBlock, limit, orderBy, orderDirection, skip },
}

return request<GetEvmDepositsQueryResponse>(url, schema).then(({ data }) =>
data.deposits.map(d => ({
// The Subgraph lowercases all the addresses when saving, so better convert them
// into checksum format to avoid errors when trying to get balances or other operations.
// GraphQL also converts BigInt as strings
...d,
blockNumber: Number(d.blockNumber),
// @ts-expect-error OP-SDK does not properly type addresses as Address
from: toChecksum(d.from),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l1Token: toChecksum(d.l1Token),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l2Token: toChecksum(d.l2Token),
timestamp: Number(d.timestamp),
// @ts-expect-error OP-SDK does not properly type addresses as Address
to: toChecksum(d.to),
})),
return request<GetEvmDepositsQueryResponse>(url, schema).then(
response => (
checkGraphQLErrors(response),
response.data.deposits.map(d => ({
// The Subgraph lowercases all the addresses when saving, so better convert them
// into checksum format to avoid errors when trying to get balances or other operations.
// GraphQL also converts BigInt as strings
...d,
blockNumber: Number(d.blockNumber),
// @ts-expect-error OP-SDK does not properly type addresses as Address
from: toChecksum(d.from),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l1Token: toChecksum(d.l1Token),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l2Token: toChecksum(d.l2Token),
timestamp: Number(d.timestamp),
// @ts-expect-error OP-SDK does not properly type addresses as Address
to: toChecksum(d.to),
}))
),
) satisfies Promise<EvmDepositOperation[]>
}

Expand Down Expand Up @@ -328,23 +350,26 @@ export const getEvmWithdrawals = function ({
variables: { address, fromBlock, limit, orderBy, orderDirection, skip },
}

return request<GetEvmWithdrawalsQueryResponse>(url, schema).then(({ data }) =>
data.evmWithdrawals.map(d => ({
// The Subgraph lowercases all the addresses when saving, so better convert them
// into checksum format to avoid errors when trying to get balances or other operations.
// GraphQL also converts BigInt as strings
...d,
blockNumber: Number(d.blockNumber),
// @ts-expect-error OP-SDK does not properly type addresses as Address
from: toChecksum(d.from),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l1Token: toChecksum(d.l1Token),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l2Token: toChecksum(d.l2Token),
timestamp: Number(d.timestamp),
// @ts-expect-error OP-SDK does not properly type addresses as Address
to: toChecksum(d.to),
})),
return request<GetEvmWithdrawalsQueryResponse>(url, schema).then(
response => (
checkGraphQLErrors(response),
response.data.evmWithdrawals.map(d => ({
// The Subgraph lowercases all the addresses when saving, so better convert them
// into checksum format to avoid errors when trying to get balances or other operations.
// GraphQL also converts BigInt as strings
...d,
blockNumber: Number(d.blockNumber),
// @ts-expect-error OP-SDK does not properly type addresses as Address
from: toChecksum(d.from),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l1Token: toChecksum(d.l1Token),
// @ts-expect-error OP-SDK does not properly type addresses as Address
l2Token: toChecksum(d.l2Token),
timestamp: Number(d.timestamp),
// @ts-expect-error OP-SDK does not properly type addresses as Address
to: toChecksum(d.to),
}))
),
)
}

Expand Down Expand Up @@ -389,11 +414,13 @@ export const getTotalStaked = function (hemiId: Chain['id']) {
}

return request<GetTotalStakedBalancesQueryResponse>(subgraphUrl, schema).then(
({ data }) =>
data.tokenStakeBalances.map(({ id, ...rest }) => ({
response => (
checkGraphQLErrors(response),
response.data.tokenStakeBalances.map(({ id, ...rest }) => ({
...rest,
// By default, The Graph store addresses as lowercase
id: toChecksum(id),
})),
}))
),
)
}