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

Improve UX in redemption details page #594

Merged
merged 5 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions src/hooks/tbtc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export * from "./useSubscribeToRedemptionRequestedEvent"
export * from "./useSubsribeToDepositRevealedEvent"
export * from "./useTBTCDepositDataFromLocalStorage"
export * from "./useTBTCVaultContract"
export * from "./useSubscribeToRedemptionsCompletedEvent"
export * from "./useFindRedemptionInBitcoinTx"
65 changes: 28 additions & 37 deletions src/hooks/tbtc/useFetchRedemptionDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import { BigNumber } from "ethers"
import { useEffect, useState } from "react"
import { useThreshold } from "../../contexts/ThresholdContext"
import {
createAddressFromOutputScript,
prependScriptPubKeyByLength,
isValidType,
fromSatoshiToTokenPrecision,
} from "../../threshold-ts/utils"
import { useGetBlock } from "../../web3/hooks"
import { isEmptyOrZeroAddress } from "../../web3/utils"
import { useFindRedemptionInBitcoinTx } from "./useFindRedemptionInBitcoinTx"

interface RedemptionDetails {
requestedAmount: string // in token precision
Expand Down Expand Up @@ -36,6 +35,7 @@ export const useFetchRedemptionDetails = (
) => {
const threshold = useThreshold()
const getBlock = useGetBlock()
const findRedemptionInBitcoinTx = useFindRedemptionInBitcoinTx()
const [isFetching, setIsFetching] = useState(false)
const [error, setError] = useState("")
const [redemptionData, setRedemptionData] = useState<
Expand Down Expand Up @@ -194,44 +194,34 @@ export const useFetchRedemptionDetails = (
txHash,
blockNumber: redemptionCompletedBlockNumber,
} of redemptionCompletedEvents) {
const { outputs } = await threshold.tbtc.getBitcoinTransaction(
redemptionBitcoinTxHash
const redemptionBitcoinTransfer = await findRedemptionInBitcoinTx(
redemptionBitcoinTxHash,
redemptionCompletedBlockNumber,
redemptionRequestedEvent.redeemerOutputScript
)

for (const { scriptPubKey, value } of outputs) {
if (
prependScriptPubKeyByLength(scriptPubKey.toString()) !==
redemptionRequestedEvent.redeemerOutputScript
)
continue
if (!redemptionBitcoinTransfer) continue

const { timestamp: redemptionCompletedTimestamp } = await getBlock(
redemptionCompletedBlockNumber
)
setRedemptionData({
requestedAmount: fromSatoshiToTokenPrecision(
redemptionRequestedEvent.amount
).toString(),
receivedAmount: value.toString(),
redemptionRequestedTxHash: redemptionRequestedEvent.txHash,
redemptionCompletedTxHash: {
chain: txHash,
bitcoin: redemptionBitcoinTxHash,
},
requestedAt: redemptionRequestedEventTimestamp,
completedAt: redemptionCompletedTimestamp,
treasuryFee: fromSatoshiToTokenPrecision(
redemptionRequestedEvent.treasuryFee
).toString(),
isTimedOut: false,
btcAddress: createAddressFromOutputScript(
scriptPubKey,
threshold.tbtc.bitcoinNetwork
),
})

return
}
setRedemptionData({
requestedAmount: fromSatoshiToTokenPrecision(
redemptionRequestedEvent.amount
).toString(),
receivedAmount: redemptionBitcoinTransfer.receivedAmount,
redemptionRequestedTxHash: redemptionRequestedEvent.txHash,
redemptionCompletedTxHash: {
chain: txHash,
bitcoin: redemptionBitcoinTxHash,
},
requestedAt: redemptionRequestedEventTimestamp,
completedAt: redemptionBitcoinTransfer.redemptionCompletedTimestamp,
treasuryFee: fromSatoshiToTokenPrecision(
redemptionRequestedEvent.treasuryFee
).toString(),
isTimedOut: false,
btcAddress: redemptionBitcoinTransfer.btcAddress,
michalsmiarowski marked this conversation as resolved.
Show resolved Hide resolved
})

return
}
} catch (error) {
console.error("Could not fetch the redemption request details!", error)
Expand All @@ -249,6 +239,7 @@ export const useFetchRedemptionDetails = (
redeemerOutputScript,
threshold,
getBlock,
findRedemptionInBitcoinTx,
])

return { isFetching, data: redemptionData, error }
Expand Down
47 changes: 47 additions & 0 deletions src/hooks/tbtc/useFindRedemptionInBitcoinTx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useCallback } from "react"
import { useThreshold } from "../../contexts/ThresholdContext"
import {
createAddressFromOutputScript,
prependScriptPubKeyByLength,
} from "../../threshold-ts/utils"
import { useGetBlock } from "../../web3/hooks"

