Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ledger Live App #655

Merged
merged 71 commits into from
Dec 14, 2023
Merged

Ledger Live App #655

merged 71 commits into from
Dec 14, 2023

Conversation

michalsmiarowski
Copy link
Contributor

@michalsmiarowski michalsmiarowski commented Nov 1, 2023

Ledger Live App

Closes: #649
Blocked by: #654

This PR allows to run our dApp as Live App withing Ledger Live. The Live Apps are displayed in the Discover section of Ledger Live on Desktop (Windows, Mac, Linux) and mobile (Android and iOS).

The main purpose of it would be to complete the whole Mint & Unmint flow, without the need to leave the Ledger Live application and do a bitcoin transaction to generated deposit address. All transactions are done within the application.

Overall Description

When running as Ledger Live App, our Token Dashboard is embedded into it and displayet differently than in the website. We are checking that with our ?embed=true query parameter, that I've put in the manifest file. Only tbtc section is needed for this, so that's why onli this section is displayed and the rest are hidden.

The user can connect his ethereum account from Ledger to communicate with eth contracts. He can also choose which of his bitcoin addresses he wants to use to send the bitcoins from.

Technical Details

Overview

The code was written based on the Ledger Live App documentations. As you can see there are two sections in the documentation: DApp and Non-DApp - both describe two different ways of embedding an application into the Ledger Live Discover section. A first natural choice in our case would be the DApp section, since our website is a Dapp. Unfortunately, that is not the case, because from my experience and research it looks like it was not possible to do a bitcoin transaction there. This is why we choose the second option, which allows to use Wallet-API. With the help of this API we are able to do bitcoin and eth transactions, and also interact with eth contracts.

The Wallet-API also has two sections in the docs: Core-API and React-API, that uses Core-API under the hood. In our case we actually use both: React-API for connecting the eth/btc accounts and sending bitcoin transactions from one account to another (in our case to deposit address) and Core-Api to interact with eth contracts. Why?

The answer is that using only React-API would require us to reorganize tBTC v2 SDK just for the Ledger Live App functionality. The API for reacts needs raw data format of the ethereum transaction when we interact with the contract, and that can be obtained using populateTransaction method from ethers lib, but we are not returning it in such form in our SDK. This is why we've decided to create a separate signer for this purpose - to avoid doing any changes in the SDK just for that feature and to not unnecessarily extend SDK responsibility.

Ledger Live Ethereum Signer (wallet-api-core)

TBTC v2 SDK allows us to pass signer when initiating it. The signer must extend the Signer class from ethers lib and this is exactly what our Ledger Live Ethereum Signer do. It uses wallet-api-core lib under the hood. The signer was placed in tbtc-v2 repo

You can see a more detailed description of that signer, its purpose and explanation of how it works in keep-network/tbtc-v2#743.

In our dApp we are requesting an eth account using wallet-api-core-react (see the subsection below) and then pass the account to the signer using setAccount method.

Connecting wallets and doing simple transactions (wallet-api-core-react)

The Ledger Live Ethereum Signer is used to integrate with eth contracts, but what about connecting the account to our dApp and sending some tokens from one account to another? This is where we use wallet-api-core-react and it's hooks.

In our dApp we have three custom hooks that use hooks from wallet-api-core-react under the hood:

  • useRequestBitcoinAccount,
  • useRequestEthereumAccount,
  • useSendBitcoinTransaction.

The first two are pretty similar to the original ones (from the lib), but I've had to write a wrapper to it so that I can connect and disconnect walletApiReactTransport there. This is needed because our Ledger Live Ethereum Signer uses different instance of the transport there, so if we won't disconnect one or another, a no ongoing request error might occur. Based on the dosc the transport should be disconnected when we are done to ensure the communication is properly closed.

The third one, useSendBitcoinTransaction, is used to create a bitcoin transaction in a proper format that is required by wallet-api-core-react. The format for our bitcoin transaction looks like this:

const bitcoinTransaction = {
  family: "bitcoin", 
  amount: new BigNumber(100000000000000),
  recipient: "<bitcoin_address>",
};

Fields:

  • family (string): The cryptocurrency family to which the transaction belongs. This could be 'ethereum', 'bitcoin', etc.
  • amount (BigNumber): The amount of cryptocurrency to be sent in the transaction, represented in the smallest unit of the currency. For instance, in Bitcoin, an amount of 1 represents 0.00000001 BTC.
  • recipient (string): The address of the recipient of the transaction.
  • nonce (number, optional): This is the number of transactions sent from the sender's address.
  • data (Buffer, optional): Input data of the transaction. This is often used for contract interactions.
  • gasPrice (BigNumber, optional): The price per gas in wei.
  • gasLimit (BigNumber, optional): The maximum amount of gas provided for the transaction.
  • maxPriorityFeePerGas (BigNumber, optional): Maximum fee per gas to be paid for a transaction to be included in a block.
  • maxFeePerGas (BigNumber, optional): Maximum fee per gas willing to be paid for a transaction.

