From 85125680df3bfc9b031d00ef2d07656d48373bb2 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Fri, 9 Jun 2023 08:49:47 +0200 Subject: [PATCH] Request redemption Add a new method to the `ITBTC` interface that requests redemption. The implementation of this interface uses the `requestRedemption` fn from `tbtc-v2.ts` lib. As a temporary soultion here we hardcode the redemption request data in component- once we merge the https://github.com/threshold-network/token-dashboard/pull/532 we will pass these params via props to the component. --- .../Modal/tBTC/InitiateUnminting.tsx | 43 +++++++++++-- src/hooks/tbtc/index.ts | 1 + src/hooks/tbtc/useRequestRedemption.ts | 19 ++++++ src/threshold-ts/tbtc/index.ts | 64 +++++++++++++++++-- 4 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 src/hooks/tbtc/useRequestRedemption.ts diff --git a/src/components/Modal/tBTC/InitiateUnminting.tsx b/src/components/Modal/tBTC/InitiateUnminting.tsx index 69d13376a..ff4d1da75 100644 --- a/src/components/Modal/tBTC/InitiateUnminting.tsx +++ b/src/components/Modal/tBTC/InitiateUnminting.tsx @@ -9,9 +9,12 @@ import { ModalFooter, ModalHeader, } 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 { BaseModalProps } from "../../../types" +import { threshold } from "../../../utils/getThresholdLib" import shortenAddress from "../../../utils/shortenAddress" import InfoBox from "../../InfoBox" import { BridgeContractLink } from "../../tBTC" @@ -21,6 +24,13 @@ import { } from "../../TransacionDetails" import ModalCloseButton from "../ModalCloseButton" import withBaseModal from "../withBaseModal" +import { getContractPastEvents } from "../../../web3/utils/index" +import { + encodeToBitcoinAddress, + UnspentTransactionOutput, +} from "@keep-network/tbtc-v2.ts/dist/src/bitcoin" +import { Hex } from "@keep-network/tbtc-v2.ts" +import { BigNumber } from "ethers" type InitiateUnmintingProps = { unmintAmount: string @@ -33,18 +43,43 @@ const InitiateUnmintingBase: FC = ({ btcAddress, }) => { const navigate = useNavigate() + const { account } = useWeb3React() // TODO: calculate the BTC amount- take into account fees const btcAmount = "1.25" const thresholdNetworkFee = "0" const btcMinerFee = "0" - // TODO: implement submit function - const initiateUnminting = () => { - // TODO: It's a temporary solution to be able to go through the whole flow. - navigate("/tBTC/unmint/redemption/123456789") + const onSuccess = () => { + // TODO: build redemption key here and redirect to the redemption details page. + const redemptionKey = "test" + navigate(`/tBTC/unmint/redemption/${redemptionKey}`) closeModal() } + const { sendTransaction } = useRequestRedemption(onSuccess) + + const initiateUnminting = async () => { + // TODO: Temporary solution- we will pass this data via props once we merge + // https://github.com/threshold-network/token-dashboard/pull/532 + const walletPublicKey = + "028ed84936be6a9f594a2dcc636d4bebf132713da3ce4dac5c61afbf8bbb47d6f7" + const utxo: UnspentTransactionOutput = { + transactionHash: Hex.from( + "0x5b6d040eb06b3de1a819890d55d251112e55c31db4a3f5eb7cfacf519fad7adb" + ), + outputIndex: 0, + value: BigNumber.from("791613461"), + } + + await sendTransaction( + account!, + walletPublicKey, + utxo, + btcAddress, + unmintAmount + ) + } + return ( <> Initiate unminting tBTC diff --git a/src/hooks/tbtc/index.ts b/src/hooks/tbtc/index.ts index 688c3cf7d..5f25dd9f9 100644 --- a/src/hooks/tbtc/index.ts +++ b/src/hooks/tbtc/index.ts @@ -7,3 +7,4 @@ export * from "./useSubscribeToOptimisticMintingRequestedEvent" export * from "./useFetchDepositDetails" export * from "./useFetchRecentDeposits" export * from "./useFetchTBTCMetrics" +export * from "./useRequestRedemption" diff --git a/src/hooks/tbtc/useRequestRedemption.ts b/src/hooks/tbtc/useRequestRedemption.ts new file mode 100644 index 000000000..1212cb09e --- /dev/null +++ b/src/hooks/tbtc/useRequestRedemption.ts @@ -0,0 +1,19 @@ +import { useThreshold } from "../../contexts/ThresholdContext" +import { + OnErrorCallback, + OnSuccessCallback, + useSendTransactionFromFn, +} from "../../web3/hooks" + +export const useRequestRedemption = ( + onSuccess?: OnSuccessCallback, + onError?: OnErrorCallback +) => { + const threshold = useThreshold() + + return useSendTransactionFromFn( + threshold.tbtc.requestRedemption, + onSuccess, + onError + ) +} diff --git a/src/threshold-ts/tbtc/index.ts b/src/threshold-ts/tbtc/index.ts index 02e11c5ec..01b99d335 100644 --- a/src/threshold-ts/tbtc/index.ts +++ b/src/threshold-ts/tbtc/index.ts @@ -28,6 +28,7 @@ import { import { ElectrumClient, EthereumBridge, + EthereumTBTCToken, } from "@keep-network/tbtc-v2.ts/dist/src" import { BitcoinConfig, BitcoinNetwork, EthereumConfig } from "../types" import TBTCVault from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" @@ -37,6 +38,8 @@ import { BigNumber, BigNumberish, Contract } from "ethers" import { ContractCall, IMulticall } from "../multicall" import { Interface } from "ethers/lib/utils" import { BlockTag } from "@ethersproject/abstract-provider" +import { requestRedemption } from "@keep-network/tbtc-v2.ts/dist/src/redemption" +import { TBTCToken as ChainTBTCToken } from "@keep-network/tbtc-v2.ts/dist/src/chain" export enum BridgeActivityStatus { PENDING = "PENDING", @@ -198,6 +201,26 @@ export interface ITBTC { ): string findAllRevealedDeposits(depositor: string): Promise + + /** + * Requests a redemption from the on-chain Bridge contract. + * @param redeemer On-chain identifier of the redeemer. + * @param walletPublicKey The Bitcoin public key of the wallet. Must be in + * the compressed form (33 bytes long with 02 or 03 prefix). + * @param mainUtxo The main UTXO of the wallet. Must match the main UTXO + * held by the on-chain Bridge contract. + * @param redeemerOutputScript - The output script that the redeemed funds + * will be locked to. Must be un-prefixed and not prepended with length. + * @param amount The amount to be redeemed in satoshis. + * @returns Transaction hash of the request redemption transaction. + */ + requestRedemption( + redeemer: string, + walletPublicKey: string, + mainUtxo: UnspentTransactionOutput, + redeemerOutputScript: string, + amount: BigNumberish + ): Promise } export class TBTC implements ITBTC { @@ -206,7 +229,8 @@ export class TBTC implements ITBTC { private _bitcoinClient: Client private _multicall: IMulticall private _bridgeContract: Contract - private _token: Contract + private _token: ChainTBTCToken + private _tokenContract: Contract /** * Deposit refund locktime duration in seconds. * This is 9 month in seconds assuming 1 month = 30 days @@ -252,7 +276,14 @@ export class TBTC implements ITBTC { ) this._multicall = multicall this._bitcoinConfig = bitcoinConfig - this._token = getContract( + this._token = new EthereumTBTCToken({ + address: TBTCToken.address, + signerOrProvider: getProviderOrSigner( + ethereumConfig.providerOrSigner as any, + ethereumConfig.account + ), + }) + this._tokenContract = getContract( TBTCToken.address, TBTCToken.abi, ethereumConfig.providerOrSigner, @@ -273,7 +304,7 @@ export class TBTC implements ITBTC { } get tokenContract() { - return this._token + return this._tokenContract } suggestDepositWallet = async (): Promise => { @@ -585,8 +616,10 @@ export class TBTC implements ITBTC { // There is only one transfer to depositor account. const transferEvent = receipt.logs - .filter((log) => isSameETHAddress(log.address, this._token.address)) - .map((log) => this._token.interface.parseLog(log)) + .filter((log) => + isSameETHAddress(log.address, this._tokenContract.address) + ) + .map((log) => this._tokenContract.interface.parseLog(log)) .filter((log) => log.name === "Transfer") .find((log) => isSameETHAddress(log.args.to, depositor)) @@ -629,4 +662,25 @@ export class TBTC implements ITBTC { depositOutputIndex ) } + + requestRedemption = async ( + redeemer: string, + walletPublicKey: string, + mainUtxo: UnspentTransactionOutput, + redeemerOutputScript: string, + amount: BigNumberish + ): Promise => { + const tx = await requestRedemption( + getChainIdentifier(redeemer), + walletPublicKey, + mainUtxo, + redeemerOutputScript, + BigNumber.from(amount), + getChainIdentifier(this._tbtcVault.address), + this._bridge, + this._token + ) + + return tx.toPrefixedString() + } }