export const useFindRedemptionInBitcoinTx = () => {
const threshold = useThreshold()
const getBlock = useGetBlock()

return useCallback(
async (
redemptionBitcoinTxHash: string,
redemptionCompletedBlockNumber: number,
redeemerOutputScript: string
) => {
const { outputs } = await threshold.tbtc.getBitcoinTransaction(
redemptionBitcoinTxHash
)

for (const { scriptPubKey, value } of outputs) {
if (
prependScriptPubKeyByLength(scriptPubKey.toString()) !==
redeemerOutputScript
)
continue

const { timestamp: redemptionCompletedTimestamp } = await getBlock(
redemptionCompletedBlockNumber
)

return {
btcAddress: createAddressFromOutputScript(
scriptPubKey,
threshold.tbtc.bitcoinNetwork
),
receivedAmount: value.toString(),
bitcoinTxHash: redemptionBitcoinTxHash,
redemptionCompletedTimestamp,
}
}
},
[threshold, getBlock]
)
}
33 changes: 33 additions & 0 deletions src/hooks/tbtc/useSubscribeToRedemptionsCompletedEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Hex } from "@keep-network/tbtc-v2.ts"
import { Event } from "ethers"
import { useSubscribeToContractEvent } from "../../web3/hooks"
import { useBridgeContract } from "./useBridgeContract"

type RedemptionsCompletedEventCallback = (
walletPublicKeyHash: string,
redemptionTxHash: string,
event: Event
) => void

export const useSubscribeToRedemptionsCompletedEventBase = (
michalsmiarowski marked this conversation as resolved.
Show resolved Hide resolved
callback: RedemptionsCompletedEventCallback,
filterParams?: any[],
shouldSubscribeIfUserNotConnected: boolean = false
) => {
const tBTCBridgeContract = useBridgeContract()

useSubscribeToContractEvent(
tBTCBridgeContract,
"RedemptionsCompleted",
//@ts-ignore
(walletPublicKeyHash, redemptionTxHash, event) => {
callback(
walletPublicKeyHash,
Hex.from(redemptionTxHash).reverse().toString(),
event
)
},
filterParams,
shouldSubscribeIfUserNotConnected
)
}
75 changes: 62 additions & 13 deletions src/pages/tBTC/Bridge/UnmintDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,39 +54,84 @@ import { featureFlags } from "../../../constants"
import { useFetchRedemptionDetails } from "../../../hooks/tbtc/useFetchRedemptionDetails"
import { BridgeProcessDetailsPageSkeleton } from "./components/BridgeProcessDetailsPageSkeleton"
import { ExternalHref } from "../../../enums"
import {
useFindRedemptionInBitcoinTx,
useSubscribeToRedemptionsCompletedEventBase,
} from "../../../hooks/tbtc"
import { useAppDispatch } from "../../../hooks/store"
import { tbtcSlice } from "../../../store/tbtc"
import { useThreshold } from "../../../contexts/ThresholdContext"