Source: https://wallet.api.live.ledger.com/appendix/transaction

In our case, for our bitcoin transaction, we only need family, amount and recipient. We only use that to send bitcoins to deposit address, so we will use the deposit address as a recipient here.

Finally, to execute the transaction, we just pass the transaction object and id of the connected bitcoin account to useSignAndBroadcastTransaction hook.

LedgerLiveAppContext

Connecting account in Ledger Live App is quite different than our actual one in the website. Normally, we use web3react for that, but in this case we need to use useRequestAccount hook form wallet-api-client-react. Because of that we need to store those accounts somewhere in our dApp, so I decided to create a LedgerLiveAppContext for that.

The context contain 5 properties:

interface LedgerLiveAppContextState {
  ethAccount: Account | undefined
  btcAccount: Account | undefined
  setEthAccount: (ethAccount: Account | undefined) => void
  setBtcAccount: (btcAccount: Account | undefined) => void
  ledgerLiveAppEthereumSigner: LedgerLiveEthereumSigner | undefined
}

As you can see we have ethAccount and btcAccount to store the connected accounts there. We can also set those account using setEthAccount and setBtcAccount methods, after we request it using our hook. The ledgerLiveAppEthereumSigner is an additional property that contains our signer for Ledger Live App. This way we will be able to set the account also in the signer.

useIsEmbed hook

Like I said earlier, we use isEmbed query parameter to determine if the dApp is used in Ledger Live or not. I've created an useIsEmbed hook that saves that query parameter to local storage and the use it to detect if we should use all the functionalities for Ledger Live App or not.

useDetectEmbed hook

Additional to above, I've created useDetectEmbed hook, that detects if application is Embed based on the ?embed=true query parameter. Besides that, it also adds ?embed=true query parameter to every url in the page.

This is needed because with only useIsEmbed hook, the ?embed=true flag was needed only in home page (https://dashboard.threshold.network?embed=true) which then was saved to local storage and the url was changed to https://dashboard.threshold.network/tBTC/mint. This worked well for Ledger Live, because we still used embed state when we refreshed the page, but was problematic for website, because adding ?embed=true parameter to the url would broke the page and required the user to manually remove the isEmbed from local storage.

With useDetectEmbed hook every url will have ?embed=true query parameter, if the isEmbed flag in local storage is set to true, meaning that adding the query param once to the url will be enough to keep it in every url. After refreshing the page it will be checked if the parameter is in the url, and then we will set the correct value in local storage. This means, that if user use ?embed=true flag in the website, he would have to just remove that query string from the url to fix it. On the Ledger Live it will still persist the embed mode once the user refreshes the page.

useIsActive hook

This is also a new hook here. His main purpose is to determine if, and what account is active. Up to this point we've used useWeb3React hook for that purpose, but in this case it won't work. So, under the hook, the useIsActive returns similar values to useWeb3React hook if the app is not embed, but if it is, then we return proper values based on the LedgerLiveAppContext.

How it works with threshold-ts lib

I've actually manage to not do any changes in our threshold-ts lib. The way it works now is that when the isEmbed flag is set to true, we pass the Ledger Live Ethereum Signer as a providerOrSigner property.

This required me to change getContract and getBlock method though, so that they return the proper values when tthe providerOrSigner is and instance of LedgerLiveEthereumSigner.

Read More

How To Test

Note: Right now it can't be tested on Ledger Live, because it does not support Sepolia. Please see last point in TODO list at the bottom of this description.

Steps to Run it in as Ledger Live App:

  1. Pull the newest changes from this branch
  2. Run Ledger Live on your device
  3. Enable the developer mode
  4. Go to Settings -> Developer
  5. Go to Add a local app row and click Browse
  6. Got to your project directory and choose manifest-ledger-live-app.json
  7. Click Open

To Do List

Task list:

In the future:

  • Write Ledger Live App Plugin so we can display proper information on the Ledger device when revealing a deposit or requesting a redemption
  • Implement/check if the plugin works on Sepolia. It's currently under development.

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.
For Ledger Live App we only need tBTC page.
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.
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.
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.
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.
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.
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.
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.
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.
`LedgerLiveEthereumSigner` -> `LedgerLiveAppEthereumSigner`
Makes two separate requestAccount hook for bitcoin and for ethereum networks.
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.
@michalsmiarowski michalsmiarowski changed the title Ledger Live App [WiP] [WiP] Ledger Live App Nov 1, 2023
Copy link

github-actions bot commented Nov 1, 2023

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`
Adds TransportProvider needed for `wallet-api-client-react`. This will allow us
to use the hooks from the libs (like `useRequestAccount` etc.).
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 `<ReduxProvider>` above
the `<ThresholdProvider`, which might broke the app in some places.

That's why I've decided to create a separate context foe Ledger Live App and
keep all data about connected accounts there. We will put the context above
`ThresholdContext` so we can use it there and later we will make sure to save
the account there each time we use `useRequestAccount` hook from
`wallet-api-client-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).
@michalsmiarowski michalsmiarowski added this to the v1.13.0 milestone Nov 2, 2023
Copy link

