Skip to content

Commit

Permalink
Merge pull request #569 from threshold-network/calculate-threshold-fe…
Browse files Browse the repository at this point in the history
…e-for-redemption

Calculate Threshold Fee for redemption

In this PR I've added the calculations for threshold fee and estimated BTC that
will be received from redemption.

### Changes:
- Rename `getEstimatedFees` function to `getEstimatedDepositFees`,
- Create ` getEstimatedRedemptionFees` function in `TBTC` class,
- Create `useRedemptionEstimatedFees` hook that will retrieve the values from
`getEstimatedRedemptionFees` function,
- Display threshold fee and estimated btc amount in proper places in the dApp
during a redemption flow.
  • Loading branch information
r-czajkowski authored Jul 14, 2023
2 parents 3dd89f7 + 110264a commit 2d46c38
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 59 deletions.
6 changes: 3 additions & 3 deletions src/components/Modal/TbtcMintingConfirmationModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,16 @@ const TbtcMintingConfirmationModal: FC<TbtcMintingConfirmationModalProps> = ({
const amount = BigNumber.from(utxo.value).toString()

useEffect(() => {
const getEstimatedFees = async () => {
const getEstimatedDepositFees = async () => {
const { treasuryFee, optimisticMintFee, amountToMint } =
await threshold.tbtc.getEstimatedFees(amount)
await threshold.tbtc.getEstimatedDepositFees(amount)

updateState("mintingFee", optimisticMintFee)
updateState("thresholdNetworkFee", treasuryFee)
updateState("tBTCMintAmount", amountToMint)
}

getEstimatedFees()
getEstimatedDepositFees()
}, [amount, updateState, threshold])

return (
Expand Down
35 changes: 20 additions & 15 deletions src/components/Modal/tBTC/InitiateUnminting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import {
ModalBody,
ModalFooter,
ModalHeader,
Skeleton,
} from "@threshold-network/components"
import { useWeb3React } from "@web3-react/core"
import { FC } from "react"
import { useNavigate } from "react-router-dom"
import { useRequestRedemption } from "../../../hooks/tbtc"
import {
useRedemptionEstimatedFees,
useRequestRedemption,
} from "../../../hooks/tbtc"
import {
BaseModalProps,
UnspentTransactionOutputPlainObject,
Expand Down Expand Up @@ -47,10 +51,8 @@ const InitiateUnmintingBase: FC<InitiateUnmintingProps> = ({
}) => {
const navigate = useNavigate()
const { account } = useWeb3React()
// TODO: calculate the BTC amount- take into account fees
const btcAmount = unmintAmount
const thresholdNetworkFee = "0"
const btcMinerFee = "0"
const { estimatedBTCAmount, thresholdNetworkFee } =
useRedemptionEstimatedFees(unmintAmount)

const onSuccess: OnSuccessCallback = (receipt) => {
navigate(
Expand Down Expand Up @@ -79,7 +81,18 @@ const InitiateUnmintingBase: FC<InitiateUnmintingProps> = ({
<InfoBox variant="modal" mb="6">
<H5>
Through unminting you will get back{" "}
<InlineTokenBalance tokenAmount={btcAmount} /> BTC
<Skeleton isLoaded={!!estimatedBTCAmount} maxW="105px" as="span">
<InlineTokenBalance
tokenSymbol="BTC"
tokenDecimals={8}
precision={6}
higherPrecision={8}
tokenAmount={estimatedBTCAmount!}
displayTildeBelow={0}
isEstimated
/>{" "}
</Skeleton>
BTC
</H5>
<BodyLg mt="4">
Unminting tBTC requires one transaction on your end.
Expand All @@ -93,20 +106,12 @@ const InitiateUnmintingBase: FC<InitiateUnmintingProps> = ({
precision={6}
higherPrecision={8}
/>
<TransactionDetailsAmountItem
label="Bitcoin Miner Fee"
tokenAmount={btcMinerFee}
tokenSymbol="BTC"
tokenDecimals={8}
precision={6}
higherPrecision={8}
/>
<TransactionDetailsAmountItem
label="Threshold Network Fee"
tokenAmount={thresholdNetworkFee}
tokenSymbol="tBTC"
precision={6}
higherPrecision={8}
tokenAmount={thresholdNetworkFee}
/>
<TransactionDetailsItem
label="BTC address"
Expand Down
17 changes: 13 additions & 4 deletions src/components/TokenBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export interface TokenBalanceProps {
withHigherPrecision?: boolean
precision?: number
higherPrecision?: number
displayTildeBelow?: number
isEstimated?: boolean
}

export const InlineTokenBalance: FC<TokenBalanceProps & BoxProps> = ({
Expand All @@ -41,14 +43,17 @@ export const InlineTokenBalance: FC<TokenBalanceProps & BoxProps> = ({
precision = 2,
higherPrecision = 6,
withHigherPrecision,
displayTildeBelow = 1,
isEstimated = false,
...restProps
}) => {
const _tokenAmount = useMemo(() => {
return formatTokenAmount(
tokenAmount || 0,
tokenFormat,
tokenDecimals,
precision
precision,
isEstimated ? 0 : displayTildeBelow
)
}, [tokenAmount, tokenFormat, tokenDecimals, precision])

Expand All @@ -57,13 +62,17 @@ export const InlineTokenBalance: FC<TokenBalanceProps & BoxProps> = ({
tokenAmount || 0,
tokenFormat,
tokenDecimals,
higherPrecision
higherPrecision,
isEstimated ? 0 : 1
)
}, [tokenAmount, tokenFormat, tokenDecimals, higherPrecision])

return (
<Tooltip label={_tokenAmountWithHigherPrecision} placement="top">
<Box as="span" {...restProps}>{`${_tokenAmount}${
<Tooltip
label={`${isEstimated ? "~" : ""}${_tokenAmountWithHigherPrecision}`}
placement="top"
>
<Box as="span" {...restProps}>{`${isEstimated ? "~" : ""}${_tokenAmount}${
withSymbol ? ` ${tokenSymbol}` : ""
}`}</Box>
</Tooltip>
Expand Down
13 changes: 7 additions & 6 deletions src/hooks/tbtc/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export * from "./useRevealDepositTransaction"
export * from "./useTBTCDepositDataFromLocalStorage"
export * from "./useTBTCVaultContract"
export * from "./useSubscribeToOptimisticMintingFinalizedEvent"
export * from "./useSubsribeToDepositRevealedEvent"
export * from "./useSubscribeToOptimisticMintingRequestedEvent"
export * from "./useFetchDepositDetails"
export * from "./useFetchRecentDeposits"
export * from "./useFetchTBTCMetrics"
export * from "./useRedemptionEstimatedFees"
export * from "./useRequestRedemption"
export * from "./useRevealDepositTransaction"
export * from "./useSubscribeToOptimisticMintingFinalizedEvent"
export * from "./useSubscribeToOptimisticMintingRequestedEvent"
export * from "./useSubscribeToRedemptionRequestedEvent"
export * from "./useSubsribeToDepositRevealedEvent"
export * from "./useTBTCDepositDataFromLocalStorage"
export * from "./useTBTCVaultContract"
2 changes: 1 addition & 1 deletion src/hooks/tbtc/useFetchDepositDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const useFetchDepositDetails = (depositKey: string | undefined) => {
threshold.tbtc.minimumNumberOfConfirmationsNeeded(deposit.amount)

const { treasuryFee, optimisticMintFee, amountToMint } =
await threshold.tbtc.getEstimatedFees(deposit.amount)
await threshold.tbtc.getEstimatedDepositFees(deposit.amount)

setDepositData({
btcTxHash: btcTxHash.toString(),
Expand Down
39 changes: 27 additions & 12 deletions src/hooks/tbtc/useFetchRedemptionDetails.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
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"

interface RedemptionDetails {
amount: string
requestedAmount: string // in token precision
receivedAmount?: string // in satoshi
redemptionRequestedTxHash: string
redemptionCompletedTxHash?: {
chain: string
bitcoin: string
}
requestedAt: number
completedAt?: number
treasuryFee: string // in token precision
isTimedOut: boolean
redemptionTimedOutTxHash?: string
btcAddress?: string
Expand Down Expand Up @@ -74,7 +78,7 @@ export const useFetchRedemptionDetails = (
// the request actually happened. We need `redeemer` address as well to
// reduce the number of records - any user can request redemption for
// the same wallet.
const redemptionRequest = (
const redemptionRequestedEvent = (
await threshold.tbtc.getRedemptionRequestedEvents({
walletPublicKeyHash,
redeemer,
Expand Down Expand Up @@ -107,12 +111,12 @@ export const useFetchRedemptionDetails = (
) === redemptionKey
)

if (!redemptionRequest) {
if (!redemptionRequestedEvent) {
throw new Error("Redemption not found...")
}

const { timestamp: redemptionRequestedEventTimestamp } = await getBlock(
redemptionRequest.blockNumber
redemptionRequestedEvent.blockNumber
)

// We need to check if the redemption has `pending` or `timedOut` status.
Expand All @@ -131,7 +135,7 @@ export const useFetchRedemptionDetails = (
? (
await threshold.tbtc.getRedemptionTimedOutEvents({
walletPublicKeyHash,
fromBlock: redemptionRequest.blockNumber,
fromBlock: redemptionRequestedEvent.blockNumber,
})
).find(
(event) => event.redeemerOutputScript === redeemerOutputScript
Expand All @@ -156,11 +160,16 @@ export const useFetchRedemptionDetails = (
requestedAt === redemptionRequestedEventTimestamp
) {
setRedemptionData({
amount: redemptionRequest.amount,
redemptionRequestedTxHash: redemptionRequest.txHash,
requestedAmount: fromSatoshiToTokenPrecision(
redemptionRequestedEvent.amount
).toString(),
redemptionRequestedTxHash: redemptionRequestedEvent.txHash,
redemptionCompletedTxHash: undefined,
requestedAt: requestedAt,
redemptionTimedOutTxHash: timedOutTxHash,
treasuryFee: fromSatoshiToTokenPrecision(
redemptionRequestedEvent.treasuryFee
).toString(),
isTimedOut,
})
return
Expand All @@ -173,7 +182,7 @@ export const useFetchRedemptionDetails = (
const redemptionCompletedEvents =
await threshold.tbtc.getRedemptionsCompletedEvents({
walletPublicKeyHash,
fromBlock: redemptionRequest.blockNumber,
fromBlock: redemptionRequestedEvent.blockNumber,
})

// For each event we should take `redemptionTxHash` param from
Expand All @@ -189,25 +198,31 @@ export const useFetchRedemptionDetails = (
redemptionBitcoinTxHash
)

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

const { timestamp: redemptionCompletedTimestamp } = await getBlock(
redemptionCompletedBlockNumber
)
setRedemptionData({
amount: redemptionRequest.amount,
redemptionRequestedTxHash: redemptionRequest.txHash,
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,
Expand Down
26 changes: 26 additions & 0 deletions src/hooks/tbtc/useRedemptionEstimatedFees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useEffect, useState } from "react"
import { useThreshold } from "../../contexts/ThresholdContext"

export const useRedemptionEstimatedFees = (unmintedAmount: string) => {
const threshold = useThreshold()
const [estimatedBTCAmount, setEstimatedBTCAmount] = useState<
string | undefined
>(undefined)
const [thresholdNetworkFee, setThresholdNetworkFee] = useState<
string | undefined
>(undefined)

useEffect(() => {
const getEstimatedRedemptionFees = async () => {
const { treasuryFee, estimatedAmountToBeReceived } =
await threshold.tbtc.getEstimatedRedemptionFees(unmintedAmount)

setThresholdNetworkFee(treasuryFee)
setEstimatedBTCAmount(estimatedAmountToBeReceived)
}

getEstimatedRedemptionFees()
}, [unmintedAmount, threshold])

return { estimatedBTCAmount, thresholdNetworkFee }
}
2 changes: 1 addition & 1 deletion src/hooks/tbtc/useSubsribeToDepositRevealedEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const useSubscribeToDepositRevealedEvent = () => {
) => {
if (!account || !isSameETHAddress(depositor, account)) return

const { amountToMint } = await threshold.tbtc.getEstimatedFees(
const { amountToMint } = await threshold.tbtc.getEstimatedDepositFees(
amount.toString()
)

Expand Down
Loading

0 comments on commit 2d46c38

Please sign in to comment.