diff --git a/manifest-ledger-live-app.json b/manifest-ledger-live-app.json new file mode 100644 index 000000000..4a0343fc0 --- /dev/null +++ b/manifest-ledger-live-app.json @@ -0,0 +1,40 @@ +{ + "id": "threshold", + "name": "Threshold", + "url": "http://localhost:3000?embed=true", + "homepageUrl": "http://localhost:3000", + "icon": "https://dashboard.threshold.network/favicon-T.png", + "platform": "all", + "apiVersion": "2.0.0", + "manifestVersion": "1", + "branch": "stable", + "categories": ["tbtc", "bitocin", "bridge"], + "currencies": ["bitcoin", "ethereum", "bitcoin_testnet", "ethereum_goerli"], + "content": { + "shortDescription": { + "en": "Threshold's tBTC is a truly decentralized bridge between Bitcoin and Ethereum." + }, + "description": { + "en": "tBTC is Threshold’s decentralized bridge to bring BTC to the Ethereum network; the only permissionless solution on the market today. Bridge your Bitcoin to Ethereum in a secure and trustless way to participate in DeFi." + } + }, + "permissions": [ + "account.list", + "account.receive", + "account.request", + "currency.list", + "device.close", + "device.exchange", + "device.transport", + "message.sign", + "transaction.sign", + "transaction.signAndBroadcast", + "storage.set", + "storage.get", + "bitcoin.getXPub", + "wallet.capabilities", + "wallet.userId", + "wallet.info" + ], + "domains": ["https://*", "wss://*"] +} diff --git a/package.json b/package.json index e08cb0572..dfe7ee063 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "@keep-network/tbtc": "development", "@keep-network/tbtc-v2.ts": "^2.3.0", "@ledgerhq/connect-kit-loader": "^1.1.2", + "@ledgerhq/wallet-api-client": "^1.2.0", + "@ledgerhq/wallet-api-client-react": "^1.1.1", "@reduxjs/toolkit": "^1.6.1", "@rehooks/local-storage": "^2.4.4", "@sentry/react": "^7.33.0", @@ -44,6 +46,7 @@ "@web3-react/types": "^6.0.7", "@web3-react/walletlink-connector": "^6.2.13", "axios": "^0.24.0", + "bignumber.js": "^9.1.2", "bitcoin-address-validation": "^2.2.1", "crypto-js": "^4.1.1", "ethereum-blockies-base64": "^1.0.2", diff --git a/src/App.tsx b/src/App.tsx index 06cd66939..e167eb9d6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,6 +40,7 @@ import { useCheckBonusEligibility } from "./hooks/useCheckBonusEligibility" import { useFetchStakingRewards } from "./hooks/useFetchStakingRewards" import { isSameETHAddress } from "./web3/utils" import { ThresholdProvider } from "./contexts/ThresholdContext" +import { LedgerLiveAppProvider } from "./contexts/LedgerLiveAppContext" import { useSubscribeToAuthorizationIncreasedEvent, useSubscribeToAuthorizationDecreaseApprovedEvent, @@ -58,6 +59,9 @@ import { useSubscribeToRedemptionRequestedEvent, } from "./hooks/tbtc" import { useSentry } from "./hooks/sentry" +import { useIsEmbed } from "./hooks/useIsEmbed" +import TBTC from "./pages/tBTC" +import { useDetectIfEmbed } from "./hooks/useDetectIfEmbed" const Web3EventHandlerComponent = () => { useSubscribeToVendingMachineContractEvents() @@ -170,6 +174,7 @@ const AppBody = () => { dispatch(fetchETHPriceUSD()) }, [dispatch]) + useDetectIfEmbed() usePosthog() useCheckBonusEligibility() useFetchStakingRewards() @@ -180,12 +185,13 @@ const AppBody = () => { } const Layout = () => { + const { isEmbed } = useIsEmbed() return ( - + {!isEmbed && } @@ -199,12 +205,15 @@ const Layout = () => { } const Routing = () => { + const { isEmbed } = useIsEmbed() + const finalPages = isEmbed ? [TBTC] : pages + const to = isEmbed ? "tBTC" : "overview" return ( }> - } /> - {pages.map(renderPageComponent)} - } /> + } /> + {finalPages.map(renderPageComponent)} + } /> ) @@ -248,17 +257,19 @@ const App: FC = () => { return ( - - - - - - - - - - - + + + + + + + + + + + + + ) diff --git a/src/components/Modal/tBTC/InitiateUnminting.tsx b/src/components/Modal/tBTC/InitiateUnminting.tsx index 25dd3fa18..cd984c8a7 100644 --- a/src/components/Modal/tBTC/InitiateUnminting.tsx +++ b/src/components/Modal/tBTC/InitiateUnminting.tsx @@ -10,7 +10,7 @@ import { ModalHeader, Skeleton, } from "@threshold-network/components" -import { useWeb3React } from "@web3-react/core" +import { useIsActive } from "../../../hooks/useIsActive" import { FC } from "react" import { useNavigate } from "react-router-dom" import { useThreshold } from "../../../contexts/ThresholdContext" @@ -43,7 +43,7 @@ const InitiateUnmintingBase: FC = ({ btcAddress, }) => { const navigate = useNavigate() - const { account } = useWeb3React() + const { account } = useIsActive() const { estimatedBTCAmount, thresholdNetworkFee } = useRedemptionEstimatedFees(unmintAmount) const threshold = useThreshold() diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index 009b407e6..8760096e2 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -1,18 +1,34 @@ -import { FC } from "react" -import { useWeb3React } from "@web3-react/core" -import { useModal } from "../../hooks/useModal" +import { FC, useEffect } from "react" import NavbarComponent from "./NavbarComponent" -import { ModalType } from "../../enums" +import { useAppDispatch } from "../../hooks/store" +import { walletConnected } from "../../store/account" +import { useRequestEthereumAccount } from "../../hooks/ledger-live-app" +import { useIsActive } from "../../hooks/useIsActive" +import { useConnectWallet } from "../../hooks/useConnectWallet" const Navbar: FC = () => { - const { openModal } = useModal() - const { account, active, chainId, deactivate } = useWeb3React() - const openWalletModal = () => openModal(ModalType.SelectWallet) + const { isActive, account, chainId, deactivate } = useIsActive() + const dispatch = useAppDispatch() + const connectWallet = useConnectWallet() + + const { account: ledgerLiveAccount, requestAccount } = + useRequestEthereumAccount() + const ledgerLiveAccountAddress = ledgerLiveAccount?.address + + const openWalletModal = () => { + connectWallet() + } + + useEffect(() => { + if (ledgerLiveAccountAddress) { + dispatch(walletConnected(ledgerLiveAccountAddress)) + } + }, [ledgerLiveAccountAddress, dispatch]) return ( void @@ -15,11 +14,15 @@ const SubmitTxButton: FC = ({ submitText = "Upgrade", ...buttonProps }) => { - const { active } = useWeb3React() - const { openModal } = useModal() + const { isActive } = useIsActive() const { isSdkInitializedWithSigner } = useIsTbtcSdkInitializing() + const connectWallet = useConnectWallet() - if (active && isSdkInitializedWithSigner) { + const onConnectWalletClick = () => { + connectWallet() + } + + if (isActive && isSdkInitializedWithSigner) { return ( + + ) +} + +export type SendBitcoinsToDepositAddressFormValues = { + amount: string +} + +type SendBitcoinsToDepositAddressFormProps = { + onSubmitForm: (values: SendBitcoinsToDepositAddressFormValues) => void +} & SendBitcoinsToDepositAddressFormBaseProps + +export const SendBitcoinsToDepositAddressForm = withFormik< + SendBitcoinsToDepositAddressFormProps, + SendBitcoinsToDepositAddressFormValues +>({ + mapPropsToValues: () => ({ + amount: "", + }), + validate: async (values, props) => { + const errors: FormikErrors = {} + + errors.amount = validateAmountInRange( + values.amount, + props.maxTokenAmount, + MINT_BITCOIN_MIN_AMOUNT, + undefined, + 8, + 8 + ) + + return getErrorsObj(errors) + }, + handleSubmit: (values, { props }) => { + props.onSubmitForm(values) + }, + displayName: "SendBitcoinsToDepositAddressForm", + enableReinitialize: true, +})(SendBitcoinsToDepositAddressFormBase) diff --git a/src/components/tBTC/index.ts b/src/components/tBTC/index.ts index 8daf3ab33..b752f2dc5 100644 --- a/src/components/tBTC/index.ts +++ b/src/components/tBTC/index.ts @@ -4,3 +4,4 @@ export * from "./Stats" export * from "./tBTCText" export * from "./BridgeActivity" export * from "./BridgeProcessIndicator" +export * from "./SendBitcoinsToDepositAddressForm" diff --git a/src/components/withOnlyConnectedWallet.tsx b/src/components/withOnlyConnectedWallet.tsx index 043bc0e40..109136212 100644 --- a/src/components/withOnlyConnectedWallet.tsx +++ b/src/components/withOnlyConnectedWallet.tsx @@ -1,14 +1,15 @@ import { ComponentType } from "react" import { H5 } from "@threshold-network/components" import { useWeb3React } from "@web3-react/core" +import { useIsActive } from "../hooks/useIsActive" function withOnlyConnectedWallet( Component: ComponentType, renderNotConnected?: () => JSX.Element ) { return (props: T & {}) => { - const { account, active } = useWeb3React() - if (!active || !account) { + const { account, isActive } = useIsActive() + if (!isActive || !account) { return renderNotConnected ? ( renderNotConnected() ) : ( diff --git a/src/contexts/LedgerLiveAppContext.tsx b/src/contexts/LedgerLiveAppContext.tsx new file mode 100644 index 000000000..78f701f18 --- /dev/null +++ b/src/contexts/LedgerLiveAppContext.tsx @@ -0,0 +1,48 @@ +import { Account } from "@ledgerhq/wallet-api-client" +import React, { createContext, useCallback, useContext, useState } from "react" +import { LedgerLiveEthereumSigner } from "@keep-network/tbtc-v2.ts" +import { ledgerLiveAppEthereumSigner } from "../utils/getLedgerLiveAppEthereumSigner" + +interface LedgerLiveAppContextState { + ethAccount: Account | undefined + btcAccount: Account | undefined + setEthAccount: (ethAccount: Account | undefined) => void + setBtcAccount: (btcAccount: Account | undefined) => void + ledgerLiveAppEthereumSigner: LedgerLiveEthereumSigner | undefined +} + +export const useLedgerLiveApp = () => { + return useContext(LedgerLiveAppContext) +} + +const LedgerLiveAppContext = createContext({ + ethAccount: undefined, + btcAccount: undefined, + setEthAccount: () => {}, + setBtcAccount: () => {}, + ledgerLiveAppEthereumSigner: undefined, +}) + +export const LedgerLiveAppProvider: React.FC = ({ children }) => { + const [ethAccount, _setEthAccount] = useState(undefined) + const [btcAccount, setBtcAccount] = useState(undefined) + + const setEthAccount = useCallback((ethAccount: Account | undefined) => { + ledgerLiveAppEthereumSigner.setAccount(ethAccount) + _setEthAccount(ethAccount) + }, []) + + return ( + + {children} + + ) +} diff --git a/src/contexts/ThresholdContext.tsx b/src/contexts/ThresholdContext.tsx index 5f0f46811..d3fd3b02d 100644 --- a/src/contexts/ThresholdContext.tsx +++ b/src/contexts/ThresholdContext.tsx @@ -16,6 +16,9 @@ import { getDefaultThresholdLibProvider, threshold, } from "../utils/getThresholdLib" +import { useLedgerLiveApp } from "./LedgerLiveAppContext" +import { useIsActive } from "../hooks/useIsActive" +import { useIsEmbed } from "../hooks/useIsEmbed" const ThresholdContext = createContext(threshold) @@ -53,10 +56,10 @@ const useInitializeTbtcSdk = () => { account ) setSdk(sdk) - setIsInitializing(false) setIsInitialized(true) const isInitializedWithSigner = account ? true : false setIsInitializedWithSigner(isInitializedWithSigner) + setIsInitializing(false) } }, [ @@ -79,7 +82,7 @@ const useInitializeTbtcSdk = () => { } export const ThresholdProvider: FC = ({ children }) => { - const { library, active, account } = useWeb3React() + const { library } = useWeb3React() const hasThresholdLibConfigBeenUpdated = useRef(false) const { sdk, @@ -89,13 +92,16 @@ export const ThresholdProvider: FC = ({ children }) => { isSdkInitializedWithSigner, setIsSdkInitializing, } = useInitializeTbtcSdk() + const { ledgerLiveAppEthereumSigner } = useLedgerLiveApp() + const { account, isActive } = useIsActive() + const { isEmbed } = useIsEmbed() useEffect(() => { - if (active && library && account) { + if (isActive) { threshold.updateConfig({ ethereum: { ...threshold.config.ethereum, - providerOrSigner: library, + providerOrSigner: isEmbed ? ledgerLiveAppEthereumSigner : library, account, }, bitcoin: threshold.config.bitcoin, @@ -104,11 +110,12 @@ export const ThresholdProvider: FC = ({ children }) => { initializeSdk(threshold.config.ethereum.providerOrSigner, account) } - if (!active && !account && hasThresholdLibConfigBeenUpdated.current) { + if (!isActive && hasThresholdLibConfigBeenUpdated.current) { threshold.updateConfig({ ethereum: { ...threshold.config.ethereum, providerOrSigner: getDefaultThresholdLibProvider(), + account: undefined, }, bitcoin: threshold.config.bitcoin, }) @@ -119,7 +126,7 @@ export const ThresholdProvider: FC = ({ children }) => { if (!sdk && !isSdkInitializing && !isSdkInitialized) { initializeSdk(threshold.config.ethereum.providerOrSigner) } - }, [library, active, account]) + }, [library, isActive, account, initializeSdk, isEmbed]) return ( diff --git a/src/contexts/TokenContext.tsx b/src/contexts/TokenContext.tsx index c8c91eea9..76eda557f 100644 --- a/src/contexts/TokenContext.tsx +++ b/src/contexts/TokenContext.tsx @@ -14,6 +14,7 @@ import { useVendingMachineRatio } from "../web3/hooks/useVendingMachineRatio" import { useFetchOwnerStakes } from "../hooks/useFetchOwnerStakes" import { useTBTCv2TokenContract } from "../web3/hooks/useTBTCv2TokenContract" import { featureFlags } from "../constants" +import { useIsActive } from "../hooks/useIsActive" interface TokenContextState extends TokenState { contract: Contract | null @@ -39,7 +40,7 @@ export const TokenContextProvider: React.FC = ({ children }) => { const tbtcv2 = useTBTCv2TokenContract() const nuConversion = useVendingMachineRatio(Token.Nu) const keepConversion = useVendingMachineRatio(Token.Keep) - const { active, chainId, account } = useWeb3React() + const { account, isActive, chainId } = useIsActive() const fetchOwnerStakes = useFetchOwnerStakes() const { @@ -59,7 +60,7 @@ export const TokenContextProvider: React.FC = ({ children }) => { const fetchBalances = useTokensBalanceCall( tokenContracts, - active ? account! : AddressZero + isActive ? account! : AddressZero ) // @@ -86,7 +87,7 @@ export const TokenContextProvider: React.FC = ({ children }) => { // FETCH BALANCES ON WALLET LOAD OR NETWORK SWITCH // React.useEffect(() => { - if (active) { + if (isActive) { fetchBalances().then( ([keepBalance, nuBalance, tBalance, tbtcv2Balance]) => { setTokenBalance(Token.Keep, keepBalance.toString()) @@ -106,7 +107,7 @@ export const TokenContextProvider: React.FC = ({ children }) => { } } } - }, [active, chainId, account]) + }, [isActive, chainId, account]) // fetch user stakes when they connect their wallet React.useEffect(() => { diff --git a/src/contexts/TransportProvider.tsx b/src/contexts/TransportProvider.tsx new file mode 100644 index 000000000..9228a3a6d --- /dev/null +++ b/src/contexts/TransportProvider.tsx @@ -0,0 +1,46 @@ +import { WalletAPIProvider } from "@ledgerhq/wallet-api-client-react" +import { WindowMessageTransport } from "@ledgerhq/wallet-api-client" +import { createContext, FC, useContext } from "react" + +const getWalletAPITransport = (): WindowMessageTransport => { + const transport = new WindowMessageTransport() + return transport +} + +export const walletApiReactTransport = getWalletAPITransport() + +interface TransportContextState { + walletApiReactTransport: WindowMessageTransport +} + +export const useWalletApiReactTransport = () => { + return useContext(WalletApiReactTransportContext) +} + +const WalletApiReactTransportContext = createContext({ + walletApiReactTransport: walletApiReactTransport, +}) + +export const WalletApiReactTransportProvider: React.FC = ({ children }) => { + return ( + + {children} + + ) +} + +const TransportProvider: FC = ({ children }) => { + return ( + + + {children} + + + ) +} + +export default TransportProvider diff --git a/src/hooks/ledger-live-app/index.ts b/src/hooks/ledger-live-app/index.ts new file mode 100644 index 000000000..86029016c --- /dev/null +++ b/src/hooks/ledger-live-app/index.ts @@ -0,0 +1,3 @@ +export * from "./useRequestBitcoinAccount" +export * from "./useRequestEthereumAccount" +export * from "./useSendBitcoinTransaction" diff --git a/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts new file mode 100644 index 000000000..b589e6100 --- /dev/null +++ b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts @@ -0,0 +1,38 @@ +import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client" +import { useRequestAccount as useWalletApiRequestAccount } from "@ledgerhq/wallet-api-client-react" +import { useCallback, useEffect } from "react" +import { useLedgerLiveApp } from "../../contexts/LedgerLiveAppContext" +import { useWalletApiReactTransport } from "../../contexts/TransportProvider" +import { supportedChainId } from "../../utils/getEnvVariable" + +type UseRequestAccountState = { + pending: boolean + account: Account | null + error: unknown +} + +type RequestAccountParams = Parameters + +type UseRequestAccountReturn = { + requestAccount: (...params: RequestAccountParams) => Promise +} & UseRequestAccountState + +export function useRequestBitcoinAccount(): UseRequestAccountReturn { + const { setBtcAccount } = useLedgerLiveApp() + const { walletApiReactTransport } = useWalletApiReactTransport() + const useRequestAccountReturn = useWalletApiRequestAccount() + const { account, requestAccount } = useRequestAccountReturn + + useEffect(() => { + setBtcAccount(account || undefined) + }, [account]) + + const requestBitcoinAccount = useCallback(async () => { + const currencyId = supportedChainId === "1" ? "bitcoin" : "bitcoin_testnet" + walletApiReactTransport.connect() + await requestAccount({ currencyIds: [currencyId] }) + walletApiReactTransport.disconnect() + }, [requestAccount, walletApiReactTransport, supportedChainId]) + + return { ...useRequestAccountReturn, requestAccount: requestBitcoinAccount } +} diff --git a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts new file mode 100644 index 000000000..daa5de550 --- /dev/null +++ b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts @@ -0,0 +1,50 @@ +import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client" +import { useRequestAccount as useWalletApiRequestAccount } from "@ledgerhq/wallet-api-client-react" +import { useCallback, useEffect } from "react" +import { useLedgerLiveApp } from "../../contexts/LedgerLiveAppContext" +import { useIsTbtcSdkInitializing } from "../../contexts/ThresholdContext" +import { useWalletApiReactTransport } from "../../contexts/TransportProvider" +import { walletConnected } from "../../store/account" +import { supportedChainId } from "../../utils/getEnvVariable" +import { useAppDispatch } from "../store/useAppDispatch" + +type UseRequestAccountState = { + pending: boolean + account: Account | null + error: unknown +} + +type RequestAccountParams = Parameters + +type UseRequestAccountReturn = { + requestAccount: (...params: RequestAccountParams) => Promise +} & UseRequestAccountState + +export function useRequestEthereumAccount(): UseRequestAccountReturn { + const { setEthAccount } = useLedgerLiveApp() + const { walletApiReactTransport } = useWalletApiReactTransport() + const useRequestAccountReturn = useWalletApiRequestAccount() + const { account, requestAccount } = useRequestAccountReturn + const dispatch = useAppDispatch() + const { setIsSdkInitializing } = useIsTbtcSdkInitializing() + + useEffect(() => { + // Setting the eth account in LedgerLiveAppContext through `setEthAccount` + // method will trigger the useEffect in Threshold Context that will + // reinitialize the lib and tBTC SDK. We can set the is initializing flag + // here to indicate as early as as possible that the sdk is in the + // initializing process. + setIsSdkInitializing(true) + setEthAccount(account || undefined) + dispatch(walletConnected(account?.address || "")) + }, [account]) + + const requestEthereumAccount = useCallback(async () => { + const currencyId = supportedChainId === "1" ? "ethereum" : "ethereum_goerli" + walletApiReactTransport.connect() + await requestAccount({ currencyIds: [currencyId] }) + walletApiReactTransport.disconnect() + }, [requestAccount, walletApiReactTransport, supportedChainId]) + + return { ...useRequestAccountReturn, requestAccount: requestEthereumAccount } +} diff --git a/src/hooks/ledger-live-app/useSendBitcoinTransaction.ts b/src/hooks/ledger-live-app/useSendBitcoinTransaction.ts new file mode 100644 index 000000000..6c396b093 --- /dev/null +++ b/src/hooks/ledger-live-app/useSendBitcoinTransaction.ts @@ -0,0 +1,49 @@ +import { useSignAndBroadcastTransaction } from "@ledgerhq/wallet-api-client-react" +import BigNumber from "bignumber.js" +import { useCallback } from "react" +import { useLedgerLiveApp } from "../../contexts/LedgerLiveAppContext" +import { useWalletApiReactTransport } from "../../contexts/TransportProvider" + +type UseSendBitcoinTransactionState = { + pending: boolean + transactionHash: string | null + error: unknown +} + +type SendBitcoinTransactionParams = Parameters< + (amount: string, recipient: string) => {} +> + +type UseSendBitcoinTransactionReturn = { + sendBitcoinTransaction: ( + ...params: SendBitcoinTransactionParams + ) => Promise +} & UseSendBitcoinTransactionState + +export function useSendBitcoinTransaction(): UseSendBitcoinTransactionReturn { + const { btcAccount } = useLedgerLiveApp() + const { walletApiReactTransport } = useWalletApiReactTransport() + const useSignAndBroadcastTransactionReturn = useSignAndBroadcastTransaction() + const { signAndBroadcastTransaction, ...rest } = + useSignAndBroadcastTransactionReturn + + const sendBitcoinTransaction = useCallback( + async (amount, recipient) => { + if (!btcAccount) { + throw new Error("Bitcoin account was not connected.") + } + + const bitcoinTransaction = { + family: "bitcoin" as const, + amount: new BigNumber(amount), + recipient: recipient, + } + walletApiReactTransport.connect() + await signAndBroadcastTransaction(btcAccount.id, bitcoinTransaction) + walletApiReactTransport.disconnect() + }, + [signAndBroadcastTransaction, btcAccount?.id, walletApiReactTransport] + ) + + return { ...rest, sendBitcoinTransaction } +} diff --git a/src/hooks/tbtc/useTBTCDepositDataFromLocalStorage.ts b/src/hooks/tbtc/useTBTCDepositDataFromLocalStorage.ts index 3098508be..25ba8ea27 100644 --- a/src/hooks/tbtc/useTBTCDepositDataFromLocalStorage.ts +++ b/src/hooks/tbtc/useTBTCDepositDataFromLocalStorage.ts @@ -1,4 +1,3 @@ -import { useWeb3React } from "@web3-react/core" import { useCallback } from "react" import { useLocalStorage } from "../useLocalStorage" import { @@ -8,9 +7,10 @@ import { TBTCDepositData, TBTCLocalStorageDepositData, } from "../../utils/tbtcLocalStorageData" +import { useIsActive } from "../useIsActive" export const useTBTCDepositDataFromLocalStorage = () => { - const { account } = useWeb3React() + const { account } = useIsActive() const [tBTCDepositData] = useLocalStorage( key, diff --git a/src/hooks/useConnectWallet.ts b/src/hooks/useConnectWallet.ts new file mode 100644 index 000000000..ccc0779d1 --- /dev/null +++ b/src/hooks/useConnectWallet.ts @@ -0,0 +1,19 @@ +import { useCallback } from "react" +import { ModalType } from "../enums" +import { useRequestEthereumAccount } from "./ledger-live-app" +import { useIsEmbed } from "./useIsEmbed" +import { useModal } from "./useModal" + +export const useConnectWallet = (): (() => void) => { + const { isEmbed } = useIsEmbed() + const { requestAccount } = useRequestEthereumAccount() + const { openModal } = useModal() + + return useCallback(() => { + if (isEmbed) { + requestAccount() + } else { + openModal(ModalType.SelectWallet) + } + }, [isEmbed, requestAccount]) +} diff --git a/src/hooks/useDetectIfEmbed.ts b/src/hooks/useDetectIfEmbed.ts new file mode 100644 index 000000000..537f064bd --- /dev/null +++ b/src/hooks/useDetectIfEmbed.ts @@ -0,0 +1,43 @@ +import { useState, useEffect } from "react" +import { useSearchParams } from "react-router-dom" +import { useIsEmbed } from "./useIsEmbed" + +/** + * Detects if application is Embed based on the `?embed=true` query parameter. + * Based on that it will save that information in local storage (under `isEmbed` + * property). + * + * Besides that it will also set `?embed=true` query parameter for every url if + * the `isEmbed` property is set to true in local storage. This will persist the + * embed flag when the user refreshed the page unless he removes the + * `?embed=true` query parameter from url. + */ +export const useDetectIfEmbed = () => { + const { enableIsEmbed, isEmbed, disableIsEmbed } = useIsEmbed() + const [searchParams, setSearchParams] = useSearchParams() + const params = new URLSearchParams(window.location.search) + const [isEmbedChecked, setIsEmbedChecked] = useState(false) + + /** + * Enables embed mode if the `?embed=true` query parameter is in the url. + */ + useEffect(() => { + if (params.get("embed") && params.get("embed") === "true") { + enableIsEmbed() + } else { + disableIsEmbed() + } + setIsEmbedChecked(true) + }, []) + + /** + * Adds query parameter to the current url if `isEmbed` flag is set to true in + * local storage. It fires every time the url changes. + */ + useEffect(() => { + if (!isEmbedChecked) return + if (isEmbed) { + setSearchParams({ embed: "true" }) + } + }, [location.pathname, isEmbed, isEmbedChecked]) +} diff --git a/src/hooks/useFetchTvl.ts b/src/hooks/useFetchTvl.ts index fc21632da..4aa621108 100644 --- a/src/hooks/useFetchTvl.ts +++ b/src/hooks/useFetchTvl.ts @@ -14,6 +14,7 @@ import { useETHData } from "./useETHData" import { useToken } from "./useToken" import { Token } from "../enums" import { toUsdBalance } from "../utils/getUsdBalance" +import { useIsTbtcSdkInitializing } from "../contexts/ThresholdContext" interface TvlRawData { ecdsaTvl: string @@ -74,6 +75,7 @@ export const useFetchTvl = (): [ const tTokenStaking = useTStakingContract() const keepTokenStaking = useKeepTokenStakingContract() const tBTCToken = useToken(Token.TBTCV2) + const { isSdkInitializing } = useIsTbtcSdkInitializing() const fetchOnChainData = useMulticall([ { @@ -117,6 +119,7 @@ export const useFetchTvl = (): [ ]) const fetchTvlData = useCallback(async () => { + if (isSdkInitializing) return initialState const chainData = await fetchOnChainData() if (chainData.length === 0) return initialState @@ -142,7 +145,7 @@ export const useFetchTvl = (): [ setRawData(data) return data - }, [fetchOnChainData]) + }, [fetchOnChainData, isSdkInitializing]) const data = useMemo(() => { const ecdsa = toUsdBalance(formatUnits(ecdsaTvl), eth.usdPrice) diff --git a/src/hooks/useIsActive.ts b/src/hooks/useIsActive.ts new file mode 100644 index 000000000..4fd22a627 --- /dev/null +++ b/src/hooks/useIsActive.ts @@ -0,0 +1,50 @@ +import { useWeb3React } from "@web3-react/core" +import { useCallback, useMemo } from "react" +import { useLedgerLiveApp } from "../contexts/LedgerLiveAppContext" +import { useIsTbtcSdkInitializing } from "../contexts/ThresholdContext" +import { supportedChainId } from "../utils/getEnvVariable" +import { useIsEmbed } from "./useIsEmbed" + +type UseIsActiveResult = { + account: string | undefined + chainId: number | undefined + isActive: boolean + deactivate: () => void +} + +/** + * Checks if eth wallet is connected to the dashboard. It works with normal + * view in the website and also inside Ledger Live App. + * @return {UseIsActiveResult} Account address and `isActive` boolean + */ +export const useIsActive = (): UseIsActiveResult => { + const { + active: _active, + account: _account, + chainId: _chainId, + deactivate: _deactivate, + } = useWeb3React() + const { ethAccount, setEthAccount } = useLedgerLiveApp() + const ledgerLiveAppEthAddress = ethAccount?.address || undefined + const { isEmbed } = useIsEmbed() + const { setIsSdkInitializing } = useIsTbtcSdkInitializing() + + const isActive = useMemo(() => { + if (isEmbed) { + return !!ledgerLiveAppEthAddress + } + return !!_active && !!_account + }, [ledgerLiveAppEthAddress, _active, _account, isEmbed]) + + const deactivateLedgerLiveApp = useCallback(() => { + setIsSdkInitializing(true) + setEthAccount(undefined) + }, [setEthAccount]) + + return { + account: (isEmbed ? ledgerLiveAppEthAddress : _account) || undefined, + chainId: isEmbed ? Number(supportedChainId) : _chainId, + isActive, + deactivate: isEmbed ? deactivateLedgerLiveApp : _deactivate, + } +} diff --git a/src/hooks/useIsEmbed.ts b/src/hooks/useIsEmbed.ts new file mode 100644 index 000000000..bbefd2709 --- /dev/null +++ b/src/hooks/useIsEmbed.ts @@ -0,0 +1,23 @@ +import { useCallback } from "react" +import { useLocalStorage } from "./useLocalStorage" + +export const useIsEmbed = () => { + const [isEmbed, setIsEmbed] = useLocalStorage( + "isEmbed", + undefined + ) + + const enableIsEmbed = useCallback(() => { + setIsEmbed(true) + }, [setIsEmbed]) + + const disableIsEmbed = useCallback(() => { + setIsEmbed(false) + }, [setIsEmbed]) + + return { + enableIsEmbed, + disableIsEmbed, + isEmbed, + } +} diff --git a/src/index.tsx b/src/index.tsx index 0ed01339d..06e51ca68 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,10 +1,13 @@ import React from "react" import ReactDOM from "react-dom" import App from "./App" +import TransportProvider from "./contexts/TransportProvider" ReactDOM.render( - + + + , document.getElementById("root") ) diff --git a/src/pages/Overview/Network/index.tsx b/src/pages/Overview/Network/index.tsx index 0cdf8002a..916ec20df 100644 --- a/src/pages/Overview/Network/index.tsx +++ b/src/pages/Overview/Network/index.tsx @@ -22,11 +22,12 @@ import { selectBridgeActivity, tbtcSlice } from "../../../store/tbtc" import ButtonLink from "../../../components/ButtonLink" import upgradeToTIcon from "../../../static/images/upgrade-to-t.svg" import { CoveragePoolsTvlCard } from "../../tBTC/CoveragePools" +import { useIsActive } from "../../../hooks/useIsActive" const Network: PageComponent = () => { const [tvlInUSD, fetchtTvlData, tvlInTokenUnits] = useFetchTvl() const [deposits, isFetching, error] = useFetchRecentDeposits() - const { account } = useWeb3React() + const { account } = useIsActive() const dispatch = useAppDispatch() const bridgeActivity = useAppSelector(selectBridgeActivity) const isBridgeActivityFetching = useAppSelector( diff --git a/src/pages/tBTC/Bridge/Mint.tsx b/src/pages/tBTC/Bridge/Mint.tsx index 0dc0d2f2e..79c07cf49 100644 --- a/src/pages/tBTC/Bridge/Mint.tsx +++ b/src/pages/tBTC/Bridge/Mint.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react" import { Outlet } from "react-router" -import { useWeb3React } from "@web3-react/core" +import { useIsActive } from "../../../hooks/useIsActive" import { MintingStep, PageComponent } from "../../../types" import { DepositDetails } from "./DepositDetails" import { ResumeDepositPage } from "./ResumeDeposit" @@ -29,7 +29,7 @@ export const MintPage: PageComponent = ({}) => { export const MintingFormPage: PageComponent = ({ ...props }) => { const { tBTCDepositData } = useTBTCDepositDataFromLocalStorage() const { btcDepositAddress, updateState } = useTbtcState() - const { account } = useWeb3React() + const { account } = useIsActive() const { isSdkInitializing, isSdkInitializedWithSigner } = useIsTbtcSdkInitializing() @@ -86,7 +86,7 @@ MintingFormPage.route = { } const MintPageLayout: PageComponent = () => { - const { active } = useWeb3React() + const { isActive } = useIsActive() const { mintingStep, utxo } = useTbtcState() const { tbtc: { @@ -104,7 +104,7 @@ const MintPageLayout: PageComponent = () => { return ( - {active ? ( + {isActive ? ( ) : ( diff --git a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx index 2cfb8fcc0..698e48c4f 100644 --- a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx +++ b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx @@ -3,12 +3,13 @@ import { BodyMd, Box, BoxLabel, + Button, Card, HStack, Stack, useColorModeValue, } from "@threshold-network/components" -import { ComponentProps, FC } from "react" +import { FC, ComponentProps, useCallback } from "react" import { CopyAddressToClipboard, CopyToClipboard, @@ -22,8 +23,17 @@ import { ViewInBlockExplorerProps } from "../../../../components/ViewInBlockExpl import withOnlyConnectedWallet from "../../../../components/withOnlyConnectedWallet" import { useTbtcState } from "../../../../hooks/useTbtcState" import { MintingStep } from "../../../../types/tbtc" -import { BridgeProcessCardSubTitle } from "../components/BridgeProcessCardSubTitle" import { BridgeProcessCardTitle } from "../components/BridgeProcessCardTitle" +import { BridgeProcessCardSubTitle } from "../components/BridgeProcessCardSubTitle" +import { useIsEmbed } from "../../../../hooks/useIsEmbed" +import { + useRequestBitcoinAccount, + useSendBitcoinTransaction, +} from "../../../../hooks/ledger-live-app" +import { + SendBitcoinsToDepositAddressForm, + SendBitcoinsToDepositAddressFormValues, +} from "../../../../components/tBTC" const AddressRow: FC< { address: string; text: string } & Pick @@ -125,6 +135,29 @@ const MakeDepositComponent: FC<{ const { btcDepositAddress, ethAddress, btcRecoveryAddress, updateState } = useTbtcState() + // ↓ Ledger Live App ↓ + const { isEmbed } = useIsEmbed() + const { requestAccount, account: ledgerBitcoinAccount } = + useRequestBitcoinAccount() + const { sendBitcoinTransaction } = useSendBitcoinTransaction() + + const chooseBitcoinAccount = useCallback(async () => { + await requestAccount() + }, [requestAccount]) + + const handleSendBitcoinTransaction = useCallback( + async (values: SendBitcoinsToDepositAddressFormValues) => { + const { amount } = values + try { + await sendBitcoinTransaction(amount, btcDepositAddress) + } catch (e) { + console.error(e) + } + }, + [btcDepositAddress, sendBitcoinTransaction] + ) + // ↑ Ledger Live App ↑ + return ( <> + {isEmbed && !!ledgerBitcoinAccount?.address && ( + + )} + {isEmbed && ( + + )} + {isEmbed && ledgerBitcoinAccount && ( + + )} ) } diff --git a/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx b/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx index cb703165b..4814daf7e 100644 --- a/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx +++ b/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx @@ -6,7 +6,7 @@ import { ProvideData } from "./ProvideData" import { InitiateMinting } from "./InitiateMinting" import { MintingSuccess } from "./MintingSuccess" import { MakeDeposit } from "./MakeDeposit" -import { useWeb3React } from "@web3-react/core" +import { useIsActive } from "../../../../hooks/useIsActive" import { useModal } from "../../../../hooks/useModal" import { ModalType } from "../../../../enums" import { BridgeContractLink } from "../../../../components/tBTC" @@ -18,7 +18,7 @@ import { useIsTbtcSdkInitializing } from "../../../../contexts/ThresholdContext" const MintingFlowRouterBase = () => { const dispatch = useAppDispatch() - const { account } = useWeb3React() + const { account } = useIsActive() const { mintingStep, updateState, btcDepositAddress, utxo } = useTbtcState() const removeDepositData = useRemoveDepositData() const { openModal } = useModal() diff --git a/src/pages/tBTC/Bridge/Minting/ProvideData.tsx b/src/pages/tBTC/Bridge/Minting/ProvideData.tsx index 811dee42d..6be507960 100644 --- a/src/pages/tBTC/Bridge/Minting/ProvideData.tsx +++ b/src/pages/tBTC/Bridge/Minting/ProvideData.tsx @@ -4,7 +4,6 @@ import { Checkbox, useColorModeValue, } from "@threshold-network/components" -import { useWeb3React } from "@web3-react/core" import { FormikErrors, FormikProps, withFormik } from "formik" import { FC, Ref, useCallback, useRef, useState } from "react" import { Form, FormikInput } from "../../../../components/Forms" @@ -25,6 +24,7 @@ import { getBridgeBTCSupportedAddressPrefixesText } from "../../../../utils/tBTC import { downloadFile, isSameETHAddress } from "../../../../web3/utils" import { BridgeProcessCardSubTitle } from "../components/BridgeProcessCardSubTitle" import { BridgeProcessCardTitle } from "../components/BridgeProcessCardTitle" +import { useIsActive } from "../../../../hooks/useIsActive" export interface FormValues { ethAddress: string @@ -111,7 +111,7 @@ export const ProvideDataComponent: FC<{ const [isSubmitButtonLoading, setSubmitButtonLoading] = useState(false) const formRef = useRef>(null) const threshold = useThreshold() - const { account } = useWeb3React() + const { account } = useIsActive() const { setDepositDataInLocalStorage } = useTBTCDepositDataFromLocalStorage() const depositTelemetry = useDepositTelemetry(threshold.tbtc.bitcoinNetwork) diff --git a/src/pages/tBTC/Bridge/ResumeDeposit.tsx b/src/pages/tBTC/Bridge/ResumeDeposit.tsx index a36df728f..2bb3a1938 100644 --- a/src/pages/tBTC/Bridge/ResumeDeposit.tsx +++ b/src/pages/tBTC/Bridge/ResumeDeposit.tsx @@ -8,7 +8,6 @@ import { FileUploader, FormControl, } from "@threshold-network/components" -import { useWeb3React } from "@web3-react/core" import { FormikErrors, FormikProps, withFormik } from "formik" import { useNavigate } from "react-router-dom" import { PageComponent } from "../../../types" @@ -33,10 +32,11 @@ import { BridgeProcessEmptyState } from "./components/BridgeProcessEmptyState" type RecoveryJsonFileData = DepositScriptParameters & { btcRecoveryAddress: string } +import { useIsActive } from "../../../hooks/useIsActive" export const ResumeDepositPage: PageComponent = () => { const { updateState } = useTbtcState() - const { account, active } = useWeb3React() + const { account, isActive } = useIsActive() const navigate = useNavigate() const { setDepositDataInLocalStorage } = useTBTCDepositDataFromLocalStorage() const threshold = useThreshold() @@ -70,7 +70,7 @@ export const ResumeDepositPage: PageComponent = () => { return ( - {active ? ( + {isActive ? ( <> diff --git a/src/pages/tBTC/Bridge/Unmint.tsx b/src/pages/tBTC/Bridge/Unmint.tsx index 2892e0046..aff05ff91 100644 --- a/src/pages/tBTC/Bridge/Unmint.tsx +++ b/src/pages/tBTC/Bridge/Unmint.tsx @@ -1,5 +1,4 @@ import { FC } from "react" -import { useWeb3React } from "@web3-react/core" import { Outlet } from "react-router-dom" import { FormikErrors, useFormikContext, withFormik } from "formik" import { @@ -50,7 +49,10 @@ import { PageComponent } from "../../../types" import { useToken } from "../../../hooks/useToken" import { ModalType, Token } from "../../../enums" import { BitcoinNetwork } from "../../../threshold-ts/types" -import { useThreshold } from "../../../contexts/ThresholdContext" +import { + useIsTbtcSdkInitializing, + useThreshold, +} from "../../../contexts/ThresholdContext" import { getBridgeBTCSupportedAddressPrefixesText, UNMINT_MIN_AMOUNT, @@ -60,6 +62,7 @@ import { UnmintDetails } from "./UnmintDetails" import { UnmintingCard } from "./UnmintingCard" import { featureFlags } from "../../../constants" import { BridgeProcessEmptyState } from "./components/BridgeProcessEmptyState" +import { useIsActive } from "../../../hooks/useIsActive" const UnmintFormPage: PageComponent = ({}) => { const { balance } = useToken(Token.TBTCV2) @@ -254,12 +257,14 @@ UnmintFormPage.route = { } export const UnmintPageLayout: PageComponent = ({}) => { - const { active } = useWeb3React() + const { isActive } = useIsActive() + const { isSdkInitializing, isSdkInitializedWithSigner } = + useIsTbtcSdkInitializing() return ( - {active ? ( + {isActive && !isSdkInitializing && isSdkInitializedWithSigner ? ( ) : ( { const isBridgeActivityFetching = useAppSelector( (state) => state.tbtc.bridgeActivity.isFetching ) - const { account } = useWeb3React() + const { account } = useIsActive() useEffect(() => { if (!hasUserResponded) openModal(ModalType.NewTBTCApp) diff --git a/src/threshold-ts/tbtc/index.ts b/src/threshold-ts/tbtc/index.ts index 88b97cd12..3aaa0921f 100644 --- a/src/threshold-ts/tbtc/index.ts +++ b/src/threshold-ts/tbtc/index.ts @@ -205,7 +205,6 @@ export interface ITBTC { providerOrSigner: providers.Provider | Signer, account?: string ): Promise - /** * Initiates a Deposit object from bitcoin recovery address. * @param btcRecoveryAddress The bitcoin address in which the user will @@ -497,7 +496,6 @@ export class TBTC implements ITBTC { account?: string ): Promise { const signer = - // Double bang to convert to boolean !!account && providerOrSigner instanceof Web3Provider ? getSigner(providerOrSigner as Web3Provider, account) : providerOrSigner diff --git a/src/threshold-ts/utils/contract.ts b/src/threshold-ts/utils/contract.ts index ddc4d2133..36c3b3a3e 100644 --- a/src/threshold-ts/utils/contract.ts +++ b/src/threshold-ts/utils/contract.ts @@ -8,6 +8,7 @@ import { } from "@keep-network/tbtc-v2.ts" import { Contract, ContractInterface, Event, providers, Signer } from "ethers" import { AddressZero, getAddress, isAddressZero } from "./address" +import { LedgerLiveEthereumSigner } from "@keep-network/tbtc-v2.ts" import BridgeArtifactMainnet from "@keep-network/tbtc-v2.ts/src/lib/ethereum/artifacts/mainnet/Bridge.json" import NuCypherStakingEscrowMainnet from "../staking/mainnet-artifacts/NuCypherStakingEscrow.json" @@ -107,11 +108,13 @@ export const getContract = ( if (!getAddress(address) || isAddressZero(address)) { throw Error(`Invalid 'address' parameter '${address}'.`) } - return new Contract( - address, - abi, - getProviderOrSigner(providerOrSigner as any, account) as any - ) + // Sets the correct provider for ledger live app if the instance of + // LedgerLiveEthereumSigner is passed as providerOrSigner. + const _providerOrSigner = + providerOrSigner instanceof LedgerLiveEthereumSigner + ? providerOrSigner + : (getProviderOrSigner(providerOrSigner as any, account) as any) + return new Contract(address, abi, _providerOrSigner) } interface EventFilterOptions { diff --git a/src/utils/forms.ts b/src/utils/forms.ts index 85c95ffa8..38778c1e1 100644 --- a/src/utils/forms.ts +++ b/src/utils/forms.ts @@ -10,7 +10,9 @@ import { isAddress, isAddressZero } from "../web3/utils" import { formatTokenAmount } from "./formatAmount" import { getBridgeBTCSupportedAddressPrefixesText } from "./tBTC" -type AmountValidationMessage = string | ((amount: string) => string) +type AmountValidationMessage = + | string + | ((amount: string, tokenDecimals?: number, precision?: number) => string) type AmountValidationOptions = { greaterThanValidationMessage: AmountValidationMessage lessThanValidationMessage: AmountValidationMessage @@ -19,19 +21,29 @@ type AmountValidationOptions = { } export const DEFAULT_MIN_VALUE = WeiPerEther.toString() -export const defaultLessThanMessage: (maxAmount: string) => string = ( - maxAmount -) => { +export const defaultLessThanMessage: ( + maxAmount: string, + tokenDecimals?: number, + precision?: number +) => string = (maxAmount, decimals = 18, precision = 2) => { return `The value should be less than or equal ${formatTokenAmount( - maxAmount + maxAmount, + "0,00.[0]0", + decimals, + precision )}` } -export const defaultGreaterThanMessage: (minAmount: string) => string = ( - minAmount -) => { +export const defaultGreaterThanMessage: ( + minAmount: string, + tokenDecimals?: number, + precision?: number +) => string = (minAmount, decimals = 18, precision = 2) => { return `The value should be greater than or equal ${formatTokenAmount( - minAmount + minAmount, + "0,00.[0]0", + decimals, + precision )}` } export const defaultAmountValidationOptions: AmountValidationOptions = { @@ -43,10 +55,12 @@ export const defaultAmountValidationOptions: AmountValidationOptions = { const getAmountInRangeValidationMessage = ( validationMessage: AmountValidationMessage, - value: string + value: string, + tokenDecimals: number = 18, + precision: number = 2 ) => { return typeof validationMessage === "function" - ? validationMessage(value) + ? validationMessage(value, tokenDecimals, precision) : validationMessage } @@ -54,7 +68,9 @@ export const validateAmountInRange = ( value: string, maxValue: string, minValue = DEFAULT_MIN_VALUE, - options: AmountValidationOptions = defaultAmountValidationOptions + options: AmountValidationOptions = defaultAmountValidationOptions, + tokenDecimals: number = 18, + precision: number = 2 ) => { if (!value) { return options.requiredMessage @@ -71,7 +87,9 @@ export const validateAmountInRange = ( if (!isMinimumValueFulfilled) { return getAmountInRangeValidationMessage( options.greaterThanValidationMessage, - minValue + minValue, + tokenDecimals, + precision ) } if (isBalanceInsufficient) { @@ -81,7 +99,9 @@ export const validateAmountInRange = ( if (isMaximumValueExceeded) { return getAmountInRangeValidationMessage( options.lessThanValidationMessage, - maxValue + maxValue, + tokenDecimals, + precision ) } } diff --git a/src/utils/getLedgerLiveAppEthereumSigner.ts b/src/utils/getLedgerLiveAppEthereumSigner.ts new file mode 100644 index 000000000..1c99d4fb2 --- /dev/null +++ b/src/utils/getLedgerLiveAppEthereumSigner.ts @@ -0,0 +1,12 @@ +import { JsonRpcProvider, Provider } from "@ethersproject/providers" +import { EnvVariable } from "../enums" +import { LedgerLiveEthereumSigner } from "@keep-network/tbtc-v2.ts" +import { getEnvVariable } from "./getEnvVariable" + +export const getLedgerLiveAppEthereumSigner = (provider: Provider) => { + return new LedgerLiveEthereumSigner(provider) +} + +export const ledgerLiveAppEthereumSigner = getLedgerLiveAppEthereumSigner( + new JsonRpcProvider(getEnvVariable(EnvVariable.ETH_HOSTNAME_HTTP)) +) diff --git a/src/utils/tBTC.ts b/src/utils/tBTC.ts index f22cdcb4b..78b3eea80 100644 --- a/src/utils/tBTC.ts +++ b/src/utils/tBTC.ts @@ -80,7 +80,8 @@ export const getBridgeBTCSupportedAddressPrefixesText = ( return supportedAddressPrefixesText[bridgeProcess][btcNetwork] } -export const UNMINT_MIN_AMOUNT = "10000000000000000" // 0.01 +export const MINT_BITCOIN_MIN_AMOUNT = "1000000" // 0.01 BTC +export const UNMINT_MIN_AMOUNT = "10000000000000000" // 0.01 ETH export class RedemptionDetailsLinkBuilder { private walletPublicKeyHash?: string diff --git a/src/web3/hooks/useGetBlock.ts b/src/web3/hooks/useGetBlock.ts index 6406176b4..99a159c3a 100644 --- a/src/web3/hooks/useGetBlock.ts +++ b/src/web3/hooks/useGetBlock.ts @@ -3,18 +3,23 @@ import { BlockTag } from "@ethersproject/abstract-provider" import { Web3Provider } from "@ethersproject/providers" import { useThreshold } from "../../contexts/ThresholdContext" import { getProviderOrSigner } from "../../threshold-ts/utils" +import { LedgerLiveEthereumSigner } from "@keep-network/tbtc-v2.ts" export const useGetBlock = () => { const threshold = useThreshold() + const providerOrSigner = threshold.config.ethereum.providerOrSigner return useCallback( async (blockTag: BlockTag) => { + if (providerOrSigner instanceof LedgerLiveEthereumSigner) { + return providerOrSigner.provider!.getBlock(blockTag) + } const provider = getProviderOrSigner( - threshold.config.ethereum.providerOrSigner as any + providerOrSigner as any ) as Web3Provider return provider.getBlock(blockTag) }, - [threshold] + [providerOrSigner] ) } diff --git a/src/web3/hooks/useSendTransaction.ts b/src/web3/hooks/useSendTransaction.ts index 25f00e219..11f2aadb7 100644 --- a/src/web3/hooks/useSendTransaction.ts +++ b/src/web3/hooks/useSendTransaction.ts @@ -5,6 +5,9 @@ import { ModalType, TransactionStatus } from "../../enums" import { useModal } from "../../hooks/useModal" import { isWalletRejectionError } from "../../utils/isWalletRejectionError" import { TransactionReceipt } from "@ethersproject/providers" +import { useLedgerLiveApp } from "../../contexts/LedgerLiveAppContext" +import { useIsEmbed } from "../../hooks/useIsEmbed" +import { useIsActive } from "../../hooks/useIsActive" type TransactionHashWithAdditionalParams = { hash: string @@ -42,11 +45,14 @@ export const useSendTransactionFromFn = < onSuccess?: OnSuccessCallback, onError?: OnErrorCallback ) => { - const { account, library } = useWeb3React() + const { library } = useWeb3React() + const { account } = useIsActive() const { openModal } = useModal() const [transactionStatus, setTransactionStatus] = useState( TransactionStatus.Idle ) + const { ledgerLiveAppEthereumSigner: signer } = useLedgerLiveApp() + const { isEmbed } = useIsEmbed() const sendTransaction = useCallback( async (...args: Parameters) => { @@ -66,9 +72,17 @@ export const useSendTransactionFromFn = < }) setTransactionStatus(TransactionStatus.PendingOnChain) - const txReceipt = await (isContractTransaction(tx) - ? (tx as ContractTransaction).wait() - : library.waitForTransaction(txHash)) + let txReceipt: TransactionReceipt + if (isEmbed) { + const transaction = await signer!.provider?.getTransaction(txHash) + if (!transaction) + throw new Error(`Transaction ${transaction} not found!`) + txReceipt = await transaction?.wait() + } else { + txReceipt = await (isContractTransaction(tx) + ? (tx as ContractTransaction).wait() + : library.waitForTransaction(txHash)) + } setTransactionStatus(TransactionStatus.Succeeded) if (onSuccess) { diff --git a/yarn.lock b/yarn.lock index 3f9ed2923..afaad4ffb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3577,7 +3577,14 @@ resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.11.0.tgz#0d28e7edcf71548506f4304686cba480ba91bbcf" integrity sha512-HHK9y4GGe4X7CXbRUCh7z8Mp+WggpJn1dmUjmuk1rNugESF6o8nAOnXA+BxwtRRNV3CgNJR3Wxdos4J9qV0Zsg== -"@ledgerhq/wallet-api-client@^1.2.1": +"@ledgerhq/wallet-api-client-react@^1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ledgerhq/wallet-api-client-react/-/wallet-api-client-react-1.1.2.tgz#da9ca900aefd671faecf121982de30b15c5c6271" + integrity sha512-ZBnp8HBHwtuDE/jqYuJmqx20Dx9dqqcZaOW4YuaY32GRwqEJJslTtcypCCgq2kArl0Y0q0irOYEd/0I7ULxdLQ== + dependencies: + "@ledgerhq/wallet-api-client" "1.2.1" + +"@ledgerhq/wallet-api-client@1.2.1", "@ledgerhq/wallet-api-client@^1.2.0", "@ledgerhq/wallet-api-client@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@ledgerhq/wallet-api-client/-/wallet-api-client-1.2.1.tgz#b47fe5b4f431282f50ddb64c8abb911545593eba" integrity sha512-uTBTZCpbLTM5y5Cd7ioQB0lcq0b3cbrU2bGzCiKuY1IEd0NUyFhr2dKliRrcLoMPDRtQRmRnSxeX0BFKinoo8Q==