github-actions bot commented Nov 2, 2023

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.
Copy link

github-actions bot commented Nov 2, 2023

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.
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.
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.
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.
Copy link

github-actions bot commented Nov 3, 2023

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.
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`.
@michalsmiarowski
Copy link
Contributor Author

I haven't tested it yet. But I had a quick look and left a few minor comments.

I wonder if you have noticed, but some applications remember the connected account. I think it would be good to do that as well.

Current behaviour

Screen.Recording.2023-11-29.at.12.07.37.mov
Good behaviour

Screen.Recording.2023-11-29.at.12.18.20.mov

I believe this is how our dApp works right now on the website too. When you refresh the page you are not automatically connected to the wallet again. I don't remember why we've changed it, but I think there were some bugs that were happening and we implemented it as a workaround 🤔

I think this could be a separate issue - to implement an automatic wallet connection with the previously used account once the page is refreshed @lukasz-zimnoch

Copy link

@kkosiorowska kkosiorowska left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall it looks good to me. ✨Only small non-blocking comments.

However, it would be good if @r-czajkowski also took a look at this solution to make sure that this PR doesn't break anything. He knows the app better. Then I think we will be ready for the merge.

src/components/Navbar/index.tsx Outdated Show resolved Hide resolved
src/components/Navbar/index.tsx Outdated Show resolved Hide resolved
src/components/SubmitTxButton.tsx Show resolved Hide resolved
Comment on lines +113 to +116
const _providerOrSigner =
providerOrSigner instanceof LedgerLiveEthereumSigner
? providerOrSigner
: (getProviderOrSigner(providerOrSigner as any, account) as any)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking comment

This logic is duplicated in
https://github.com/threshold-network/token-dashboard/blob/ledger-live-app/src/web3/hooks/useGetBlock.ts#L14-L19

I would suggest wrapping it in one method.

src/contexts/TokenContext.tsx Outdated Show resolved Hide resolved
r-czajkowski
r-czajkowski previously approved these changes Dec 12, 2023
To this point we detected if the application is embed in Ledger Live App by
checking if it had `?embed=true` query parameter in the url and then saving it
it local storage. It was problematic because the query parameter would have to
be in the url only once to apply that change, so refreshing the page would not
reset that state (which is good for ledger live app, but bad is someone pass
that query parameter on the website - he would have to remove it manually from
the local storage).

To resolve this I've created `useDetectIfEmbed` hook that detects if application
is Embed based on the `?embed=true` query parameter and saves that information
to local storage (under `isEmbed` property).  Besides that it will also set
`?embed=true` query parameter for every url if the `isEmbed` property is set to
true in local storage. This will persist the embed flag when the user refreshes
the page unless he removes the `?embed=true` query parameter from the url.

This will produce the result we desire - it will persist the embed mode for
ledger live app and remove it on the website once the query parameter is not
provided in the url.
The param is needed only in "url".
Copy link

Let's keep it the same as it is on `main` branch.
Extracts `SendBitcoinsToDepositAddressForm` to a separate file to make the
`MakeDeposit` file cleaner.
Copy link

@r-czajkowski r-czajkowski merged commit a3352e0 into main Dec 14, 2023
5 checks passed
@r-czajkowski r-czajkowski deleted the ledger-live-app branch December 14, 2023 09:50
r-czajkowski added a commit that referenced this pull request Dec 15, 2023
…fect-bug

Fix Ledger Live use effect bug

Fix bug that was introduced in #655. The `useRequestEthereumAccount` hook that
supposed to be used only in embed mode, was firing it's `useEffect` in the
website where it set the empty account in the redux state. This prevented some
functionality in the dApp to work properly. For example the Operator Mapping
Card was not displayed when user connected the account on the Staking page.

The fix is adding `isEmbed` check for all `useEffects` related to ledger live
app.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ledger Live app: Mint & unmint flow
3 participants