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

Sdk: expose bridge tx status #1658

Merged
merged 17 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from 16 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
767 changes: 767 additions & 0 deletions packages/sdk-router/src/abi/SynapseCCTP.json

Large diffs are not rendered by default.

64 changes: 55 additions & 9 deletions packages/sdk-router/src/operations/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,46 @@ export async function bridgeQuote(
return bestSet.finalizeBridgeRoute(bestRoute, deadline)
}

/**
* Gets the unique Synapse txId for a bridge operation that happened within a given transaction.
* Synapse txId is known as "kappa" for SynapseBridge contract and "requestID" for SynapseCCTP contract.
* This function is meant to abstract away the differences between the two bridge modules.
*
* @param originChainId - The ID of the origin chain.
* @param bridgeModuleName - The name of the bridge module.
* @param txHash - The transaction hash of the bridge operation on the origin chain.
* @returns A promise that resolves to the unique Synapse txId of the bridge operation.
*/
export async function getSynapseTxId(
this: SynapseSDK,
originChainId: number,
bridgeModuleName: string,
txHash: string
): Promise<string> {
return getRouterSet
.call(this, bridgeModuleName)
.getSynapseTxId(originChainId, txHash)
}

/**
* Checks whether a bridge operation has been completed on the destination chain.
*
* @param destChainId - The ID of the destination chain.
* @param bridgeModuleName - The name of the bridge module.
* @param synapseTxId - The unique Synapse txId of the bridge operation.
* @returns A promise that resolves to a boolean indicating whether the bridge operation has been completed.
*/
export async function getBridgeTxStatus(
this: SynapseSDK,
destChainId: number,
bridgeModuleName: string,
synapseTxId: string
): Promise<boolean> {
return getRouterSet
.call(this, bridgeModuleName)
.getBridgeTxStatus(destChainId, synapseTxId)
}

/**
* Returns the name of the bridge module that emits the given event.
* This will be either SynapseBridge or SynapseCCTP.
Expand All @@ -143,22 +183,18 @@ export function getBridgeModuleName(
* or the bridge token.
*
* @param originChainId - The ID of the origin chain.
* @param bridgeNoduleName - The name of the bridge module.
* @param bridgeModuleName - The name of the bridge module.
* @returns - The estimated time for a bridge operation, in seconds.
* @throws - Will throw an error if the bridge module is unknown for the given chain.
*/
export function getEstimatedTime(
this: SynapseSDK,
originChainId: number,
bridgeNoduleName: string
bridgeModuleName: string
): number {
if (this.synapseRouterSet.bridgeModuleName === bridgeNoduleName) {
return this.synapseRouterSet.getEstimatedTime(originChainId)
}
if (this.synapseCCTPRouterSet.bridgeModuleName === bridgeNoduleName) {
return this.synapseCCTPRouterSet.getEstimatedTime(originChainId)
}
throw new Error('Unknown bridge module')
return getRouterSet
.call(this, bridgeModuleName)
.getEstimatedTime(originChainId)
}

/**
Expand All @@ -174,3 +210,13 @@ export async function getBridgeGas(
): Promise<BigNumber> {
return this.synapseRouterSet.getSynapseRouter(chainId).chainGasAmount()
}

function getRouterSet(this: SynapseSDK, bridgeModuleName: string): RouterSet {
if (this.synapseRouterSet.bridgeModuleName === bridgeModuleName) {
return this.synapseRouterSet
}
if (this.synapseCCTPRouterSet.bridgeModuleName === bridgeModuleName) {
return this.synapseCCTPRouterSet
}
throw new Error('Unknown bridge module')
}
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
19 changes: 19 additions & 0 deletions packages/sdk-router/src/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ export abstract class Router {
destQuery: Query
): Promise<PopulatedTransaction>

/**
* Returns the Synapse transaction ID for a given transaction hash on the current chain.
* This is used to track the status of a bridge transaction originating from the current chain.
*
* @param txHash - The transaction hash of the bridge transaction.
* @returns A promise that resolves to the Synapse transaction ID.
*/
abstract getSynapseTxId(txHash: string): Promise<string>

