From e4764769b5db1a2cc484bf7acce85d359b677dd5 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Tue, 31 Oct 2023 11:36:11 +0100 Subject: [PATCH 01/69] Create `useEmbedFeatureFlag` hook This hook will check if there is an `isEmbed` query param in the page url. If it is, then it will save that value in local storage. We will use that param in our manifest.json file for our Ledger Live App. --- src/App.tsx | 8 ++++++++ src/hooks/useEmbedFeatureFlag.ts | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/hooks/useEmbedFeatureFlag.ts diff --git a/src/App.tsx b/src/App.tsx index 06cd66939..b9e007a74 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -58,6 +58,7 @@ import { useSubscribeToRedemptionRequestedEvent, } from "./hooks/tbtc" import { useSentry } from "./hooks/sentry" +import { useEmbedFeatureFlag } from "./hooks/useEmbedFeatureFlag" const Web3EventHandlerComponent = () => { useSubscribeToVendingMachineContractEvents() @@ -129,6 +130,13 @@ const useSubscribeToVendingMachineContractEvents = () => { const AppBody = () => { const dispatch = useDispatch() const { connector, account, deactivate } = useWeb3React() + const { enableIsEmbedFeatureFlag } = useEmbedFeatureFlag() + + const params = new URLSearchParams(window.location.search) + if (params.get("embed")) { + console.log("Application is embed.") + enableIsEmbedFeatureFlag() + } useEffect(() => { const updateHandler = (update: ConnectorUpdate) => { diff --git a/src/hooks/useEmbedFeatureFlag.ts b/src/hooks/useEmbedFeatureFlag.ts new file mode 100644 index 000000000..c18722c29 --- /dev/null +++ b/src/hooks/useEmbedFeatureFlag.ts @@ -0,0 +1,23 @@ +import { useCallback } from "react" +import { useLocalStorage } from "./useLocalStorage" + +export const useEmbedFeatureFlag = () => { + const [isEmbed, setIsEmbed] = useLocalStorage( + "isEmbed", + undefined + ) + + const enableIsEmbedFeatureFlag = useCallback(() => { + setIsEmbed(true) + }, [setIsEmbed]) + + const disableIsEmbedFeatureFlag = useCallback(() => { + setIsEmbed(false) + }, [setIsEmbed]) + + return { + enableIsEmbedFeatureFlag, + disableIsEmbedFeatureFlag, + isEmbed, + } +} From a69c7070047247a0e374f69011aa9cb839f42ff7 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Tue, 31 Oct 2023 11:44:25 +0100 Subject: [PATCH 02/69] Make only tBTC section available For Ledger Live App we only need tBTC page. --- src/App.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index b9e007a74..0c261c319 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -59,6 +59,7 @@ import { } from "./hooks/tbtc" import { useSentry } from "./hooks/sentry" import { useEmbedFeatureFlag } from "./hooks/useEmbedFeatureFlag" +import TBTC from "./pages/tBTC" const Web3EventHandlerComponent = () => { useSubscribeToVendingMachineContractEvents() @@ -207,12 +208,15 @@ const Layout = () => { } const Routing = () => { + const { isEmbed } = useEmbedFeatureFlag() + const finalPages = isEmbed ? [TBTC] : pages + const to = isEmbed ? "tBTC" : "overview" return ( }> - } /> - {pages.map(renderPageComponent)} - } /> + } /> + {finalPages.map(renderPageComponent)} + } /> ) From 5155964cdf55fcd3287af0f6130ff26e3e8f556b Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Tue, 31 Oct 2023 11:46:23 +0100 Subject: [PATCH 03/69] Hide sidebar Since we only have tbtc section in our Ledger Live App the Sidebar is not needed, so we hide it if the `isEmbed` flag is set to true. --- src/App.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 0c261c319..bb06d5cb4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -189,12 +189,13 @@ const AppBody = () => { } const Layout = () => { + const { isEmbed } = useEmbedFeatureFlag() return ( - + {!isEmbed && } From a7f76edd4107564c79346e656424e19879e0274b Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Wed, 1 Nov 2023 17:13:07 +0100 Subject: [PATCH 04/69] Create LedgerLiveAppManager class The `LedgerLiveAppManager` will be a core for all the logic used in the LedgerLive app. It will contain two separate managers for ethereum and bitcoin transactions. For ethereum manager we also have to create a separate LedgerLiveApp signer that implements Signer class from Ethers. It is needed so that we can pass it to our new tbtc-v2 SDK and use it to sign transactions and communicatin with the contracts. This way we won't have to change anything in the SDK just for the LedgerLive implementation and we keep it clean. In this commit only ethereum manager is created. In the future we should also create bitcoinManager and put it here, which allow the user to send bitcoins to a specific bitcoin address. --- package.json | 1 + src/ledger-live-app-manager/ethereum/index.ts | 32 ++++ .../ethereum/signer/index.ts | 163 ++++++++++++++++++ src/ledger-live-app-manager/index.ts | 14 ++ .../wallet-api/index.ts | 16 ++ yarn.lock | 63 +++++++ 6 files changed, 289 insertions(+) create mode 100644 src/ledger-live-app-manager/ethereum/index.ts create mode 100644 src/ledger-live-app-manager/ethereum/signer/index.ts create mode 100644 src/ledger-live-app-manager/index.ts create mode 100644 src/ledger-live-app-manager/wallet-api/index.ts diff --git a/package.json b/package.json index d2bc7489c..3d57738b5 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@keep-network/tbtc-v2": "development", "@keep-network/tbtc-v2.ts": "2.0.0-dev.0", "@ledgerhq/connect-kit-loader": "^1.1.2", + "@ledgerhq/wallet-api-client": "^1.2.0", "@reduxjs/toolkit": "^1.6.1", "@rehooks/local-storage": "^2.4.4", "@sentry/react": "^7.33.0", diff --git a/src/ledger-live-app-manager/ethereum/index.ts b/src/ledger-live-app-manager/ethereum/index.ts new file mode 100644 index 000000000..714663cb4 --- /dev/null +++ b/src/ledger-live-app-manager/ethereum/index.ts @@ -0,0 +1,32 @@ +import { LedgerLiveEthereumSigner } from "./signer" +import { Provider } from "@ethersproject/providers" +import { getWalletAPIClient, getWindowMessageTransport } from "../wallet-api" +import { Account } from "@ledgerhq/wallet-api-client" + +export class EthereumManager { + private _signer: LedgerLiveEthereumSigner + + constructor(provider: Provider) { + const windowMessageTransport = getWindowMessageTransport() + const walletApiClient = getWalletAPIClient(windowMessageTransport) + this._signer = new LedgerLiveEthereumSigner( + provider, + windowMessageTransport, + walletApiClient + ) + } + + get signer(): LedgerLiveEthereumSigner { + return this._signer + } + + get account(): Account | undefined { + return this._signer.account + } + + connectAccount = async ( + params: { currencyIds?: string[] | undefined } | undefined + ): Promise => { + return await this._signer.requestAccount(params) + } +} diff --git a/src/ledger-live-app-manager/ethereum/signer/index.ts b/src/ledger-live-app-manager/ethereum/signer/index.ts new file mode 100644 index 000000000..4f3172a75 --- /dev/null +++ b/src/ledger-live-app-manager/ethereum/signer/index.ts @@ -0,0 +1,163 @@ +import { ethers, Signer } from "ethers" +import { + Account, + Transaction, + WalletAPIClient, + WindowMessageTransport, +} from "@ledgerhq/wallet-api-client" +import { Bytes } from "@ethersproject/bytes" +import BigNumber from "bignumber.js" +import { Hex } from "@keep-network/tbtc-v2.ts" +import { AddressZero } from "@ethersproject/constants" +import { Deferrable } from "@ethersproject/properties" + +export class LedgerLiveEthereumSigner extends Signer { + private _walletApiClient: WalletAPIClient + private _windowMessageTransport: WindowMessageTransport + account: Account | undefined + + constructor( + provider: ethers.providers.Provider, + windowMessageTransport: WindowMessageTransport, + walletApiClient: WalletAPIClient + ) { + super() + ethers.utils.defineReadOnly(this, "provider", provider || null) + this._windowMessageTransport = windowMessageTransport + this._walletApiClient = walletApiClient + } + + async requestAccount( + params: { currencyIds?: string[] | undefined } | undefined + ): Promise { + this._windowMessageTransport.connect() + const _account = await this._walletApiClient.account.request(params) + this._windowMessageTransport.disconnect() + this.account = _account + return this.account + } + + getAccountId(): string { + if (!this.account || !this.account.id) { + throw new Error( + "Account not found. Please use `requestAccount` method first." + ) + } + return this.account.id + } + + async getAddress(): Promise { + if (!this.account || !this.account.address) { + throw new Error( + "Account not found. Please use `requestAccount` method first." + ) + } + return this.account.address + } + + async signMessage(message: string): Promise { + if (!this.account || !this.account.address) { + throw new Error( + "Account not found. Please use `requestAccount` method first." + ) + } + this._windowMessageTransport.connect() + const buffer = await this._walletApiClient.message.sign( + this.account.id, + Buffer.from(message) + ) + this._windowMessageTransport.disconnect() + return buffer.toString() + } + + async signTransaction( + transaction: ethers.providers.TransactionRequest + ): Promise { + if (!this.account || !this.account.address) { + throw new Error( + "Account not found. Please use `requestAccount` method first." + ) + } + + const { value, to, nonce, data, gasPrice, gasLimit } = transaction + + const ethereumTransaction: any = { + family: "ethereum" as const, + amount: value ? new BigNumber(value.toString()) : new BigNumber(0), + recipient: to ? to : AddressZero, + } + + if (nonce) ethereumTransaction.nonce = nonce + if (data) + ethereumTransaction.data = Buffer.from( + Hex.from(data.toString()).toString(), + "hex" + ) + if (gasPrice) + ethereumTransaction.gasPrice = new BigNumber(gasPrice.toString()) + if (gasLimit) + ethereumTransaction.gasLimit = new BigNumber(gasLimit.toString()) + + this._windowMessageTransport.connect() + const buffer = await this._walletApiClient.transaction.sign( + this.account.id, + ethereumTransaction + ) + this._windowMessageTransport.disconnect() + return buffer.toString() + } + + async sendTransaction( + transaction: Deferrable + ): Promise { + if (!this.account || !this.account.address) { + throw new Error( + "Account not found. Please use `requestAccount` method first." + ) + } + + const { value, to, nonce, data, gasPrice, gasLimit } = transaction + + const ethereumTransaction: any = { + family: "ethereum" as const, + amount: value ? new BigNumber(value.toString()) : new BigNumber(0), + recipient: to ? to : AddressZero, + } + + if (nonce) ethereumTransaction.nonce = nonce + if (data) + ethereumTransaction.data = Buffer.from( + Hex.from(data.toString()).toString(), + "hex" + ) + if (gasPrice) + ethereumTransaction.gasPrice = new BigNumber(gasPrice.toString()) + if (gasLimit) + ethereumTransaction.gasLimit = new BigNumber(gasLimit.toString()) + + this._windowMessageTransport.connect() + const transactionHash = + await this._walletApiClient.transaction.signAndBroadcast( + this.account.id, + ethereumTransaction + ) + this._windowMessageTransport.disconnect() + + const transactionResponse = this.provider?.getTransaction(transactionHash) + console.log("Transaction response: ", transactionResponse) + + if (!transactionResponse) { + throw new Error("Transaction response not found!") + } + + return transactionResponse + } + + connect(provider: ethers.providers.Provider): Signer { + return new LedgerLiveEthereumSigner( + provider, + this._windowMessageTransport, + this._walletApiClient + ) + } +} diff --git a/src/ledger-live-app-manager/index.ts b/src/ledger-live-app-manager/index.ts new file mode 100644 index 000000000..854efef60 --- /dev/null +++ b/src/ledger-live-app-manager/index.ts @@ -0,0 +1,14 @@ +import { EthereumManager } from "./ethereum" +import { Provider } from "@ethersproject/providers" + +/** + * Class that manages all transactions on LedgerLiveApp + */ +export class LedgerLiveAppManager { + //TODO: Add bitcoinManager + ethereumManager: EthereumManager + + constructor(provider: Provider) { + this.ethereumManager = new EthereumManager(provider) + } +} diff --git a/src/ledger-live-app-manager/wallet-api/index.ts b/src/ledger-live-app-manager/wallet-api/index.ts new file mode 100644 index 000000000..fa50d5273 --- /dev/null +++ b/src/ledger-live-app-manager/wallet-api/index.ts @@ -0,0 +1,16 @@ +import { + WalletAPIClient, + WindowMessageTransport, +} from "@ledgerhq/wallet-api-client" + +export const getWindowMessageTransport = () => { + return new WindowMessageTransport() +} + +export const getWalletAPIClient = ( + windowMessageTransport: WindowMessageTransport +) => { + const walletApiClient = new WalletAPIClient(windowMessageTransport) + + return walletApiClient +} diff --git a/yarn.lock b/yarn.lock index c9f164032..a9b5d7f94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3507,11 +3507,26 @@ rxjs "6" semver "^7.3.5" +"@ledgerhq/devices@^8.0.7": + version "8.0.7" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.0.7.tgz#206434dbd8a097529bbfc95f5eef94c2923c7578" + integrity sha512-BbPyET52lXnVs7CxJWrGYqmtGdbGzj+XnfCqLsDnA7QYr1CZREysxmie+Rr6BKpNDBRVesAovXjtaVaZOn+upw== + dependencies: + "@ledgerhq/errors" "^6.14.0" + "@ledgerhq/logs" "^6.10.1" + rxjs "6" + semver "^7.3.5" + "@ledgerhq/errors@^5.50.0": version "5.50.0" resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.50.0.tgz#e3a6834cb8c19346efca214c1af84ed28e69dad9" integrity sha512-gu6aJ/BHuRlpU7kgVpy2vcYk6atjB4iauP2ymF7Gk0ez0Y/6VSMVSJvubeEQN+IV60+OBK0JgeIZG7OiHaw8ow== +"@ledgerhq/errors@^6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.14.0.tgz#0bf253983773ef12eebce2091f463bc719223b37" + integrity sha512-ZWJw2Ti6Dq1Ott/+qYqJdDWeZm16qI3VNG5rFlb0TQ3UcAyLIQZbnnzzdcVVwVeZiEp66WIpINd/pBdqsHVyOA== + "@ledgerhq/hw-app-eth@^5.11.0": version "5.53.0" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-5.53.0.tgz#5df2d7427db9f387099d0cc437e9730101d7c404" @@ -3533,11 +3548,44 @@ "@ledgerhq/errors" "^5.50.0" events "^3.3.0" +"@ledgerhq/hw-transport@^6.28.8": + version "6.28.8" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.28.8.tgz#f99a5c71c5c09591e9bfb1b970c42aafbe81351f" + integrity sha512-XxQVl4htd018u/M66r0iu5nlHi+J6QfdPsORzDF6N39jaz+tMqItb7tUlXM/isggcuS5lc7GJo7NOuJ8rvHZaQ== + dependencies: + "@ledgerhq/devices" "^8.0.7" + "@ledgerhq/errors" "^6.14.0" + events "^3.3.0" + "@ledgerhq/logs@^5.50.0": version "5.50.0" resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.50.0.tgz#29c6419e8379d496ab6d0426eadf3c4d100cd186" integrity sha512-swKHYCOZUGyVt4ge0u8a7AwNcA//h4nx5wIi0sruGye1IJ5Cva0GyK9L2/WdX+kWVTKp92ZiEo1df31lrWGPgA== +"@ledgerhq/logs@^6.10.1": + version "6.10.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.10.1.tgz#5bd16082261d7364eabb511c788f00937dac588d" + integrity sha512-z+ILK8Q3y+nfUl43ctCPuR4Y2bIxk/ooCQFwZxhtci1EhAtMDzMAx2W25qx8G1PPL9UUOdnUax19+F0OjXoj4w== + +"@ledgerhq/wallet-api-client@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/wallet-api-client/-/wallet-api-client-1.2.0.tgz#324eef4068d1513b3e78029f8fe4e300de4eb5ca" + integrity sha512-WnMnRBrQLYya61Ao6eqHDOlCpS5AItZyEH6sx4hgIp3VoGmQavm8xj3+Iu74lUBhR8d8Xxn2iEGNKWeGgEhy8g== + dependencies: + "@ledgerhq/hw-transport" "^6.28.8" + "@ledgerhq/wallet-api-core" "1.3.0" + bignumber.js "^9.1.2" + +"@ledgerhq/wallet-api-core@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/wallet-api-core/-/wallet-api-core-1.3.0.tgz#b3a92ce5eb89c15585496d8c3c574d8f7d515b8e" + integrity sha512-Z1YUJIaeRrop+GeVXwDeLj9T/+Za6TxC/+NgesCWFrp2zg6ib4MaOEAoWwGq5DgXBvRunKAo0OF8gU0TaKo47g== + dependencies: + "@ledgerhq/errors" "^6.14.0" + bignumber.js "^9.1.2" + uuid "^9.0.0" + zod "^3.22.2" + "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz#64df34e2f12e68e78ac57e571d25ec07fa460ca9" @@ -6331,6 +6379,11 @@ bignumber.js@^7.2.0: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== +bignumber.js@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -18216,6 +18269,11 @@ uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -19727,3 +19785,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.22.2: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== From 8b066bbb984de50e19dc0d6cd885802cc0318467 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Wed, 1 Nov 2023 17:26:20 +0100 Subject: [PATCH 05/69] Put ledgerLiveAppManager inside threshold-ts lib The Threshold Ledger Live App will show only tbtc flow so it makes sense to put the `ledgerLiveAppManager` inside our `threshold-ts` lib in `TBTC` class. Besides, we will use the signer from that class and pass it to our tbtc-v2 SDK. --- src/contexts/ThresholdContext.tsx | 2 ++ src/threshold-ts/index.ts | 7 ++++++- src/threshold-ts/tbtc/index.ts | 13 ++++++++++++- src/threshold-ts/types/index.ts | 2 ++ src/utils/getLedgerLiveAppManager.ts | 13 +++++++++++++ src/utils/getThresholdLib.ts | 2 ++ 6 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/utils/getLedgerLiveAppManager.ts diff --git a/src/contexts/ThresholdContext.tsx b/src/contexts/ThresholdContext.tsx index e11eabd05..c1766cb92 100644 --- a/src/contexts/ThresholdContext.tsx +++ b/src/contexts/ThresholdContext.tsx @@ -25,6 +25,7 @@ export const ThresholdProvider: FC = ({ children }) => { account, }, bitcoin: threshold.config.bitcoin, + ledgerLiveAppManager: threshold.config.ledgerLiveAppManager, }) hasThresholdLibConfigBeenUpdated.current = true } @@ -36,6 +37,7 @@ export const ThresholdProvider: FC = ({ children }) => { providerOrSigner: getDefaultThresholdLibProvider(), }, bitcoin: threshold.config.bitcoin, + ledgerLiveAppManager: threshold.config.ledgerLiveAppManager, }) hasThresholdLibConfigBeenUpdated.current = false } diff --git a/src/threshold-ts/index.ts b/src/threshold-ts/index.ts index 09e9019c1..159b1041d 100644 --- a/src/threshold-ts/index.ts +++ b/src/threshold-ts/index.ts @@ -31,7 +31,12 @@ export class Threshold { this.multicall, config.ethereum ) - this.tbtc = new TBTC(config.ethereum, config.bitcoin, this.multicall) + this.tbtc = new TBTC( + config.ethereum, + config.bitcoin, + this.multicall, + config.ledgerLiveAppManager + ) } updateConfig = (config: ThresholdConfig) => { diff --git a/src/threshold-ts/tbtc/index.ts b/src/threshold-ts/tbtc/index.ts index 6c37ff349..261f914d1 100644 --- a/src/threshold-ts/tbtc/index.ts +++ b/src/threshold-ts/tbtc/index.ts @@ -54,6 +54,7 @@ import { findWalletForRedemption, } from "@keep-network/tbtc-v2.ts/dist/src/redemption" import { TBTCToken as ChainTBTCToken } from "@keep-network/tbtc-v2.ts/dist/src/chain" +import { LedgerLiveAppManager } from "../../ledger-live-app-manager" export enum BridgeActivityStatus { PENDING = "PENDING", @@ -178,6 +179,8 @@ export interface ITBTC { readonly tokenContract: Contract + readonly ledgerLiveAppManager: LedgerLiveAppManager | undefined + /** * Suggests a wallet that should be used as the deposit target at the given * moment. @@ -421,10 +424,13 @@ export class TBTC implements ITBTC { private _redemptionTreasuryFeeDivisor: BigNumber | undefined + private _ledgerLiveAppManager: LedgerLiveAppManager | undefined + constructor( ethereumConfig: EthereumConfig, bitcoinConfig: BitcoinConfig, - multicall: IMulticall + multicall: IMulticall, + ledgerLiveAppManager?: LedgerLiveAppManager ) { if (!bitcoinConfig.client && !bitcoinConfig.credentials) { throw new Error( @@ -471,6 +477,11 @@ export class TBTC implements ITBTC { ethereumConfig.providerOrSigner, ethereumConfig.account ) + this._ledgerLiveAppManager = ledgerLiveAppManager + } + + get ledgerLiveAppManager(): LedgerLiveAppManager | undefined { + return this._ledgerLiveAppManager } get bitcoinNetwork(): BitcoinNetwork { diff --git a/src/threshold-ts/types/index.ts b/src/threshold-ts/types/index.ts index 2f879df53..ebb56be22 100644 --- a/src/threshold-ts/types/index.ts +++ b/src/threshold-ts/types/index.ts @@ -8,6 +8,7 @@ import { Credentials, } from "@keep-network/tbtc-v2.ts/dist/src/electrum" import { providers, Signer } from "ethers" +import { LedgerLiveAppManager } from "../../ledger-live-app-manager" export interface EthereumConfig { providerOrSigner: providers.Provider | Signer @@ -46,6 +47,7 @@ export interface BitcoinConfig { export interface ThresholdConfig { ethereum: EthereumConfig bitcoin: BitcoinConfig + ledgerLiveAppManager?: LedgerLiveAppManager } export { BitcoinNetwork } diff --git a/src/utils/getLedgerLiveAppManager.ts b/src/utils/getLedgerLiveAppManager.ts new file mode 100644 index 000000000..e66166c05 --- /dev/null +++ b/src/utils/getLedgerLiveAppManager.ts @@ -0,0 +1,13 @@ +import { JsonRpcProvider, Provider } from "@ethersproject/providers" +import { EnvVariable } from "../enums" +import { LedgerLiveAppManager } from "../ledger-live-app-manager" +import { getEnvVariable } from "./getEnvVariable" +import { getDefaultThresholdLibProvider } from "./getThresholdLib" + +export const getLedgerLiveAppManager = (provider?: Provider) => { + return new LedgerLiveAppManager(provider || getDefaultThresholdLibProvider()) +} + +export const ledgerLiveAppManager = getLedgerLiveAppManager( + new JsonRpcProvider(getEnvVariable(EnvVariable.ETH_HOSTNAME_HTTP)) +) diff --git a/src/utils/getThresholdLib.ts b/src/utils/getThresholdLib.ts index 5b83a1f7a..db8964f49 100644 --- a/src/utils/getThresholdLib.ts +++ b/src/utils/getThresholdLib.ts @@ -9,6 +9,7 @@ import { BitcoinNetwork, BitcoinClientCredentials, } from "../threshold-ts/types" +import { getLedgerLiveAppManager } from "./getLedgerLiveAppManager" function getBitcoinConfig(): BitcoinConfig { const network = @@ -47,6 +48,7 @@ export const getThresholdLib = (providerOrSigner?: Provider | Signer) => { providerOrSigner: providerOrSigner || getDefaultThresholdLibProvider(), }, bitcoin: getBitcoinConfig(), + ledgerLiveAppManager: getLedgerLiveAppManager(), }) } From e9645da7df6f67aed57f867211a5dc542625e47f Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Wed, 1 Nov 2023 17:30:16 +0100 Subject: [PATCH 06/69] Add possibility to connect ledger ethereum account As of now it will only allow to use goerli accounts. Left TODOs around the places to not forget to fix that. I've created `useRequestEthereumAccount` which actually works similar to `useRequestAccount` from `@ledgerhq/wallet-api-client-react`, but it uses our ledgerLiveManager under the hood. This way it will save the account in our signer, which then will be used in our tbtc-v2 SDK to interact with contracts. Since we save our connected address in redux store we will now display it when the address is connected instead of getting the `account` property from `useWeb3React` hook. --- src/components/Navbar/index.tsx | 38 +++++++++- src/hooks/ledger-live-app/index.ts | 1 + .../useRequestEthereumAccount.ts | 69 +++++++++++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/hooks/ledger-live-app/index.ts create mode 100644 src/hooks/ledger-live-app/useRequestEthereumAccount.ts diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index 009b407e6..14e177456 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -1,13 +1,45 @@ -import { FC } from "react" +import { FC, useEffect } from "react" import { useWeb3React } from "@web3-react/core" import { useModal } from "../../hooks/useModal" import NavbarComponent from "./NavbarComponent" import { ModalType } from "../../enums" +import { useSelector } from "react-redux" +import { useAppDispatch } from "../../hooks/store" +import { useEmbedFeatureFlag } from "../../hooks/useEmbedFeatureFlag" +import { RootState } from "../../store" +import { walletConnected } from "../../store/account" +import { useRequestEthereumAccount } from "../../hooks/ledger-live-app" const Navbar: FC = () => { const { openModal } = useModal() - const { account, active, chainId, deactivate } = useWeb3React() - const openWalletModal = () => openModal(ModalType.SelectWallet) + // TODO: Determinate if active and deactivate props are necessary for + // LedgerLive app + const { active, chainId: web3ReactChainId, deactivate } = useWeb3React() + const { isEmbed } = useEmbedFeatureFlag() + const dispatch = useAppDispatch() + const { address } = useSelector((state: RootState) => state.account) + + const { account: ledgerLiveAccount, requestAccount } = + useRequestEthereumAccount() + + const openWalletModal = () => { + if (isEmbed) { + // TODO: Use proper currency based on chainID + requestAccount({ currencyIds: ["ethereum_goerli"] }) + } else { + openModal(ModalType.SelectWallet) + } + } + + useEffect(() => { + if (ledgerLiveAccount) { + dispatch(walletConnected(ledgerLiveAccount.address)) + } + }, [ledgerLiveAccount]) + + const account = address + // TODO: Use proper chainId here for ledger live app + const chainId = isEmbed ? 5 : web3ReactChainId return ( + +type UseRequestAccountReturn = { + requestAccount: (...params: RequestAccountParams) => Promise +} & UseRequestAccountState + +const initialState: UseRequestAccountState = { + pending: false, + account: null, + error: null, +} + +export function useRequestEthereumAccount(): UseRequestAccountReturn { + const [state, setState] = useState(initialState) + const threshold = useThreshold() + const ledgerEthereumManager = + threshold.tbtc.ledgerLiveAppManager?.ethereumManager + + const requestAccount = useCallback( + async (...params: RequestAccountParams) => { + try { + setState((oldState) => ({ + ...oldState, + pending: true, + error: null, + })) + + if (!ledgerEthereumManager) { + throw new Error("Ledger Live Ethereum Manager is not initialized") + } + + const account = await ledgerEthereumManager.connectAccount(...params) + + setState((oldState) => ({ + ...oldState, + pending: false, + account, + })) + } catch (error) { + setState((oldState) => ({ + ...oldState, + pending: false, + error, + })) + } + }, + [ledgerEthereumManager] + ) + + const result = useMemo( + () => ({ + requestAccount, + ...state, + }), + [requestAccount, state] + ) + + return result +} From 8bcd8303491d55806e8c94956a2575a08b7866ad Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Wed, 1 Nov 2023 17:40:29 +0100 Subject: [PATCH 07/69] Fix minting flow not siplaying for connected users Minting flow was not displaying for connected users in Ledger Live app. I've fixed it by checking if the address is present in redux store. Since we save it for normal wallets and also for wallets in ledger live app, it will work for both. --- src/components/withOnlyConnectedWallet.tsx | 6 ++++-- src/pages/tBTC/Bridge/Mint.tsx | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/withOnlyConnectedWallet.tsx b/src/components/withOnlyConnectedWallet.tsx index 043bc0e40..84954295d 100644 --- a/src/components/withOnlyConnectedWallet.tsx +++ b/src/components/withOnlyConnectedWallet.tsx @@ -1,14 +1,16 @@ import { ComponentType } from "react" import { H5 } from "@threshold-network/components" import { useWeb3React } from "@web3-react/core" +import { useAppSelector } from "../hooks/store" +import { selectAccountState } from "../store/account" function withOnlyConnectedWallet( Component: ComponentType, renderNotConnected?: () => JSX.Element ) { return (props: T & {}) => { - const { account, active } = useWeb3React() - if (!active || !account) { + const { address } = useAppSelector(selectAccountState) + if (!address) { return renderNotConnected ? ( renderNotConnected() ) : ( diff --git a/src/pages/tBTC/Bridge/Mint.tsx b/src/pages/tBTC/Bridge/Mint.tsx index 8d43736f4..111686de2 100644 --- a/src/pages/tBTC/Bridge/Mint.tsx +++ b/src/pages/tBTC/Bridge/Mint.tsx @@ -16,6 +16,8 @@ import { BridgeLayoutMainSection, } from "./BridgeLayout" import { BridgeProcessEmptyState } from "./components/BridgeProcessEmptyState" +import { useAppSelector } from "../../../hooks/store" +import { selectAccountState } from "../../../store/account" export const MintPage: PageComponent = ({}) => { return @@ -68,12 +70,12 @@ MintingFormPage.route = { } const MintPageLayout: PageComponent = () => { - const { active } = useWeb3React() + const { address } = useAppSelector(selectAccountState) return ( - {active ? ( + {address ? ( ) : ( From b6c18141786502a4a39374c48df23019f93e978a Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Wed, 1 Nov 2023 17:44:40 +0100 Subject: [PATCH 08/69] Assign eth address from the store to the form Assign eth address from the redux store to the provide data form. It will be an address that will be used to generate deposit address. --- src/pages/tBTC/Bridge/Minting/ProvideData.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/tBTC/Bridge/Minting/ProvideData.tsx b/src/pages/tBTC/Bridge/Minting/ProvideData.tsx index 355eb7ac0..2aa82eccb 100644 --- a/src/pages/tBTC/Bridge/Minting/ProvideData.tsx +++ b/src/pages/tBTC/Bridge/Minting/ProvideData.tsx @@ -26,6 +26,8 @@ import { useDepositTelemetry } from "../../../../hooks/tbtc/useDepositTelemetry" import { isSameETHAddress } from "../../../../web3/utils" import { supportedChainId } from "../../../../utils/getEnvVariable" import { getBridgeBTCSupportedAddressPrefixesText } from "../../../../utils/tBTC" +import { useAppSelector } from "../../../../hooks/store" +import { selectAccountState } from "../../../../store/account" export interface FormValues { ethAddress: string @@ -107,14 +109,14 @@ export const ProvideDataComponent: FC<{ const formRef = useRef>(null) const { openModal } = useModal() const threshold = useThreshold() - const { account } = useWeb3React() + const { address } = useAppSelector(selectAccountState) const { setDepositDataInLocalStorage } = useTBTCDepositDataFromLocalStorage() const depositTelemetry = useDepositTelemetry(threshold.tbtc.bitcoinNetwork) const textColor = useColorModeValue("gray.500", "gray.300") const onSubmit = async (values: FormValues) => { - if (account && !isSameETHAddress(values.ethAddress, account)) { + if (address && !isSameETHAddress(values.ethAddress, address)) { throw new Error( "The account used to generate the deposit address must be the same as the connected wallet." ) @@ -180,7 +182,7 @@ export const ProvideDataComponent: FC<{ Date: Wed, 1 Nov 2023 18:19:45 +0100 Subject: [PATCH 09/69] Make `account` property private in ledger signer We don't want to allow the account to be changed outside of the ledgerLive etherumManager so we are making the `account` property inside `LedgerLiveEthereum` signer private. The account should be changed by using `connectAccount` method in our manager. --- .../ethereum/signer/index.ts | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/ledger-live-app-manager/ethereum/signer/index.ts b/src/ledger-live-app-manager/ethereum/signer/index.ts index 4f3172a75..175416418 100644 --- a/src/ledger-live-app-manager/ethereum/signer/index.ts +++ b/src/ledger-live-app-manager/ethereum/signer/index.ts @@ -14,7 +14,7 @@ import { Deferrable } from "@ethersproject/properties" export class LedgerLiveEthereumSigner extends Signer { private _walletApiClient: WalletAPIClient private _windowMessageTransport: WindowMessageTransport - account: Account | undefined + private _account: Account | undefined constructor( provider: ethers.providers.Provider, @@ -27,43 +27,47 @@ export class LedgerLiveEthereumSigner extends Signer { this._walletApiClient = walletApiClient } + get account() { + return this._account + } + async requestAccount( params: { currencyIds?: string[] | undefined } | undefined ): Promise { this._windowMessageTransport.connect() - const _account = await this._walletApiClient.account.request(params) + const account = await this._walletApiClient.account.request(params) this._windowMessageTransport.disconnect() - this.account = _account - return this.account + this._account = account + return this._account } getAccountId(): string { - if (!this.account || !this.account.id) { + if (!this._account || !this._account.id) { throw new Error( "Account not found. Please use `requestAccount` method first." ) } - return this.account.id + return this._account.id } async getAddress(): Promise { - if (!this.account || !this.account.address) { + if (!this._account || !this._account.address) { throw new Error( "Account not found. Please use `requestAccount` method first." ) } - return this.account.address + return this._account.address } async signMessage(message: string): Promise { - if (!this.account || !this.account.address) { + if (!this._account || !this._account.address) { throw new Error( "Account not found. Please use `requestAccount` method first." ) } this._windowMessageTransport.connect() const buffer = await this._walletApiClient.message.sign( - this.account.id, + this._account.id, Buffer.from(message) ) this._windowMessageTransport.disconnect() @@ -73,7 +77,7 @@ export class LedgerLiveEthereumSigner extends Signer { async signTransaction( transaction: ethers.providers.TransactionRequest ): Promise { - if (!this.account || !this.account.address) { + if (!this._account || !this._account.address) { throw new Error( "Account not found. Please use `requestAccount` method first." ) @@ -100,7 +104,7 @@ export class LedgerLiveEthereumSigner extends Signer { this._windowMessageTransport.connect() const buffer = await this._walletApiClient.transaction.sign( - this.account.id, + this._account.id, ethereumTransaction ) this._windowMessageTransport.disconnect() @@ -110,7 +114,7 @@ export class LedgerLiveEthereumSigner extends Signer { async sendTransaction( transaction: Deferrable ): Promise { - if (!this.account || !this.account.address) { + if (!this._account || !this._account.address) { throw new Error( "Account not found. Please use `requestAccount` method first." ) @@ -138,7 +142,7 @@ export class LedgerLiveEthereumSigner extends Signer { this._windowMessageTransport.connect() const transactionHash = await this._walletApiClient.transaction.signAndBroadcast( - this.account.id, + this._account.id, ethereumTransaction ) this._windowMessageTransport.disconnect() From 4f8266fb7c9ae2c82c2959ac334fe145ff1c1ab1 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Wed, 1 Nov 2023 18:26:59 +0100 Subject: [PATCH 10/69] Create LedgerLiveAppBitcoinManager This class will manage all bitcoin transactions/message signing inside the Ledger Live app. Besides I've also renamed `EthereumManager` to `LedgerLiveAppEthereumManager` to make it more specific. --- src/ledger-live-app-manager/bitcoin/index.ts | 32 +++++++++++++++++++ src/ledger-live-app-manager/ethereum/index.ts | 10 +++--- src/ledger-live-app-manager/index.ts | 10 +++--- 3 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 src/ledger-live-app-manager/bitcoin/index.ts diff --git a/src/ledger-live-app-manager/bitcoin/index.ts b/src/ledger-live-app-manager/bitcoin/index.ts new file mode 100644 index 000000000..1468ce35c --- /dev/null +++ b/src/ledger-live-app-manager/bitcoin/index.ts @@ -0,0 +1,32 @@ +import { getWalletAPIClient, getWindowMessageTransport } from "../wallet-api" +import { + Account, + WalletAPIClient, + WindowMessageTransport, +} from "@ledgerhq/wallet-api-client" + +export class LedgerLiveAppBitcoinManager { + private _account: Account | undefined + private _windowMessageTransport: WindowMessageTransport + private _walletApiClient: WalletAPIClient + + constructor() { + this._account = undefined + this._windowMessageTransport = getWindowMessageTransport() + this._walletApiClient = getWalletAPIClient(this._windowMessageTransport) + } + + get account(): Account | undefined { + return this._account + } + + async connectAccount( + params: { currencyIds?: string[] | undefined } | undefined + ): Promise { + this._windowMessageTransport.connect() + const account = await this._walletApiClient.account.request(params) + this._windowMessageTransport.disconnect() + this._account = account + return this._account + } +} diff --git a/src/ledger-live-app-manager/ethereum/index.ts b/src/ledger-live-app-manager/ethereum/index.ts index 714663cb4..cdbd578b1 100644 --- a/src/ledger-live-app-manager/ethereum/index.ts +++ b/src/ledger-live-app-manager/ethereum/index.ts @@ -1,22 +1,22 @@ -import { LedgerLiveEthereumSigner } from "./signer" +import { LedgerLiveAppEthereumSigner } from "./signer" import { Provider } from "@ethersproject/providers" import { getWalletAPIClient, getWindowMessageTransport } from "../wallet-api" import { Account } from "@ledgerhq/wallet-api-client" -export class EthereumManager { - private _signer: LedgerLiveEthereumSigner +export class LedgerLiveAppEthereumManager { + private _signer: LedgerLiveAppEthereumSigner constructor(provider: Provider) { const windowMessageTransport = getWindowMessageTransport() const walletApiClient = getWalletAPIClient(windowMessageTransport) - this._signer = new LedgerLiveEthereumSigner( + this._signer = new LedgerLiveAppEthereumSigner( provider, windowMessageTransport, walletApiClient ) } - get signer(): LedgerLiveEthereumSigner { + get signer(): LedgerLiveAppEthereumSigner { return this._signer } diff --git a/src/ledger-live-app-manager/index.ts b/src/ledger-live-app-manager/index.ts index 854efef60..ffcec6a1e 100644 --- a/src/ledger-live-app-manager/index.ts +++ b/src/ledger-live-app-manager/index.ts @@ -1,14 +1,16 @@ -import { EthereumManager } from "./ethereum" +import { LedgerLiveAppEthereumManager } from "./ethereum" import { Provider } from "@ethersproject/providers" +import { LedgerLiveAppBitcoinManager } from "./bitcoin" /** * Class that manages all transactions on LedgerLiveApp */ export class LedgerLiveAppManager { - //TODO: Add bitcoinManager - ethereumManager: EthereumManager + bitcoinManager: LedgerLiveAppBitcoinManager + ethereumManager: LedgerLiveAppEthereumManager constructor(provider: Provider) { - this.ethereumManager = new EthereumManager(provider) + this.bitcoinManager = new LedgerLiveAppBitcoinManager() + this.ethereumManager = new LedgerLiveAppEthereumManager(provider) } } From b059feb4477c893e1dbdc6b6c765ab43663db2a2 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Wed, 1 Nov 2023 18:28:38 +0100 Subject: [PATCH 11/69] Change name for `LedgerLiveEthereumSigner` class `LedgerLiveEthereumSigner` -> `LedgerLiveAppEthereumSigner` --- src/ledger-live-app-manager/ethereum/signer/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ledger-live-app-manager/ethereum/signer/index.ts b/src/ledger-live-app-manager/ethereum/signer/index.ts index 175416418..d380dd5c9 100644 --- a/src/ledger-live-app-manager/ethereum/signer/index.ts +++ b/src/ledger-live-app-manager/ethereum/signer/index.ts @@ -11,7 +11,7 @@ import { Hex } from "@keep-network/tbtc-v2.ts" import { AddressZero } from "@ethersproject/constants" import { Deferrable } from "@ethersproject/properties" -export class LedgerLiveEthereumSigner extends Signer { +export class LedgerLiveAppEthereumSigner extends Signer { private _walletApiClient: WalletAPIClient private _windowMessageTransport: WindowMessageTransport private _account: Account | undefined @@ -158,7 +158,7 @@ export class LedgerLiveEthereumSigner extends Signer { } connect(provider: ethers.providers.Provider): Signer { - return new LedgerLiveEthereumSigner( + return new LedgerLiveAppEthereumSigner( provider, this._windowMessageTransport, this._walletApiClient From 62d0f961ccc55534173d51ff6963cd8e6c36809f Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Wed, 1 Nov 2023 18:29:58 +0100 Subject: [PATCH 12/69] Make two separate hooks for bitcoin and ethereum Makes two separate requestAccount hook for bitcoin and for ethereum networks. --- src/hooks/ledger-live-app/index.ts | 2 + .../ledger-live-app/useRequestAccount.ts | 73 +++++++++++++++++++ .../useRequestBitcoinAccount.ts | 6 ++ .../useRequestEthereumAccount.ts | 67 +---------------- 4 files changed, 83 insertions(+), 65 deletions(-) create mode 100644 src/hooks/ledger-live-app/useRequestAccount.ts create mode 100644 src/hooks/ledger-live-app/useRequestBitcoinAccount.ts diff --git a/src/hooks/ledger-live-app/index.ts b/src/hooks/ledger-live-app/index.ts index ae6c0f91c..59b5035f1 100644 --- a/src/hooks/ledger-live-app/index.ts +++ b/src/hooks/ledger-live-app/index.ts @@ -1 +1,3 @@ +export * from "./useRequestAccount" +export * from "./useRequestBitcoinAccount" export * from "./useRequestEthereumAccount" diff --git a/src/hooks/ledger-live-app/useRequestAccount.ts b/src/hooks/ledger-live-app/useRequestAccount.ts new file mode 100644 index 000000000..3e4c3e9c5 --- /dev/null +++ b/src/hooks/ledger-live-app/useRequestAccount.ts @@ -0,0 +1,73 @@ +import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client" +import { useContext, useState, useCallback, useMemo } from "react" +import { useThreshold } from "../../contexts/ThresholdContext" + +type UseRequestAccountState = { + pending: boolean + account: Account | null + error: unknown +} + +type RequestAccountParams = Parameters + +export type UseRequestAccountReturn = { + requestAccount: (...params: RequestAccountParams) => Promise +} & UseRequestAccountState + +const initialState: UseRequestAccountState = { + pending: false, + account: null, + error: null, +} + +export function useRequestAccount( + network: "Bitcoin" | "Ethereum" +): UseRequestAccountReturn { + const [state, setState] = useState(initialState) + const threshold = useThreshold() + const ledgerNetworkManager = + network === "Bitcoin" + ? threshold.tbtc.ledgerLiveAppManager?.bitcoinManager + : threshold.tbtc.ledgerLiveAppManager?.ethereumManager + + const requestAccount = useCallback( + async (...params: RequestAccountParams) => { + try { + setState((oldState) => ({ + ...oldState, + pending: true, + error: null, + })) + + if (!ledgerNetworkManager) { + throw new Error(`Ledger Live ${network} Manager is not initialized`) + } + + const account = await ledgerNetworkManager.connectAccount(...params) + + setState((oldState) => ({ + ...oldState, + pending: false, + account, + })) + } catch (error) { + setState((oldState) => ({ + ...oldState, + pending: false, + error, + })) + } + }, + [ledgerNetworkManager] + ) + + const result = useMemo( + () => ({ + requestAccount, + ...state, + }), + [requestAccount, state] + ) + + return result +} diff --git a/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts new file mode 100644 index 000000000..4365ce80f --- /dev/null +++ b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts @@ -0,0 +1,6 @@ +import { useRequestAccount, UseRequestAccountReturn } from "./useRequestAccount" + +export function useRequestBitcoinAccount(): UseRequestAccountReturn { + const result = useRequestAccount("Bitcoin") + return result +} diff --git a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts index 52a48e703..c73287afd 100644 --- a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts +++ b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts @@ -1,69 +1,6 @@ -import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client" -import { useContext, useState, useCallback, useMemo } from "react" -import { useThreshold } from "../../contexts/ThresholdContext" - -type UseRequestAccountState = { - pending: boolean - account: Account | null - error: unknown -} - -type RequestAccountParams = Parameters - -type UseRequestAccountReturn = { - requestAccount: (...params: RequestAccountParams) => Promise -} & UseRequestAccountState - -const initialState: UseRequestAccountState = { - pending: false, - account: null, - error: null, -} +import { UseRequestAccountReturn, useRequestAccount } from "./useRequestAccount" export function useRequestEthereumAccount(): UseRequestAccountReturn { - const [state, setState] = useState(initialState) - const threshold = useThreshold() - const ledgerEthereumManager = - threshold.tbtc.ledgerLiveAppManager?.ethereumManager - - const requestAccount = useCallback( - async (...params: RequestAccountParams) => { - try { - setState((oldState) => ({ - ...oldState, - pending: true, - error: null, - })) - - if (!ledgerEthereumManager) { - throw new Error("Ledger Live Ethereum Manager is not initialized") - } - - const account = await ledgerEthereumManager.connectAccount(...params) - - setState((oldState) => ({ - ...oldState, - pending: false, - account, - })) - } catch (error) { - setState((oldState) => ({ - ...oldState, - pending: false, - error, - })) - } - }, - [ledgerEthereumManager] - ) - - const result = useMemo( - () => ({ - requestAccount, - ...state, - }), - [requestAccount, state] - ) - + const result = useRequestAccount("Ethereum") return result } From 100b0c68b957fb201e78bd522239543cf53f0704 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Wed, 1 Nov 2023 18:32:06 +0100 Subject: [PATCH 13/69] Make it possible to connect bitcoin account We want to connect bitcoin account at step 2 of the minting flow. For this case I've created a separate button that allows the user to connect a bitcoin account inside ledger live app. After that he will be able to send the bitcoins form the account he had choosen to a deposit address that he just generated. --- src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx index fb2b9814e..b24279a5e 100644 --- a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx +++ b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx @@ -1,4 +1,4 @@ -import { FC, ComponentProps } from "react" +import { FC, ComponentProps, useCallback } from "react" import { BodyMd, Box, @@ -24,6 +24,8 @@ import { MintingStep } from "../../../../types/tbtc" import { QRCode } from "../../../../components/QRCode" import withOnlyConnectedWallet from "../../../../components/withOnlyConnectedWallet" import { ViewInBlockExplorerProps } from "../../../../components/ViewInBlockExplorer" +import { useEmbedFeatureFlag } from "../../../../hooks/useEmbedFeatureFlag" +import { useRequestBitcoinAccount } from "../../../../hooks/ledger-live-app" const AddressRow: FC< { address: string; text: string } & Pick @@ -121,6 +123,15 @@ const MakeDepositComponent: FC<{ const { btcDepositAddress, ethAddress, btcRecoveryAddress, updateState } = useTbtcState() + const { isEmbed } = useEmbedFeatureFlag() + const { requestAccount, account: ledgerBitcoinAccount } = + useRequestBitcoinAccount() + + const chooseBitcoinAccount = useCallback(async () => { + // TODO: Use currencyId based on the chainId that is used + await requestAccount({ currencyIds: ["bitcoin_testnet"] }) + }, [requestAccount]) + return ( <> {/* TODO: No need to use button here. We can replace it with just some text */} + {isEmbed && !!ledgerBitcoinAccount?.address && ( + + )} + {isEmbed && ( + + )} ) From dad6a2e9fdc4b4196077232e2528850ebcc2d4b5 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Thu, 2 Nov 2023 14:22:42 +0100 Subject: [PATCH 14/69] Rewamp (1) - install wallet-connect-client-react I've decided to rewamp the whole implementation as it seems that we don't really need to keep everything in ledgerLiveAppManager. The idea now is to keep only LedgerLiveAppEthereumSigner (which in the future will be placed in tbtc-v2 SDK) for integrating with contracts, and create a context for ledger live app where we will keep connected ethereum and bitcoin addresses. The LedgerLiveAppEthereumSigner will use `@ledgerhq/wallet-api-client` package, and for the rest things (connecting ethereum wallet, connecting bitcoin wallet, sending bitcoins to address), we will use `wallet-connect-client-react` lib. The first step is to install the `wallet-connect-client-react` --- package.json | 1 + yarn.lock | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d57738b5..3b4731969 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@keep-network/tbtc-v2.ts": "2.0.0-dev.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", diff --git a/yarn.lock b/yarn.lock index a9b5d7f94..55bf77d1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3567,7 +3567,14 @@ resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.10.1.tgz#5bd16082261d7364eabb511c788f00937dac588d" integrity sha512-z+ILK8Q3y+nfUl43ctCPuR4Y2bIxk/ooCQFwZxhtci1EhAtMDzMAx2W25qx8G1PPL9UUOdnUax19+F0OjXoj4w== -"@ledgerhq/wallet-api-client@^1.2.0": +"@ledgerhq/wallet-api-client-react@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/wallet-api-client-react/-/wallet-api-client-react-1.1.1.tgz#3785ed0fae3634e2d8baa7fbff16dc749f07ec0a" + integrity sha512-51FkETxWjiLAVPIRvg65AMZfaLMl9ClQXT0P0Dvh11Ws5fsTXDomhJ6bioenrHpAQRyW37e8oFzOJLYiMX2ukA== + dependencies: + "@ledgerhq/wallet-api-client" "1.2.0" + +"@ledgerhq/wallet-api-client@1.2.0", "@ledgerhq/wallet-api-client@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ledgerhq/wallet-api-client/-/wallet-api-client-1.2.0.tgz#324eef4068d1513b3e78029f8fe4e300de4eb5ca" integrity sha512-WnMnRBrQLYya61Ao6eqHDOlCpS5AItZyEH6sx4hgIp3VoGmQavm8xj3+Iu74lUBhR8d8Xxn2iEGNKWeGgEhy8g== From b22246953f299f1f0c384e6cbc66ffc5b27201b5 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Thu, 2 Nov 2023 14:39:10 +0100 Subject: [PATCH 15/69] Rewamp (2) - TransportProvider Adds TransportProvider needed for `wallet-api-client-react`. This will allow us to use the hooks from the libs (like `useRequestAccount` etc.). --- src/contexts/TransportProvider.tsx | 24 ++++++++++++++++++++++++ src/index.tsx | 5 ++++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/contexts/TransportProvider.tsx diff --git a/src/contexts/TransportProvider.tsx b/src/contexts/TransportProvider.tsx new file mode 100644 index 000000000..76417ecb2 --- /dev/null +++ b/src/contexts/TransportProvider.tsx @@ -0,0 +1,24 @@ +import { WalletAPIProvider } from "@ledgerhq/wallet-api-client-react" +import { Transport, WindowMessageTransport } from "@ledgerhq/wallet-api-client" +import { FC } from "react" + +const TransportProvider: FC = ({ children }) => { + const getWalletAPITransport = (): Transport => { + if (typeof window === "undefined") { + return { + onMessage: undefined, + send: () => {}, + } + } + + const transport = new WindowMessageTransport() + transport.connect() + return transport + } + + const transport = getWalletAPITransport() + + return {children} +} + +export default TransportProvider 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") ) From edf9afddf6393d11dd86ce1b8bc856f3e2849a8e Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Thu, 2 Nov 2023 14:41:30 +0100 Subject: [PATCH 16/69] Rewamp (3) - LedgerLiveAppContext This context is where we will store our eth and btc addresses for ledger live app. Why we need it? We have to detect when the address is changed in our LedgerLiveApp and then update our signer inside threshold lib in `ThresholdContext`. For website we could just detect it with our `useWeb3React` hook, but in this case it is not that simple. The only way in the previous implementation was to get the actual address from the redux store, but this required to move `` above the ` { return ( - - - - - - - - - - - + + + + + + + + + + + + + ) diff --git a/src/contexts/LedgerLiveAppContext.tsx b/src/contexts/LedgerLiveAppContext.tsx new file mode 100644 index 000000000..5b1d8e1f5 --- /dev/null +++ b/src/contexts/LedgerLiveAppContext.tsx @@ -0,0 +1,33 @@ +import React, { createContext, useState } from "react" + +interface LedgerLiveAppContextState { + ethAddress: string | undefined + btcAddress: string | undefined + setEthAddress: React.Dispatch> + setBtcAddress: React.Dispatch> +} + +export const LedgerLiveAppContext = createContext({ + ethAddress: undefined, + btcAddress: undefined, + setEthAddress: () => {}, + setBtcAddress: () => {}, +}) + +export const LedgerLiveAppProvider: React.FC = ({ children }) => { + const [ethAddress, setEthAddress] = useState(undefined) + const [btcAddress, setBtcAddress] = useState(undefined) + + return ( + + {children} + + ) +} From 7b5105e8e882a96b592df295b780722502ea16c4 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Thu, 2 Nov 2023 14:59:51 +0100 Subject: [PATCH 17/69] Rewamp (4) - use ledger's wallet api for react This is probably final step (or one of the final steps) in this rewamp. There are few bigger changes in this commit: 1. Remove `LedgerLiveAppManager` completely We won't need that class anymore. The only thing we need is `LedgerLiveAppEthereumSigner` to interact with eth contracts. The rest thigns (like connecting wallets, and sending bitcoins) will be handled with hooks from ledger's wallet-api for react. 2. Store `LedgerLiveAppEthereumSigner` instance in our `TBTC` class in `thershold-ts` lib. We also remove the need to pass anything related to ledger live app through `ThresholdConfig`, like we did with `LedgerLiveAppManager`. The Ledger Live App Signer will be created inside the class if necessary. 3. Refactor `useRequestEthereumAccount` and `useRequestBitcoinAccount` Those hooks will now use `useRequestAccount` hook from `@ledgerht/wallet-api-client-react`, but additionaly the `requestAccount` method will save the bitcoin/ethereum account in the LedgerLiveContext and ethereum account in the LedgerLiveAppEthereumSigner (through TBTC method). --- src/components/Navbar/index.tsx | 2 +- src/contexts/ThresholdContext.tsx | 10 ++- src/hooks/ledger-live-app/index.ts | 1 - .../ledger-live-app/useRequestAccount.ts | 73 ------------------- .../useRequestBitcoinAccount.ts | 44 ++++++++++- .../useRequestEthereumAccount.ts | 43 ++++++++++- .../index.ts | 21 +++--- .../utils.ts} | 0 src/ledger-live-app-manager/bitcoin/index.ts | 32 -------- src/ledger-live-app-manager/ethereum/index.ts | 32 -------- src/ledger-live-app-manager/index.ts | 16 ---- src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx | 2 +- src/threshold-ts/index.ts | 7 +- src/threshold-ts/tbtc/index.ts | 38 +++++++--- src/threshold-ts/types/index.ts | 2 - src/utils/getLedgerLiveAppManager.ts | 13 ---- src/utils/getThresholdLib.ts | 2 - 17 files changed, 128 insertions(+), 210 deletions(-) delete mode 100644 src/hooks/ledger-live-app/useRequestAccount.ts rename src/{ledger-live-app-manager/ethereum/signer => ledger-live-app-eth-signer}/index.ts (92%) rename src/{ledger-live-app-manager/wallet-api/index.ts => ledger-live-app-eth-signer/utils.ts} (100%) delete mode 100644 src/ledger-live-app-manager/bitcoin/index.ts delete mode 100644 src/ledger-live-app-manager/ethereum/index.ts delete mode 100644 src/ledger-live-app-manager/index.ts delete mode 100644 src/utils/getLedgerLiveAppManager.ts diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index 14e177456..a4f122eff 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -25,7 +25,7 @@ const Navbar: FC = () => { const openWalletModal = () => { if (isEmbed) { // TODO: Use proper currency based on chainID - requestAccount({ currencyIds: ["ethereum_goerli"] }) + requestAccount() } else { openModal(ModalType.SelectWallet) } diff --git a/src/contexts/ThresholdContext.tsx b/src/contexts/ThresholdContext.tsx index c1766cb92..d3c416203 100644 --- a/src/contexts/ThresholdContext.tsx +++ b/src/contexts/ThresholdContext.tsx @@ -5,6 +5,7 @@ import { threshold, } from "../utils/getThresholdLib" import { supportedChainId } from "../utils/getEnvVariable" +import { LedgerLiveAppContext } from "./LedgerLiveAppContext" const ThresholdContext = createContext(threshold) @@ -15,6 +16,7 @@ export const useThreshold = () => { export const ThresholdProvider: FC = ({ children }) => { const { library, active, account } = useWeb3React() const hasThresholdLibConfigBeenUpdated = useRef(false) + const { ethAddress, btcAddress } = useContext(LedgerLiveAppContext) useEffect(() => { if (active && library && account) { @@ -25,7 +27,6 @@ export const ThresholdProvider: FC = ({ children }) => { account, }, bitcoin: threshold.config.bitcoin, - ledgerLiveAppManager: threshold.config.ledgerLiveAppManager, }) hasThresholdLibConfigBeenUpdated.current = true } @@ -37,12 +38,17 @@ export const ThresholdProvider: FC = ({ children }) => { providerOrSigner: getDefaultThresholdLibProvider(), }, bitcoin: threshold.config.bitcoin, - ledgerLiveAppManager: threshold.config.ledgerLiveAppManager, }) hasThresholdLibConfigBeenUpdated.current = false } }, [library, active, account]) + // TODO: Remove this useEffect + useEffect(() => { + console.log("ethAddress: ", ethAddress) + console.log("btcAddress: ", btcAddress) + }, [ethAddress, btcAddress]) + return ( {children} diff --git a/src/hooks/ledger-live-app/index.ts b/src/hooks/ledger-live-app/index.ts index 59b5035f1..1763656ca 100644 --- a/src/hooks/ledger-live-app/index.ts +++ b/src/hooks/ledger-live-app/index.ts @@ -1,3 +1,2 @@ -export * from "./useRequestAccount" export * from "./useRequestBitcoinAccount" export * from "./useRequestEthereumAccount" diff --git a/src/hooks/ledger-live-app/useRequestAccount.ts b/src/hooks/ledger-live-app/useRequestAccount.ts deleted file mode 100644 index 3e4c3e9c5..000000000 --- a/src/hooks/ledger-live-app/useRequestAccount.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client" -import { useContext, useState, useCallback, useMemo } from "react" -import { useThreshold } from "../../contexts/ThresholdContext" - -type UseRequestAccountState = { - pending: boolean - account: Account | null - error: unknown -} - -type RequestAccountParams = Parameters - -export type UseRequestAccountReturn = { - requestAccount: (...params: RequestAccountParams) => Promise -} & UseRequestAccountState - -const initialState: UseRequestAccountState = { - pending: false, - account: null, - error: null, -} - -export function useRequestAccount( - network: "Bitcoin" | "Ethereum" -): UseRequestAccountReturn { - const [state, setState] = useState(initialState) - const threshold = useThreshold() - const ledgerNetworkManager = - network === "Bitcoin" - ? threshold.tbtc.ledgerLiveAppManager?.bitcoinManager - : threshold.tbtc.ledgerLiveAppManager?.ethereumManager - - const requestAccount = useCallback( - async (...params: RequestAccountParams) => { - try { - setState((oldState) => ({ - ...oldState, - pending: true, - error: null, - })) - - if (!ledgerNetworkManager) { - throw new Error(`Ledger Live ${network} Manager is not initialized`) - } - - const account = await ledgerNetworkManager.connectAccount(...params) - - setState((oldState) => ({ - ...oldState, - pending: false, - account, - })) - } catch (error) { - setState((oldState) => ({ - ...oldState, - pending: false, - error, - })) - } - }, - [ledgerNetworkManager] - ) - - const result = useMemo( - () => ({ - requestAccount, - ...state, - }), - [requestAccount, state] - ) - - return result -} diff --git a/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts index 4365ce80f..4f02ffbed 100644 --- a/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts +++ b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts @@ -1,6 +1,44 @@ -import { useRequestAccount, UseRequestAccountReturn } from "./useRequestAccount" +import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client" +import { useRequestAccount as useWalletApiRequestAccount } from "@ledgerhq/wallet-api-client-react" +import { useCallback, useContext, useEffect } from "react" +import { LedgerLiveAppContext } from "../../contexts/LedgerLiveAppContext" +import { useThreshold } from "../../contexts/ThresholdContext" + +type UseRequestAccountState = { + pending: boolean + account: Account | null + error: unknown +} + +type RequestAccountParams = Parameters + +type UseRequestAccountReturn = { + requestAccount: (...params: RequestAccountParams) => Promise +} & UseRequestAccountState export function useRequestBitcoinAccount(): UseRequestAccountReturn { - const result = useRequestAccount("Bitcoin") - return result + const { setBtcAddress } = useContext(LedgerLiveAppContext) + const useRequestAccountReturn = useWalletApiRequestAccount() + const { account, requestAccount } = useRequestAccountReturn + const threshold = useThreshold() + + useEffect(() => { + // TODO: Get currencyId based on the chainId + if (account && account.address && account?.currency === "bitcoin_testnet") { + setBtcAddress(account?.address || undefined) + threshold.tbtc.setLedgerLiveAppEthAccount(account) + } + + if (!account || !account.address) { + setBtcAddress(undefined) + } + }, [account]) + + const requestBitcoinAccount = useCallback(async () => { + console.log("requesting Bitcoin Account...") + // TODO: Get currencyId based on the chainId + await requestAccount({ currencyIds: ["bitcoin_testnet"] }) + }, [requestAccount]) + + return { ...useRequestAccountReturn, requestAccount: requestBitcoinAccount } } diff --git a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts index c73287afd..2f50c768d 100644 --- a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts +++ b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts @@ -1,6 +1,43 @@ -import { UseRequestAccountReturn, useRequestAccount } from "./useRequestAccount" +import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client" +import { useRequestAccount as useWalletApiRequestAccount } from "@ledgerhq/wallet-api-client-react" +import { useCallback, useContext, useEffect } from "react" +import { LedgerLiveAppContext } from "../../contexts/LedgerLiveAppContext" +import { useThreshold } from "../../contexts/ThresholdContext" + +type UseRequestAccountState = { + pending: boolean + account: Account | null + error: unknown +} + +type RequestAccountParams = Parameters + +type UseRequestAccountReturn = { + requestAccount: (...params: RequestAccountParams) => Promise +} & UseRequestAccountState export function useRequestEthereumAccount(): UseRequestAccountReturn { - const result = useRequestAccount("Ethereum") - return result + const { setEthAddress } = useContext(LedgerLiveAppContext) + const useRequestAccountReturn = useWalletApiRequestAccount() + const { account, requestAccount } = useRequestAccountReturn + const threshold = useThreshold() + + useEffect(() => { + // TODO: Get currencyId based on the chainId + if (account && account.address && account?.currency === "ethereum_goerli") { + setEthAddress(account?.address || undefined) + threshold.tbtc.setLedgerLiveAppEthAccount(account) + } + + if (!account || !account.address) { + setEthAddress(undefined) + } + }, [account]) + + const requestEthereumAccount = useCallback(async () => { + // TODO: Get currencyId based on the chainId + await requestAccount({ currencyIds: ["ethereum_goerli"] }) + }, [requestAccount]) + + return { ...useRequestAccountReturn, requestAccount: requestEthereumAccount } } diff --git a/src/ledger-live-app-manager/ethereum/signer/index.ts b/src/ledger-live-app-eth-signer/index.ts similarity index 92% rename from src/ledger-live-app-manager/ethereum/signer/index.ts rename to src/ledger-live-app-eth-signer/index.ts index d380dd5c9..6a1427747 100644 --- a/src/ledger-live-app-manager/ethereum/signer/index.ts +++ b/src/ledger-live-app-eth-signer/index.ts @@ -10,27 +10,28 @@ import BigNumber from "bignumber.js" import { Hex } from "@keep-network/tbtc-v2.ts" import { AddressZero } from "@ethersproject/constants" import { Deferrable } from "@ethersproject/properties" +import { getWalletAPIClient, getWindowMessageTransport } from "./utils" export class LedgerLiveAppEthereumSigner extends Signer { private _walletApiClient: WalletAPIClient private _windowMessageTransport: WindowMessageTransport private _account: Account | undefined - constructor( - provider: ethers.providers.Provider, - windowMessageTransport: WindowMessageTransport, - walletApiClient: WalletAPIClient - ) { + constructor(provider: ethers.providers.Provider) { super() ethers.utils.defineReadOnly(this, "provider", provider || null) - this._windowMessageTransport = windowMessageTransport - this._walletApiClient = walletApiClient + this._windowMessageTransport = getWindowMessageTransport() + this._walletApiClient = getWalletAPIClient(this._windowMessageTransport) } get account() { return this._account } + setAccount(account: Account): void { + this._account = account + } + async requestAccount( params: { currencyIds?: string[] | undefined } | undefined ): Promise { @@ -158,10 +159,6 @@ export class LedgerLiveAppEthereumSigner extends Signer { } connect(provider: ethers.providers.Provider): Signer { - return new LedgerLiveAppEthereumSigner( - provider, - this._windowMessageTransport, - this._walletApiClient - ) + return new LedgerLiveAppEthereumSigner(provider) } } diff --git a/src/ledger-live-app-manager/wallet-api/index.ts b/src/ledger-live-app-eth-signer/utils.ts similarity index 100% rename from src/ledger-live-app-manager/wallet-api/index.ts rename to src/ledger-live-app-eth-signer/utils.ts diff --git a/src/ledger-live-app-manager/bitcoin/index.ts b/src/ledger-live-app-manager/bitcoin/index.ts deleted file mode 100644 index 1468ce35c..000000000 --- a/src/ledger-live-app-manager/bitcoin/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { getWalletAPIClient, getWindowMessageTransport } from "../wallet-api" -import { - Account, - WalletAPIClient, - WindowMessageTransport, -} from "@ledgerhq/wallet-api-client" - -export class LedgerLiveAppBitcoinManager { - private _account: Account | undefined - private _windowMessageTransport: WindowMessageTransport - private _walletApiClient: WalletAPIClient - - constructor() { - this._account = undefined - this._windowMessageTransport = getWindowMessageTransport() - this._walletApiClient = getWalletAPIClient(this._windowMessageTransport) - } - - get account(): Account | undefined { - return this._account - } - - async connectAccount( - params: { currencyIds?: string[] | undefined } | undefined - ): Promise { - this._windowMessageTransport.connect() - const account = await this._walletApiClient.account.request(params) - this._windowMessageTransport.disconnect() - this._account = account - return this._account - } -} diff --git a/src/ledger-live-app-manager/ethereum/index.ts b/src/ledger-live-app-manager/ethereum/index.ts deleted file mode 100644 index cdbd578b1..000000000 --- a/src/ledger-live-app-manager/ethereum/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { LedgerLiveAppEthereumSigner } from "./signer" -import { Provider } from "@ethersproject/providers" -import { getWalletAPIClient, getWindowMessageTransport } from "../wallet-api" -import { Account } from "@ledgerhq/wallet-api-client" - -export class LedgerLiveAppEthereumManager { - private _signer: LedgerLiveAppEthereumSigner - - constructor(provider: Provider) { - const windowMessageTransport = getWindowMessageTransport() - const walletApiClient = getWalletAPIClient(windowMessageTransport) - this._signer = new LedgerLiveAppEthereumSigner( - provider, - windowMessageTransport, - walletApiClient - ) - } - - get signer(): LedgerLiveAppEthereumSigner { - return this._signer - } - - get account(): Account | undefined { - return this._signer.account - } - - connectAccount = async ( - params: { currencyIds?: string[] | undefined } | undefined - ): Promise => { - return await this._signer.requestAccount(params) - } -} diff --git a/src/ledger-live-app-manager/index.ts b/src/ledger-live-app-manager/index.ts deleted file mode 100644 index ffcec6a1e..000000000 --- a/src/ledger-live-app-manager/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { LedgerLiveAppEthereumManager } from "./ethereum" -import { Provider } from "@ethersproject/providers" -import { LedgerLiveAppBitcoinManager } from "./bitcoin" - -/** - * Class that manages all transactions on LedgerLiveApp - */ -export class LedgerLiveAppManager { - bitcoinManager: LedgerLiveAppBitcoinManager - ethereumManager: LedgerLiveAppEthereumManager - - constructor(provider: Provider) { - this.bitcoinManager = new LedgerLiveAppBitcoinManager() - this.ethereumManager = new LedgerLiveAppEthereumManager(provider) - } -} diff --git a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx index b24279a5e..cd481b831 100644 --- a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx +++ b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx @@ -129,7 +129,7 @@ const MakeDepositComponent: FC<{ const chooseBitcoinAccount = useCallback(async () => { // TODO: Use currencyId based on the chainId that is used - await requestAccount({ currencyIds: ["bitcoin_testnet"] }) + await requestAccount() }, [requestAccount]) return ( diff --git a/src/threshold-ts/index.ts b/src/threshold-ts/index.ts index 159b1041d..09e9019c1 100644 --- a/src/threshold-ts/index.ts +++ b/src/threshold-ts/index.ts @@ -31,12 +31,7 @@ export class Threshold { this.multicall, config.ethereum ) - this.tbtc = new TBTC( - config.ethereum, - config.bitcoin, - this.multicall, - config.ledgerLiveAppManager - ) + this.tbtc = new TBTC(config.ethereum, config.bitcoin, this.multicall) } updateConfig = (config: ThresholdConfig) => { diff --git a/src/threshold-ts/tbtc/index.ts b/src/threshold-ts/tbtc/index.ts index 261f914d1..eeb8ca4df 100644 --- a/src/threshold-ts/tbtc/index.ts +++ b/src/threshold-ts/tbtc/index.ts @@ -45,7 +45,7 @@ import { import TBTCVault from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" import Bridge from "@keep-network/tbtc-v2/artifacts/Bridge.json" import TBTCToken from "@keep-network/tbtc-v2/artifacts/TBTC.json" -import { BigNumber, BigNumberish, Contract, utils } from "ethers" +import { BigNumber, BigNumberish, Contract, ethers, utils } from "ethers" import { ContractCall, IMulticall } from "../multicall" import { BlockTag } from "@ethersproject/abstract-provider" import { LogDescription } from "ethers/lib/utils" @@ -54,7 +54,8 @@ import { findWalletForRedemption, } from "@keep-network/tbtc-v2.ts/dist/src/redemption" import { TBTCToken as ChainTBTCToken } from "@keep-network/tbtc-v2.ts/dist/src/chain" -import { LedgerLiveAppManager } from "../../ledger-live-app-manager" +import { LedgerLiveAppEthereumSigner } from "../../ledger-live-app-eth-signer" +import { Account } from "@ledgerhq/wallet-api-client" export enum BridgeActivityStatus { PENDING = "PENDING", @@ -179,7 +180,12 @@ export interface ITBTC { readonly tokenContract: Contract - readonly ledgerLiveAppManager: LedgerLiveAppManager | undefined + /** + * Saves the Account object to the Ledger Live App Ethereum signer. + * @param account Account object returned from `requestAccount` function (from + * ledger's wallet-api). + */ + setLedgerLiveAppEthAccount(account: Account): void /** * Suggests a wallet that should be used as the deposit target at the given @@ -424,13 +430,12 @@ export class TBTC implements ITBTC { private _redemptionTreasuryFeeDivisor: BigNumber | undefined - private _ledgerLiveAppManager: LedgerLiveAppManager | undefined + private _ledgerLiveAppEthereumSigner: LedgerLiveAppEthereumSigner | undefined constructor( ethereumConfig: EthereumConfig, bitcoinConfig: BitcoinConfig, - multicall: IMulticall, - ledgerLiveAppManager?: LedgerLiveAppManager + multicall: IMulticall ) { if (!bitcoinConfig.client && !bitcoinConfig.credentials) { throw new Error( @@ -477,11 +482,13 @@ export class TBTC implements ITBTC { ethereumConfig.providerOrSigner, ethereumConfig.account ) - this._ledgerLiveAppManager = ledgerLiveAppManager - } - - get ledgerLiveAppManager(): LedgerLiveAppManager | undefined { - return this._ledgerLiveAppManager + if (ethereumConfig.providerOrSigner instanceof ethers.providers.Provider) { + this._ledgerLiveAppEthereumSigner = new LedgerLiveAppEthereumSigner( + ethereumConfig.providerOrSigner + ) + } else { + this._ledgerLiveAppEthereumSigner = undefined + } } get bitcoinNetwork(): BitcoinNetwork { @@ -500,6 +507,15 @@ export class TBTC implements ITBTC { return this._tokenContract } + setLedgerLiveAppEthAccount(account: Account): void { + if (!this._ledgerLiveAppEthereumSigner) { + throw new Error( + "Ledger Live App Ethereum Signer is not defined in threshold-ts lib." + ) + } + this._ledgerLiveAppEthereumSigner.setAccount(account) + } + suggestDepositWallet = async (): Promise => { return await this._bridge.activeWalletPublicKey() } diff --git a/src/threshold-ts/types/index.ts b/src/threshold-ts/types/index.ts index ebb56be22..2f879df53 100644 --- a/src/threshold-ts/types/index.ts +++ b/src/threshold-ts/types/index.ts @@ -8,7 +8,6 @@ import { Credentials, } from "@keep-network/tbtc-v2.ts/dist/src/electrum" import { providers, Signer } from "ethers" -import { LedgerLiveAppManager } from "../../ledger-live-app-manager" export interface EthereumConfig { providerOrSigner: providers.Provider | Signer @@ -47,7 +46,6 @@ export interface BitcoinConfig { export interface ThresholdConfig { ethereum: EthereumConfig bitcoin: BitcoinConfig - ledgerLiveAppManager?: LedgerLiveAppManager } export { BitcoinNetwork } diff --git a/src/utils/getLedgerLiveAppManager.ts b/src/utils/getLedgerLiveAppManager.ts deleted file mode 100644 index e66166c05..000000000 --- a/src/utils/getLedgerLiveAppManager.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { JsonRpcProvider, Provider } from "@ethersproject/providers" -import { EnvVariable } from "../enums" -import { LedgerLiveAppManager } from "../ledger-live-app-manager" -import { getEnvVariable } from "./getEnvVariable" -import { getDefaultThresholdLibProvider } from "./getThresholdLib" - -export const getLedgerLiveAppManager = (provider?: Provider) => { - return new LedgerLiveAppManager(provider || getDefaultThresholdLibProvider()) -} - -export const ledgerLiveAppManager = getLedgerLiveAppManager( - new JsonRpcProvider(getEnvVariable(EnvVariable.ETH_HOSTNAME_HTTP)) -) diff --git a/src/utils/getThresholdLib.ts b/src/utils/getThresholdLib.ts index db8964f49..5b83a1f7a 100644 --- a/src/utils/getThresholdLib.ts +++ b/src/utils/getThresholdLib.ts @@ -9,7 +9,6 @@ import { BitcoinNetwork, BitcoinClientCredentials, } from "../threshold-ts/types" -import { getLedgerLiveAppManager } from "./getLedgerLiveAppManager" function getBitcoinConfig(): BitcoinConfig { const network = @@ -48,7 +47,6 @@ export const getThresholdLib = (providerOrSigner?: Provider | Signer) => { providerOrSigner: providerOrSigner || getDefaultThresholdLibProvider(), }, bitcoin: getBitcoinConfig(), - ledgerLiveAppManager: getLedgerLiveAppManager(), }) } From 46344d77d2b967786def53d4dfba219d0d5571ef Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Thu, 2 Nov 2023 15:57:42 +0100 Subject: [PATCH 18/69] Rewamp (5) - create `useIsActive` hook This hook will work both in website view and inside the Ledger Live App. If `isEmbed` flag is set to false it will return the values based on the `useWeb3React` hook. If it's true the returned values will be based on `LedgerLiveApp` context. --- src/components/withOnlyConnectedWallet.tsx | 7 ++-- src/hooks/useIsActive.ts | 33 +++++++++++++++++++ src/pages/tBTC/Bridge/Mint.tsx | 7 ++-- src/pages/tBTC/Bridge/Minting/ProvideData.tsx | 10 +++--- 4 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 src/hooks/useIsActive.ts diff --git a/src/components/withOnlyConnectedWallet.tsx b/src/components/withOnlyConnectedWallet.tsx index 84954295d..109136212 100644 --- a/src/components/withOnlyConnectedWallet.tsx +++ b/src/components/withOnlyConnectedWallet.tsx @@ -1,16 +1,15 @@ import { ComponentType } from "react" import { H5 } from "@threshold-network/components" import { useWeb3React } from "@web3-react/core" -import { useAppSelector } from "../hooks/store" -import { selectAccountState } from "../store/account" +import { useIsActive } from "../hooks/useIsActive" function withOnlyConnectedWallet( Component: ComponentType, renderNotConnected?: () => JSX.Element ) { return (props: T & {}) => { - const { address } = useAppSelector(selectAccountState) - if (!address) { + const { account, isActive } = useIsActive() + if (!isActive || !account) { return renderNotConnected ? ( renderNotConnected() ) : ( diff --git a/src/hooks/useIsActive.ts b/src/hooks/useIsActive.ts new file mode 100644 index 000000000..350925cf4 --- /dev/null +++ b/src/hooks/useIsActive.ts @@ -0,0 +1,33 @@ +import { useWeb3React } from "@web3-react/core" +import { useContext, useMemo } from "react" +import { LedgerLiveAppContext } from "../contexts/LedgerLiveAppContext" +import { useEmbedFeatureFlag } from "./useEmbedFeatureFlag" +import { useLocalStorage } from "./useLocalStorage" + +type UseIsActiveResult = { + account: string | undefined + isActive: boolean +} + +/** + * 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, account } = useWeb3React() + const { ethAddress } = useContext(LedgerLiveAppContext) + const { isEmbed } = useEmbedFeatureFlag() + + const isActive = useMemo(() => { + if (isEmbed) { + return !!ethAddress + } + return !!active && !!account + }, [ethAddress, active, account]) + + return { + account: (isEmbed ? ethAddress : account) || undefined, + isActive, + } +} diff --git a/src/pages/tBTC/Bridge/Mint.tsx b/src/pages/tBTC/Bridge/Mint.tsx index 111686de2..4933d5fe7 100644 --- a/src/pages/tBTC/Bridge/Mint.tsx +++ b/src/pages/tBTC/Bridge/Mint.tsx @@ -16,8 +16,7 @@ import { BridgeLayoutMainSection, } from "./BridgeLayout" import { BridgeProcessEmptyState } from "./components/BridgeProcessEmptyState" -import { useAppSelector } from "../../../hooks/store" -import { selectAccountState } from "../../../store/account" +import { useIsActive } from "../../../hooks/useIsActive" export const MintPage: PageComponent = ({}) => { return @@ -70,12 +69,12 @@ MintingFormPage.route = { } const MintPageLayout: PageComponent = () => { - const { address } = useAppSelector(selectAccountState) + const { isActive } = useIsActive() return ( - {address ? ( + {isActive ? ( ) : ( diff --git a/src/pages/tBTC/Bridge/Minting/ProvideData.tsx b/src/pages/tBTC/Bridge/Minting/ProvideData.tsx index 2aa82eccb..80e20c873 100644 --- a/src/pages/tBTC/Bridge/Minting/ProvideData.tsx +++ b/src/pages/tBTC/Bridge/Minting/ProvideData.tsx @@ -18,7 +18,6 @@ import { MintingStep } from "../../../../types/tbtc" import { useModal } from "../../../../hooks/useModal" import { ModalType } from "../../../../enums" import { useThreshold } from "../../../../contexts/ThresholdContext" -import { useWeb3React } from "@web3-react/core" import { BitcoinNetwork } from "../../../../threshold-ts/types" import { useTBTCDepositDataFromLocalStorage } from "../../../../hooks/tbtc" import withOnlyConnectedWallet from "../../../../components/withOnlyConnectedWallet" @@ -26,8 +25,7 @@ import { useDepositTelemetry } from "../../../../hooks/tbtc/useDepositTelemetry" import { isSameETHAddress } from "../../../../web3/utils" import { supportedChainId } from "../../../../utils/getEnvVariable" import { getBridgeBTCSupportedAddressPrefixesText } from "../../../../utils/tBTC" -import { useAppSelector } from "../../../../hooks/store" -import { selectAccountState } from "../../../../store/account" +import { useIsActive } from "../../../../hooks/useIsActive" export interface FormValues { ethAddress: string @@ -109,14 +107,14 @@ export const ProvideDataComponent: FC<{ const formRef = useRef>(null) const { openModal } = useModal() const threshold = useThreshold() - const { address } = useAppSelector(selectAccountState) + const { account } = useIsActive() const { setDepositDataInLocalStorage } = useTBTCDepositDataFromLocalStorage() const depositTelemetry = useDepositTelemetry(threshold.tbtc.bitcoinNetwork) const textColor = useColorModeValue("gray.500", "gray.300") const onSubmit = async (values: FormValues) => { - if (address && !isSameETHAddress(values.ethAddress, address)) { + if (account && !isSameETHAddress(values.ethAddress, account)) { throw new Error( "The account used to generate the deposit address must be the same as the connected wallet." ) @@ -182,7 +180,7 @@ export const ProvideDataComponent: FC<{ Date: Thu, 2 Nov 2023 23:39:42 +0100 Subject: [PATCH 19/69] Add the possibility to upload JSON file For this we've added `Continue` button in Step 1 - it is visible only if the app is embed. The `useIsActive` hook is used to get the proper account base on the `isEmbed` feature flag. --- src/hooks/tbtc/useTBTCDepositDataFromLocalStorage.ts | 4 ++-- src/pages/tBTC/Bridge/Mint.tsx | 3 +-- src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx | 4 ++-- src/pages/tBTC/Bridge/Minting/ProvideData.tsx | 4 ++++ src/pages/tBTC/Bridge/ResumeDeposit.tsx | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) 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/pages/tBTC/Bridge/Mint.tsx b/src/pages/tBTC/Bridge/Mint.tsx index 4933d5fe7..dee9660ad 100644 --- a/src/pages/tBTC/Bridge/Mint.tsx +++ b/src/pages/tBTC/Bridge/Mint.tsx @@ -1,6 +1,5 @@ import { useEffect } from "react" import { Outlet } from "react-router" -import { useWeb3React } from "@web3-react/core" import { PageComponent } from "../../../types" import { DepositDetails } from "./DepositDetails" import { ResumeDepositPage } from "./ResumeDeposit" @@ -25,7 +24,7 @@ export const MintPage: PageComponent = ({}) => { export const MintingFormPage: PageComponent = ({ ...props }) => { const { tBTCDepositData } = useTBTCDepositDataFromLocalStorage() const { btcDepositAddress, updateState } = useTbtcState() - const { account } = useWeb3React() + const { account } = useIsActive() useEffect(() => { // Update the store with the deposit data if the account is placed in tbtc diff --git a/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx b/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx index b4ad68963..f45b203ee 100644 --- a/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx +++ b/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx @@ -6,7 +6,6 @@ import { ProvideData } from "./ProvideData" import { InitiateMinting } from "./InitiateMinting" import { MintingSuccess } from "./MintingSuccess" import { MakeDeposit } from "./MakeDeposit" -import { useWeb3React } from "@web3-react/core" import { useModal } from "../../../../hooks/useModal" import { ModalType } from "../../../../enums" import { BridgeContractLink } from "../../../../components/tBTC" @@ -14,10 +13,11 @@ import { BridgeProcessCardTitle } from "../components/BridgeProcessCardTitle" import { useRemoveDepositData } from "../../../../hooks/tbtc/useRemoveDepositData" import { useAppDispatch } from "../../../../hooks/store" import { tbtcSlice } from "../../../../store/tbtc" +import { useIsActive } from "../../../../hooks/useIsActive" 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 80e20c873..165daed8f 100644 --- a/src/pages/tBTC/Bridge/Minting/ProvideData.tsx +++ b/src/pages/tBTC/Bridge/Minting/ProvideData.tsx @@ -26,6 +26,7 @@ import { isSameETHAddress } from "../../../../web3/utils" import { supportedChainId } from "../../../../utils/getEnvVariable" import { getBridgeBTCSupportedAddressPrefixesText } from "../../../../utils/tBTC" import { useIsActive } from "../../../../hooks/useIsActive" +import ButtonLink from "../../../../components/ButtonLink" export interface FormValues { ethAddress: string @@ -195,6 +196,9 @@ export const ProvideDataComponent: FC<{ > Generate Deposit Address + + Continue mint + ) } diff --git a/src/pages/tBTC/Bridge/ResumeDeposit.tsx b/src/pages/tBTC/Bridge/ResumeDeposit.tsx index 2f41e7552..6dd1e66ac 100644 --- a/src/pages/tBTC/Bridge/ResumeDeposit.tsx +++ b/src/pages/tBTC/Bridge/ResumeDeposit.tsx @@ -7,7 +7,6 @@ import { FileUploader, FormControl, } from "@threshold-network/components" -import { useWeb3React } from "@web3-react/core" import { FormikErrors, FormikProps, withFormik } from "formik" import { DepositScriptParameters } from "@keep-network/tbtc-v2.ts/dist/src/deposit" import { useNavigate } from "react-router-dom" @@ -27,10 +26,11 @@ import { getErrorsObj } from "../../../utils/forms" import { useTBTCDepositDataFromLocalStorage } from "../../../hooks/tbtc" import { useThreshold } from "../../../contexts/ThresholdContext" import HelperErrorText from "../../../components/Forms/HelperErrorText" +import { useIsActive } from "../../../hooks/useIsActive" export const ResumeDepositPage: PageComponent = () => { const { updateState } = useTbtcState() - const { account } = useWeb3React() + const { account } = useIsActive() const navigate = useNavigate() const { setDepositDataInLocalStorage } = useTBTCDepositDataFromLocalStorage() const threshold = useThreshold() From 7d6d897ad65ff969354826e246161495d3c4f692 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Fri, 3 Nov 2023 00:15:45 +0100 Subject: [PATCH 20/69] Refactor useEffect inside useRequestAccount hooks For both bitcoin and ethereum hooks we can just save the account address immidietely to the LedgerLiveApp Context - we don't have to check if the address is undefined or not in the if statements like before. Additionally we can also save the eth account for the LedgerLiveAppEthereumSigner using one-liner. I've also remove saving bitcoin address to the LedgerLiveApp signer. It was a mistake. Only eth addresses should be saved there, since it is a signer for ethereum chain. --- .../ledger-live-app/useRequestBitcoinAccount.ts | 12 +----------- .../ledger-live-app/useRequestEthereumAccount.ts | 11 ++--------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts index 4f02ffbed..1ea0f150a 100644 --- a/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts +++ b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts @@ -20,22 +20,12 @@ export function useRequestBitcoinAccount(): UseRequestAccountReturn { const { setBtcAddress } = useContext(LedgerLiveAppContext) const useRequestAccountReturn = useWalletApiRequestAccount() const { account, requestAccount } = useRequestAccountReturn - const threshold = useThreshold() useEffect(() => { - // TODO: Get currencyId based on the chainId - if (account && account.address && account?.currency === "bitcoin_testnet") { - setBtcAddress(account?.address || undefined) - threshold.tbtc.setLedgerLiveAppEthAccount(account) - } - - if (!account || !account.address) { - setBtcAddress(undefined) - } + setBtcAddress(account?.address || undefined) }, [account]) const requestBitcoinAccount = useCallback(async () => { - console.log("requesting Bitcoin Account...") // TODO: Get currencyId based on the chainId await requestAccount({ currencyIds: ["bitcoin_testnet"] }) }, [requestAccount]) diff --git a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts index 2f50c768d..0b9086268 100644 --- a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts +++ b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts @@ -23,15 +23,8 @@ export function useRequestEthereumAccount(): UseRequestAccountReturn { const threshold = useThreshold() useEffect(() => { - // TODO: Get currencyId based on the chainId - if (account && account.address && account?.currency === "ethereum_goerli") { - setEthAddress(account?.address || undefined) - threshold.tbtc.setLedgerLiveAppEthAccount(account) - } - - if (!account || !account.address) { - setEthAddress(undefined) - } + setEthAddress(account?.address || undefined) + threshold.tbtc.setLedgerLiveAppEthAccount(account ? account : undefined) }, [account]) const requestEthereumAccount = useCallback(async () => { From ad0c8432e7af3a8619febcbe1faa35394f318ea3 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Fri, 3 Nov 2023 00:23:08 +0100 Subject: [PATCH 21/69] Store Account object in Ledger Live App Context I've decided to store Account object (from ledger's wallet-api) in Ledger Live App Context, instead of just account address. This will be helpful when sending a bitcoin transaction, because it need the account id, that is stored in Account object. --- src/contexts/LedgerLiveAppContext.tsx | 29 ++++++++++--------- src/contexts/ThresholdContext.tsx | 8 ++--- .../useRequestBitcoinAccount.ts | 4 +-- .../useRequestEthereumAccount.ts | 6 ++-- src/hooks/useIsActive.ts | 3 +- src/ledger-live-app-eth-signer/index.ts | 2 +- src/threshold-ts/tbtc/index.ts | 4 +-- 7 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/contexts/LedgerLiveAppContext.tsx b/src/contexts/LedgerLiveAppContext.tsx index 5b1d8e1f5..b3d0c6534 100644 --- a/src/contexts/LedgerLiveAppContext.tsx +++ b/src/contexts/LedgerLiveAppContext.tsx @@ -1,30 +1,31 @@ +import { Account } from "@ledgerhq/wallet-api-client" import React, { createContext, useState } from "react" interface LedgerLiveAppContextState { - ethAddress: string | undefined - btcAddress: string | undefined - setEthAddress: React.Dispatch> - setBtcAddress: React.Dispatch> + ethAccount: Account | undefined + btcAccount: Account | undefined + setEthAccount: React.Dispatch> + setBtcAccount: React.Dispatch> } export const LedgerLiveAppContext = createContext({ - ethAddress: undefined, - btcAddress: undefined, - setEthAddress: () => {}, - setBtcAddress: () => {}, + ethAccount: undefined, + btcAccount: undefined, + setEthAccount: () => {}, + setBtcAccount: () => {}, }) export const LedgerLiveAppProvider: React.FC = ({ children }) => { - const [ethAddress, setEthAddress] = useState(undefined) - const [btcAddress, setBtcAddress] = useState(undefined) + const [ethAccount, setEthAccount] = useState(undefined) + const [btcAccount, setBtcAccount] = useState(undefined) return ( {children} diff --git a/src/contexts/ThresholdContext.tsx b/src/contexts/ThresholdContext.tsx index d3c416203..66b9588d6 100644 --- a/src/contexts/ThresholdContext.tsx +++ b/src/contexts/ThresholdContext.tsx @@ -16,7 +16,7 @@ export const useThreshold = () => { export const ThresholdProvider: FC = ({ children }) => { const { library, active, account } = useWeb3React() const hasThresholdLibConfigBeenUpdated = useRef(false) - const { ethAddress, btcAddress } = useContext(LedgerLiveAppContext) + const { ethAccount, btcAccount } = useContext(LedgerLiveAppContext) useEffect(() => { if (active && library && account) { @@ -45,9 +45,9 @@ export const ThresholdProvider: FC = ({ children }) => { // TODO: Remove this useEffect useEffect(() => { - console.log("ethAddress: ", ethAddress) - console.log("btcAddress: ", btcAddress) - }, [ethAddress, btcAddress]) + console.log("ethAccount: ", ethAccount) + console.log("btcAccount: ", btcAccount) + }, [ethAccount?.address, btcAccount?.address]) return ( diff --git a/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts index 1ea0f150a..aab367b3a 100644 --- a/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts +++ b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts @@ -17,12 +17,12 @@ type UseRequestAccountReturn = { } & UseRequestAccountState export function useRequestBitcoinAccount(): UseRequestAccountReturn { - const { setBtcAddress } = useContext(LedgerLiveAppContext) + const { setBtcAccount } = useContext(LedgerLiveAppContext) const useRequestAccountReturn = useWalletApiRequestAccount() const { account, requestAccount } = useRequestAccountReturn useEffect(() => { - setBtcAddress(account?.address || undefined) + setBtcAccount(account || undefined) }, [account]) const requestBitcoinAccount = useCallback(async () => { diff --git a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts index 0b9086268..4e2c81e3e 100644 --- a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts +++ b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts @@ -17,14 +17,14 @@ type UseRequestAccountReturn = { } & UseRequestAccountState export function useRequestEthereumAccount(): UseRequestAccountReturn { - const { setEthAddress } = useContext(LedgerLiveAppContext) + const { setEthAccount } = useContext(LedgerLiveAppContext) const useRequestAccountReturn = useWalletApiRequestAccount() const { account, requestAccount } = useRequestAccountReturn const threshold = useThreshold() useEffect(() => { - setEthAddress(account?.address || undefined) - threshold.tbtc.setLedgerLiveAppEthAccount(account ? account : undefined) + setEthAccount(account || undefined) + threshold.tbtc.setLedgerLiveAppEthAccount(account || undefined) }, [account]) const requestEthereumAccount = useCallback(async () => { diff --git a/src/hooks/useIsActive.ts b/src/hooks/useIsActive.ts index 350925cf4..cd9e634bd 100644 --- a/src/hooks/useIsActive.ts +++ b/src/hooks/useIsActive.ts @@ -16,7 +16,8 @@ type UseIsActiveResult = { */ export const useIsActive = (): UseIsActiveResult => { const { active, account } = useWeb3React() - const { ethAddress } = useContext(LedgerLiveAppContext) + const { ethAccount } = useContext(LedgerLiveAppContext) + const ethAddress = ethAccount?.address || undefined const { isEmbed } = useEmbedFeatureFlag() const isActive = useMemo(() => { diff --git a/src/ledger-live-app-eth-signer/index.ts b/src/ledger-live-app-eth-signer/index.ts index 6a1427747..d00104747 100644 --- a/src/ledger-live-app-eth-signer/index.ts +++ b/src/ledger-live-app-eth-signer/index.ts @@ -28,7 +28,7 @@ export class LedgerLiveAppEthereumSigner extends Signer { return this._account } - setAccount(account: Account): void { + setAccount(account: Account | undefined): void { this._account = account } diff --git a/src/threshold-ts/tbtc/index.ts b/src/threshold-ts/tbtc/index.ts index eeb8ca4df..b61f5946a 100644 --- a/src/threshold-ts/tbtc/index.ts +++ b/src/threshold-ts/tbtc/index.ts @@ -185,7 +185,7 @@ export interface ITBTC { * @param account Account object returned from `requestAccount` function (from * ledger's wallet-api). */ - setLedgerLiveAppEthAccount(account: Account): void + setLedgerLiveAppEthAccount(account: Account | undefined): void /** * Suggests a wallet that should be used as the deposit target at the given @@ -507,7 +507,7 @@ export class TBTC implements ITBTC { return this._tokenContract } - setLedgerLiveAppEthAccount(account: Account): void { + setLedgerLiveAppEthAccount(account: Account | undefined): void { if (!this._ledgerLiveAppEthereumSigner) { throw new Error( "Ledger Live App Ethereum Signer is not defined in threshold-ts lib." From 95acc74f9201489f230eded98e082c1654e19ae7 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Fri, 3 Nov 2023 10:55:42 +0100 Subject: [PATCH 22/69] Allow sending bitcoins to deposit address Creates `useSendBitcoinTransaction` hook that use `useSignAndBroadcast` hook from `ledgerhq/wallet-api-client-react` under the hood. This will send a specific amount of bitcoins to a specific address. --- src/hooks/ledger-live-app/index.ts | 1 + .../useSendBitcoinTransaction.ts | 46 +++++++++++++++++++ src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx | 20 ++++++-- 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 src/hooks/ledger-live-app/useSendBitcoinTransaction.ts diff --git a/src/hooks/ledger-live-app/index.ts b/src/hooks/ledger-live-app/index.ts index 1763656ca..86029016c 100644 --- a/src/hooks/ledger-live-app/index.ts +++ b/src/hooks/ledger-live-app/index.ts @@ -1,2 +1,3 @@ export * from "./useRequestBitcoinAccount" export * from "./useRequestEthereumAccount" +export * from "./useSendBitcoinTransaction" diff --git a/src/hooks/ledger-live-app/useSendBitcoinTransaction.ts b/src/hooks/ledger-live-app/useSendBitcoinTransaction.ts new file mode 100644 index 000000000..6d993983c --- /dev/null +++ b/src/hooks/ledger-live-app/useSendBitcoinTransaction.ts @@ -0,0 +1,46 @@ +import { useSignAndBroadcastTransaction } from "@ledgerhq/wallet-api-client-react" +import BigNumber from "bignumber.js" +import { useCallback, useContext, useEffect } from "react" +import { LedgerLiveAppContext } from "../../contexts/LedgerLiveAppContext" + +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 } = useContext(LedgerLiveAppContext) + 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, + } + + await signAndBroadcastTransaction(btcAccount.id, bitcoinTransaction) + }, + [signAndBroadcastTransaction, btcAccount?.id] + ) + + return { ...rest, sendBitcoinTransaction } +} diff --git a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx index cd481b831..01f235f37 100644 --- a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx +++ b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx @@ -25,7 +25,10 @@ import { QRCode } from "../../../../components/QRCode" import withOnlyConnectedWallet from "../../../../components/withOnlyConnectedWallet" import { ViewInBlockExplorerProps } from "../../../../components/ViewInBlockExplorer" import { useEmbedFeatureFlag } from "../../../../hooks/useEmbedFeatureFlag" -import { useRequestBitcoinAccount } from "../../../../hooks/ledger-live-app" +import { + useRequestBitcoinAccount, + useSendBitcoinTransaction, +} from "../../../../hooks/ledger-live-app" const AddressRow: FC< { address: string; text: string } & Pick @@ -126,12 +129,22 @@ const MakeDepositComponent: FC<{ const { isEmbed } = useEmbedFeatureFlag() const { requestAccount, account: ledgerBitcoinAccount } = useRequestBitcoinAccount() + const { sendBitcoinTransaction } = useSendBitcoinTransaction() const chooseBitcoinAccount = useCallback(async () => { - // TODO: Use currencyId based on the chainId that is used await requestAccount() }, [requestAccount]) + const handleSendBitcoinTransaction = useCallback(async () => { + try { + // TODO: Allow user to specify how many bitcoins he want to send ( + do a + // validation [min 0.01 BTC])) + await sendBitcoinTransaction("1000000", btcDepositAddress) // 0.01 BTC + } catch (e) { + console.error(e) + } + }, [btcDepositAddress, sendBitcoinTransaction]) + return ( <> { - // TODO - // if (isEmbed) Send bitcoins + if (isEmbed) handleSendBitcoinTransaction() }} > {isEmbed ? "Send 0.01 BTC" : "I sent the BTC"} From efe582ff3005073b6b4e6e53c6a81b9f55e865e4 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Fri, 3 Nov 2023 22:26:00 +0100 Subject: [PATCH 23/69] Add `bignumber.js` to package.json Unfortunately we need `bignumber.js` library in our dApp to work with ledger's wallet-api. Unfortunately, because we already use `BigNumber` lib from `ethers` for our calcuulations. The commit changes only `package.json` because `yarn.lock` already contains `bignumber.js` registry, since it's a dependency of wallet-api. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3b4731969..ec609e205 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,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", From 5d4be01dbc821afb38d80cbc8b319cdcf8c1cac5 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Fri, 3 Nov 2023 22:28:07 +0100 Subject: [PATCH 24/69] Change Signer class that LedgerSigner extends from Extending `LedgerLiveAppEthereumSigner` from `Signer` class from `ethers` lib did not work as expected when passing it to tbtc-v2 SDK. The SDK did not recognize that it was an instance of Signer. What works is changing the way we import `Signer` class - instead of doing it from `ethers` we now do it from `@ethersproject/abstract-signer`. --- src/ledger-live-app-eth-signer/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ledger-live-app-eth-signer/index.ts b/src/ledger-live-app-eth-signer/index.ts index d00104747..141c0b809 100644 --- a/src/ledger-live-app-eth-signer/index.ts +++ b/src/ledger-live-app-eth-signer/index.ts @@ -1,4 +1,4 @@ -import { ethers, Signer } from "ethers" +import { ethers } from "ethers" import { Account, Transaction, @@ -11,7 +11,10 @@ import { Hex } from "@keep-network/tbtc-v2.ts" import { AddressZero } from "@ethersproject/constants" import { Deferrable } from "@ethersproject/properties" import { getWalletAPIClient, getWindowMessageTransport } from "./utils" +import { Signer } from "@ethersproject/abstract-signer" +// TODO: Investigate why it works with `Signer` from +// `@ethersproject/abstract-signer` and not the one from `ethers` lib. export class LedgerLiveAppEthereumSigner extends Signer { private _walletApiClient: WalletAPIClient private _windowMessageTransport: WindowMessageTransport From 4051270431f3728b1787cc6ed4ee4233c20a5055 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Fri, 3 Nov 2023 23:05:42 +0100 Subject: [PATCH 25/69] Pass ledgerLiveAppEhereumSigner to Threshold lib I've decided to add additional property to thresholdConfig.ethereum: `ledgerLiveAppEthereumSigner`. This will allow us to pass the signer when creating a new instance of TBTC. It will be useful when connecting account. Our threshold library reinitializes all classed inside and we don't want to lose the data from our signer (especcially the connected address that we store there). That's why we pass an old signer from therhols lib to `updateConfig` method when we connect an account. --- src/contexts/ThresholdContext.tsx | 18 +++++++++++++----- src/threshold-ts/tbtc/index.ts | 11 +++-------- src/threshold-ts/types/index.ts | 6 ++++++ src/utils/getLedgerLiveAppEthereumSigner.ts | 12 ++++++++++++ src/utils/getThresholdLib.ts | 2 ++ 5 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 src/utils/getLedgerLiveAppEthereumSigner.ts diff --git a/src/contexts/ThresholdContext.tsx b/src/contexts/ThresholdContext.tsx index 66b9588d6..1b789a6a1 100644 --- a/src/contexts/ThresholdContext.tsx +++ b/src/contexts/ThresholdContext.tsx @@ -6,6 +6,7 @@ import { } from "../utils/getThresholdLib" import { supportedChainId } from "../utils/getEnvVariable" import { LedgerLiveAppContext } from "./LedgerLiveAppContext" +import { useIsActive } from "../hooks/useIsActive" const ThresholdContext = createContext(threshold) @@ -14,34 +15,41 @@ export const useThreshold = () => { } export const ThresholdProvider: FC = ({ children }) => { - const { library, active, account } = useWeb3React() + const { library } = useWeb3React() const hasThresholdLibConfigBeenUpdated = useRef(false) const { ethAccount, btcAccount } = useContext(LedgerLiveAppContext) + const { account, isActive } = useIsActive() useEffect(() => { - if (active && library && account) { + if (isActive) { + // TODO: Maybe we could pass ledgerLiveAppEthereumSigner as + // `providerOrSigner`? This would require some testing. threshold.updateConfig({ ethereum: { chainId: supportedChainId, - providerOrSigner: library, + providerOrSigner: library || getDefaultThresholdLibProvider(), account, + ledgerLiveAppEthereumSigner: + threshold.config.ethereum.ledgerLiveAppEthereumSigner, }, bitcoin: threshold.config.bitcoin, }) hasThresholdLibConfigBeenUpdated.current = true } - if (!active && !account && hasThresholdLibConfigBeenUpdated.current) { + if (!isActive && hasThresholdLibConfigBeenUpdated.current) { threshold.updateConfig({ ethereum: { chainId: supportedChainId, providerOrSigner: getDefaultThresholdLibProvider(), + ledgerLiveAppEthereumSigner: + threshold.config.ethereum.ledgerLiveAppEthereumSigner, }, bitcoin: threshold.config.bitcoin, }) hasThresholdLibConfigBeenUpdated.current = false } - }, [library, active, account]) + }, [library, isActive, account]) // TODO: Remove this useEffect useEffect(() => { diff --git a/src/threshold-ts/tbtc/index.ts b/src/threshold-ts/tbtc/index.ts index b61f5946a..b6c42f731 100644 --- a/src/threshold-ts/tbtc/index.ts +++ b/src/threshold-ts/tbtc/index.ts @@ -45,7 +45,7 @@ import { import TBTCVault from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" import Bridge from "@keep-network/tbtc-v2/artifacts/Bridge.json" import TBTCToken from "@keep-network/tbtc-v2/artifacts/TBTC.json" -import { BigNumber, BigNumberish, Contract, ethers, utils } from "ethers" +import { BigNumber, BigNumberish, Contract, utils } from "ethers" import { ContractCall, IMulticall } from "../multicall" import { BlockTag } from "@ethersproject/abstract-provider" import { LogDescription } from "ethers/lib/utils" @@ -482,13 +482,8 @@ export class TBTC implements ITBTC { ethereumConfig.providerOrSigner, ethereumConfig.account ) - if (ethereumConfig.providerOrSigner instanceof ethers.providers.Provider) { - this._ledgerLiveAppEthereumSigner = new LedgerLiveAppEthereumSigner( - ethereumConfig.providerOrSigner - ) - } else { - this._ledgerLiveAppEthereumSigner = undefined - } + this._ledgerLiveAppEthereumSigner = + ethereumConfig.ledgerLiveAppEthereumSigner } get bitcoinNetwork(): BitcoinNetwork { diff --git a/src/threshold-ts/types/index.ts b/src/threshold-ts/types/index.ts index 2f879df53..718562ba7 100644 --- a/src/threshold-ts/types/index.ts +++ b/src/threshold-ts/types/index.ts @@ -8,11 +8,17 @@ import { Credentials, } from "@keep-network/tbtc-v2.ts/dist/src/electrum" import { providers, Signer } from "ethers" +import { LedgerLiveAppEthereumSigner } from "../../ledger-live-app-eth-signer" export interface EthereumConfig { providerOrSigner: providers.Provider | Signer chainId: string | number account?: string + /** + * Ethereum Signer for Ledger Live App, that allows us to communicate with + * ethereum contracts. + */ + ledgerLiveAppEthereumSigner?: LedgerLiveAppEthereumSigner } export type BitcoinClientCredentials = Credentials diff --git a/src/utils/getLedgerLiveAppEthereumSigner.ts b/src/utils/getLedgerLiveAppEthereumSigner.ts new file mode 100644 index 000000000..24fd0c983 --- /dev/null +++ b/src/utils/getLedgerLiveAppEthereumSigner.ts @@ -0,0 +1,12 @@ +import { JsonRpcProvider, Provider } from "@ethersproject/providers" +import { EnvVariable } from "../enums" +import { LedgerLiveAppEthereumSigner } from "../ledger-live-app-eth-signer" +import { getEnvVariable } from "./getEnvVariable" + +export const getLedgerLiveAppEthereumSigner = (provider: Provider) => { + return new LedgerLiveAppEthereumSigner(provider) +} + +export const ledgerLiveAppEthereumSigner = getLedgerLiveAppEthereumSigner( + new JsonRpcProvider(getEnvVariable(EnvVariable.ETH_HOSTNAME_HTTP)) +) diff --git a/src/utils/getThresholdLib.ts b/src/utils/getThresholdLib.ts index 5b83a1f7a..ed2221bb2 100644 --- a/src/utils/getThresholdLib.ts +++ b/src/utils/getThresholdLib.ts @@ -9,6 +9,7 @@ import { BitcoinNetwork, BitcoinClientCredentials, } from "../threshold-ts/types" +import { ledgerLiveAppEthereumSigner } from "./getLedgerLiveAppEthereumSigner" function getBitcoinConfig(): BitcoinConfig { const network = @@ -45,6 +46,7 @@ export const getThresholdLib = (providerOrSigner?: Provider | Signer) => { ethereum: { chainId: supportedChainId, providerOrSigner: providerOrSigner || getDefaultThresholdLibProvider(), + ledgerLiveAppEthereumSigner: ledgerLiveAppEthereumSigner, }, bitcoin: getBitcoinConfig(), }) From 3f5d279e51cedbb9f85685fb0e7433a74dbd1988 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Fri, 3 Nov 2023 23:18:01 +0100 Subject: [PATCH 26/69] Install tbtc-v2.ts SDK as a separate package Since the version 2 of `tbtc-v2.ts` lib is not implemented in our dApp yet, I've decided to install it as a separate package for now. This way it will be easier to change things, once the integration is ready. --- package.json | 1 + yarn.lock | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ec609e205..73205efac 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@keep-network/tbtc": "development", "@keep-network/tbtc-v2": "development", "@keep-network/tbtc-v2.ts": "2.0.0-dev.0", + "@keep-network/sdk-tbtc-v2.ts": "npm:@keep-network/tbtc-v2.ts@2.1.0", "@ledgerhq/connect-kit-loader": "^1.1.2", "@ledgerhq/wallet-api-client": "^1.2.0", "@ledgerhq/wallet-api-client-react": "^1.1.1", diff --git a/yarn.lock b/yarn.lock index 55bf77d1c..284c1c166 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3302,6 +3302,17 @@ "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" "@threshold-network/solidity-contracts" "1.3.0-dev.5" +"@keep-network/ecdsa@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@keep-network/ecdsa/-/ecdsa-2.0.0.tgz#96d301cd272e61334bec173b5c4945758fc80853" + integrity sha512-KXSUOkZIKHR3I4H99CpgkPtuneI9AxgGo8+DmqGR56QB28kJCEXm7GC/yTInWS1lb7LzDwkjIm9VYAIGdLrsZw== + dependencies: + "@keep-network/random-beacon" "2.0.0" + "@keep-network/sortition-pools" "2.0.0" + "@openzeppelin/contracts" "^4.6.0" + "@openzeppelin/contracts-upgradeable" "^4.6.0" + "@threshold-network/solidity-contracts" "1.2.1" + "@keep-network/ecdsa@2.1.0-dev.11", "@keep-network/ecdsa@development": version "2.1.0-dev.11" resolved "https://registry.yarnpkg.com/@keep-network/ecdsa/-/ecdsa-2.1.0-dev.11.tgz#c25fa6cfebe1ca7964329b54c44526a782391234" @@ -3335,6 +3346,23 @@ "@openzeppelin/contracts-upgradeable" "^4.6.0" "@threshold-network/solidity-contracts" "1.3.0-dev.8" +"@keep-network/keep-core@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@keep-network/keep-core/-/keep-core-1.3.0.tgz#09c1fd4d8d021b2afdff4801c7743d4c65b7bce3" + integrity sha512-c8efKWPx5da6OSdcm9/uvdDqrfwDcYAExNqPvomhLFC0dATEEFHsr/QOAqiBm4wCZZtWMKt0dYTm5QpGtx5TXQ== + dependencies: + "@openzeppelin/contracts-ethereum-package" "^2.4.0" + "@openzeppelin/upgrades" "^2.7.2" + openzeppelin-solidity "2.4.0" + +"@keep-network/keep-core@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@keep-network/keep-core/-/keep-core-1.7.0.tgz#0923d539fc431810bd9b239f91e09a92a2b255a0" + integrity sha512-jU0ol4L5a7vFUXCTlYGsjZYhl87cUpiAYz9LgDgvM3sGmwNIVZ9dY3gziINXIbSSFZjoqh3eGDxDPcQmA+Rjrg== + dependencies: + "@openzeppelin/upgrades" "^2.7.2" + openzeppelin-solidity "2.4.0" + "@keep-network/keep-core@1.8.0-dev.5": version "1.8.0-dev.5" resolved "https://registry.yarnpkg.com/@keep-network/keep-core/-/keep-core-1.8.0-dev.5.tgz#8b4d08ec437f29c94723ee54fcf76456ba5408c3" @@ -3359,6 +3387,16 @@ "@openzeppelin/upgrades" "^2.7.2" openzeppelin-solidity "2.4.0" +"@keep-network/keep-ecdsa@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@keep-network/keep-ecdsa/-/keep-ecdsa-1.2.1.tgz#5dcbc2c3808cce55a6e0d9e524868a0e9a0f1562" + integrity sha512-Oz0thgLoemt/nK6dl+MONU7TEoNR3lfbnMY/Aa0oWGcwR5PlNMiBVzYY7OT4oLZIK/MxEHDOWRQAijZEQffI7g== + dependencies: + "@keep-network/keep-core" "1.3.0" + "@keep-network/sortition-pools" "1.1.2" + "@openzeppelin/upgrades" "^2.7.2" + openzeppelin-solidity "2.3.0" + "@keep-network/keep-ecdsa@>1.9.0-dev <1.9.0-ropsten": version "1.9.0-goerli.0" resolved "https://registry.yarnpkg.com/@keep-network/keep-ecdsa/-/keep-ecdsa-1.9.0-goerli.0.tgz#ce58b6639062bb4f73a257557aebb16447889e08" @@ -3387,6 +3425,16 @@ version "0.0.1" resolved "https://codeload.github.com/keep-network/prettier-config-keep/tar.gz/d6ec02e80dd76edfba073ca58ef99aee39002c2c" +"@keep-network/random-beacon@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@keep-network/random-beacon/-/random-beacon-2.0.0.tgz#ac52dd03da49ec5b0d39ba85c51de6c4c8f1b1dc" + integrity sha512-c5AodlBkMRTEID7bDE7BA9lqDZcH3AFqOPz5b9f5syy62DnuiMkHTHIEIvdWWCL26m21A4PPyPjw7XirauE/OA== + dependencies: + "@keep-network/sortition-pools" "2.0.0" + "@openzeppelin/contracts" "^4.6.0" + "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" + "@threshold-network/solidity-contracts" "1.2.1" + "@keep-network/random-beacon@2.1.0-dev.10", "@keep-network/random-beacon@development": version "2.1.0-dev.10" resolved "https://registry.yarnpkg.com/@keep-network/random-beacon/-/random-beacon-2.1.0-dev.10.tgz#61c9d3e98257f40292264f4b9e1991acdc11f3c3" @@ -3417,6 +3465,29 @@ "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" "@threshold-network/solidity-contracts" "1.3.0-dev.6" +"@keep-network/sdk-tbtc-v2.ts@npm:@keep-network/tbtc-v2.ts@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@keep-network/tbtc-v2.ts/-/tbtc-v2.ts-2.1.0.tgz#c09e6af4e0a72433a339edcb1c5c0470a19c43f9" + integrity sha512-I8O7HBMMTA5n176CdW9G7sPD1yMojqP/dWEOPDl5Ta+AwWkQkyOpJo3+BUbuEw5Av/OGkU+rh200wLFadw6JWA== + dependencies: + "@keep-network/ecdsa" "2.0.0" + "@keep-network/tbtc-v2" "1.5.1" + bitcoinjs-lib "^6.1.5" + bufio "^1.0.6" + ecpair "^2.1.0" + electrum-client-js "git+https://github.com/keep-network/electrum-client-js.git#v0.1.1" + ethers "^5.5.2" + p-timeout "^4.1.0" + tiny-secp256k1 "^2.2.3" + wif "2.0.6" + +"@keep-network/sortition-pools@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@keep-network/sortition-pools/-/sortition-pools-1.1.2.tgz#cfeb31d574b02d1ae32649df01b87af07229010a" + integrity sha512-bBaKyxkXDc8kJHq3qeESMrJ02m+Wbh6Uz9qUpWn8Zq3aTZaKXRZfGWT+J71OiBlAdyB4WoHZymrddWHkjImHdQ== + dependencies: + "@openzeppelin/contracts" "^2.4.0" + "@keep-network/sortition-pools@1.2.0-dev.1": version "1.2.0-dev.1" resolved "https://registry.yarnpkg.com/@keep-network/sortition-pools/-/sortition-pools-1.2.0-dev.1.tgz#2ee371f1dd1ff71f6d05c9ddc2a83a4a93ff56b3" @@ -3424,7 +3495,7 @@ dependencies: "@openzeppelin/contracts" "^2.4.0" -"@keep-network/sortition-pools@^2.0.0-pre.16": +"@keep-network/sortition-pools@2.0.0", "@keep-network/sortition-pools@^2.0.0-pre.16": version "2.0.0" resolved "https://registry.yarnpkg.com/@keep-network/sortition-pools/-/sortition-pools-2.0.0.tgz#04e29ec756d74e00d13505a3e2a7763b06d7a08d" integrity sha512-82pDOKcDBvHBFblCt0ALVr6qC6mxk339ZqnCfYx1zIPaPhzkw1RKOv28AqPoqzhzcdqLIoPh8g9RS/M2Lplh1A== @@ -3448,6 +3519,19 @@ tiny-secp256k1 "^2.2.3" wif "2.0.6" +"@keep-network/tbtc-v2@1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@keep-network/tbtc-v2/-/tbtc-v2-1.5.1.tgz#f1855b30f0c777cd7c612bf25b6c0e4d43356255" + integrity sha512-kSzdF2r2O4AZ4rmxSmMCxfZW9UmhE4s2UmKiAnp9aDTrTgIXppOY/5n9vOF9u6jhXQvHesp9VYtG7QDJaZtZ1Q== + dependencies: + "@keep-network/bitcoin-spv-sol" "3.4.0-solc-0.8" + "@keep-network/ecdsa" "2.0.0" + "@keep-network/random-beacon" "2.0.0" + "@keep-network/tbtc" "1.1.0" + "@openzeppelin/contracts" "^4.8.1" + "@openzeppelin/contracts-upgradeable" "^4.8.1" + "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" + "@keep-network/tbtc-v2@1.6.0-dev.0": version "1.6.0-dev.0" resolved "https://registry.yarnpkg.com/@keep-network/tbtc-v2/-/tbtc-v2-1.6.0-dev.0.tgz#ba95805cef3f04bde7379d3c3b14e882a9cfa080" @@ -3474,6 +3558,16 @@ "@openzeppelin/contracts-upgradeable" "^4.8.1" "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" +"@keep-network/tbtc@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@keep-network/tbtc/-/tbtc-1.1.0.tgz#9574ff355af04e77fb73a7bbe3e74cfd8764cf3c" + integrity sha512-V4sGR/t61PgkEF11GDZL5QNijVSdDhL7A7larcOSSCmKJOugxd5s+d+NdhYcHZhX9IS58ebtepvZan8TydHUHw== + dependencies: + "@keep-network/keep-ecdsa" "1.2.1" + "@summa-tx/bitcoin-spv-sol" "^3.1.0" + "@summa-tx/relay-sol" "^2.0.2" + openzeppelin-solidity "2.3.0" + "@keep-network/tbtc@1.1.2-dev.1", "@keep-network/tbtc@development": version "1.1.2-dev.1" resolved "https://registry.yarnpkg.com/@keep-network/tbtc/-/tbtc-1.1.2-dev.1.tgz#dd1e734c0fed50474c74d7170c8749127231d1f9" @@ -3721,6 +3815,11 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@openzeppelin/contracts-ethereum-package@^2.4.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-ethereum-package/-/contracts-ethereum-package-2.5.0.tgz#cfb4b91f8132edde7e04bcd032575d4c6b544f4a" + integrity sha512-14CijdTyy4Y/3D3UUeFC2oW12nt1Yq1M8gFOtkuODEvSYPe3YSAKnKyhUeGf0UDNCZzwfGr15KdiFK6AoJjoSQ== + "@openzeppelin/contracts-upgradeable@^4.6.0", "@openzeppelin/contracts-upgradeable@^4.8.1": version "4.9.1" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.1.tgz#03e33b8059ce43884995e69e4479f5a7f084b404" @@ -4397,6 +4496,16 @@ "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" "@threshold-network/solidity-contracts" "1.3.0-dev.5" +"@threshold-network/solidity-contracts@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@threshold-network/solidity-contracts/-/solidity-contracts-1.2.1.tgz#8f9d8fe52f24b51acc52fdff765fdef3b8546587" + integrity sha512-v19gnQzdU52DLB6ZpkCW4YHTvApmwA1EG/YRxh+FRrzeDOC6rv+EqUhLgKpMtSpgBZT1ryqqLXm1QmjkW6CIGw== + dependencies: + "@keep-network/keep-core" "1.7.0" + "@openzeppelin/contracts" "~4.5.0" + "@openzeppelin/contracts-upgradeable" "~4.5.2" + "@thesis/solidity-contracts" "github:thesis/solidity-contracts#4985bcf" + "@threshold-network/solidity-contracts@1.3.0-dev.5", "@threshold-network/solidity-contracts@development": version "1.3.0-dev.5" resolved "https://registry.yarnpkg.com/@threshold-network/solidity-contracts/-/solidity-contracts-1.3.0-dev.5.tgz#f7a2727d627a10218f0667bc0d33e19ed8f87fdc" From 168aaed18518b6e63c54091ac8a792d69f8dc589 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Mon, 6 Nov 2023 10:36:42 +0100 Subject: [PATCH 27/69] Implement minting flow with SDK v2 Implements whole minting process with tbtc-v2.ts SDK (v2). The implement process is being implemented in a separate PR, so this is a quick implementation to make the whole process easier once the implementation PR is merged (and to make sure it will work with new SDK). For this, I've created a separate methods in our `TBTC` class (in `threshold-ts` lib) and I've added `SdkV2` at the end, so it will be easier to determinate which method is related to the new SDK. It will also be easier to integrate the changes once the SDK implementation PR is done (I hope :D). For example now, in `TBTC` class we have two methods: - `calculateDepositAddress` - which uses old tbtc-v2.ts version - `calculateDepositAddressSdkV2` - which uses new tbtc-v2.ts version. All the minting flow is using `SdkV2` methods now. --- .../TbtcMintingConfirmationModal/index.tsx | 2 +- src/pages/tBTC/Bridge/Minting/ProvideData.tsx | 44 ++++-- src/store/tbtc/effects.ts | 6 +- src/threshold-ts/tbtc/index.ts | 147 +++++++++++++++++- 4 files changed, 175 insertions(+), 24 deletions(-) diff --git a/src/components/Modal/TbtcMintingConfirmationModal/index.tsx b/src/components/Modal/TbtcMintingConfirmationModal/index.tsx index d5adb3eae..e91a59686 100644 --- a/src/components/Modal/TbtcMintingConfirmationModal/index.tsx +++ b/src/components/Modal/TbtcMintingConfirmationModal/index.tsx @@ -70,7 +70,7 @@ const TbtcMintingConfirmationModal: FC = ({ refundLocktime, } - await revealDeposit(utxo, depositScriptParameters) + await revealDeposit() } const amount = BigNumber.from(utxo.value).toString() diff --git a/src/pages/tBTC/Bridge/Minting/ProvideData.tsx b/src/pages/tBTC/Bridge/Minting/ProvideData.tsx index 165daed8f..b9c13f534 100644 --- a/src/pages/tBTC/Bridge/Minting/ProvideData.tsx +++ b/src/pages/tBTC/Bridge/Minting/ProvideData.tsx @@ -121,47 +121,57 @@ export const ProvideDataComponent: FC<{ ) } setSubmitButtonLoading(true) - const depositScriptParameters = - await threshold.tbtc.createDepositScriptParameters( - values.ethAddress, + + const depositScriptParameters2 = + await threshold.tbtc.createDepositScriptParametersSdkV2( values.btcRecoveryAddress ) - const depositAddress = await threshold.tbtc.calculateDepositAddress( - depositScriptParameters - ) + const depositAddress = await threshold.tbtc.calculateDepositAddressSdkV2() // update state, updateState("ethAddress", values.ethAddress) - updateState("blindingFactor", depositScriptParameters.blindingFactor) + updateState( + "blindingFactor", + depositScriptParameters2.blindingFactor.toString() + ) updateState("btcRecoveryAddress", values.btcRecoveryAddress) updateState( "walletPublicKeyHash", - depositScriptParameters.walletPublicKeyHash + depositScriptParameters2.walletPublicKeyHash.toString() + ) + updateState( + "refundLocktime", + depositScriptParameters2.refundLocktime.toString() ) - updateState("refundLocktime", depositScriptParameters.refundLocktime) // create a new deposit address, updateState("btcDepositAddress", depositAddress) setDepositDataInLocalStorage({ ethAddress: values.ethAddress, - blindingFactor: depositScriptParameters.blindingFactor, + blindingFactor: depositScriptParameters2.blindingFactor.toString(), btcRecoveryAddress: values.btcRecoveryAddress, - walletPublicKeyHash: depositScriptParameters.walletPublicKeyHash, - refundLocktime: depositScriptParameters.refundLocktime, + walletPublicKeyHash: + depositScriptParameters2.walletPublicKeyHash.toString(), + refundLocktime: depositScriptParameters2.refundLocktime.toString(), btcDepositAddress: depositAddress, }) - depositTelemetry(depositScriptParameters, depositAddress) + // TODO: Commenting it out for now to make the deposit flow working. This + // probably will be addressed in a separate PR anyway (with SDK + // implementation) + // depositTelemetry(depositScriptParameters2, depositAddress) // if the user has NOT declined the json file, ask the user if they want to accept the new file openModal(ModalType.TbtcRecoveryJson, { ethAddress: values.ethAddress, - blindingFactor: depositScriptParameters.blindingFactor, - walletPublicKeyHash: depositScriptParameters.walletPublicKeyHash, - refundPublicKeyHash: depositScriptParameters.refundPublicKeyHash, - refundLocktime: depositScriptParameters.refundLocktime, + blindingFactor: depositScriptParameters2.blindingFactor.toString(), + walletPublicKeyHash: + depositScriptParameters2.walletPublicKeyHash.toString(), + refundPublicKeyHash: + depositScriptParameters2.refundPublicKeyHash.toString(), + refundLocktime: depositScriptParameters2.refundLocktime.toString(), btcDepositAddress: depositAddress, }) updateState("mintingStep", MintingStep.Deposit) diff --git a/src/store/tbtc/effects.ts b/src/store/tbtc/effects.ts index 8022c5fcb..83baebc8d 100644 --- a/src/store/tbtc/effects.ts +++ b/src/store/tbtc/effects.ts @@ -58,9 +58,7 @@ export const findUtxoEffect = async ( while (true) { // Looking for utxo. const utxos = await forkApi.pause( - listenerApi.extra.threshold.tbtc.findAllUnspentTransactionOutputs( - btcDepositAddress - ) + listenerApi.extra.threshold.tbtc.findAllUnspentTransactionOutputsSdkV2() ) if (!utxos || utxos.length === 0) { @@ -92,7 +90,7 @@ export const findUtxoEffect = async ( for (let i = utxos.length - 1; i >= 0; i--) { // Check if deposit is revealed. const deposit = await forkApi.pause( - listenerApi.extra.threshold.tbtc.getRevealedDeposit(utxos[i]) + listenerApi.extra.threshold.tbtc.getRevealedDepositSdkV2(utxos[i]) ) const isDepositRevealed = deposit.revealedAt !== 0 diff --git a/src/threshold-ts/tbtc/index.ts b/src/threshold-ts/tbtc/index.ts index b6c42f731..0c8c8972d 100644 --- a/src/threshold-ts/tbtc/index.ts +++ b/src/threshold-ts/tbtc/index.ts @@ -20,6 +20,7 @@ import { AddressZero, isPayToScriptHashTypeAddress, fromSatoshiToTokenPrecision, + getSigner, } from "../utils" import { Client, @@ -56,6 +57,15 @@ import { import { TBTCToken as ChainTBTCToken } from "@keep-network/tbtc-v2.ts/dist/src/chain" import { LedgerLiveAppEthereumSigner } from "../../ledger-live-app-eth-signer" import { Account } from "@ledgerhq/wallet-api-client" +import { + BitcoinUtxo, + Deposit, + DepositReceipt, + DepositRequest, + TBTC as SDK, +} from "@keep-network/sdk-tbtc-v2.ts" +import { Web3Provider } from "@ethersproject/providers" +import { ledgerLiveAppEthereumSigner } from "../../utils/getLedgerLiveAppEthereumSigner" export enum BridgeActivityStatus { PENDING = "PENDING", @@ -180,6 +190,8 @@ export interface ITBTC { readonly tokenContract: Contract + readonly sdk: SDK | undefined + /** * Saves the Account object to the Ledger Live App Ethereum signer. * @param account Account object returned from `requestAccount` function (from @@ -209,6 +221,14 @@ export interface ITBTC { btcRecoveryAddress: string ): Promise + //TODO: We might remove this when SDK v2 is implemented + initiateDepositSdkV2(btcRecoveryAddress: string): Promise + + //TODO: Remove this when SDK v2 is implemented + createDepositScriptParametersSdkV2( + btcRecoveryAddress: string + ): Promise + /** * Calculates the deposit address from the deposit script parameters * @param depositScriptParameters Deposit script parameters. You can get them @@ -219,6 +239,9 @@ export interface ITBTC { depositScriptParameters: DepositScriptParameters ): Promise + //TODO: Remove this when SDK v2 is implemented + calculateDepositAddressSdkV2(): Promise + /** * Finds all unspent transaction outputs (UTXOs) for a given Bitcoin address. * @param address - Bitcoin address UTXOs should be determined for. @@ -228,6 +251,9 @@ export interface ITBTC { address: string ): Promise + //TODO: Remove this when SDK v2 is implemented + findAllUnspentTransactionOutputsSdkV2(): Promise + /** * Gets estimated fees that will be payed during a reveal and estimated amount * of tBTC token that will be minted. @@ -252,6 +278,9 @@ export interface ITBTC { depositScriptParameters: DepositScriptParameters ): Promise + //TODO: Remove this when SDK v2 is implemented + revealDepositSdkV2(): Promise + /** * Gets a revealed deposit from the bridge. * @param utxo Deposit UTXO of the revealed deposit @@ -259,6 +288,9 @@ export interface ITBTC { */ getRevealedDeposit(utxo: UnspentTransactionOutput): Promise + //TODO: Remove this when SDK v2 is implemented + getRevealedDepositSdkV2(utxo: BitcoinUtxo): Promise + /** * Gets the number of confirmations that a given transaction has accumulated * so far. @@ -431,6 +463,8 @@ export class TBTC implements ITBTC { private _redemptionTreasuryFeeDivisor: BigNumber | undefined private _ledgerLiveAppEthereumSigner: LedgerLiveAppEthereumSigner | undefined + private _sdk: SDK | undefined + private _deposit: Deposit | undefined constructor( ethereumConfig: EthereumConfig, @@ -482,8 +516,40 @@ export class TBTC implements ITBTC { ethereumConfig.providerOrSigner, ethereumConfig.account ) - this._ledgerLiveAppEthereumSigner = - ethereumConfig.ledgerLiveAppEthereumSigner + this._ledgerLiveAppEthereumSigner = ledgerLiveAppEthereumSigner + this._handleSDKInitialization(ethereumConfig) + } + + private async _handleSDKInitialization(ethereumConfig: EthereumConfig) { + const { providerOrSigner, account } = ethereumConfig + + const initializeFunction = + this.bitcoinNetwork === BitcoinNetwork.Mainnet + ? SDK.initializeMainnet + : SDK.initializeGoerli + + const shouldUseSigner = account && providerOrSigner instanceof Web3Provider + + // TODO: This should be checked outside of this class + const shouldUseLedgerLiveAppSigner = JSON.parse( + localStorage.getItem("isEmbed") || "" + ) + + if (shouldUseLedgerLiveAppSigner) { + this._sdk = await initializeFunction( + !!ethereumConfig.account && !!this._ledgerLiveAppEthereumSigner + ? this._ledgerLiveAppEthereumSigner + : providerOrSigner + ) + } else { + this._sdk = await initializeFunction( + shouldUseSigner + ? getSigner(providerOrSigner as Web3Provider, account) + : providerOrSigner + ) + } + // TODO: Remove this console log in the future + console.log("THIS.sdk: ", this._sdk) } get bitcoinNetwork(): BitcoinNetwork { @@ -502,6 +568,10 @@ export class TBTC implements ITBTC { return this._tokenContract } + get sdk() { + return this._sdk + } + setLedgerLiveAppEthAccount(account: Account | undefined): void { if (!this._ledgerLiveAppEthereumSigner) { throw new Error( @@ -563,6 +633,24 @@ export class TBTC implements ITBTC { return depositScriptParameters } + //TODO: We might remove/rename this when SDK v2 is implemented + initiateDepositSdkV2 = async (btcRecoveryAddress: string) => { + if (!this._sdk) throw new EmptySdkObjectError() + + this._deposit = await this._sdk?.deposits.initiateDeposit( + btcRecoveryAddress + ) + } + + //TODO: Remove this when SDK v2 is implemented + createDepositScriptParametersSdkV2 = async ( + btcRecoveryAddress: string + ): Promise => { + await this.initiateDepositSdkV2(btcRecoveryAddress) + if (!this._deposit) throw new EmptyDepositObjectError() + return this._deposit.getReceipt() + } + calculateDepositAddress = async ( depositScriptParameters: DepositScriptParameters ): Promise => { @@ -573,12 +661,30 @@ export class TBTC implements ITBTC { ) } + //TODO: Remove this when SDK v2 is implemented + calculateDepositAddressSdkV2 = async (): Promise => { + if (!this._deposit) throw new EmptyDepositObjectError() + const depositAddress = await this._deposit.getBitcoinAddress() + if (!depositAddress) { + throw new Error("Calculated deposit address is empty.") + } + return depositAddress + } + findAllUnspentTransactionOutputs = async ( address: string ): Promise => { return await this._bitcoinClient.findAllUnspentTransactionOutputs(address) } + //TODO: Remove this when SDK v2 is implemented + findAllUnspentTransactionOutputsSdkV2 = async (): Promise => { + if (!this._deposit) throw new EmptyDepositObjectError() + const fundingDetected = await this._deposit?.detectFunding() + console.log("fundingDetected: ", fundingDetected) + return fundingDetected || [] + } + getEstimatedDepositFees = async (depositAmount: string) => { const { depositTreasuryFeeDivisor, optimisticMintingFeeDivisor } = await this._getDepositFees() @@ -670,12 +776,37 @@ export class TBTC implements ITBTC { ) } + //TODO: Remove this when SDK v2 is implemented + revealDepositSdkV2 = async (): Promise => { + if (!this._deposit) throw new EmptyDepositObjectError() + const revealedDepositHash = await this._deposit.initiateMinting() + if (!revealedDepositHash) { + throw new Error("Revealed deposit hash not found!") + } + return revealedDepositHash.toString() + } + getRevealedDeposit = async ( utxo: UnspentTransactionOutput ): Promise => { return await tBTCgetRevealedDeposit(utxo, this._bridge) } + //TODO: Remove this when SDK v2 is implemented + getRevealedDepositSdkV2 = async ( + utxo: BitcoinUtxo + ): Promise => { + if (!this._sdk) throw new EmptySdkObjectError() + const deposit = await this._sdk?.tbtcContracts.bridge.deposits( + utxo.transactionHash, + utxo.outputIndex + ) + if (!deposit) { + throw new Error("Deposit not found!") + } + return deposit + } + getTransactionConfirmations = async ( transactionHash: TransactionHash ): Promise => { @@ -1305,3 +1436,15 @@ export class TBTC implements ITBTC { return this._redemptionTreasuryFeeDivisor } } + +class EmptySdkObjectError extends Error { + constructor() { + super("SDK object is not initiated.") + } +} + +class EmptyDepositObjectError extends Error { + constructor() { + super("Deposit object is not initiated.") + } +} From a35eeaf5bdd1e5eb6cac55ce0f12229b817ea9d3 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Mon, 6 Nov 2023 11:24:49 +0100 Subject: [PATCH 28/69] Implement Resume Deposit with SDK v2 Implements resuming deposit (from file) with SDK v2. For that we have to create deposit receipt from the values that we store in the file. Since those values are in plain text we have to change them to `Hex` objects (except `depositor`). Then, we initiate deposit object from receipt and calculate the correct btc deposit address. --- src/pages/tBTC/Bridge/ResumeDeposit.tsx | 14 +++++++++++--- src/threshold-ts/tbtc/index.ts | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/pages/tBTC/Bridge/ResumeDeposit.tsx b/src/pages/tBTC/Bridge/ResumeDeposit.tsx index 6dd1e66ac..9d53b2724 100644 --- a/src/pages/tBTC/Bridge/ResumeDeposit.tsx +++ b/src/pages/tBTC/Bridge/ResumeDeposit.tsx @@ -27,6 +27,7 @@ import { useTBTCDepositDataFromLocalStorage } from "../../../hooks/tbtc" import { useThreshold } from "../../../contexts/ThresholdContext" import HelperErrorText from "../../../components/Forms/HelperErrorText" import { useIsActive } from "../../../hooks/useIsActive" +import { DepositReceipt, Hex } from "@keep-network/sdk-tbtc-v2.ts" export const ResumeDepositPage: PageComponent = () => { const { updateState } = useTbtcState() @@ -53,9 +54,16 @@ export const ResumeDepositPage: PageComponent = () => { const { depositParameters: { btcRecoveryAddress, ...restDepositParameters }, } = values - const btcDepositAddress = await threshold.tbtc.calculateDepositAddress( - restDepositParameters - ) + const depositReceipt: DepositReceipt = { + depositor: restDepositParameters.depositor, + blindingFactor: Hex.from(restDepositParameters.blindingFactor), + walletPublicKeyHash: Hex.from(restDepositParameters.walletPublicKeyHash), + refundPublicKeyHash: Hex.from(restDepositParameters.refundPublicKeyHash), + refundLocktime: Hex.from(restDepositParameters.refundLocktime), + } + await threshold.tbtc.initiateDepositFromReceiptSdkV2(depositReceipt) + const btcDepositAddress = + await threshold.tbtc.calculateDepositAddressSdkV2() setDepositDataInLocalStorage({ ethAddress: restDepositParameters?.depositor.identifierHex!, diff --git a/src/threshold-ts/tbtc/index.ts b/src/threshold-ts/tbtc/index.ts index 0c8c8972d..048f97586 100644 --- a/src/threshold-ts/tbtc/index.ts +++ b/src/threshold-ts/tbtc/index.ts @@ -224,6 +224,9 @@ export interface ITBTC { //TODO: We might remove this when SDK v2 is implemented initiateDepositSdkV2(btcRecoveryAddress: string): Promise + //TODO: We might remove this when SDK v2 is implemented + initiateDepositFromReceiptSdkV2(depositReceipt: DepositReceipt): Promise + //TODO: Remove this when SDK v2 is implemented createDepositScriptParametersSdkV2( btcRecoveryAddress: string @@ -642,6 +645,17 @@ export class TBTC implements ITBTC { ) } + //TODO: We might remove/rename this when SDK v2 is implemented + initiateDepositFromReceiptSdkV2 = async (depositReceipt: DepositReceipt) => { + if (!this._sdk) throw new EmptySdkObjectError() + + this._deposit = await Deposit.fromReceipt( + depositReceipt, + this._sdk.tbtcContracts, + this._sdk.bitcoinClient + ) + } + //TODO: Remove this when SDK v2 is implemented createDepositScriptParametersSdkV2 = async ( btcRecoveryAddress: string From ef13ab8d93ab72d11dfc4132307410346f742b85 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Mon, 6 Nov 2023 11:32:34 +0100 Subject: [PATCH 29/69] Implement reveal deposit for ledger live app Implements reveal deposit functionalityin ledger live app. For this we create a separate `useSendLedgerLiveAppTransactionFromFn` hook, that is a copy of `useSendTransactionFromFn`, but changed in a way to work with our LedgerLiveApp Signer. We then check which hook should we use inside `useRevealDepositTrnasaction`, based on the `isEmbed` flag. We are also adding `await` to `sendTransaction` method in `LedgerLiveAppEthereumSigner`, because we need to return that transaction response (not promise) from that function. --- src/hooks/ledger-live-app/web3/index.ts | 1 + .../useSendLedgerLiveAppTransactionFromFn.ts | 81 +++++++++++++++++++ src/hooks/tbtc/useRevealDepositTransaction.ts | 19 +++-- src/ledger-live-app-eth-signer/index.ts | 4 +- 4 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 src/hooks/ledger-live-app/web3/index.ts create mode 100644 src/hooks/ledger-live-app/web3/useSendLedgerLiveAppTransactionFromFn.ts diff --git a/src/hooks/ledger-live-app/web3/index.ts b/src/hooks/ledger-live-app/web3/index.ts new file mode 100644 index 000000000..9fa26551b --- /dev/null +++ b/src/hooks/ledger-live-app/web3/index.ts @@ -0,0 +1 @@ +export * from "./useSendLedgerLiveAppTransactionFromFn" diff --git a/src/hooks/ledger-live-app/web3/useSendLedgerLiveAppTransactionFromFn.ts b/src/hooks/ledger-live-app/web3/useSendLedgerLiveAppTransactionFromFn.ts new file mode 100644 index 000000000..9865f97dd --- /dev/null +++ b/src/hooks/ledger-live-app/web3/useSendLedgerLiveAppTransactionFromFn.ts @@ -0,0 +1,81 @@ +import { useCallback, useState } from "react" +import { TransactionReceipt } from "@ethersproject/providers" +import { useModal } from "../../../hooks/useModal" +import { TransactionStatus, ModalType } from "../../../enums" +import { isWalletRejectionError } from "../../../utils/isWalletRejectionError" +import { useIsActive } from "../../useIsActive" +import { useThreshold } from "../../../contexts/ThresholdContext" + +export type ContractTransactionFunction = Promise + +export type OnSuccessCallback = ( + receipt: TransactionReceipt +) => void | Promise + +export type OnErrorCallback = (error: any) => void | Promise + +export const useSendLedgerLiveAppTransactionFromFn = < + F extends (...args: never[]) => ContractTransactionFunction +>( + fn: F, + onSuccess?: OnSuccessCallback, + onError?: OnErrorCallback +) => { + const { account } = useIsActive() + const { openModal } = useModal() + const [transactionStatus, setTransactionStatus] = useState( + TransactionStatus.Idle + ) + const threshold = useThreshold() + const signer = threshold.config.ethereum.ledgerLiveAppEthereumSigner + + const sendTransaction = useCallback( + async (...args: Parameters) => { + if (!account) { + // Maybe we should do something here? + return + } + + try { + setTransactionStatus(TransactionStatus.PendingWallet) + openModal(ModalType.TransactionIsWaitingForConfirmation) + const txHash = await fn(...args) + const prefixedTxHash = `0x${txHash}` + openModal(ModalType.TransactionIsPending, { + transactionHash: txHash, + }) + setTransactionStatus(TransactionStatus.PendingOnChain) + setTransactionStatus(TransactionStatus.Succeeded) + const txReceipt = await signer!.provider?.getTransactionReceipt( + prefixedTxHash + ) + // TODO: Fix Success modal. It does not appear after successful + // transaction in Ledger Live App. + if (onSuccess && txReceipt) { + onSuccess(txReceipt) + } + return txReceipt + } catch (error: any) { + setTransactionStatus( + isWalletRejectionError(error) + ? TransactionStatus.Rejected + : TransactionStatus.Failed + ) + + if (onError) { + onError(error) + } else { + openModal(ModalType.TransactionFailed, { + transactionHash: error?.transaction?.hash, + error: error?.message, + // TODO: how to check if an error is expandable? + isExpandableError: true, + }) + } + } + }, + [fn, account, onError, onSuccess, openModal] + ) + + return { sendTransaction, status: transactionStatus } +} diff --git a/src/hooks/tbtc/useRevealDepositTransaction.ts b/src/hooks/tbtc/useRevealDepositTransaction.ts index 707fc823f..3722bc999 100644 --- a/src/hooks/tbtc/useRevealDepositTransaction.ts +++ b/src/hooks/tbtc/useRevealDepositTransaction.ts @@ -4,16 +4,25 @@ import { OnSuccessCallback, useSendTransactionFromFn, } from "../../web3/hooks" +import { useSendLedgerLiveAppTransactionFromFn } from "../ledger-live-app/web3" +import { useEmbedFeatureFlag } from "../useEmbedFeatureFlag" export const useRevealDepositTransaction = ( onSuccess?: OnSuccessCallback, onError?: OnErrorCallback ) => { const threshold = useThreshold() + const { isEmbed } = useEmbedFeatureFlag() - return useSendTransactionFromFn( - threshold.tbtc.revealDeposit, - onSuccess, - onError - ) + return isEmbed + ? useSendLedgerLiveAppTransactionFromFn( + threshold.tbtc.revealDepositSdkV2, + onSuccess, + onError + ) + : useSendTransactionFromFn( + threshold.tbtc.revealDepositSdkV2, + onSuccess, + onError + ) } diff --git a/src/ledger-live-app-eth-signer/index.ts b/src/ledger-live-app-eth-signer/index.ts index 141c0b809..0ba559004 100644 --- a/src/ledger-live-app-eth-signer/index.ts +++ b/src/ledger-live-app-eth-signer/index.ts @@ -151,7 +151,9 @@ export class LedgerLiveAppEthereumSigner extends Signer { ) this._windowMessageTransport.disconnect() - const transactionResponse = this.provider?.getTransaction(transactionHash) + const transactionResponse = await this.provider?.getTransaction( + transactionHash + ) console.log("Transaction response: ", transactionResponse) if (!transactionResponse) { From 39e589cecf857551483a7e7b6d97695f8c074085 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Mon, 6 Nov 2023 11:36:37 +0100 Subject: [PATCH 30/69] Fix fetching Bridge Activity in Ledger Live App The fetching was not working in Ledger Live App, because it used the account from `useWeb3React` hook, which doesn't work here. I've replaced it with `useIsActive` hook, which returns either web3 account or connected ledger account based on `isEmbed` flag. The bridge activity still uses the old version of tbtc-v2.ts, but I don't think it's necessary to fix that in this PR. The main reason to use a new SDK in this PR is to see if the LedgerLiveAppEthereumSigner will work properly with it. --- src/pages/Overview/Network/index.tsx | 3 ++- src/pages/tBTC/Bridge/index.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) 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/index.tsx b/src/pages/tBTC/Bridge/index.tsx index cfe2ba51b..b6c220a42 100644 --- a/src/pages/tBTC/Bridge/index.tsx +++ b/src/pages/tBTC/Bridge/index.tsx @@ -13,6 +13,7 @@ import { useWeb3React } from "@web3-react/core" import { Outlet } from "react-router" import { MintPage } from "./Mint" import { UnmintPage } from "./Unmint" +import { useIsActive } from "../../../hooks/useIsActive" const gridTemplateAreas = { base: ` @@ -30,7 +31,7 @@ const TBTCBridge: PageComponent = (props) => { const isBridgeActivityFetching = useAppSelector( (state) => state.tbtc.bridgeActivity.isFetching ) - const { account } = useWeb3React() + const { account } = useIsActive() useEffect(() => { if (!hasUserResponded) openModal(ModalType.NewTBTCApp) From 234fda35bdbff0f65e7d49d6272bcd87610c708a Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Mon, 6 Nov 2023 12:20:51 +0100 Subject: [PATCH 31/69] Fix `no ongoingRequest` error There was an error when doing a reveal that displayed `no ongoingReques: `. It was happening because we were using a separate transport object in our LedgerLiveAppEthereumSigner, and a separate one for our react hooks. The one in our react hook was always connected and we didn't disconnect it after doing a transaction. This way, when we used our LedgerLiveApp Signer we had two transport objects connected, and two returned the same message, but the second time the message was already cleared. As a workaround we will save the transport object that we use in react in a separate context and we connect it only before doing a transaction, and disconnect it right after. --- src/contexts/TransportProvider.tsx | 62 +++++++++++++------ .../useRequestBitcoinAccount.ts | 7 ++- .../useRequestEthereumAccount.ts | 6 +- .../useSendBitcoinTransaction.ts | 9 ++- 4 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/contexts/TransportProvider.tsx b/src/contexts/TransportProvider.tsx index 76417ecb2..602aaa99f 100644 --- a/src/contexts/TransportProvider.tsx +++ b/src/contexts/TransportProvider.tsx @@ -1,24 +1,50 @@ import { WalletAPIProvider } from "@ledgerhq/wallet-api-client-react" -import { Transport, WindowMessageTransport } from "@ledgerhq/wallet-api-client" -import { FC } from "react" +import { WindowMessageTransport } from "@ledgerhq/wallet-api-client" +import { createContext, FC } from "react" + +const getWalletAPITransport = (): WindowMessageTransport => { + // if (typeof window === "undefined") { + // return { + // onMessage: undefined, + // send: () => {}, + // } + // } + + const transport = new WindowMessageTransport() + return transport +} + +export const walletApiReactTransport = getWalletAPITransport() + +interface TransportContextState { + walletApiReactTransport: WindowMessageTransport +} + +export const WalletApiReactTransportContext = + createContext({ + walletApiReactTransport: walletApiReactTransport, + }) + +export const WalletApiReactTransportProvider: React.FC = ({ children }) => { + return ( + + {children} + + ) +} const TransportProvider: FC = ({ children }) => { - const getWalletAPITransport = (): Transport => { - if (typeof window === "undefined") { - return { - onMessage: undefined, - send: () => {}, - } - } - - const transport = new WindowMessageTransport() - transport.connect() - return transport - } - - const transport = getWalletAPITransport() - - return {children} + return ( + + + {children} + + + ) } export default TransportProvider diff --git a/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts index aab367b3a..0416e2d35 100644 --- a/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts +++ b/src/hooks/ledger-live-app/useRequestBitcoinAccount.ts @@ -2,7 +2,7 @@ import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client" import { useRequestAccount as useWalletApiRequestAccount } from "@ledgerhq/wallet-api-client-react" import { useCallback, useContext, useEffect } from "react" import { LedgerLiveAppContext } from "../../contexts/LedgerLiveAppContext" -import { useThreshold } from "../../contexts/ThresholdContext" +import { WalletApiReactTransportContext } from "../../contexts/TransportProvider" type UseRequestAccountState = { pending: boolean @@ -18,6 +18,7 @@ type UseRequestAccountReturn = { export function useRequestBitcoinAccount(): UseRequestAccountReturn { const { setBtcAccount } = useContext(LedgerLiveAppContext) + const { walletApiReactTransport } = useContext(WalletApiReactTransportContext) const useRequestAccountReturn = useWalletApiRequestAccount() const { account, requestAccount } = useRequestAccountReturn @@ -27,8 +28,10 @@ export function useRequestBitcoinAccount(): UseRequestAccountReturn { const requestBitcoinAccount = useCallback(async () => { // TODO: Get currencyId based on the chainId + walletApiReactTransport.connect() await requestAccount({ currencyIds: ["bitcoin_testnet"] }) - }, [requestAccount]) + walletApiReactTransport.disconnect() + }, [requestAccount, walletApiReactTransport]) return { ...useRequestAccountReturn, requestAccount: requestBitcoinAccount } } diff --git a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts index 4e2c81e3e..bdfafae39 100644 --- a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts +++ b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts @@ -3,6 +3,7 @@ import { useRequestAccount as useWalletApiRequestAccount } from "@ledgerhq/walle import { useCallback, useContext, useEffect } from "react" import { LedgerLiveAppContext } from "../../contexts/LedgerLiveAppContext" import { useThreshold } from "../../contexts/ThresholdContext" +import { WalletApiReactTransportContext } from "../../contexts/TransportProvider" type UseRequestAccountState = { pending: boolean @@ -18,6 +19,7 @@ type UseRequestAccountReturn = { export function useRequestEthereumAccount(): UseRequestAccountReturn { const { setEthAccount } = useContext(LedgerLiveAppContext) + const { walletApiReactTransport } = useContext(WalletApiReactTransportContext) const useRequestAccountReturn = useWalletApiRequestAccount() const { account, requestAccount } = useRequestAccountReturn const threshold = useThreshold() @@ -29,8 +31,10 @@ export function useRequestEthereumAccount(): UseRequestAccountReturn { const requestEthereumAccount = useCallback(async () => { // TODO: Get currencyId based on the chainId + walletApiReactTransport.connect() await requestAccount({ currencyIds: ["ethereum_goerli"] }) - }, [requestAccount]) + walletApiReactTransport.connect() + }, [requestAccount, walletApiReactTransport]) return { ...useRequestAccountReturn, requestAccount: requestEthereumAccount } } diff --git a/src/hooks/ledger-live-app/useSendBitcoinTransaction.ts b/src/hooks/ledger-live-app/useSendBitcoinTransaction.ts index 6d993983c..54f5c86b7 100644 --- a/src/hooks/ledger-live-app/useSendBitcoinTransaction.ts +++ b/src/hooks/ledger-live-app/useSendBitcoinTransaction.ts @@ -1,7 +1,8 @@ import { useSignAndBroadcastTransaction } from "@ledgerhq/wallet-api-client-react" import BigNumber from "bignumber.js" -import { useCallback, useContext, useEffect } from "react" +import { useCallback, useContext } from "react" import { LedgerLiveAppContext } from "../../contexts/LedgerLiveAppContext" +import { WalletApiReactTransportContext } from "../../contexts/TransportProvider" type UseSendBitcoinTransactionState = { pending: boolean @@ -21,6 +22,7 @@ type UseSendBitcoinTransactionReturn = { export function useSendBitcoinTransaction(): UseSendBitcoinTransactionReturn { const { btcAccount } = useContext(LedgerLiveAppContext) + const { walletApiReactTransport } = useContext(WalletApiReactTransportContext) const useSignAndBroadcastTransactionReturn = useSignAndBroadcastTransaction() const { signAndBroadcastTransaction, ...rest } = useSignAndBroadcastTransactionReturn @@ -36,10 +38,11 @@ export function useSendBitcoinTransaction(): UseSendBitcoinTransactionReturn { amount: new BigNumber(amount), recipient: recipient, } - + walletApiReactTransport.connect() await signAndBroadcastTransaction(btcAccount.id, bitcoinTransaction) + walletApiReactTransport.disconnect() }, - [signAndBroadcastTransaction, btcAccount?.id] + [signAndBroadcastTransaction, btcAccount?.id, walletApiReactTransport] ) return { ...rest, sendBitcoinTransaction } From 46afb9b8d36005e39fe9d6dd8f359363817429b4 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Mon, 6 Nov 2023 13:52:04 +0100 Subject: [PATCH 32/69] Fix minting success page redirect There was a bug when, after doing a reveal, the use was not redirected to minting success page. It happened, because the transaction receipt, that we were getting with `provider.getTransactionReceipt(txHash)` method in `useSendLedgerLiveAppTransactionFromFn`, was not found (because it was not mined yet). I've implemented a fix based on our `useSendTransactionFromFn` hook. We are now getting a `TransactionResponse` object using `provider.getTransaction(txHash)` method and the wait for the transaction to be mined with `.wait()` method. This method will return a `TransactionReceipt` object once the transaction is mined. --- .../web3/useSendLedgerLiveAppTransactionFromFn.ts | 13 +++++++------ src/ledger-live-app-eth-signer/index.ts | 1 - 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hooks/ledger-live-app/web3/useSendLedgerLiveAppTransactionFromFn.ts b/src/hooks/ledger-live-app/web3/useSendLedgerLiveAppTransactionFromFn.ts index 9865f97dd..c60c9dc56 100644 --- a/src/hooks/ledger-live-app/web3/useSendLedgerLiveAppTransactionFromFn.ts +++ b/src/hooks/ledger-live-app/web3/useSendLedgerLiveAppTransactionFromFn.ts @@ -41,16 +41,17 @@ export const useSendLedgerLiveAppTransactionFromFn = < openModal(ModalType.TransactionIsWaitingForConfirmation) const txHash = await fn(...args) const prefixedTxHash = `0x${txHash}` + openModal(ModalType.TransactionIsPending, { transactionHash: txHash, }) setTransactionStatus(TransactionStatus.PendingOnChain) + + const tx = await signer!.provider?.getTransaction(prefixedTxHash) + if (!tx) throw new Error(`Transaction ${prefixedTxHash} not found!`) + const txReceipt = await tx?.wait() + setTransactionStatus(TransactionStatus.Succeeded) - const txReceipt = await signer!.provider?.getTransactionReceipt( - prefixedTxHash - ) - // TODO: Fix Success modal. It does not appear after successful - // transaction in Ledger Live App. if (onSuccess && txReceipt) { onSuccess(txReceipt) } @@ -74,7 +75,7 @@ export const useSendLedgerLiveAppTransactionFromFn = < } } }, - [fn, account, onError, onSuccess, openModal] + [fn, account, onError, onSuccess, openModal, signer] ) return { sendTransaction, status: transactionStatus } diff --git a/src/ledger-live-app-eth-signer/index.ts b/src/ledger-live-app-eth-signer/index.ts index 0ba559004..deca40f12 100644 --- a/src/ledger-live-app-eth-signer/index.ts +++ b/src/ledger-live-app-eth-signer/index.ts @@ -154,7 +154,6 @@ export class LedgerLiveAppEthereumSigner extends Signer { const transactionResponse = await this.provider?.getTransaction( transactionHash ) - console.log("Transaction response: ", transactionResponse) if (!transactionResponse) { throw new Error("Transaction response not found!") From d04a69aec30e948858bc5c89e1ac1e9e53acd3d9 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Tue, 7 Nov 2023 10:03:00 +0100 Subject: [PATCH 33/69] Disconnect transport when requesting eth account The walletApiTransport was not disconnected properly after requesting and eth account in `useRequestEthereumAccount()` hook. This leads to `no ongoingRequest` error when doing a contract transaction that uses `wallet-api-client`. I've actually used `connect()` instead of `disconnect()` by mistake. For more context please see 234fda35bdbff0f65e7d49d6272bcd87610c708a. --- src/hooks/ledger-live-app/useRequestEthereumAccount.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts index bdfafae39..c771c6108 100644 --- a/src/hooks/ledger-live-app/useRequestEthereumAccount.ts +++ b/src/hooks/ledger-live-app/useRequestEthereumAccount.ts @@ -33,7 +33,7 @@ export function useRequestEthereumAccount(): UseRequestAccountReturn { // TODO: Get currencyId based on the chainId walletApiReactTransport.connect() await requestAccount({ currencyIds: ["ethereum_goerli"] }) - walletApiReactTransport.connect() + walletApiReactTransport.disconnect() }, [requestAccount, walletApiReactTransport]) return { ...useRequestAccountReturn, requestAccount: requestEthereumAccount } From 947c8a598e73d74eb5095f483780bfef24c21e25 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Tue, 7 Nov 2023 10:11:53 +0100 Subject: [PATCH 34/69] Handle async SDK initialization The SDK in our Threshold lib is initialized asynchronously - the `_handleSdkInitialization` method returns a Promise. We need to know whenever the SDK is initializing or not, becuase ther might be a situation when we want to call a function and the SDK is in the middle of initializing. Such case happens right now for `findUtxoEffect`, after disconnecting and reconnecting the wallet at step 2 of the minting flow. The deposit data is updated in the store which triggers the `findUtxo` effect, but it can find any UTXOs, because `deposit` object is not yet initialized and the SDK is in the middle of initializing with a new signer. Becuase of that I've decided to make `_handleSdkInitialization` method public and rename it to `initializeSdk`. We thenn put it in `useInitializeSdk` hook where we can use state to indicate if the sdk is initializing or not. We also check if the SDK is initialized with signer or not - this will allow us to wait with data fetch until the we know the signer is initialized with signer. Finally, we call the `initiateSdk` method in our `ThresholdContext` and save the state data (`isSdkInitializing` and `isSdkInitializedWithSigner`) in our context. Now we only need to initialize `deposit` object in our `findUtxo` effect. We just check there if `deposit` is defined, and if it's not, we are taking the data from the redux store and use it to initialize deposit object, befor we call `findAllUnspentTransactionOutputsSdkV2` method. --- src/contexts/ThresholdContext.tsx | 29 ++++++++++- src/hooks/tbtc/useInitializeSdk.ts | 36 +++++++++++++ src/pages/tBTC/Bridge/Mint.tsx | 9 +++- .../tBTC/Bridge/Minting/MintingFlowRouter.tsx | 21 +++++++- src/store/tbtc/effects.ts | 51 +++++++++++++++++++ src/threshold-ts/tbtc/index.ts | 33 +++++++++--- 6 files changed, 166 insertions(+), 13 deletions(-) create mode 100644 src/hooks/tbtc/useInitializeSdk.ts diff --git a/src/contexts/ThresholdContext.tsx b/src/contexts/ThresholdContext.tsx index 1b789a6a1..35571aa63 100644 --- a/src/contexts/ThresholdContext.tsx +++ b/src/contexts/ThresholdContext.tsx @@ -7,18 +7,33 @@ import { import { supportedChainId } from "../utils/getEnvVariable" import { LedgerLiveAppContext } from "./LedgerLiveAppContext" import { useIsActive } from "../hooks/useIsActive" +import { useInitializeSdk } from "../hooks/tbtc/useInitializeSdk" const ThresholdContext = createContext(threshold) +// TODO: We should probably put the `isSdkInitializing` information in +// ThresholdContext, but that would require a lot of change through app, so for +// now we will keep it in a separate context. +const IsSdkInitializingContext = createContext({ + isSdkInitializing: false, + isSdkInitializedWithSigner: false, +}) + export const useThreshold = () => { return useContext(ThresholdContext) } +export const useIsSdkInitializing = () => { + return useContext(IsSdkInitializingContext) +} + export const ThresholdProvider: FC = ({ children }) => { const { library } = useWeb3React() const hasThresholdLibConfigBeenUpdated = useRef(false) const { ethAccount, btcAccount } = useContext(LedgerLiveAppContext) const { account, isActive } = useIsActive() + const { sdk, initializeSdk, isSdkInitializing, isSdkInitializedWithSigner } = + useInitializeSdk() useEffect(() => { if (isActive) { @@ -35,6 +50,7 @@ export const ThresholdProvider: FC = ({ children }) => { bitcoin: threshold.config.bitcoin, }) hasThresholdLibConfigBeenUpdated.current = true + initializeSdk(threshold.config.ethereum.providerOrSigner, account) } if (!isActive && hasThresholdLibConfigBeenUpdated.current) { @@ -48,8 +64,13 @@ export const ThresholdProvider: FC = ({ children }) => { bitcoin: threshold.config.bitcoin, }) hasThresholdLibConfigBeenUpdated.current = false + initializeSdk(threshold.config.ethereum.providerOrSigner) + } + + if (!sdk) { + initializeSdk(threshold.config.ethereum.providerOrSigner) } - }, [library, isActive, account]) + }, [library, isActive, account, initializeSdk]) // TODO: Remove this useEffect useEffect(() => { @@ -59,7 +80,11 @@ export const ThresholdProvider: FC = ({ children }) => { return ( - {children} + + {children} + ) } diff --git a/src/hooks/tbtc/useInitializeSdk.ts b/src/hooks/tbtc/useInitializeSdk.ts new file mode 100644 index 000000000..6c548b34e --- /dev/null +++ b/src/hooks/tbtc/useInitializeSdk.ts @@ -0,0 +1,36 @@ +import { TBTC as SDK } from "@keep-network/sdk-tbtc-v2.ts" +import { providers, Signer } from "ethers" +import { useCallback, useState } from "react" +import { useThreshold } from "../../contexts/ThresholdContext" + +export const useInitializeSdk = () => { + const [sdk, setSdk] = useState(undefined) + const [isInitializing, setIsInitializing] = useState(false) + const [isInitializedWithSigner, setIsInitializedWithSigner] = useState(false) + const threshold = useThreshold() + + const initializeSdk = useCallback( + async (providerOrSigner: providers.Provider | Signer, account?: string) => { + if (!isInitializing) { + setIsInitializing(true) + const sdk = await threshold.tbtc.initializeSdk( + providerOrSigner, + account + ) + setSdk(sdk) + setIsInitializing(false) + const isInitializedWithSigner = account ? true : false + setIsInitializedWithSigner(isInitializedWithSigner) + } + }, + [threshold, setSdk, setIsInitializing, setIsInitializedWithSigner] + ) + + return { + sdk, + isSdkInitializing: isInitializing, + isSdkInitializedWithSigner: isInitializedWithSigner, + setIsInitializing, + initializeSdk, + } +} diff --git a/src/pages/tBTC/Bridge/Mint.tsx b/src/pages/tBTC/Bridge/Mint.tsx index dee9660ad..861a44687 100644 --- a/src/pages/tBTC/Bridge/Mint.tsx +++ b/src/pages/tBTC/Bridge/Mint.tsx @@ -16,6 +16,7 @@ import { } from "./BridgeLayout" import { BridgeProcessEmptyState } from "./components/BridgeProcessEmptyState" import { useIsActive } from "../../../hooks/useIsActive" +import { useIsSdkInitializing } from "../../../contexts/ThresholdContext" export const MintPage: PageComponent = ({}) => { return @@ -25,6 +26,8 @@ export const MintingFormPage: PageComponent = ({ ...props }) => { const { tBTCDepositData } = useTBTCDepositDataFromLocalStorage() const { btcDepositAddress, updateState } = useTbtcState() const { account } = useIsActive() + const { isSdkInitializing, isSdkInitializedWithSigner } = + useIsSdkInitializing() useEffect(() => { // Update the store with the deposit data if the account is placed in tbtc @@ -34,7 +37,9 @@ export const MintingFormPage: PageComponent = ({ ...props }) => { account && tBTCDepositData[account] && isSameETHAddress(tBTCDepositData[account].ethAddress, account) && - tBTCDepositData[account].btcDepositAddress !== btcDepositAddress + tBTCDepositData[account].btcDepositAddress !== btcDepositAddress && + !isSdkInitializing && + isSdkInitializedWithSigner ) { const { btcDepositAddress, @@ -56,7 +61,7 @@ export const MintingFormPage: PageComponent = ({ ...props }) => { updateState("mintingStep", undefined) updateState("btcDepositAddress", btcDepositAddress) } - }, [account]) + }, [account, isSdkInitializing, isSdkInitializedWithSigner]) return } diff --git a/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx b/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx index f45b203ee..467308b28 100644 --- a/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx +++ b/src/pages/tBTC/Bridge/Minting/MintingFlowRouter.tsx @@ -14,6 +14,7 @@ import { useRemoveDepositData } from "../../../../hooks/tbtc/useRemoveDepositDat import { useAppDispatch } from "../../../../hooks/store" import { tbtcSlice } from "../../../../store/tbtc" import { useIsActive } from "../../../../hooks/useIsActive" +import { useIsSdkInitializing } from "../../../../contexts/ThresholdContext" const MintingFlowRouterBase = () => { const dispatch = useAppDispatch() @@ -21,6 +22,8 @@ const MintingFlowRouterBase = () => { const { mintingStep, updateState, btcDepositAddress, utxo } = useTbtcState() const removeDepositData = useRemoveDepositData() const { openModal } = useModal() + const { isSdkInitializing, isSdkInitializedWithSigner } = + useIsSdkInitializing() const onPreviousStepClick = (previousStep?: MintingStep) => { if (mintingStep === MintingStep.MintingSuccess) { @@ -36,11 +39,25 @@ const MintingFlowRouterBase = () => { } useEffect(() => { - if (!btcDepositAddress || !account) return + if ( + !btcDepositAddress || + !account || + isSdkInitializing || + !isSdkInitializedWithSigner + ) { + return + } + dispatch( tbtcSlice.actions.findUtxo({ btcDepositAddress, depositor: account }) ) - }, [btcDepositAddress, account, dispatch]) + }, [ + btcDepositAddress, + account, + dispatch, + isSdkInitializing, + isSdkInitializedWithSigner, + ]) switch (mintingStep) { case MintingStep.ProvideData: { diff --git a/src/store/tbtc/effects.ts b/src/store/tbtc/effects.ts index 83baebc8d..b1a7ec6d5 100644 --- a/src/store/tbtc/effects.ts +++ b/src/store/tbtc/effects.ts @@ -10,6 +10,13 @@ import { removeDataForAccount, } from "../../utils/tbtcLocalStorageData" import { TransactionHash } from "@keep-network/tbtc-v2.ts/dist/src/bitcoin" +import { getChainIdentifier } from "../../threshold-ts/utils" +import { + BitcoinAddressConverter, + BitcoinNetwork, + BitcoinScriptUtils, + Hex, +} from "@keep-network/sdk-tbtc-v2.ts" export const fetchBridgeactivityEffect = async ( action: ReturnType, @@ -44,6 +51,16 @@ export const findUtxoEffect = async ( ) => { const { btcDepositAddress, depositor } = action.payload + const { + tbtc: { + ethAddress, + blindingFactor, + walletPublicKeyHash, + refundLocktime, + btcRecoveryAddress, + }, + } = listenerApi.getState() + if ( !btcDepositAddress || (!isAddress(depositor) && !isAddressZero(depositor)) @@ -57,6 +74,40 @@ export const findUtxoEffect = async ( try { while (true) { // Looking for utxo. + if (!listenerApi.extra.threshold.tbtc.deposit) { + // // TODO: Get bitcoin network by chainId + const recoveryOutputScript = + BitcoinAddressConverter.addressToOutputScript( + btcRecoveryAddress, + BitcoinNetwork.Testnet + ) + + // TODO: We could probably check that with our + // `isPublicKeyHashTypeAddress` method from threshold lib utils + if ( + !BitcoinScriptUtils.isP2PKHScript(recoveryOutputScript) && + !BitcoinScriptUtils.isP2WPKHScript(recoveryOutputScript) + ) { + throw new Error("Bitcoin recovery address must be P2PKH or P2WPKH") + } + + // TODO: Get bitcoin network by chainId + const refundPublicKeyHash = + BitcoinAddressConverter.addressToPublicKeyHash( + btcRecoveryAddress, + BitcoinNetwork.Testnet + ) + + await forkApi.pause( + listenerApi.extra.threshold.tbtc.initiateDepositFromReceiptSdkV2({ + depositor: getChainIdentifier(ethAddress), + blindingFactor: Hex.from(blindingFactor), + walletPublicKeyHash: Hex.from(walletPublicKeyHash), + refundPublicKeyHash: refundPublicKeyHash, + refundLocktime: Hex.from(refundLocktime), + }) + ) + } const utxos = await forkApi.pause( listenerApi.extra.threshold.tbtc.findAllUnspentTransactionOutputsSdkV2() ) diff --git a/src/threshold-ts/tbtc/index.ts b/src/threshold-ts/tbtc/index.ts index 048f97586..f30bcfb08 100644 --- a/src/threshold-ts/tbtc/index.ts +++ b/src/threshold-ts/tbtc/index.ts @@ -46,7 +46,14 @@ import { import TBTCVault from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" import Bridge from "@keep-network/tbtc-v2/artifacts/Bridge.json" import TBTCToken from "@keep-network/tbtc-v2/artifacts/TBTC.json" -import { BigNumber, BigNumberish, Contract, utils } from "ethers" +import { + BigNumber, + BigNumberish, + Contract, + providers, + Signer, + utils, +} from "ethers" import { ContractCall, IMulticall } from "../multicall" import { BlockTag } from "@ethersproject/abstract-provider" import { LogDescription } from "ethers/lib/utils" @@ -192,6 +199,13 @@ export interface ITBTC { readonly sdk: SDK | undefined + readonly deposit: Deposit | undefined + + initializeSdk( + providerOrSigner: providers.Provider | Signer, + account?: string + ): Promise + /** * Saves the Account object to the Ledger Live App Ethereum signer. * @param account Account object returned from `requestAccount` function (from @@ -520,12 +534,12 @@ export class TBTC implements ITBTC { ethereumConfig.account ) this._ledgerLiveAppEthereumSigner = ledgerLiveAppEthereumSigner - this._handleSDKInitialization(ethereumConfig) } - private async _handleSDKInitialization(ethereumConfig: EthereumConfig) { - const { providerOrSigner, account } = ethereumConfig - + async initializeSdk( + providerOrSigner: providers.Provider | Signer, + account?: string + ): Promise { const initializeFunction = this.bitcoinNetwork === BitcoinNetwork.Mainnet ? SDK.initializeMainnet @@ -540,7 +554,7 @@ export class TBTC implements ITBTC { if (shouldUseLedgerLiveAppSigner) { this._sdk = await initializeFunction( - !!ethereumConfig.account && !!this._ledgerLiveAppEthereumSigner + !!account && !!this._ledgerLiveAppEthereumSigner ? this._ledgerLiveAppEthereumSigner : providerOrSigner ) @@ -553,6 +567,7 @@ export class TBTC implements ITBTC { } // TODO: Remove this console log in the future console.log("THIS.sdk: ", this._sdk) + return this._sdk } get bitcoinNetwork(): BitcoinNetwork { @@ -575,6 +590,10 @@ export class TBTC implements ITBTC { return this._sdk } + get deposit() { + return this._deposit + } + setLedgerLiveAppEthAccount(account: Account | undefined): void { if (!this._ledgerLiveAppEthereumSigner) { throw new Error( @@ -811,7 +830,7 @@ export class TBTC implements ITBTC { utxo: BitcoinUtxo ): Promise => { if (!this._sdk) throw new EmptySdkObjectError() - const deposit = await this._sdk?.tbtcContracts.bridge.deposits( + const deposit = await this._sdk.tbtcContracts.bridge.deposits( utxo.transactionHash, utxo.outputIndex ) From 7b86fba85e7c49a2f2c57b925fc3e2a1564e4130 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Tue, 7 Nov 2023 13:51:26 +0100 Subject: [PATCH 35/69] Fix fetching token amount in Ledger Live App For the tbtc balance to fetch property (in Ledger Live App) we have to use the `useIsActive` hook to get the account based on the `isEmbed` flag. This will return Leger Live App connected account, if the app is run in Ledger Live, or web3 account, if the app is run on the browser. --- src/contexts/TokenContext.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/contexts/TokenContext.tsx b/src/contexts/TokenContext.tsx index c8c91eea9..028344d99 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,8 @@ 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 { chainId } = useWeb3React() + const { account, isActive } = useIsActive() const fetchOwnerStakes = useFetchOwnerStakes() const { @@ -59,7 +61,7 @@ export const TokenContextProvider: React.FC = ({ children }) => { const fetchBalances = useTokensBalanceCall( tokenContracts, - active ? account! : AddressZero + isActive ? account! : AddressZero ) // @@ -86,7 +88,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 +108,7 @@ export const TokenContextProvider: React.FC = ({ children }) => { } } } - }, [active, chainId, account]) + }, [isActive, chainId, account]) // fetch user stakes when they connect their wallet React.useEffect(() => { From 9a44303ead16529d190a644565905e69e291eb72 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Tue, 7 Nov 2023 14:23:13 +0100 Subject: [PATCH 36/69] Fix Connect Wallet submit button for Ledger Live Fixes `Connect Wallet` submit button (that shows up instead of a real submit button when wallet is not connected). The button did open selectWallet modal in LedgerLive app, when in this case it should request the account. --- src/components/SubmitTxButton.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/SubmitTxButton.tsx b/src/components/SubmitTxButton.tsx index 5214d511f..06ef62234 100644 --- a/src/components/SubmitTxButton.tsx +++ b/src/components/SubmitTxButton.tsx @@ -1,8 +1,10 @@ import React, { FC } from "react" import { Button, ButtonProps } from "@chakra-ui/react" import { ModalType } from "../enums" -import { useWeb3React } from "@web3-react/core" import { useModal } from "../hooks/useModal" +import { useIsActive } from "../hooks/useIsActive" +import { useEmbedFeatureFlag } from "../hooks/useEmbedFeatureFlag" +import { useRequestEthereumAccount } from "../hooks/ledger-live-app" interface Props extends ButtonProps { onSubmit?: () => void @@ -14,10 +16,20 @@ const SubmitTxButton: FC = ({ submitText = "Upgrade", ...buttonProps }) => { - const { active } = useWeb3React() + const { isActive } = useIsActive() + const { isEmbed } = useEmbedFeatureFlag() + const { requestAccount } = useRequestEthereumAccount() const { openModal } = useModal() - if (active) { + const connectWallet = () => { + if (isEmbed) { + requestAccount() + } else { + openModal(ModalType.SelectWallet) + } + } + + if (isActive) { return ( - - Continue mint - + {isEmbed && ( + + Continue mint + + )} ) } From 15617dd93435bcbf798afafcbfcdc2cbe078451a Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Wed, 8 Nov 2023 23:59:47 +0100 Subject: [PATCH 42/69] Add a posisbility to send exact amount of BTC Adds an input form that allows the user to enter bitcoin amount that he wants to send to the deposit address. It will only show when `isEmbed` flag is set to true. There were some additional changes required to make it possible. First of all, I've adedd additional parameter to `TokenBalanceInputProps` - `tokenDecimals`. This way it will be possible to pass that parameter to `TokenBalanceInput` component through `FormikTokenBalanceInput` component, and it will allow to specify the input for the token we want to use. For example, without that, whenever we put the amount in `FormikTokenBalanceInput`, it will automatically convert that to wei (amount*10^18), which means that we can't use it with bitcoin, that has only 8 decimal places. After the change, we can pass `tokenDecimals = 8`, to make the `FormikTokenBalanceInput` working for bitcoin amounts. Second of all, there were some changes needed in `stc/utils/forms`, espcially with the `defaultGreaterThanMsg` and `defaultLessThanMsg`. The form uses `formatTokenAmount` which also worked with default 18 decimals and 2 precision. This means, that when we've put a value that is greater than our bitcoin balance (let's say our btc balance is 0.66666), it would siplay an error: The value should be less than or equal `~0.67` + it would break the app. To fix that we have to pass 8 decimals nad 8 precision there. --- src/components/TokenBalanceInput/index.tsx | 18 +-- src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx | 145 +++++++++++++++--- src/utils/forms.ts | 38 +++-- src/utils/tBTC.ts | 3 +- 4 files changed, 161 insertions(+), 43 deletions(-) diff --git a/src/components/TokenBalanceInput/index.tsx b/src/components/TokenBalanceInput/index.tsx index 1924aadce..3ff966ada 100644 --- a/src/components/TokenBalanceInput/index.tsx +++ b/src/components/TokenBalanceInput/index.tsx @@ -31,6 +31,7 @@ export interface TokenBalanceInputProps hasError?: boolean errorMsgText?: string | JSX.Element helperText?: string | JSX.Element + tokenDecimals?: number } const TokenBalanceInput: FC = ({ @@ -42,6 +43,7 @@ const TokenBalanceInput: FC = ({ errorMsgText, helperText, hasError = false, + tokenDecimals = web3Constants.STANDARD_ERC20_DECIMALS, ...inputProps }) => { const inputRef = useRef() @@ -64,24 +66,18 @@ const TokenBalanceInput: FC = ({ const setToMax = () => { let remainder = Zero const { decimalScale } = inputProps - if ( - decimalScale && - decimalScale > 0 && - decimalScale < web3Constants.STANDARD_ERC20_DECIMALS - ) { + if (decimalScale && decimalScale > 0 && decimalScale < tokenDecimals) { remainder = BigNumber.from(max).mod( - BigNumber.from(10).pow( - web3Constants.STANDARD_ERC20_DECIMALS - decimalScale - ) + BigNumber.from(10).pow(tokenDecimals - decimalScale) ) } - _setAmount(formatUnits(BigNumber.from(max).sub(remainder))) + _setAmount(formatUnits(BigNumber.from(max).sub(remainder), tokenDecimals)) setAmount(valueRef.current) } const _setAmount = (value: string | number) => { valueRef.current = value - ? parseUnits(value.toString()).toString() + ? parseUnits(value.toString(), tokenDecimals).toString() : undefined } @@ -106,7 +102,7 @@ const TokenBalanceInput: FC = ({ onValueChange={(values: NumberFormatInputValues) => _setAmount(values.value) } - value={amount ? formatUnits(amount) : undefined} + value={amount ? formatUnits(amount, tokenDecimals) : undefined} onChange={() => { setAmount(valueRef.current) }} diff --git a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx index 01f235f37..b74620627 100644 --- a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx +++ b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx @@ -10,6 +10,7 @@ import { Divider, useColorModeValue, Card, + BodySm, } from "@threshold-network/components" import { BridgeProcessCardTitle } from "../components/BridgeProcessCardTitle" import { BridgeProcessCardSubTitle } from "../components/BridgeProcessCardSubTitle" @@ -29,6 +30,12 @@ import { useRequestBitcoinAccount, useSendBitcoinTransaction, } from "../../../../hooks/ledger-live-app" +import { Form, FormikTokenBalanceInput } from "../../../../components/Forms" +import { InlineTokenBalance } from "../../../../components/TokenBalance" +import { tBTCFillBlack } from "../../../../static/icons/tBTCFillBlack" +import { FormikErrors, useFormikContext, withFormik } from "formik" +import { getErrorsObj, validateAmountInRange } from "../../../../utils/forms" +import { MINT_BITCOIN_MIN_AMOUNT } from "../../../../utils/tBTC" const AddressRow: FC< { address: string; text: string } & Pick @@ -135,15 +142,17 @@ const MakeDepositComponent: FC<{ await requestAccount() }, [requestAccount]) - const handleSendBitcoinTransaction = useCallback(async () => { - try { - // TODO: Allow user to specify how many bitcoins he want to send ( + do a - // validation [min 0.01 BTC])) - await sendBitcoinTransaction("1000000", btcDepositAddress) // 0.01 BTC - } catch (e) { - console.error(e) - } - }, [btcDepositAddress, sendBitcoinTransaction]) + const handleSendBitcoinTransaction = useCallback( + async (values: SendBitcoinsToDepositAddressFormValues) => { + const { amount } = values + try { + await sendBitcoinTransaction(amount, btcDepositAddress) + } catch (e) { + console.error(e) + } + }, + [btcDepositAddress, sendBitcoinTransaction] + ) return ( <> @@ -210,20 +219,118 @@ const MakeDepositComponent: FC<{ : "Choose bitcoin account"} )} + {isEmbed && ledgerBitcoinAccount && ( + + )} + {!isEmbed && ( + /* TODO: No need to use button here. We can replace it with just some text */ + + )} + + ) +} + +/** + * Formik for Ledger Live App where we can send bitcoins without leaving our + * T dashboard. It should only be visible with `isEmbed` flag. + */ + +type SendBitcoinsToDepositAddressFormBaseProps = { + maxTokenAmount: string +} + +const SendBitcoinsToDepositAddressFormBase: FC< + SendBitcoinsToDepositAddressFormBaseProps +> = ({ maxTokenAmount }) => { + const { isSubmitting } = + useFormikContext() + + return ( +
+ + Amount + + Balance:{" "} + + + + } + placeholder="Amount of bitcoins you want to send to deposit address." + max={maxTokenAmount} + icon={tBTCFillBlack} + tokenDecimals={8} + /> - + ) } +type SendBitcoinsToDepositAddressFormValues = { + amount: string +} + +type SendBitcoinsToDepositAddressFormProps = { + onSubmitForm: (values: SendBitcoinsToDepositAddressFormValues) => void +} & SendBitcoinsToDepositAddressFormBaseProps + +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) + export const MakeDeposit = withOnlyConnectedWallet(MakeDepositComponent) diff --git a/src/utils/forms.ts b/src/utils/forms.ts index 25496ff71..3fa7316ef 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 ValidationMsg = string | ((amount: string) => string) +type ValidationMsg = + | string + | ((amount: string, tokenDecimals?: number, precision?: number) => string) type ValidationOptions = { greaterThanValidationMsg: ValidationMsg lessThanValidationMsg: ValidationMsg @@ -18,19 +20,29 @@ type ValidationOptions = { } export const DEFAULT_MIN_VALUE = WeiPerEther.toString() -export const defaultLessThanMsg: (minAmount: string) => string = ( - minAmount -) => { +export const defaultLessThanMsg: ( + minAmount: string, + tokenDecimals?: number, + precision?: number +) => string = (minAmount, decimals = 18, precision = 2) => { return `The value should be less than or equal ${formatTokenAmount( - minAmount + minAmount, + "0,00.[0]0", + decimals, + precision )}` } -export const defaultGreaterThanMsg: (minAmount: string) => string = ( - maxAmount -) => { +export const defaultGreaterThanMsg: ( + minAmount: string, + tokenDecimals?: number, + precision?: number +) => string = (maxAmount, decimals = 18, precision = 2) => { return `The value should be greater than or equal ${formatTokenAmount( - maxAmount + maxAmount, + "0,00.[0]0", + decimals, + precision )}` } export const defaultValidationOptions: ValidationOptions = { @@ -43,7 +55,9 @@ export const validateAmountInRange = ( value: string, maxValue: string, minValue = DEFAULT_MIN_VALUE, - options: ValidationOptions = defaultValidationOptions + options: ValidationOptions = defaultValidationOptions, + tokenDecimals: number = 18, + precision: number = 2 ) => { if (!value) { return options.requiredMsg @@ -55,11 +69,11 @@ export const validateAmountInRange = ( if (valueInBN.gt(maxValueInBN)) { return typeof options.lessThanValidationMsg === "function" - ? options.lessThanValidationMsg(maxValue) + ? options.lessThanValidationMsg(maxValue, tokenDecimals, precision) : options.lessThanValidationMsg } else if (valueInBN.lt(minValueInBN)) { return typeof options.greaterThanValidationMsg === "function" - ? options.greaterThanValidationMsg(minValue) + ? options.greaterThanValidationMsg(minValue, tokenDecimals, precision) : options.greaterThanValidationMsg } } diff --git a/src/utils/tBTC.ts b/src/utils/tBTC.ts index 00edad7a3..142edcecb 100644 --- a/src/utils/tBTC.ts +++ b/src/utils/tBTC.ts @@ -79,7 +79,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 From 4d6d4e185d16750db687a1dfe6b94b37a3f8e2f0 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Thu, 9 Nov 2023 00:29:51 +0100 Subject: [PATCH 43/69] Improve data loading after connecting account This improves data loading with SDK v2 when we connect a wallet. Right now the problem is that when we've genrated a deposit address (so we are at step 2 of the minting flow), then refresh the page and then connect again, the page will show step 1 (provide data) for a second before displaying loading state and then step 2 (when we finished before we refreshed a page). This commit fixes that, and it won't show step 1 in such case. Still, there is small issue, because before SDK v2, the data was laoded just after connecting an account and now you have to wait ~1sec for it to start loading (due to sdk async initialization). I will attempt to fix that in future commits. --- src/components/SubmitTxButton.tsx | 4 +++- src/pages/tBTC/Bridge/Mint.tsx | 4 +++- src/pages/tBTC/Bridge/Unmint.tsx | 9 +++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/SubmitTxButton.tsx b/src/components/SubmitTxButton.tsx index 06ef62234..2ac5ab1d8 100644 --- a/src/components/SubmitTxButton.tsx +++ b/src/components/SubmitTxButton.tsx @@ -5,6 +5,7 @@ import { useModal } from "../hooks/useModal" import { useIsActive } from "../hooks/useIsActive" import { useEmbedFeatureFlag } from "../hooks/useEmbedFeatureFlag" import { useRequestEthereumAccount } from "../hooks/ledger-live-app" +import { useIsSdkInitializing } from "../contexts/ThresholdContext" interface Props extends ButtonProps { onSubmit?: () => void @@ -17,6 +18,7 @@ const SubmitTxButton: FC = ({ ...buttonProps }) => { const { isActive } = useIsActive() + const { isSdkInitializedWithSigner } = useIsSdkInitializing() const { isEmbed } = useEmbedFeatureFlag() const { requestAccount } = useRequestEthereumAccount() const { openModal } = useModal() @@ -29,7 +31,7 @@ const SubmitTxButton: FC = ({ } } - if (isActive) { + 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/pages/tBTC/Bridge/Minting/MakeDeposit.tsx b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx index f003b442b..698e48c4f 100644 --- a/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx +++ b/src/pages/tBTC/Bridge/Minting/MakeDeposit.tsx @@ -1,7 +1,6 @@ import { Badge, BodyMd, - BodySm, Box, BoxLabel, Button, @@ -31,12 +30,10 @@ import { useRequestBitcoinAccount, useSendBitcoinTransaction, } from "../../../../hooks/ledger-live-app" -import { Form, FormikTokenBalanceInput } from "../../../../components/Forms" -import { InlineTokenBalance } from "../../../../components/TokenBalance" -import { tBTCFillBlack } from "../../../../static/icons/tBTCFillBlack" -import { FormikErrors, useFormikContext, withFormik } from "formik" -import { getErrorsObj, validateAmountInRange } from "../../../../utils/forms" -import { MINT_BITCOIN_MIN_AMOUNT } from "../../../../utils/tBTC" +import { + SendBitcoinsToDepositAddressForm, + SendBitcoinsToDepositAddressFormValues, +} from "../../../../components/tBTC" const AddressRow: FC< { address: string; text: string } & Pick @@ -138,6 +135,7 @@ const MakeDepositComponent: FC<{ const { btcDepositAddress, ethAddress, btcRecoveryAddress, updateState } = useTbtcState() + // ↓ Ledger Live App ↓ const { isEmbed } = useIsEmbed() const { requestAccount, account: ledgerBitcoinAccount } = useRequestBitcoinAccount() @@ -158,6 +156,7 @@ const MakeDepositComponent: FC<{ }, [btcDepositAddress, sendBitcoinTransaction] ) + // ↑ Ledger Live App ↑ return ( <> @@ -246,96 +245,4 @@ const MakeDepositComponent: FC<{ ) } -/** - * Formik for Ledger Live App where we can send bitcoins without leaving our - * T dashboard. It should only be visible with `isEmbed` flag. - */ - -type SendBitcoinsToDepositAddressFormBaseProps = { - maxTokenAmount: string -} - -const SendBitcoinsToDepositAddressFormBase: FC< - SendBitcoinsToDepositAddressFormBaseProps -> = ({ maxTokenAmount }) => { - const { isSubmitting } = - useFormikContext() - - return ( -
- - Amount - - Balance:{" "} - - - - } - placeholder="Amount of bitcoins you want to send to deposit address." - max={maxTokenAmount} - icon={tBTCFillBlack} - tokenDecimals={8} - /> - - - ) -} - -type SendBitcoinsToDepositAddressFormValues = { - amount: string -} - -type SendBitcoinsToDepositAddressFormProps = { - onSubmitForm: (values: SendBitcoinsToDepositAddressFormValues) => void -} & SendBitcoinsToDepositAddressFormBaseProps - -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) - export const MakeDeposit = withOnlyConnectedWallet(MakeDepositComponent)