From 46ed33f08ac847a16486ec657f61cb8f8e837dc2 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 24 Apr 2024 16:21:07 +0200 Subject: [PATCH 1/5] Remove Ethereum account from the wallet context We do not need to connect Ethereum account with the dapp since we want to operate only on the Bitcoin wallet. The Acre SDK will generate the Ethereum address based on the Bitcoin address under the hood and the user's identifier should always be bitcoin address and the SDK will take a care of converting it to Ethereum address under the hood. Still, some features in the Acre SDK expect the Ethereum address eg. to get the estimated bitcoin balance. Hence as a temporary solution we set the ethereum account to zero address to not break the dapp. In the future we want to completely get rid of the ethereum account from the dapp wallet context and we will only pass the bitcoin address to the Acre SDK. --- .../acre-react/contexts/AcreSdkContext.tsx | 1 + .../TransactionModal/ActionFormModal.tsx | 9 +++---- .../ActiveStakingStep/DepositBTCModal.tsx | 2 +- .../TransactionModal/ModalContentWrapper.tsx | 15 ++--------- dapp/src/contexts/StakeFlowContext.tsx | 9 +++---- dapp/src/contexts/WalletContext.tsx | 6 ++--- dapp/src/hooks/index.ts | 1 - dapp/src/hooks/sdk/useFetchBTCBalance.ts | 2 +- dapp/src/hooks/sdk/useInitializeAcreSdk.ts | 7 ++--- dapp/src/hooks/toasts/useInitGlobalToasts.ts | 1 - .../hooks/toasts/useShowWalletErrorToast.ts | 8 ++---- dapp/src/hooks/useRequestEthereumAccount.ts | 26 ------------------- dapp/src/hooks/useWallet.ts | 19 +++++++++----- dapp/src/types/toast.ts | 2 -- 14 files changed, 34 insertions(+), 74 deletions(-) delete mode 100644 dapp/src/hooks/useRequestEthereumAccount.ts diff --git a/dapp/src/acre-react/contexts/AcreSdkContext.tsx b/dapp/src/acre-react/contexts/AcreSdkContext.tsx index 75dd77a60..6949d8a8e 100644 --- a/dapp/src/acre-react/contexts/AcreSdkContext.tsx +++ b/dapp/src/acre-react/contexts/AcreSdkContext.tsx @@ -20,6 +20,7 @@ export function AcreSdkProvider({ children }: { children: React.ReactNode }) { const [acre, setAcre] = useState(undefined) const [isInitialized, setIsInitialized] = useState(false) + // TODO: initialize Acre SDK w/o Ethereum address. const init = useCallback( async (ethereumAddress: string, network: EthereumNetwork) => { if (!ethereumAddress) throw new Error("Ethereum address not defined") diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index 0f7c824c9..c509f2fd1 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -23,7 +23,7 @@ import UnstakeFormModal from "./ActiveUnstakingStep/UnstakeFormModal" const TABS = Object.values(ACTION_FLOW_TYPES) function ActionFormModal({ defaultType }: { defaultType: ActionFlowType }) { - const { btcAccount, ethAccount } = useWalletContext() + const { btcAccount } = useWalletContext() const { type, setType } = useModalFlowContext() const { setTokenAmount } = useTransactionContext() const { initStake } = useStakeFlowContext() @@ -32,12 +32,11 @@ function ActionFormModal({ defaultType }: { defaultType: ActionFlowType }) { const handleInitStake = useCallback(async () => { const btcAddress = btcAccount?.address - const ethAddress = ethAccount?.address - if (btcAddress && ethAddress) { - await initStake(btcAddress, ethAddress) + if (btcAddress) { + await initStake(btcAddress) } - }, [btcAccount?.address, ethAccount?.address, initStake]) + }, [btcAccount?.address, initStake]) const handleSubmitForm = useCallback( async (values: TokenAmountFormValues) => { diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 4b68afa54..a5b9a0bff 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -75,7 +75,7 @@ export default function DepositBTCModal() { const response = await depositTelemetry( depositReceipt, btcAddress, - ethAccount.address, + ethAccount, ) setIsLoading(false) diff --git a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx index 33c94bdbe..7fe4973d1 100644 --- a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx +++ b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx @@ -2,11 +2,10 @@ import React from "react" import { useModalFlowContext, useRequestBitcoinAccount, - useRequestEthereumAccount, useTransactionContext, useWalletContext, } from "#/hooks" -import { BitcoinIcon, EthereumIcon } from "#/assets/icons" +import { BitcoinIcon } from "#/assets/icons" import { ActionFlowType, PROCESS_STATUSES } from "#/types" import { isSupportedBTCAddressType } from "#/utils" import ActionFormModal from "./ActionFormModal" @@ -23,9 +22,8 @@ export default function ModalContentWrapper({ defaultType: ActionFlowType children: React.ReactNode }) { - const { btcAccount, ethAccount } = useWalletContext() + const { btcAccount } = useWalletContext() const { requestAccount: requestBitcoinAccount } = useRequestBitcoinAccount() - const { requestAccount: requestEthereumAccount } = useRequestEthereumAccount() const { type, status, onClose, onResume } = useModalFlowContext() const { tokenAmount } = useTransactionContext() @@ -38,15 +36,6 @@ export default function ModalContentWrapper({ /> ) - if (!ethAccount) - return ( - - ) - if (!tokenAmount) return if (status === PROCESS_STATUSES.PAUSED) diff --git a/dapp/src/contexts/StakeFlowContext.tsx b/dapp/src/contexts/StakeFlowContext.tsx index adadd561c..18938b86b 100644 --- a/dapp/src/contexts/StakeFlowContext.tsx +++ b/dapp/src/contexts/StakeFlowContext.tsx @@ -7,10 +7,7 @@ import { import { REFERRAL } from "#/constants" type StakeFlowContextValue = Omit & { - initStake: ( - bitcoinRecoveryAddress: string, - ethereumAddress: string, - ) => Promise + initStake: (bitcoinAddress: string) => Promise } export const StakeFlowContext = React.createContext({ @@ -30,10 +27,10 @@ export function StakeFlowProvider({ children }: { children: React.ReactNode }) { } = useStakeFlow() const initStake = useCallback( - async (bitcoinRecoveryAddress: string, ethereumAddress: string) => { + async (bitcoinAddress: string) => { if (!acre) throw new Error("Acre SDK not defined") - await acreInitStake(bitcoinRecoveryAddress, ethereumAddress, REFERRAL) + await acreInitStake(bitcoinAddress, REFERRAL) }, [acreInitStake, acre], ) diff --git a/dapp/src/contexts/WalletContext.tsx b/dapp/src/contexts/WalletContext.tsx index 82cb00ffd..5d8cca3a3 100644 --- a/dapp/src/contexts/WalletContext.tsx +++ b/dapp/src/contexts/WalletContext.tsx @@ -4,8 +4,8 @@ import React, { createContext, useEffect, useMemo, useState } from "react" type WalletContextValue = { btcAccount: Account | undefined setBtcAccount: React.Dispatch> - ethAccount: Account | undefined - setEthAccount: React.Dispatch> + ethAccount: string | undefined + setEthAccount: React.Dispatch> isConnected: boolean } @@ -23,7 +23,7 @@ export function WalletContextProvider({ children: React.ReactNode }): React.ReactElement { const [btcAccount, setBtcAccount] = useState(undefined) - const [ethAccount, setEthAccount] = useState(undefined) + const [ethAccount, setEthAccount] = useState(undefined) const [isConnected, setIsConnected] = useState(false) useEffect(() => { diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index 5524c6f4b..abcb24f8e 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -4,7 +4,6 @@ export * from "./sdk" export * from "./subgraph" export * from "./useDetectThemeMode" export * from "./useRequestBitcoinAccount" -export * from "./useRequestEthereumAccount" export * from "./useWalletContext" export * from "./useSidebar" export * from "./useDocsDrawer" diff --git a/dapp/src/hooks/sdk/useFetchBTCBalance.ts b/dapp/src/hooks/sdk/useFetchBTCBalance.ts index ab73285d3..284ecaf67 100644 --- a/dapp/src/hooks/sdk/useFetchBTCBalance.ts +++ b/dapp/src/hooks/sdk/useFetchBTCBalance.ts @@ -15,7 +15,7 @@ export function useFetchBTCBalance() { const getBtcBalance = async () => { if (!isInitialized || !ethAccount || !acre) return - const chainIdentifier = EthereumAddress.from(ethAccount.address) + const chainIdentifier = EthereumAddress.from(ethAccount) const sharesBalance = await acre.staking.sharesBalance(chainIdentifier) const estimatedBitcoinBalance = await acre.staking.estimatedBitcoinBalance(chainIdentifier) diff --git a/dapp/src/hooks/sdk/useInitializeAcreSdk.ts b/dapp/src/hooks/sdk/useInitializeAcreSdk.ts index 56af453e1..79c04a4c7 100644 --- a/dapp/src/hooks/sdk/useInitializeAcreSdk.ts +++ b/dapp/src/hooks/sdk/useInitializeAcreSdk.ts @@ -9,11 +9,12 @@ export function useInitializeAcreSdk() { const { init } = useAcreContext() useEffect(() => { - if (!ethAccount?.address) return + // TODO: Init Acre SDK w/o Ethereum account. + if (!ethAccount) return const initSDK = async (ethAddress: string) => { await init(ethAddress, ETHEREUM_NETWORK) } - logPromiseFailure(initSDK(ethAccount.address)) - }, [ethAccount?.address, init]) + logPromiseFailure(initSDK(ethAccount)) + }, [ethAccount, init]) } diff --git a/dapp/src/hooks/toasts/useInitGlobalToasts.ts b/dapp/src/hooks/toasts/useInitGlobalToasts.ts index 0fec6ef12..fd2bcdbcf 100644 --- a/dapp/src/hooks/toasts/useInitGlobalToasts.ts +++ b/dapp/src/hooks/toasts/useInitGlobalToasts.ts @@ -1,6 +1,5 @@ import { useShowWalletErrorToast } from "./useShowWalletErrorToast" export function useInitGlobalToasts() { - useShowWalletErrorToast("ethereum") useShowWalletErrorToast("bitcoin") } diff --git a/dapp/src/hooks/toasts/useShowWalletErrorToast.ts b/dapp/src/hooks/toasts/useShowWalletErrorToast.ts index df57b308c..0ed8130c8 100644 --- a/dapp/src/hooks/toasts/useShowWalletErrorToast.ts +++ b/dapp/src/hooks/toasts/useShowWalletErrorToast.ts @@ -6,21 +6,17 @@ import { useToast } from "./useToast" import { useWallet } from "../useWallet" import { useTimeout } from "../useTimeout" -const { BITCOIN_WALLET_ERROR, ETHEREUM_WALLET_ERROR } = TOAST_IDS +const { BITCOIN_WALLET_ERROR } = TOAST_IDS const WALLET_ERROR_TOAST_ID = { bitcoin: { id: BITCOIN_WALLET_ERROR, Component: TOASTS[BITCOIN_WALLET_ERROR], }, - ethereum: { - id: ETHEREUM_WALLET_ERROR, - Component: TOASTS[ETHEREUM_WALLET_ERROR], - }, } export function useShowWalletErrorToast( - type: "bitcoin" | "ethereum", + type: "bitcoin", delay = ONE_SEC_IN_MILLISECONDS, ) { const { diff --git a/dapp/src/hooks/useRequestEthereumAccount.ts b/dapp/src/hooks/useRequestEthereumAccount.ts deleted file mode 100644 index 1fa5c1f16..000000000 --- a/dapp/src/hooks/useRequestEthereumAccount.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useRequestAccount } from "@ledgerhq/wallet-api-client-react" -import { useCallback, useContext, useEffect } from "react" -import { WalletContext } from "#/contexts" -import { UseRequestAccountReturn } from "#/types" -import { CURRENCY_ID_ETHEREUM } from "#/constants" -import { useWalletApiReactTransport } from "./useWalletApiReactTransport" - -export function useRequestEthereumAccount(): UseRequestAccountReturn { - const { setEthAccount } = useContext(WalletContext) - const { account, requestAccount } = useRequestAccount() - const { walletApiReactTransport } = useWalletApiReactTransport() - - useEffect(() => { - if (account) { - setEthAccount(account) - } - }, [account, setEthAccount]) - - const requestEthereumAccount = useCallback(async () => { - walletApiReactTransport.connect() - await requestAccount({ currencyIds: [CURRENCY_ID_ETHEREUM] }) - walletApiReactTransport.disconnect() - }, [requestAccount, walletApiReactTransport]) - - return { requestAccount: requestEthereumAccount } -} diff --git a/dapp/src/hooks/useWallet.ts b/dapp/src/hooks/useWallet.ts index 7511494d6..f97cec66d 100644 --- a/dapp/src/hooks/useWallet.ts +++ b/dapp/src/hooks/useWallet.ts @@ -1,18 +1,25 @@ import { useMemo } from "react" +import { ZeroAddress } from "ethers" import { useWalletContext } from "./useWalletContext" import { useRequestBitcoinAccount } from "./useRequestBitcoinAccount" -import { useRequestEthereumAccount } from "./useRequestEthereumAccount" export function useWallet() { - const { btcAccount, ethAccount } = useWalletContext() + const { btcAccount, ethAccount, setEthAccount } = useWalletContext() const { requestAccount: requestBitcoinAccount } = useRequestBitcoinAccount() - const { requestAccount: requestEthereumAccount } = useRequestEthereumAccount() return useMemo( () => ({ - bitcoin: { account: btcAccount, requestAccount: requestBitcoinAccount }, - ethereum: { account: ethAccount, requestAccount: requestEthereumAccount }, + bitcoin: { + account: btcAccount, + requestAccount: async () => { + await requestBitcoinAccount() + // TODO: Temporary solution - we do not need the eth account and we + // want to create the Acre SDK w/o passing the Ethereum Account. + setEthAccount(ZeroAddress) + }, + }, + ethereum: { account: ethAccount }, }), - [btcAccount, requestBitcoinAccount, ethAccount, requestEthereumAccount], + [btcAccount, requestBitcoinAccount, ethAccount, setEthAccount], ) } diff --git a/dapp/src/types/toast.ts b/dapp/src/types/toast.ts index c4a3fe962..7bb564427 100644 --- a/dapp/src/types/toast.ts +++ b/dapp/src/types/toast.ts @@ -7,7 +7,6 @@ import { export const TOAST_IDS = { BITCOIN_WALLET_ERROR: "bitcoin-wallet-error", - ETHEREUM_WALLET_ERROR: "ethereum-wallet-error", SIGNING_ERROR: "signing-error", DEPOSIT_TRANSACTION_ERROR: "deposit-transaction-error", } as const @@ -17,7 +16,6 @@ export type ToastID = (typeof TOAST_IDS)[keyof typeof TOAST_IDS] // eslint-disable-next-line @typescript-eslint/no-explicit-any export const TOASTS: Record ReactNode> = { [TOAST_IDS.BITCOIN_WALLET_ERROR]: WalletErrorToast, - [TOAST_IDS.ETHEREUM_WALLET_ERROR]: WalletErrorToast, [TOAST_IDS.SIGNING_ERROR]: SigningMessageErrorToast, [TOAST_IDS.DEPOSIT_TRANSACTION_ERROR]: DepositTransactionErrorToast, } From 315b54521b1d94bd1f1e3b1fd8668084e3634f4d Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 24 Apr 2024 17:11:47 +0200 Subject: [PATCH 2/5] Initialize the Acre SDK w/o Ethereum address --- .../acre-react/contexts/AcreSdkContext.tsx | 8 ++--- dapp/src/hooks/sdk/useInitializeAcreSdk.ts | 15 +++++---- dapp/src/web3/ledger-live-signer.ts | 33 ++++++++++++------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/dapp/src/acre-react/contexts/AcreSdkContext.tsx b/dapp/src/acre-react/contexts/AcreSdkContext.tsx index 6949d8a8e..a05a9de9f 100644 --- a/dapp/src/acre-react/contexts/AcreSdkContext.tsx +++ b/dapp/src/acre-react/contexts/AcreSdkContext.tsx @@ -6,7 +6,7 @@ const TBTC_API_ENDPOINT = import.meta.env.VITE_TBTC_API_ENDPOINT type AcreSdkContextValue = { acre?: Acre - init: (ethereumAddress: string, network: EthereumNetwork) => Promise + init: (bitcoinAddress: string, network: EthereumNetwork) => Promise isInitialized: boolean } @@ -22,11 +22,11 @@ export function AcreSdkProvider({ children }: { children: React.ReactNode }) { // TODO: initialize Acre SDK w/o Ethereum address. const init = useCallback( - async (ethereumAddress: string, network: EthereumNetwork) => { - if (!ethereumAddress) throw new Error("Ethereum address not defined") + async (bitcoinAddress: string, network: EthereumNetwork) => { + if (!bitcoinAddress) throw new Error("Bitcoin address not defined") const sdk = await Acre.initializeEthereum( - await LedgerLiveEthereumSigner.fromAddress(ethereumAddress), + await LedgerLiveEthereumSigner.fromAddress(bitcoinAddress), network, TBTC_API_ENDPOINT, ) diff --git a/dapp/src/hooks/sdk/useInitializeAcreSdk.ts b/dapp/src/hooks/sdk/useInitializeAcreSdk.ts index 79c04a4c7..3e97ebf4d 100644 --- a/dapp/src/hooks/sdk/useInitializeAcreSdk.ts +++ b/dapp/src/hooks/sdk/useInitializeAcreSdk.ts @@ -5,16 +5,17 @@ import { useAcreContext } from "#/acre-react/hooks" import { useWalletContext } from "../useWalletContext" export function useInitializeAcreSdk() { - const { ethAccount } = useWalletContext() + const { btcAccount } = useWalletContext() const { init } = useAcreContext() + const bitcoinAddress = btcAccount?.address useEffect(() => { - // TODO: Init Acre SDK w/o Ethereum account. - if (!ethAccount) return + if (!bitcoinAddress) return - const initSDK = async (ethAddress: string) => { - await init(ethAddress, ETHEREUM_NETWORK) + const initSDK = async (_bitcoinAddress: string) => { + await init(_bitcoinAddress, ETHEREUM_NETWORK) } - logPromiseFailure(initSDK(ethAccount)) - }, [ethAccount, init]) + + logPromiseFailure(initSDK(bitcoinAddress)) + }, [bitcoinAddress, init]) } diff --git a/dapp/src/web3/ledger-live-signer.ts b/dapp/src/web3/ledger-live-signer.ts index 7bdd658c6..95812617e 100644 --- a/dapp/src/web3/ledger-live-signer.ts +++ b/dapp/src/web3/ledger-live-signer.ts @@ -12,7 +12,7 @@ import { TypedDataField, TransactionResponse, } from "ethers" -import { CURRENCY_ID_ETHEREUM } from "#/constants" +import { CURRENCY_ID_BITCOIN } from "#/constants" import { EthereumSignerCompatibleWithEthersV5 } from "@acre-btc/sdk" import { getLedgerWalletAPITransport as getDappLedgerWalletAPITransport, @@ -28,10 +28,10 @@ class LedgerLiveEthereumSigner extends EthereumSignerCompatibleWithEthersV5 { readonly #client: WalletAPIClient - readonly #account: Account + readonly #bitcoinAccount: Account static async fromAddress( - address: string, + bitcoinAddress: string, getLedgerWalletAPITransport: () => WindowMessageTransport = getDappLedgerWalletAPITransport, ) { const dappTransport = getLedgerWalletAPITransport() @@ -41,14 +41,14 @@ class LedgerLiveEthereumSigner extends EthereumSignerCompatibleWithEthersV5 { const client = new WalletAPIClient(dappTransport) const accountsList = await client.account.list({ - currencyIds: [CURRENCY_ID_ETHEREUM], + currencyIds: [CURRENCY_ID_BITCOIN], }) dappTransport.disconnect() - const account = accountsList.find((acc) => acc.address === address) + const account = accountsList.find((acc) => acc.address === bitcoinAddress) - if (!account) throw new Error("Account not found") + if (!account) throw new Error("Bitcoin Account not found") return new LedgerLiveEthereumSigner( dappTransport, @@ -65,13 +65,19 @@ class LedgerLiveEthereumSigner extends EthereumSignerCompatibleWithEthersV5 { provider: Provider | null, ) { super(provider) - this.#account = account + this.#bitcoinAccount = account this.#transport = transport this.#client = walletApiClient } + // eslint-disable-next-line class-methods-use-this getAddress(): Promise { - return Promise.resolve(this.#account.address) + // TODO: We should return the Ethereum address created based on the Bitcoin + // address. We probably will use the Signer from OrangeKit once it is + // implemented. For now, the `Signer.getAddress` is not used anywhere in the + // Acre SDK, only during the tBTC-v2.ts SDK initialization, so we can set + // random ethereum account. + return Promise.resolve("0x7b570B83D53e0671271DCa2CDf3429E9C4CAb12E") } async #clientRequest(callback: () => Promise) { @@ -87,7 +93,7 @@ class LedgerLiveEthereumSigner extends EthereumSignerCompatibleWithEthersV5 { return new LedgerLiveEthereumSigner( this.#transport, this.#client, - this.#account, + this.#bitcoinAccount, provider, ) } @@ -97,7 +103,10 @@ class LedgerLiveEthereumSigner extends EthereumSignerCompatibleWithEthersV5 { serializeLedgerWalletApiEthereumTransaction(transaction) const buffer = await this.#clientRequest(() => - this.#client.transaction.sign(this.#account.id, ethereumTransaction), + this.#client.transaction.sign( + this.#bitcoinAccount.id, + ethereumTransaction, + ), ) return buffer.toString() @@ -111,7 +120,7 @@ class LedgerLiveEthereumSigner extends EthereumSignerCompatibleWithEthersV5 { const transactionHash = await this.#clientRequest(() => this.#client.transaction.signAndBroadcast( - this.#account.id, + this.#bitcoinAccount.id, ethereumTransaction, ), ) @@ -129,7 +138,7 @@ class LedgerLiveEthereumSigner extends EthereumSignerCompatibleWithEthersV5 { async signMessage(message: string | Uint8Array): Promise { const buffer = await this.#clientRequest(() => this.#client.message.sign( - this.#account.id, + this.#bitcoinAccount.id, Buffer.from(message.toString()), ), ) From bb9147779b8e13eda184b3c32ff8883de10f844d Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 8 May 2024 22:07:26 +0200 Subject: [PATCH 3/5] Init the Acre SDK in dapp w/o ethereum address Create the Bitcoin Provider based on the selected bitcoin account id providede by the Ledger Live Wallet API. --- dapp/ledger-manifest-development.json | 3 ++- .../acre-react/contexts/AcreSdkContext.tsx | 14 +++++++------- dapp/src/acre-react/hooks/useStakeFlow.ts | 9 ++++++--- dapp/src/constants/chains.ts | 14 +++++++++++--- dapp/src/contexts/StakeFlowContext.tsx | 13 +++++-------- dapp/src/hooks/sdk/useInitializeAcreSdk.ts | 19 +++++++++++-------- 6 files changed, 42 insertions(+), 30 deletions(-) diff --git a/dapp/ledger-manifest-development.json b/dapp/ledger-manifest-development.json index a986387d0..1a938120c 100644 --- a/dapp/ledger-manifest-development.json +++ b/dapp/ledger-manifest-development.json @@ -23,7 +23,8 @@ "account.list", "message.sign", "transaction.sign", - "transaction.signAndBroadcast" + "transaction.signAndBroadcast", + "bitcoin.getXPub" ], "domains": ["http://*"], "type": "walletApp" diff --git a/dapp/src/acre-react/contexts/AcreSdkContext.tsx b/dapp/src/acre-react/contexts/AcreSdkContext.tsx index a05a9de9f..2cff3d675 100644 --- a/dapp/src/acre-react/contexts/AcreSdkContext.tsx +++ b/dapp/src/acre-react/contexts/AcreSdkContext.tsx @@ -1,12 +1,15 @@ import React, { useCallback, useMemo, useState } from "react" -import { LedgerLiveEthereumSigner } from "#/web3" import { Acre, EthereumNetwork } from "@acre-btc/sdk" +import { BitcoinProvider } from "@acre-btc/sdk/dist/src/lib/bitcoin/providers" const TBTC_API_ENDPOINT = import.meta.env.VITE_TBTC_API_ENDPOINT type AcreSdkContextValue = { acre?: Acre - init: (bitcoinAddress: string, network: EthereumNetwork) => Promise + init: ( + bitcoinProvider: BitcoinProvider, + network: EthereumNetwork, + ) => Promise isInitialized: boolean } @@ -20,13 +23,10 @@ export function AcreSdkProvider({ children }: { children: React.ReactNode }) { const [acre, setAcre] = useState(undefined) const [isInitialized, setIsInitialized] = useState(false) - // TODO: initialize Acre SDK w/o Ethereum address. const init = useCallback( - async (bitcoinAddress: string, network: EthereumNetwork) => { - if (!bitcoinAddress) throw new Error("Bitcoin address not defined") - + async (bitcoinProvider: BitcoinProvider, network: EthereumNetwork) => { const sdk = await Acre.initializeEthereum( - await LedgerLiveEthereumSigner.fromAddress(bitcoinAddress), + bitcoinProvider, network, TBTC_API_ENDPOINT, ) diff --git a/dapp/src/acre-react/hooks/useStakeFlow.ts b/dapp/src/acre-react/hooks/useStakeFlow.ts index b7fd25f04..87a4fd0f8 100644 --- a/dapp/src/acre-react/hooks/useStakeFlow.ts +++ b/dapp/src/acre-react/hooks/useStakeFlow.ts @@ -3,7 +3,10 @@ import { StakeInitialization, DepositReceipt } from "@acre-btc/sdk" import { useAcreContext } from "./useAcreContext" export type UseStakeFlowReturn = { - initStake: (bitcoinAddress: string, referral: number) => Promise + initStake: ( + referral: number, + bitcoinRecoveryAddress?: string, + ) => Promise btcAddress?: string depositReceipt?: DepositReceipt signMessage: () => Promise @@ -22,12 +25,12 @@ export function useStakeFlow(): UseStakeFlowReturn { >(undefined) const initStake = useCallback( - async (bitcoinAddress: string, referral: number) => { + async (referral: number, bitcoinRecoveryAddress?: string) => { if (!acre || !isInitialized) throw new Error("Acre SDK not defined") const initializedStakeFlow = await acre.staking.initializeStake( - bitcoinAddress, referral, + bitcoinRecoveryAddress, ) const btcDepositAddress = await initializedStakeFlow.getBitcoinAddress() diff --git a/dapp/src/constants/chains.ts b/dapp/src/constants/chains.ts index 150d7575f..2da4a4a90 100644 --- a/dapp/src/constants/chains.ts +++ b/dapp/src/constants/chains.ts @@ -1,5 +1,13 @@ import { Chain } from "#/types" -import { EthereumNetwork, BitcoinNetwork } from "@acre-btc/sdk" +import { + EthereumNetwork, + BitcoinNetwork as AcreSDKBitcoinNetwork, +} from "@acre-btc/sdk" + +export type BitcoinNetwork = Exclude< + AcreSDKBitcoinNetwork, + AcreSDKBitcoinNetwork.Unknown +> export const BLOCK_EXPLORER: Record = { ethereum: { title: "Etherscan", url: "https://etherscan.io" }, @@ -11,5 +19,5 @@ export const ETHEREUM_NETWORK: EthereumNetwork = export const BITCOIN_NETWORK: BitcoinNetwork = import.meta.env.VITE_USE_TESTNET === "true" - ? BitcoinNetwork.Testnet - : BitcoinNetwork.Mainnet + ? AcreSDKBitcoinNetwork.Testnet + : AcreSDKBitcoinNetwork.Mainnet diff --git a/dapp/src/contexts/StakeFlowContext.tsx b/dapp/src/contexts/StakeFlowContext.tsx index 18938b86b..8e08b0366 100644 --- a/dapp/src/contexts/StakeFlowContext.tsx +++ b/dapp/src/contexts/StakeFlowContext.tsx @@ -7,7 +7,7 @@ import { import { REFERRAL } from "#/constants" type StakeFlowContextValue = Omit & { - initStake: (bitcoinAddress: string) => Promise + initStake: () => Promise } export const StakeFlowContext = React.createContext({ @@ -26,14 +26,11 @@ export function StakeFlowProvider({ children }: { children: React.ReactNode }) { stake, } = useStakeFlow() - const initStake = useCallback( - async (bitcoinAddress: string) => { - if (!acre) throw new Error("Acre SDK not defined") + const initStake = useCallback(async () => { + if (!acre) throw new Error("Acre SDK not defined") - await acreInitStake(bitcoinAddress, REFERRAL) - }, - [acreInitStake, acre], - ) + await acreInitStake(REFERRAL) + }, [acreInitStake, acre]) const context = useMemo( () => ({ diff --git a/dapp/src/hooks/sdk/useInitializeAcreSdk.ts b/dapp/src/hooks/sdk/useInitializeAcreSdk.ts index 3e97ebf4d..02c1b9ab8 100644 --- a/dapp/src/hooks/sdk/useInitializeAcreSdk.ts +++ b/dapp/src/hooks/sdk/useInitializeAcreSdk.ts @@ -1,21 +1,24 @@ import { useEffect } from "react" -import { ETHEREUM_NETWORK } from "#/constants" +import { BITCOIN_NETWORK, ETHEREUM_NETWORK } from "#/constants" import { logPromiseFailure } from "#/utils" import { useAcreContext } from "#/acre-react/hooks" +import { LedgerLiveWalletApiBitcoinProvider } from "@acre-btc/sdk/dist/src/lib/bitcoin/providers" import { useWalletContext } from "../useWalletContext" export function useInitializeAcreSdk() { const { btcAccount } = useWalletContext() const { init } = useAcreContext() - const bitcoinAddress = btcAccount?.address useEffect(() => { - if (!bitcoinAddress) return + if (!btcAccount?.id) return - const initSDK = async (_bitcoinAddress: string) => { - await init(_bitcoinAddress, ETHEREUM_NETWORK) + const initSDK = async (bitcoinAccountId: string) => { + const bitcoinProvider = await LedgerLiveWalletApiBitcoinProvider.init( + bitcoinAccountId, + BITCOIN_NETWORK, + ) + await init(bitcoinProvider, ETHEREUM_NETWORK) } - - logPromiseFailure(initSDK(bitcoinAddress)) - }, [bitcoinAddress, init]) + logPromiseFailure(initSDK(btcAccount.id)) + }, [btcAccount?.id, init]) } From 9fb193cc3d68882a37630a824ab0dbd219aad5cc Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 15 May 2024 10:12:45 +0200 Subject: [PATCH 4/5] Update Acre SDK initialization in dapp Use `initializeMainnet` or `initializeTestnet` fn to init the Acre SDK. --- dapp/.env | 3 ++ .../acre-react/contexts/AcreSdkContext.tsx | 32 ++++++++++++------- dapp/src/vite-env.d.ts | 1 + 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/dapp/.env b/dapp/.env index 7a39c66c5..6fa402df3 100644 --- a/dapp/.env +++ b/dapp/.env @@ -14,3 +14,6 @@ VITE_REFERRAL=123 VITE_DEFENDER_RELAYER_WEBHOOK_URL="https://api.defender.openzeppelin.com/actions/a0d6d2e2-ce9c-4619-aa2b-6c874fe97af7/runs/webhook/b1f17c89-8230-46e3-866f-a3213887974c/Sbddsy54cJ6sPg2bLPyuHJ" VITE_ACRE_SUBGRAPH_URL="https://api.studio.thegraph.com/query/73600/acre/version/latest" + +# TODO: Set this env variable in CI. +VITE_TBTC_API_ENDPOINT="" diff --git a/dapp/src/acre-react/contexts/AcreSdkContext.tsx b/dapp/src/acre-react/contexts/AcreSdkContext.tsx index 2cff3d675..0d2eb16b4 100644 --- a/dapp/src/acre-react/contexts/AcreSdkContext.tsx +++ b/dapp/src/acre-react/contexts/AcreSdkContext.tsx @@ -1,15 +1,14 @@ import React, { useCallback, useMemo, useState } from "react" -import { Acre, EthereumNetwork } from "@acre-btc/sdk" +import { Acre, BitcoinNetwork } from "@acre-btc/sdk" import { BitcoinProvider } from "@acre-btc/sdk/dist/src/lib/bitcoin/providers" +import { BITCOIN_NETWORK } from "#/constants" const TBTC_API_ENDPOINT = import.meta.env.VITE_TBTC_API_ENDPOINT +const ETH_RPC_URL = import.meta.env.VITE_ETH_HOSTNAME_HTTP type AcreSdkContextValue = { acre?: Acre - init: ( - bitcoinProvider: BitcoinProvider, - network: EthereumNetwork, - ) => Promise + init: (bitcoinProvider: BitcoinProvider) => Promise isInitialized: boolean } @@ -24,12 +23,23 @@ export function AcreSdkProvider({ children }: { children: React.ReactNode }) { const [isInitialized, setIsInitialized] = useState(false) const init = useCallback( - async (bitcoinProvider: BitcoinProvider, network: EthereumNetwork) => { - const sdk = await Acre.initializeEthereum( - bitcoinProvider, - network, - TBTC_API_ENDPOINT, - ) + async (bitcoinProvider: BitcoinProvider) => { + let sdk: Acre + + if (BITCOIN_NETWORK === BitcoinNetwork.Mainnet) { + sdk = await Acre.initializeMainnet( + bitcoinProvider, + TBTC_API_ENDPOINT, + ETH_RPC_URL, + ) + } else { + sdk = await Acre.initializeTestnet( + bitcoinProvider, + TBTC_API_ENDPOINT, + ETH_RPC_URL, + ) + } + setAcre(sdk) setIsInitialized(true) }, diff --git a/dapp/src/vite-env.d.ts b/dapp/src/vite-env.d.ts index 477206fe4..efe1127b6 100644 --- a/dapp/src/vite-env.d.ts +++ b/dapp/src/vite-env.d.ts @@ -6,6 +6,7 @@ interface ImportMetaEnv { readonly VITE_REFERRAL: number readonly VITE_TBTC_API_ENDPOINT: string readonly VITE_ACRE_SUBGRAPH_URL: string + readonly VITE_TBTC_API_ENDPOINT: string } interface ImportMeta { From 3cdb91ed239537294a492d9b01a9c1c360b9a928 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 15 May 2024 10:15:03 +0200 Subject: [PATCH 5/5] Remove unused ethers signer for Ledger Live Now the Acre SDK requires the bitcoin provider not ethereum singer. --- dapp/src/web3/index.ts | 1 - dapp/src/web3/ledger-live-signer.ts | 165 ---------------------------- dapp/src/web3/utils/index.ts | 1 - dapp/src/web3/utils/ledger-live.ts | 56 ---------- 4 files changed, 223 deletions(-) delete mode 100644 dapp/src/web3/index.ts delete mode 100644 dapp/src/web3/ledger-live-signer.ts delete mode 100644 dapp/src/web3/utils/index.ts delete mode 100644 dapp/src/web3/utils/ledger-live.ts diff --git a/dapp/src/web3/index.ts b/dapp/src/web3/index.ts deleted file mode 100644 index f38e7e840..000000000 --- a/dapp/src/web3/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ledger-live-signer" diff --git a/dapp/src/web3/ledger-live-signer.ts b/dapp/src/web3/ledger-live-signer.ts deleted file mode 100644 index 95812617e..000000000 --- a/dapp/src/web3/ledger-live-signer.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { - Account, - WalletAPIClient, - WindowMessageTransport, -} from "@ledgerhq/wallet-api-client" -import { - TypedDataEncoder, - Provider, - Signer, - TransactionRequest, - TypedDataDomain, - TypedDataField, - TransactionResponse, -} from "ethers" -import { CURRENCY_ID_BITCOIN } from "#/constants" -import { EthereumSignerCompatibleWithEthersV5 } from "@acre-btc/sdk" -import { - getLedgerWalletAPITransport as getDappLedgerWalletAPITransport, - getLedgerLiveProvider, - serializeLedgerWalletApiEthereumTransaction, -} from "./utils" - -// Created based on the -// https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/utils/ledger.ts -// but with support for ethers v6. -class LedgerLiveEthereumSigner extends EthereumSignerCompatibleWithEthersV5 { - readonly #transport: WindowMessageTransport - - readonly #client: WalletAPIClient - - readonly #bitcoinAccount: Account - - static async fromAddress( - bitcoinAddress: string, - getLedgerWalletAPITransport: () => WindowMessageTransport = getDappLedgerWalletAPITransport, - ) { - const dappTransport = getLedgerWalletAPITransport() - - dappTransport.connect() - - const client = new WalletAPIClient(dappTransport) - - const accountsList = await client.account.list({ - currencyIds: [CURRENCY_ID_BITCOIN], - }) - - dappTransport.disconnect() - - const account = accountsList.find((acc) => acc.address === bitcoinAddress) - - if (!account) throw new Error("Bitcoin Account not found") - - return new LedgerLiveEthereumSigner( - dappTransport, - client, - account, - getLedgerLiveProvider(), - ) - } - - private constructor( - transport: WindowMessageTransport, - walletApiClient: WalletAPIClient, - account: Account, - provider: Provider | null, - ) { - super(provider) - this.#bitcoinAccount = account - this.#transport = transport - this.#client = walletApiClient - } - - // eslint-disable-next-line class-methods-use-this - getAddress(): Promise { - // TODO: We should return the Ethereum address created based on the Bitcoin - // address. We probably will use the Signer from OrangeKit once it is - // implemented. For now, the `Signer.getAddress` is not used anywhere in the - // Acre SDK, only during the tBTC-v2.ts SDK initialization, so we can set - // random ethereum account. - return Promise.resolve("0x7b570B83D53e0671271DCa2CDf3429E9C4CAb12E") - } - - async #clientRequest(callback: () => Promise) { - try { - this.#transport.connect() - return await callback() - } finally { - this.#transport.disconnect() - } - } - - connect(provider: Provider | null): Signer { - return new LedgerLiveEthereumSigner( - this.#transport, - this.#client, - this.#bitcoinAccount, - provider, - ) - } - - async signTransaction(transaction: TransactionRequest): Promise { - const ethereumTransaction = - serializeLedgerWalletApiEthereumTransaction(transaction) - - const buffer = await this.#clientRequest(() => - this.#client.transaction.sign( - this.#bitcoinAccount.id, - ethereumTransaction, - ), - ) - - return buffer.toString() - } - - async sendTransaction(tx: TransactionRequest): Promise { - const populatedTransaction = await this.populateTransaction(tx) - - const ethereumTransaction = - serializeLedgerWalletApiEthereumTransaction(populatedTransaction) - - const transactionHash = await this.#clientRequest(() => - this.#client.transaction.signAndBroadcast( - this.#bitcoinAccount.id, - ethereumTransaction, - ), - ) - - const transactionResponse = - await this.provider?.getTransaction(transactionHash) - - if (!transactionResponse) { - throw new Error("Transaction response not found!") - } - - return transactionResponse - } - - async signMessage(message: string | Uint8Array): Promise { - const buffer = await this.#clientRequest(() => - this.#client.message.sign( - this.#bitcoinAccount.id, - Buffer.from(message.toString()), - ), - ) - - return buffer.toString("hex") - } - - async signTypedData( - domain: TypedDataDomain, - types: Record, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: Record, - ): Promise { - const payload = TypedDataEncoder.getPayload( - domain, - types, - value, - ) as unknown as object - - return this.signMessage(JSON.stringify(payload)) - } -} - -export { LedgerLiveEthereumSigner } diff --git a/dapp/src/web3/utils/index.ts b/dapp/src/web3/utils/index.ts deleted file mode 100644 index 222c65f9b..000000000 --- a/dapp/src/web3/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ledger-live" diff --git a/dapp/src/web3/utils/ledger-live.ts b/dapp/src/web3/utils/ledger-live.ts deleted file mode 100644 index 5ee8122f7..000000000 --- a/dapp/src/web3/utils/ledger-live.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - EthereumTransaction, - WindowMessageTransport, -} from "@ledgerhq/wallet-api-client" -import { TransactionRequest, ZeroAddress, JsonRpcProvider } from "ethers" -import { Hex } from "@acre-btc/sdk" - -export const getLedgerWalletAPITransport = () => new WindowMessageTransport() - -export const getLedgerLiveProvider = () => - new JsonRpcProvider(import.meta.env.VITE_ETH_HOSTNAME_HTTP) - -// Created based on the -// https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/lib/utils/ledger.ts. -export function serializeLedgerWalletApiEthereumTransaction( - transaction: TransactionRequest, -): EthereumTransaction { - const { - value, - to, - nonce, - data, - gasPrice, - gasLimit, - maxFeePerGas, - maxPriorityFeePerGas, - } = transaction - - const ethereumTransaction: EthereumTransaction = { - family: "ethereum" as const, - // @ts-expect-error We do not want to install external bignumber.js lib so - // here we use bigint. The Ledger Wallet Api just converts the bignumber.js - // object to string so we can pass bigint. See: - // https://github.com/LedgerHQ/wallet-api/blob/main/packages/core/src/families/ethereum/serializer.ts#L4 - amount: value ?? 0, - recipient: to?.toString() || ZeroAddress, - nonce: nonce ?? undefined, - // @ts-expect-error See comment above. - gasPrice: gasPrice ?? undefined, - // @ts-expect-error See comment above. - gasLimit: gasLimit ?? undefined, - // @ts-expect-error See comment above. - maxFeePerGas: maxFeePerGas ?? undefined, - // @ts-expect-error See comment above. - maxPriorityFeePerGas: maxPriorityFeePerGas ?? undefined, - } - - if (nonce) ethereumTransaction.nonce = Number(nonce) - if (data) - ethereumTransaction.data = Buffer.from( - Hex.from(data.toString()).toString(), - "hex", - ) - - return ethereumTransaction -}