/**
* Checks whether a bridge transaction has been completed on the current chain.
* This is used to track the status of a bridge transaction originating from another chain, having
* current chain as the destination chain.
*
* @param synapseTxId - The unique Synapse txId of the bridge transaction.
* @returns A promise that resolves to a boolean indicating whether the bridge transaction has been completed.
*/
abstract getBridgeTxStatus(synapseTxId: string): Promise<boolean>

/**
* Fetches bridge tokens for a destination chain and output token.
*
Expand Down
25 changes: 25 additions & 0 deletions packages/sdk-router/src/router/routerSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,31 @@ export abstract class RouterSet {
*/
abstract getEstimatedTime(chainId: number): number

/**
* Returns the Synapse transaction ID for a given transaction hash on a given chain.
* This is used to track the status of a bridge transaction.
*
* @param originChainId - The ID of the origin chain.
* @param txHash - The transaction hash of the bridge transaction.
* @returns A promise that resolves to the Synapse transaction ID.
*/
abstract getSynapseTxId(
originChainId: number,
txHash: string
): Promise<string>

/**
* Checks whether a bridge transaction has been completed on the destination chain.
*
* @param destChainId - The ID of the destination chain.
* @param synapseTxId - The unique Synapse txId of the bridge transaction.
* @returns A promise that resolves to a boolean indicating whether the bridge transaction has been completed.
*/
abstract getBridgeTxStatus(
destChainId: number,
synapseTxId: string
): Promise<boolean>

/**
* Returns the existing Router instance for the given address on the given chain.
* If the router address is not valid, it will return undefined.
Expand Down
44 changes: 44 additions & 0 deletions packages/sdk-router/src/router/synapseCCTPRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import cctpRouterAbi from '../abi/SynapseCCTPRouter.json'
import { SynapseCCTPRouter as SynapseCCTPRouterContract } from '../typechain/SynapseCCTPRouter'
import { Router } from './router'
import { Query, narrowToCCTPRouterQuery, reduceToQuery } from './query'
import cctpAbi from '../abi/SynapseCCTP.json'
import { getMatchingTxLog } from '../utils/logs'
import { BigintIsh } from '../constants'
import {
BridgeToken,
Expand All @@ -27,6 +29,10 @@ export class SynapseCCTPRouter extends Router {
public readonly address: string

private readonly routerContract: SynapseCCTPRouterContract
private cctpContractCache: Contract | undefined

// All possible events emitted by the SynapseCCTP contract in the origin transaction
private readonly originEvents = ['CircleRequestSent']

constructor(chainId: number, provider: Provider, address: string) {
// Parent constructor throws if chainId or provider are undefined
Expand Down Expand Up @@ -114,4 +120,42 @@ export class SynapseCCTPRouter extends Router {
narrowToCCTPRouterQuery(destQuery)
)
}

/**
* @inheritdoc Router.getSynapseTxId
*/
public async getSynapseTxId(txHash: string): Promise<string> {
const cctpContract = await this.getCctpContract()
const cctpLog = await getMatchingTxLog(
this.provider,
txHash,
cctpContract,
this.originEvents
)
// RequestID always exists in the log as we are using the correct ABI
const parsedLog = cctpContract.interface.parseLog(cctpLog)
return parsedLog.args.requestID
}

/**
* @inheritdoc Router.getBridgeTxStatus
*/
public async getBridgeTxStatus(synapseTxId: string): Promise<boolean> {
const cctpContract = await this.getCctpContract()
return cctpContract.isRequestFulfilled(synapseTxId)
}

private async getCctpContract(): Promise<Contract> {
// Populate the cache if necessary
if (!this.cctpContractCache) {
const cctpAddress = await this.routerContract.synapseCCTP()
this.cctpContractCache = new Contract(
cctpAddress,
new Interface(cctpAbi),
this.provider
)
}
// Return the cached contract
return this.cctpContractCache
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
}
}
20 changes: 20 additions & 0 deletions packages/sdk-router/src/router/synapseCCTPRouterSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ export class SynapseCCTPRouterSet extends RouterSet {
return medianTime
}

