packages/rfq-indexer/api/src/graphql/resolvers.ts
import { sql } from 'kysely'
import { db } from '../db'
// typical fields to return for a BridgeRequest
const qDeposits = () => {
return db
.selectFrom('BridgeRequestEvents')
.select([
'BridgeRequestEvents.transactionId as transactionId_deposit',
'BridgeRequestEvents.blockNumber as blockNumber_deposit',
'BridgeRequestEvents.blockTimestamp as blockTimestamp_deposit',
'BridgeRequestEvents.transactionHash as transactionHash_deposit',
'BridgeRequestEvents.originChain',
'BridgeRequestEvents.destChain',
'BridgeRequestEvents.originChainId',
'BridgeRequestEvents.destChainId',
'BridgeRequestEvents.originToken',
'BridgeRequestEvents.destToken',
'BridgeRequestEvents.originAmountFormatted',
'BridgeRequestEvents.destAmountFormatted',
'BridgeRequestEvents.sender',
'BridgeRequestEvents.request',
'BridgeRequestEvents.sendChainGas',
])
.where('BridgeRequestEvents.blockTimestamp', '>', 1722729600)
// if index is partially loaded, we must limit lookback or will have various data issues from relays
// that happened to be in flight at the point of the index's start.
// may also improve query performance
}
// typical fields to return for a BridgeRelayed event when it is joined to a BridgeRequest
const qRelays = () => {
return db
.selectFrom('BridgeRelayedEvents')
.select([
'BridgeRelayedEvents.transactionId as transactionId_relay',
'BridgeRelayedEvents.blockNumber as blockNumber_relay',
'BridgeRelayedEvents.blockTimestamp as blockTimestamp_relay',
'BridgeRelayedEvents.transactionHash as transactionHash_relay',
'BridgeRelayedEvents.relayer as relayer_relay',
'BridgeRelayedEvents.to as to_relay',
])
}
// typical fields to return for a BridgeProofProvided event when it is joined to a BridgeRequest
const qProofs = () => {
return db
.selectFrom('BridgeProofProvidedEvents')
.select([
'BridgeProofProvidedEvents.transactionId as transactionId_proof',
'BridgeProofProvidedEvents.blockNumber as blockNumber_proof',
'BridgeProofProvidedEvents.blockTimestamp as blockTimestamp_proof',
'BridgeProofProvidedEvents.transactionHash as transactionHash_proof',
'BridgeProofProvidedEvents.relayer as relayer_proof',
])
}
// typical fields to return for a BridgeDepositClaimed event when it is joined to a BridgeRequest
const qClaims = () => {
return db
.selectFrom('BridgeDepositClaimedEvents')
.select([
'BridgeDepositClaimedEvents.transactionId as transactionId_claim',
'BridgeDepositClaimedEvents.blockNumber as blockNumber_claim',
'BridgeDepositClaimedEvents.blockTimestamp as blockTimestamp_claim',
'BridgeDepositClaimedEvents.transactionHash as transactionHash_claim',
'BridgeDepositClaimedEvents.to as to_claim',
'BridgeDepositClaimedEvents.relayer as relayer_claim',
'BridgeDepositClaimedEvents.amountFormatted as amountFormatted_claim',
])
}
// typical fields to return for a BridgeDepositRefunded event when it is joined to a BridgeRequest
const qRefunds = () => {
return db
.selectFrom('BridgeDepositRefundedEvents')
.select([
'BridgeDepositRefundedEvents.transactionId as transactionId_refund',
'BridgeDepositRefundedEvents.blockNumber as blockNumber_refund',
'BridgeDepositRefundedEvents.blockTimestamp as blockTimestamp_refund',
'BridgeDepositRefundedEvents.transactionHash as transactionHash_refund',
'BridgeDepositRefundedEvents.to as to_refund',
'BridgeDepositRefundedEvents.amountFormatted as amountFormatted_refund',
])
}
// typical fields to return for a BridgeProofDisputed event when it is joined to a BridgeRequest
const qDisputes = () => {
return db
.selectFrom('BridgeProofDisputedEvents')
.select([
'BridgeProofDisputedEvents.transactionId as transactionId_dispute',
'BridgeProofDisputedEvents.blockNumber as blockNumber_dispute',
'BridgeProofDisputedEvents.blockTimestamp as blockTimestamp_dispute',
'BridgeProofDisputedEvents.transactionHash as transactionHash_dispute',
'BridgeProofDisputedEvents.originChainId as originChainId_dispute',
'BridgeProofDisputedEvents.originChain as originChain_dispute',
])
}
// using the suffix of a field, move it into a nested sub-object. This is a cleaner final resultset
// example: transactionHash_deposit:0xyz would get moved into BridgeRequest{transactionHash:0xyz}
//
// also note that transactionId_xxxx will just convert into a single "transactionId"
const nest_results = (sqlResults: any[]) => {
return sqlResults.map((transaction: any) => {
const bridgeRequest: { [key: string]: any } = {}
const bridgeRelay: { [key: string]: any } = {}
const bridgeProof: { [key: string]: any } = {}
const bridgeClaim: { [key: string]: any } = {}
const bridgeRefund: { [key: string]: any } = {}
const bridgeDispute: { [key: string]: any } = {}
const transactionFields: { [key: string]: any } = {}
let transactionIdSet = false
for (const [key, value] of Object.entries(transaction)) {
if (key.startsWith('transactionId')) {
if (!transactionIdSet) {
transactionFields[key.replace(/_.+$/, '')] = value
transactionIdSet = true
}
// Ignore other transactionId fields
} else if (key.endsWith('_deposit')) {
bridgeRequest[key.replace('_deposit', '')] = value
} else if (key.endsWith('_relay')) {
bridgeRelay[key.replace('_relay', '')] = value
} else if (key.endsWith('_proof')) {
bridgeProof[key.replace('_proof', '')] = value
} else if (key.endsWith('_claim')) {
bridgeClaim[key.replace('_claim', '')] = value
} else if (key.endsWith('_refund')) {
bridgeRefund[key.replace('_refund', '')] = value
} else if (key.endsWith('_dispute')) {
bridgeDispute[key.replace('_dispute', '')] = value
} else {
transactionFields[key] = value
}
}
const result: { [key: string]: any } = { Bridge: transactionFields }
if (Object.keys(bridgeRequest).length) {
result.BridgeRequest = bridgeRequest
}
if (Object.keys(bridgeRelay).length) {
result.BridgeRelay = bridgeRelay
}
if (Object.keys(bridgeProof).length) {
result.BridgeProof = bridgeProof
}
if (Object.keys(bridgeClaim).length) {
result.BridgeClaim = bridgeClaim
}
if (Object.keys(bridgeRefund).length) {
result.BridgeRefund = bridgeRefund
}
if (Object.keys(bridgeDispute).length) {
result.BridgeDispute = bridgeDispute
}
return result
})
}
const resolvers = {
Query: {
events: async (
_: any,
{ first = 10, after, filter }: { first?: any; after?: any; filter?: any }
) => {
let query = db
.selectFrom('BridgeRequestEvents')
.select([
'BridgeRequestEvents.id',
'BridgeRequestEvents.transactionId',
'BridgeRequestEvents.blockNumber',
'BridgeRequestEvents.blockTimestamp',
'BridgeRequestEvents.transactionHash',
'BridgeRequestEvents.originChainId',
'BridgeRequestEvents.originChain',
])
.unionAll(
db
.selectFrom('BridgeRelayedEvents')
.select([
'BridgeRelayedEvents.id',
'BridgeRelayedEvents.transactionId',
'BridgeRelayedEvents.blockNumber',
'BridgeRelayedEvents.blockTimestamp',
'BridgeRelayedEvents.transactionHash',
'BridgeRelayedEvents.originChainId',
'BridgeRelayedEvents.originChain',
])
)
.unionAll(
db
.selectFrom('BridgeProofProvidedEvents')
.select([
'BridgeProofProvidedEvents.id',
'BridgeProofProvidedEvents.transactionId',
'BridgeProofProvidedEvents.blockNumber',
'BridgeProofProvidedEvents.blockTimestamp',
'BridgeProofProvidedEvents.transactionHash',
'BridgeProofProvidedEvents.originChainId',
'BridgeProofProvidedEvents.originChain',
])
)
.unionAll(
db
.selectFrom('BridgeDepositRefundedEvents')
.select([
'BridgeDepositRefundedEvents.id',
'BridgeDepositRefundedEvents.transactionId',
'BridgeDepositRefundedEvents.blockNumber',
'BridgeDepositRefundedEvents.blockTimestamp',
'BridgeDepositRefundedEvents.transactionHash',
'BridgeDepositRefundedEvents.originChainId',
'BridgeDepositRefundedEvents.originChain',
])
)
.unionAll(
db
.selectFrom('BridgeDepositClaimedEvents')
.select([
'BridgeDepositClaimedEvents.id',
'BridgeDepositClaimedEvents.transactionId',
'BridgeDepositClaimedEvents.blockNumber',
'BridgeDepositClaimedEvents.blockTimestamp',
'BridgeDepositClaimedEvents.transactionHash',
'BridgeDepositClaimedEvents.originChainId',
'BridgeDepositClaimedEvents.originChain',
])
)
.unionAll(
db
.selectFrom('BridgeProofDisputedEvents')
.select([
'BridgeProofDisputedEvents.id',
'BridgeProofDisputedEvents.transactionId',
'BridgeProofDisputedEvents.blockNumber',
'BridgeProofDisputedEvents.blockTimestamp',
'BridgeProofDisputedEvents.transactionHash',
'BridgeProofDisputedEvents.originChainId',
'BridgeProofDisputedEvents.originChain',
])
)
if (filter) {
if (filter.transactionId) {
query = query.where('transactionId', '=', filter.transactionId)
}
if (filter.originChainId) {
query = query.where('originChainId', '=', filter.originChainId)
}
// Add more filters as needed
}
if (after) {
query = query.where('id', '>', after)
}
const events = await query
.orderBy('blockTimestamp', 'desc')
.limit(first + 1)
.execute()
const hasNextPage = events.length > first
const edges = events.slice(0, first).map((event: any) => ({
node: event,
cursor: event.id,
}))
return {
edges,
pageInfo: {
hasNextPage,
endCursor: edges.length > 0 ? edges[edges.length - 1]?.cursor : null,
},
}
},
pendingTransactionsMissingRelay: async () => {
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('refunds', () => qRefunds())
.with(
'combined',
(qb) =>
qb
.selectFrom('deposits')
.selectAll('deposits')
.leftJoin(
'relays',
'transactionId_deposit',
'transactionId_relay'
)
.leftJoin(
'refunds',
'transactionId_deposit',
'transactionId_refund'
)
.where('transactionId_relay', 'is', null) // is not relayed
.where('transactionId_refund', 'is', null) // is not refunded
)
.selectFrom('combined')
.selectAll()
.orderBy('blockTimestamp_deposit', 'desc')
return nest_results(await query.execute())
},
pendingTransactionsMissingProof: async () => {
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('proofs', () => qProofs())
.with('combined', (qb) =>
qb
.selectFrom('deposits')
.innerJoin('relays', 'transactionId_deposit', 'transactionId_relay')
.leftJoin('proofs', 'transactionId_deposit', 'transactionId_proof')
.selectAll('deposits')
.selectAll('relays')
.where('transactionId_proof', 'is', null)
)
.selectFrom('combined')
.selectAll()
.orderBy('blockTimestamp_relay', 'desc')
return nest_results(await query.execute())
},
pendingTransactionsMissingClaim: async () => {
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('proofs', () => qProofs())
.with('claims', () => qClaims())
.with('combined', (qb) =>
qb
.selectFrom('deposits')
.innerJoin('relays', 'transactionId_deposit', 'transactionId_relay')
.innerJoin('proofs', 'transactionId_deposit', 'transactionId_proof')
.leftJoin('claims', 'transactionId_deposit', 'transactionId_claim')
.selectAll('deposits')
.selectAll('relays')
.selectAll('proofs')
.where('transactionId_claim', 'is', null)
)
.selectFrom('combined')
.selectAll()
.orderBy('blockTimestamp_proof', 'desc')
return nest_results(await query.execute())
},
recentInvalidRelays: async () => {
const query = db
.selectFrom('BridgeRelayedEvents')
.leftJoin(
'BridgeRequestEvents',
'BridgeRelayedEvents.transactionId',
'BridgeRequestEvents.transactionId'
)
.select([
'BridgeRelayedEvents.transactionId',
'BridgeRelayedEvents.blockNumber',
'BridgeRelayedEvents.blockTimestamp',
'BridgeRelayedEvents.transactionHash',
'BridgeRelayedEvents.originChain',
'BridgeRelayedEvents.destChain',
'BridgeRelayedEvents.originChainId',
'BridgeRelayedEvents.destChainId',
'BridgeRelayedEvents.originToken',
'BridgeRelayedEvents.destToken',
'BridgeRelayedEvents.originAmountFormatted',
'BridgeRelayedEvents.destAmountFormatted',
'BridgeRelayedEvents.to',
'BridgeRelayedEvents.relayer',
])
// lookback approx 2 weeks
.where(
'BridgeRelayedEvents.blockTimestamp',
'>',
Math.floor(Date.now() / 1000) - 2 * 7 * 24 * 60 * 60
)
.where('BridgeRequestEvents.transactionId', 'is', null)
// intentionally do not nest - doesnt make sense w/ this dataset because the whole point is that no Deposit exists
return query.execute()
},
refundedAndRelayedTransactions: async () => {
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('refunds', () => qRefunds())
.with('combined', (qb) =>
qb
.selectFrom('deposits')
.innerJoin('relays', 'transactionId_deposit', 'transactionId_relay')
.innerJoin(
'refunds',
'transactionId_deposit',
'transactionId_refund'
)
.selectAll('deposits')
.selectAll('relays')
.selectAll('refunds')
)
.selectFrom('combined')
.selectAll()
.orderBy('blockTimestamp_refund', 'desc')
return nest_results(await query.execute())
},
transactionById: async (
_: any,
{ transactionId }: { transactionId: string }
) => {
const query = db
.with('deposits', () =>
qDeposits().where('transactionId', '=', transactionId)
)
.with('relays', () => qRelays())
.with('proofs', () => qProofs())
.with('claims', () => qClaims())
.with('refunds', () => qRefunds())
.with('combined', (qb) =>
qb
.selectFrom('deposits')
.leftJoin('relays', 'transactionId_deposit', 'transactionId_relay')
.leftJoin('proofs', 'transactionId_deposit', 'transactionId_proof')
.leftJoin('claims', 'transactionId_deposit', 'transactionId_claim')
.leftJoin(
'refunds',
'transactionId_deposit',
'transactionId_refund'
)
.selectAll('deposits')
.selectAll('relays')
.selectAll('proofs')
.selectAll('claims')
.selectAll('refunds')
)
.selectFrom('combined')
.selectAll()
const nestedResult = nest_results(await query.execute())[0] || null
if (nestedResult) {
return Object.fromEntries(
Object.entries(nestedResult).filter(([_, value]) => {
if (value === null) {
return false
}
if (typeof value !== 'object') {
return true
}
return Object.values(value).some((v) => v !== null)
})
)
}
return null
},
conflictingProofs: async () => {
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('proofs', () => qProofs())
.with('combined', (qb) =>
qb
.selectFrom('deposits')
.leftJoin('relays', 'transactionId_deposit', 'transactionId_relay')
.leftJoin('proofs', 'transactionId_deposit', 'transactionId_proof')
.selectAll('deposits')
.selectAll('relays')
.selectAll('proofs')
)
.selectFrom('combined')
.selectAll()
.where('relayer_proof', 'is not', null)
.where('relayer_relay', 'is not', null)
.where(
(eb) =>
sql<boolean>`LOWER(${eb.ref('relayer_relay')}) != LOWER(${eb.ref(
'relayer_proof'
)})`
)
.orderBy('blockTimestamp_proof', 'desc')
return nest_results(await query.execute())
},
disputedRelays: async () => {
const query = db
.with('deposits', () => qDeposits())
.with('relays', () => qRelays())
.with('proofs', () => qProofs())
.with('disputes', () => qDisputes())
.with('combined', (qb) =>
qb
.selectFrom('proofs')
.leftJoin(
'disputes',
'transactionId_proof',
'transactionId_dispute'
)
.selectAll('proofs')
.selectAll('disputes')
)
.selectFrom('combined')
.selectAll()
.orderBy('blockTimestamp_proof', 'desc')
return nest_results(await query.execute())
},
},
BridgeEvent: {
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
__resolveType(obj: any) {
// Implement logic to determine the event type based on the object properties
// For example:
if ('sender' in obj) {
return 'BridgeRequestEvent'
}
if ('relayer' in obj && 'to' in obj) {
return 'BridgeRelayedEvent'
}
if ('relayer' in obj && !('to' in obj)) {
return 'BridgeProofProvidedEvent'
}
if ('to' in obj && 'token' in obj) {
return 'BridgeDepositRefundedEvent'
}
if ('relayer' in obj && 'to' in obj && 'token' in obj) {
return 'BridgeDepositClaimedEvent'
}
return null
},
},
}
export { resolvers }