export const UnmintDetails: PageComponent = () => {
const [searchParams] = useSearchParams()
const walletPublicKeyHash = searchParams.get("walletPublicKeyHash")
const redeemerOutputScript = searchParams.get("redeemerOutputScript")
const redeemer = searchParams.get("redeemer")
const { redemptionRequestedTxHash } = useParams()
const dispatch = useAppDispatch()
const threshold = useThreshold()

const { data, isFetching, error } = useFetchRedemptionDetails(
redemptionRequestedTxHash,
walletPublicKeyHash,
redeemerOutputScript,
redeemer
)
const findRedemptionInBitcoinTx = useFindRedemptionInBitcoinTx()
const [redemptionFromBitcoinTx, setRedemptionFromBitcoinTx] = useState<
Awaited<ReturnType<typeof findRedemptionInBitcoinTx>> | undefined
>(undefined)

useSubscribeToRedemptionsCompletedEventBase(
async (eventWalletPublicKeyHash, redemptionTxHash, event) => {
if (eventWalletPublicKeyHash !== walletPublicKeyHash) return

const redemption = await findRedemptionInBitcoinTx(
redemptionTxHash,
event.blockNumber,
redeemerOutputScript!
)
if (!redemption) return

setRedemptionFromBitcoinTx(redemption)

if (redemptionRequestedTxHash && redeemerOutputScript) {
dispatch(
tbtcSlice.actions.redemptionCompleted({
redemptionKey: threshold.tbtc.buildRedemptionKey(
walletPublicKeyHash,
redeemerOutputScript
),
redemptionRequestedTxHash,
})
)
}
},
[],
true
)

const [shouldDisplaySuccessStep, setShouldDisplaySuccessStep] =
useState(false)

const _isFetching = (isFetching || !data) && !error
const wasDataFetched = !isFetching && !!data && !error

const btcTxHash = data?.redemptionCompletedTxHash?.bitcoin
useEffect(() => {
setShouldDisplaySuccessStep(!!btcTxHash)
}, [btcTxHash])
const isProcessCompleted = !!redemptionFromBitcoinTx?.bitcoinTxHash
const shoudlForceIsProcessCompleted =
michalsmiarowski marked this conversation as resolved.
Show resolved Hide resolved
!!data?.redemptionCompletedTxHash?.bitcoin

const isProcessCompleted = !!data?.redemptionCompletedTxHash?.bitcoin
const requestedAmount = data?.requestedAmount ?? "0"
const receivedAmount = data?.receivedAmount ?? "0"
const receivedAmount =
data?.receivedAmount ?? redemptionFromBitcoinTx?.receivedAmount ?? "0"
const btcTxHash =
data?.redemptionCompletedTxHash?.bitcoin ??
redemptionFromBitcoinTx?.bitcoinTxHash

const thresholdNetworkFee = data?.treasuryFee ?? "0"
const btcAddress = data?.btcAddress
const redemptionCompletedAt = data?.completedAt
const btcAddress = data?.btcAddress ?? redemptionFromBitcoinTx?.btcAddress
const redemptionCompletedAt =
data?.completedAt ?? redemptionFromBitcoinTx?.redemptionCompletedTimestamp
const redemptionRequestedAt = data?.requestedAt
const [redemptionTime, setRedemptionTime] = useState<
ReturnType<typeof dateAs>
Expand Down Expand Up @@ -131,7 +176,7 @@ export const UnmintDetails: PageComponent = () => {
},
{
label: "BTC sent",
txHash: data?.redemptionCompletedTxHash?.bitcoin,
txHash: btcTxHash,
chain: "bitcoin",
},
]
Expand Down Expand Up @@ -204,11 +249,15 @@ export const UnmintDetails: PageComponent = () => {
</TimelineContent>
</TimelineItem>
<TimelineItem
status={isProcessCompleted ? "active" : "semi-active"}
status={
isProcessCompleted || shoudlForceIsProcessCompleted
? "active"
: "semi-active"
}
>
<TimelineBreakpoint>
<TimelineDot position="relative">
{isProcessCompleted && (
{(isProcessCompleted || shoudlForceIsProcessCompleted) && (
<Icon
as={IoCheckmarkSharp}
position="absolute"
Expand All @@ -229,7 +278,7 @@ export const UnmintDetails: PageComponent = () => {
</TimelineContent>
</TimelineItem>
</Timeline>
{shouldDisplaySuccessStep || isProcessCompleted ? (
{shouldDisplaySuccessStep || shoudlForceIsProcessCompleted ? (
<SuccessStep
requestedAmount={requestedAmount}
receivedAmount={receivedAmount}
Expand All @@ -240,7 +289,7 @@ export const UnmintDetails: PageComponent = () => {
<BridgeProcessStep
title="Unminting in progress"
chain="ethereum"
txHash={"0x0"}
txHash={redemptionRequestedTxHash}
progressBarColor="brand.500"
isCompleted={isProcessCompleted}
icon={<ProcessCompletedBrandGradientIcon />}
Expand Down
26 changes: 26 additions & 0 deletions src/store/tbtc/tbtcSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,32 @@ export const tbtcSlice = createSlice({
...state.bridgeActivity.data,
]
},
redemptionCompleted: (
state,
action: PayloadAction<{
redemptionKey: string
redemptionRequestedTxHash: string
}>
) => {
const {
payload: { redemptionKey, redemptionRequestedTxHash },
} = action

const { itemToUpdate, index } = findRedemptionActivity(
state.bridgeActivity.data,
redemptionKey,
redemptionRequestedTxHash
)

if (!itemToUpdate) return

state.bridgeActivity.data[index] = {
...itemToUpdate,
activityKey: redemptionKey,
txHash: redemptionRequestedTxHash,
status: BridgeActivityStatus.UNMINTED,
}
},
},
})

Expand Down
Loading