Skip to content

Commit

Permalink
feat(synapse-interface): refund RFQ transaction [SLT-272] (#3197)
Browse files Browse the repository at this point in the history
* Txn transaction refund tracking

* Update store to support tracking

* Query FastBridge contract for `bridgeStatuses` to find refund status

* Track bridge transaction `bridgeQuote.routerAddress` in store

* Fetch FastBridge contract address when only provided router address

* add translations
  • Loading branch information
bigboydiamonds authored Sep 26, 2024
1 parent d8338fe commit f0b13bc
Show file tree
Hide file tree
Showing 21 changed files with 1,430 additions and 14 deletions.
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'
)

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) {
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)
}
}

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)
}
}
Loading

0 comments on commit f0b13bc

Please sign in to comment.