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

feat(synapse-interface): refund RFQ transaction [SLT-272] #3197

Merged
merged 15 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { TransactionSupport } from './components/TransactionSupport'
import { RightArrow } from '@/components/icons/RightArrow'
import { Address } from 'viem'
import { useIsTxReverted } from './helpers/useIsTxReverted'
import { useTxRefundStatus } from './helpers/useTxRefundStatus'

interface _TransactionProps {
connectedAddress: string
Expand All @@ -30,11 +31,12 @@ interface _TransactionProps {
destinationToken: Token
originTxHash: string
bridgeModuleName: string
routerAddress: string
estimatedTime: number // in seconds
timestamp: number
currentTime: number
kappa?: string
status: 'pending' | 'completed' | 'reverted'
status: 'pending' | 'completed' | 'reverted' | 'refunded'
disabled: boolean
}

Expand All @@ -49,6 +51,7 @@ export const _Transaction = ({
destinationToken,
originTxHash,
bridgeModuleName,
routerAddress,
estimatedTime,
timestamp,
currentTime,
Expand Down Expand Up @@ -80,6 +83,7 @@ export const _Transaction = ({
isEstimatedTimeReached,
isCheckTxStatus,
isCheckTxForRevert,
isCheckTxForRefund,
} = calculateEstimatedTimeStatus(currentTime, timestamp, estimatedTime)

const [isTxCompleted, _kappa] = useBridgeTxStatus({
Expand All @@ -98,18 +102,29 @@ export const _Transaction = ({
isCheckTxForRevert && status === 'pending'
)

const isTxRefunded = useTxRefundStatus(
kappa,
routerAddress as Address,
originChain,
isCheckTxForRefund &&
status === 'pending' &&
bridgeModuleName === 'SynapseRFQ'
)

Comment on lines +105 to +113
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate Parameters in 'useTxRefundStatus' Hook

Ensure that all parameters passed to useTxRefundStatus are correct and handle potential edge cases:

  • Confirm that kappa and routerAddress are defined before usage.
  • Ensure routerAddress is correctly cast to Address and is a valid Ethereum address.
  • Verify the logical conditions in the check parameter accurately reflect when to check for a refund.

Consider adding null or undefined checks for kappa and routerAddress to prevent runtime errors. For example:

if (kappa && routerAddress) {
  const isTxRefunded = useTxRefundStatus(
    kappa,
    routerAddress as Address,
    originChain,
    isCheckTxForRefund &&
      status === 'pending' &&
      bridgeModuleName === 'SynapseRFQ'
  )
}

useBridgeTxUpdater(
connectedAddress,
destinationChain,
_kappa,
originTxHash,
isTxCompleted,
isTxReverted
isTxReverted,
isTxRefunded
)

// Show transaction support if the transaction is delayed by more than 5 minutes and not finalized or reverted
const showTransactionSupport =
status === 'reverted' ||
status === 'refunded' ||
(status === 'pending' && delayedTimeInMin && delayedTimeInMin <= -5)

return (
Expand Down Expand Up @@ -184,7 +199,7 @@ export const _Transaction = ({
{status !== 'pending' && (
<MenuItem
text={
isTxReverted
isTxReverted || isTxRefunded
? t('Clear notification')
: t('Clear transaction')
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const _Transactions = ({
destinationToken={destinationToken}
originTxHash={tx.originTxHash}
bridgeModuleName={tx.bridgeModuleName}
routerAddress={tx.routerAddress}
estimatedTime={tx.estimatedTime}
kappa={tx?.kappa}
timestamp={tx.timestamp}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getUnixTimeMinutesBeforeNow } from '@/utils/time'
* @param {string} id - The unique ID of a rendered instance.
* @param {number} startTime - The start time of an event as a timestamp.
* @param {number} duration - The estimated total duration of an event.
* @param {'pending' | 'completed' | 'reverted'} status - The current status of an event.
* @param {'pending' | 'completed' | 'reverted' | 'refunded'} status - The current status of an event.
*/
export const AnimatedProgressBar = memo(
({
Expand All @@ -17,15 +17,15 @@ export const AnimatedProgressBar = memo(
id: string
startTime: number
estDuration: number
status: 'pending' | 'completed' | 'reverted'
status: 'pending' | 'completed' | 'reverted' | 'refunded'
}) => {
const currentTime = getUnixTimeMinutesBeforeNow(0)
const elapsedTime = currentTime - startTime
const remainingTime = estDuration - elapsedTime
const percentElapsed = (elapsedTime / estDuration) * 100

const isComplete = status === 'completed'
const isError = status === 'reverted'
const isError = status === 'reverted' || status === 'refunded'

let duration = isComplete ? 0.5 : remainingTime

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const TimeRemaining = ({
isDelayed: boolean
remainingTime: number
delayedTime: number | null
status: 'pending' | 'completed' | 'reverted'
status: 'pending' | 'completed' | 'reverted' | 'refunded'
}) => {
const t = useTranslations('Time')

Expand All @@ -36,6 +36,14 @@ export const TimeRemaining = ({
)
}

if (status === 'refunded') {
return (
<span className="flex items-center space-x-1 text-sm">
<ExclamationIcon className="w-4 h-4" /> <span>{t('Refunded')}</span>
</span>
)
}

if (isDelayed) {
const delayedTimeInMin = Math.floor(delayedTime / 60)
const absoluteDelayedTime = Math.abs(delayedTimeInMin)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { useTranslations } from 'next-intl'
import { TRANSACTION_SUPPORT_URL, DISCORD_URL } from '@/constants/urls'

export const TransactionSupport = ({ status }: { status: string }) => {
const isReverted = status === 'reverted'
export const TransactionSupport = ({
status,
}: {
status: 'pending' | 'completed' | 'reverted' | 'refunded'
}) => {
const t = useTranslations('Time')

return (
<div
id="transaction-support"
className="flex items-center justify-between w-full py-1 pl-3 pr-1 text-sm"
>
{isReverted ? (
{status === 'reverted' && (
<div>{t('Transaction reverted, funds returned')}</div>
) : (
<div>{t("What's taking so long?")}</div>
)}

{status === 'refunded' && (
<div>{t('Transaction refunded, funds returned')}</div>
)}

{status === 'pending' && <div>{t("What's taking so long?")}</div>}

<div className="flex items-center">
<a
href={TRANSACTION_SUPPORT_URL}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ export const calculateEstimatedTimeStatus = (
const targetTime = initialTime + estimatedTime

const oneMinuteInSeconds = 60
const fourHoursInSeconds = 14400

const isEstimatedTimeReached = remainingTime < 0
const isCheckTxStatus = remainingTime < oneMinuteInSeconds
const isCheckTxForRevert = elapsedTime > 30
const isCheckTxForRefund = elapsedTime > fourHoursInSeconds

const delayedTime = isEstimatedTimeReached ? remainingTime : null
const delayedTimeInMin = remainingTime ? Math.floor(remainingTime / 60) : null
Expand All @@ -33,5 +35,6 @@ export const calculateEstimatedTimeStatus = (
isEstimatedTimeReached,
isCheckTxStatus,
isCheckTxForRevert,
isCheckTxForRefund,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
updateTransactionKappa,
completeTransaction,
revertTransaction,
refundTransaction,
_TransactionDetails,
} from '@/slices/_transactions/reducer'
import { fetchAndStoreSingleNetworkPortfolioBalances } from '@/slices/portfolio/hooks'
Expand All @@ -27,7 +28,8 @@ export const useBridgeTxUpdater = (
kappa: string,
originTxHash: string,
isTxComplete: boolean,
isTxReverted: boolean
isTxReverted: boolean,
isTxRefunded: boolean
) => {
const dispatch = useAppDispatch()
const { transactions } = use_TransactionsState()
Expand All @@ -49,6 +51,13 @@ export const useBridgeTxUpdater = (
}
}, [isTxReverted])

/** Update tx for refunds in store */
useEffect(() => {
if (isTxRefunded && storedTx.status !== 'refunded') {
dispatch(refundTransaction({ originTxHash }))
}
}, [isTxRefunded])

/** Update tx for completion in store */
useEffect(() => {
if (isTxComplete && originTxHash && kappa) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { type Address } from 'viem'
import { isNumber, isString } from 'lodash'
import { useEffect, useState } from 'react'
import { readContract } from '@wagmi/core'

import { type Chain } from '@/utils/types'
import { useIntervalTimer } from '@/utils/hooks/useIntervalTimer'
import { wagmiConfig } from '@/wagmiConfig'
import fastBridgeAbi from '@/constants/abis/fastBridge.json'
import fastBridgeRouterAbi from '@/constants/abis/fastBridgeRouter.json'

enum BridgeStatus {
NULL,
REQUESTED,
RELAYER_PROVED,
RELAYER_CLAIMED,
REFUNDED,
}

export const useTxRefundStatus = (
txId: string | undefined,
routerAddress: Address,
chain: Chain,
checkForRefund: boolean
) => {
const [isRefunded, setIsRefunded] = useState<boolean>(false)
const currentTime = useIntervalTimer(600000)

const getTxRefundStatus = async () => {
try {
const bridgeContract = await getRFQBridgeContract(
routerAddress,
chain?.id
)

const status = await checkRFQTxBridgeStatus(
txId,
bridgeContract as Address,
chain?.id
)

if (status === BridgeStatus.REFUNDED) {
setIsRefunded(true)
}
} catch (error) {
Comment on lines +29 to +45
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure required parameters are defined before proceeding

In the getTxRefundStatus function, txId, chain?.id, and bridgeContract might be undefined, which could lead to runtime errors when calling checkRFQTxBridgeStatus. Add checks to ensure these values are defined before using them.

Apply this diff to add checks:

       try {
+        if (!txId || !chain?.id) {
+          throw new Error('txId or chainId is undefined')
+        }
         const bridgeContract = await getRFQBridgeContract(
           routerAddress,
           chain?.id
         )
+        if (!bridgeContract) {
+          throw new Error('Bridge contract address is undefined')
+        }
 
         const status = await checkRFQTxBridgeStatus(
           txId,
           bridgeContract as Address,
           chain?.id
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getTxRefundStatus = async () => {
try {
const bridgeContract = await getRFQBridgeContract(
routerAddress,
chain?.id
)
const status = await checkRFQTxBridgeStatus(
txId,
bridgeContract as Address,
chain?.id
)
if (status === BridgeStatus.REFUNDED) {
setIsRefunded(true)
}
} catch (error) {
const getTxRefundStatus = async () => {
try {
if (!txId || !chain?.id) {
throw new Error('txId or chainId is undefined')
}
const bridgeContract = await getRFQBridgeContract(
routerAddress,
chain?.id
)
if (!bridgeContract) {
throw new Error('Bridge contract address is undefined')
}
const status = await checkRFQTxBridgeStatus(
txId,
bridgeContract as Address,
chain?.id
)
if (status === BridgeStatus.REFUNDED) {
setIsRefunded(true)
}
} catch (error) {

console.error('Failed to get transaction refund status:', error)
}
}

useEffect(() => {
if (checkForRefund) {
getTxRefundStatus()
}
}, [checkForRefund, txId, chain, currentTime])

return isRefunded
}

const getRFQBridgeContract = async (
routerAddress: Address,
chainId: number
): Promise<string | undefined> => {
try {
const fastBridgeAddress = await readContract(wagmiConfig, {
abi: fastBridgeRouterAbi,
address: routerAddress,
functionName: 'fastBridge',
chainId,
})

if (!isString(fastBridgeAddress)) {
throw new Error('Invalid address')
}

return fastBridgeAddress
} catch (error) {
throw new Error(error)
}
Comment on lines +77 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix error rethrowing to properly capture error messages

In the getRFQBridgeContract function, rethrowing an error using throw new Error(error) can cause issues if error is already an Error object. The Error constructor expects a string message, not an object. It's better to rethrow the original error to preserve the error information.

Apply this diff to fix the error rethrowing:

-        throw new Error(error)
+        throw error
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
throw new Error(error)
}
throw error
}

}

const checkRFQTxBridgeStatus = async (
txId: string,
bridgeContract: Address,
chainId: number
): Promise<number | undefined> => {
try {
const status = await readContract(wagmiConfig, {
abi: fastBridgeAbi,
address: bridgeContract,
functionName: 'bridgeStatuses',
args: [txId],
chainId,
})

if (!isNumber(status)) {
throw new Error('Invalid status code')
}

return status
} catch (error) {
throw new Error(error)
}
Comment on lines +101 to +102
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix error rethrowing to properly capture error messages

Similarly, in the checkRFQTxBridgeStatus function, rethrowing an error with throw new Error(error) may lose important error details if error is an Error object. Rethrow the original error instead.

Apply this diff to fix the error rethrowing:

-        throw new Error(error)
+        throw error
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
throw new Error(error)
}
throw error
}

}
Loading
Loading