/**
* @inheritdoc RouterSet.getSynapseTxId
*/
public async getSynapseTxId(
originChainId: number,
txHash: string
): Promise<string> {
return this.getSynapseCCTPRouter(originChainId).getSynapseTxId(txHash)
}

/**
* @inheritdoc RouterSet.getBridgeTxStatus
*/
public async getBridgeTxStatus(
destChainId: number,
synapseTxId: string
): Promise<boolean> {
return this.getSynapseCCTPRouter(destChainId).getBridgeTxStatus(synapseTxId)
}

/**
* Returns the existing SynapseCCTPRouter instance for the given chain.
*
Expand Down
36 changes: 36 additions & 0 deletions packages/sdk-router/src/router/synapseRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import invariant from 'tiny-invariant'
import { Contract, PopulatedTransaction } from '@ethersproject/contracts'
import { Interface } from '@ethersproject/abi'
import { BigNumber } from '@ethersproject/bignumber'
import { solidityKeccak256 } from 'ethers/lib/utils'

import routerAbi from '../abi/SynapseRouter.json'
import {
Expand All @@ -24,6 +25,7 @@ import {
reduceToFeeConfig,
reduceToPoolToken,
} from './types'
import { getMatchingTxLog } from '../utils/logs'

/**
* Wraps [tokens, lpToken] returned by the SynapseRouter contract into a PoolInfo object.
Expand Down Expand Up @@ -59,6 +61,16 @@ export class SynapseRouter extends Router {
private readonly routerContract: SynapseRouterContract
private bridgeContractCache: Contract | undefined

// All possible events emitted by the SynapseBridge contract in the origin transaction (in alphabetical order)
private readonly originEvents = [
'TokenDeposit',
'TokenDepositAndSwap',
'TokenRedeem',
'TokenRedeemAndRemove',
'TokenRedeemAndSwap',
'TokenRedeemV2',
]

constructor(chainId: number, provider: Provider, address: string) {
// Parent constructor throws if chainId or provider are undefined
super(chainId, provider)
Expand Down Expand Up @@ -137,6 +149,30 @@ export class SynapseRouter extends Router {
)
}

/**
* @inheritdoc Router.getSynapseTxId
*/
public async getSynapseTxId(txHash: string): Promise<string> {
// Check that the transaction hash refers to an origin transaction
const bridgeContract = await this.getBridgeContract()
await getMatchingTxLog(
this.provider,
txHash,
bridgeContract,
this.originEvents
)
// Once we know the transaction is an origin transaction, we can calculate the Synapse txId
return solidityKeccak256(['string'], [txHash])
}

/**
* @inheritdoc Router.getBridgeTxStatus
*/
public async getBridgeTxStatus(synapseTxId: string): Promise<boolean> {
const bridgeContract = await this.getBridgeContract()
return bridgeContract.kappaExists(synapseTxId)
}

// ═════════════════════════════════════════ SYNAPSE ROUTER (V1) ONLY ══════════════════════════════════════════════

private async getBridgeContract(): Promise<Contract> {
Expand Down
20 changes: 20 additions & 0 deletions packages/sdk-router/src/router/synapseRouterSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ export class SynapseRouterSet extends RouterSet {
return medianTime
}

/**
* @inheritdoc RouterSet.getSynapseTxId
*/
public async getSynapseTxId(
originChainId: number,
txHash: string
): Promise<string> {
return this.getSynapseRouter(originChainId).getSynapseTxId(txHash)
}

/**
* @inheritdoc RouterSet.getBridgeTxStatus
*/
public async getBridgeTxStatus(
destChainId: number,
synapseTxId: string
): Promise<boolean> {
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
return this.getSynapseRouter(destChainId).getBridgeTxStatus(synapseTxId)
}

/**
* Returns the existing SynapseRouter instance for the given chain.
*
Expand Down
Loading
Loading