-
Notifications
You must be signed in to change notification settings - Fork 202
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: subgraphs for ETH transfers to custom destination (#1943)
- Loading branch information
Showing
3 changed files
with
311 additions
and
11 deletions.
There are no files selected for viewing
157 changes: 157 additions & 0 deletions
157
packages/arb-token-bridge-ui/src/pages/api/eth-deposits-custom-destination.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import { NextApiRequest, NextApiResponse } from 'next' | ||
import { gql } from '@apollo/client' | ||
|
||
import { | ||
getL1SubgraphClient, | ||
getSourceFromSubgraphClient | ||
} from '../../api-utils/ServerSubgraphUtils' | ||
import { FetchEthDepositsToCustomDestinationFromSubgraphResult } from '../../util/deposits/fetchEthDepositsToCustomDestinationFromSubgraph' | ||
|
||
type NextApiRequestWithDepositParams = NextApiRequest & { | ||
query: { | ||
sender?: string | ||
receiver?: string | ||
l2ChainId: string | ||
search?: string | ||
page?: string | ||
pageSize?: string | ||
fromBlock?: string | ||
toBlock?: string | ||
} | ||
} | ||
|
||
type RetryableFromSubgraph = { | ||
destAddr: string | ||
sender: string | ||
timestamp: string | ||
transactionHash: string | ||
id: string | ||
l2Callvalue: string | ||
blockCreatedAt: string | ||
} | ||
|
||
type EthDepositsToCustomDestinationResponse = { | ||
meta?: { source: string | null } | ||
data: FetchEthDepositsToCustomDestinationFromSubgraphResult[] | ||
message?: string | ||
} | ||
|
||
export default async function handler( | ||
req: NextApiRequestWithDepositParams, | ||
res: NextApiResponse<EthDepositsToCustomDestinationResponse> | ||
) { | ||
try { | ||
const { | ||
sender, | ||
receiver, | ||
search = '', | ||
l2ChainId, | ||
page = '0', | ||
pageSize = '10', | ||
fromBlock, | ||
toBlock | ||
} = req.query | ||
|
||
if (req.method !== 'GET') { | ||
res | ||
.status(400) | ||
.send({ message: `invalid_method: ${req.method}`, data: [] }) | ||
return | ||
} | ||
|
||
const errorMessage = [] | ||
if (!l2ChainId) errorMessage.push('<l2ChainId> is required') | ||
if (!sender && !receiver) | ||
errorMessage.push('<sender> or <receiver> is required') | ||
|
||
if (errorMessage.length) { | ||
res.status(400).json({ | ||
message: `incomplete request: ${errorMessage.join(', ')}`, | ||
data: [] | ||
}) | ||
return | ||
} | ||
|
||
// if invalid pageSize, send empty data instead of error | ||
if (isNaN(Number(pageSize)) || Number(pageSize) === 0) { | ||
res.status(200).json({ | ||
data: [] | ||
}) | ||
return | ||
} | ||
|
||
const additionalFilters = `${ | ||
typeof fromBlock !== 'undefined' | ||
? `blockCreatedAt_gte: ${Number(fromBlock)},` | ||
: '' | ||
} | ||
${ | ||
typeof toBlock !== 'undefined' | ||
? `blockCreatedAt_lte: ${Number(toBlock)},` | ||
: '' | ||
} | ||
${search ? `transactionHash_contains: "${search}",` : ''} | ||
l2Callvalue_gt: 0 | ||
l2Calldata: "0x" | ||
` | ||
|
||
const subgraphClient = getL1SubgraphClient(Number(l2ChainId)) | ||
|
||
const subgraphResult = await subgraphClient.query({ | ||
query: gql(`{ | ||
retryables( | ||
where: { | ||
or: [ | ||
${sender ? `{ sender: "${sender}", ${additionalFilters} },` : ''} | ||
${ | ||
receiver | ||
? `{ destAddr: "${receiver}", ${additionalFilters} },` | ||
: '' | ||
} | ||
] | ||
} | ||
orderBy: blockCreatedAt | ||
orderDirection: desc | ||
first: ${Number(pageSize)}, | ||
skip: ${Number(page) * Number(pageSize)} | ||
) { | ||
destAddr | ||
sender | ||
timestamp | ||
transactionHash | ||
id | ||
l2Callvalue | ||
blockCreatedAt | ||
} | ||
}`) | ||
}) | ||
|
||
const retryablesFromSubgraph: RetryableFromSubgraph[] = | ||
subgraphResult.data.retryables | ||
|
||
const transactions: FetchEthDepositsToCustomDestinationFromSubgraphResult[] = | ||
retryablesFromSubgraph.map(retryable => { | ||
return { | ||
receiver: retryable.destAddr, | ||
sender: retryable.sender, | ||
timestamp: retryable.timestamp, | ||
transactionHash: retryable.transactionHash, | ||
type: 'EthDeposit', | ||
isClassic: false, | ||
id: retryable.id, | ||
ethValue: retryable.l2Callvalue, | ||
blockCreatedAt: retryable.blockCreatedAt | ||
} | ||
}) | ||
|
||
res.status(200).json({ | ||
meta: { source: getSourceFromSubgraphClient(subgraphClient) }, | ||
data: transactions | ||
}) | ||
} catch (error: any) { | ||
res.status(500).json({ | ||
message: error?.message ?? 'Something went wrong', | ||
data: [] | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
.../arb-token-bridge-ui/src/util/deposits/fetchEthDepositsToCustomDestinationFromSubgraph.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { hasL1Subgraph } from '../SubgraphUtils' | ||
import { | ||
getAPIBaseUrl, | ||
isExperimentalFeatureEnabled, | ||
sanitizeQueryParams | ||
} from '../index' | ||
|
||
export type FetchEthDepositsToCustomDestinationFromSubgraphResult = { | ||
receiver: string | ||
sender: string | ||
timestamp: string | ||
transactionHash: string | ||
type: 'EthDeposit' | ||
isClassic: false | ||
id: string | ||
ethValue: string | ||
blockCreatedAt: string | ||
} | ||
|
||
/** | ||
* Fetches initiated retryable deposits (ETH transfers to custom destination) from subgraph in range of [fromBlock, toBlock] and pageParams. | ||
* | ||
* @param query Query params | ||
* @param query.sender Address that initiated the deposit | ||
* @param query.receiver Address that received the funds | ||
* @param query.fromBlock Start at this block number (including) | ||
* @param query.toBlock Stop at this block number (including) | ||
* @param query.l2ChainId Chain id for the L2 network | ||
* @param query.pageSize Fetch these many records from subgraph | ||
* @param query.pageNumber Fetch records starting [pageNumber * pageSize] records | ||
* @param query.searchString Searches records through the l1TxHash | ||
*/ | ||
|
||
export const fetchEthDepositsToCustomDestinationFromSubgraph = async ({ | ||
sender, | ||
receiver, | ||
fromBlock, | ||
toBlock, | ||
l2ChainId, | ||
pageSize = 10, | ||
pageNumber = 0, | ||
searchString = '' | ||
}: { | ||
sender?: string | ||
receiver?: string | ||
fromBlock: number | ||
toBlock?: number | ||
l2ChainId: number | ||
pageSize?: number | ||
pageNumber?: number | ||
searchString?: string | ||
}): Promise<FetchEthDepositsToCustomDestinationFromSubgraphResult[]> => { | ||
if (!isExperimentalFeatureEnabled('eth-custom-dest')) { | ||
return [] | ||
} | ||
|
||
if (toBlock && fromBlock >= toBlock) { | ||
// if fromBlock > toBlock or both are equal / 0 | ||
return [] | ||
} | ||
|
||
const urlParams = new URLSearchParams( | ||
sanitizeQueryParams({ | ||
sender, | ||
receiver, | ||
fromBlock, | ||
toBlock, | ||
l2ChainId, | ||
pageSize, | ||
page: pageNumber, | ||
search: searchString | ||
}) | ||
) | ||
|
||
if (!hasL1Subgraph(Number(l2ChainId))) { | ||
throw new Error(`L1 subgraph not available for network: ${l2ChainId}`) | ||
} | ||
|
||
if (pageSize === 0) return [] // don't query subgraph if nothing requested | ||
|
||
const response = await fetch( | ||
`${getAPIBaseUrl()}/api/eth-deposits-custom-destination?${urlParams}`, | ||
{ | ||
method: 'GET', | ||
headers: { 'Content-Type': 'application/json' } | ||
} | ||
) | ||
|
||
const transactions: FetchEthDepositsToCustomDestinationFromSubgraphResult[] = | ||
(await response.json()).data | ||
|
||
return transactions | ||
} |