Skip to content

Commit

Permalink
Connect accounts with dApp
Browse files Browse the repository at this point in the history
To connect accounts with the dApp let's use the `useRequestAccount` hook from the WALLET API for this.  Also, we should store the account data in context to have global access to it.
  • Loading branch information
kkosiorowska committed Nov 17, 2023
1 parent 7a49c62 commit 65ab5ab
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 9 deletions.
1 change: 1 addition & 0 deletions dapp/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_USE_TESTNET=true
8 changes: 6 additions & 2 deletions dapp/manifest-ledger-live-app.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
],
"currencies": [
"bitcoin",
"bitcoin_testnet"
"bitcoin_testnet",
"ethereum",
"ethereum_goerli"
],
"content": {
"shortDescription": {
Expand All @@ -23,7 +25,9 @@
"en": "Bitcoin Liquid Staking"
}
},
"permissions": [],
"permissions": [
"account.request"
],
"domains": [
"http://*"
]
Expand Down
26 changes: 19 additions & 7 deletions dapp/src/DApp.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
import React from "react"
import { ChakraProvider, Button, Box } from "@chakra-ui/react"
import React, { useContext } from "react"
import { ChakraProvider, Box, Text } from "@chakra-ui/react"
import { useDetectThemeMode } from "./hooks"
import { LedgerWalletAPIProvider } from "./providers"
import theme from "./theme"
import {
LedgerLiveAppContext,
LedgerLiveAppProvider,
} from "./contexts/LedgerLiveAppContext"
import Navbar from "./components/Navbar"

function DApp() {
useDetectThemeMode()

const { btcAccount, ethAccount } = useContext(LedgerLiveAppContext)

return (
<Box p={4}>
<Box>
<Navbar />
<h1>Ledger live - Acre dApp</h1>
<Button>Test button</Button>
{btcAccount && <Text>Account: {btcAccount.address}</Text>}
{ethAccount && <Text>Account: {ethAccount.address}</Text>}
</Box>
)
}

function DAppProviders() {
return (
<LedgerWalletAPIProvider>
<ChakraProvider theme={theme}>
<DApp />
</ChakraProvider>
<LedgerLiveAppProvider>
<ChakraProvider theme={theme}>
<DApp />
</ChakraProvider>
</LedgerLiveAppProvider>
</LedgerWalletAPIProvider>
)
}
Expand Down
16 changes: 16 additions & 0 deletions dapp/src/assets/bitcoin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions dapp/src/assets/ethereum.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions dapp/src/assets/info.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions dapp/src/components/Navbar/ConnectWallet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useContext } from "react"
import { Box, Button, Image, Text } from "@chakra-ui/react"
import { Account } from "@ledgerhq/wallet-api-client"
import BitcoinIcon from "../../assets/bitcoin.svg"
import EthereumIcon from "../../assets/ethereum.svg"
import InfoIcon from "../../assets/info.svg"
import { BITCOIN } from "../../constants"
import {
useRequestBitcoinAccount,
useRequestEthereumAccount,
} from "../../hooks"
import { LedgerLiveAppContext } from "../../contexts/LedgerLiveAppContext"

export type ConnectButtonsProps = {
leftIcon: string
rightIcon: string
account: Account | null
requestAccount: () => Promise<void>
}

function ConnectButton({
leftIcon,
rightIcon,
account,
requestAccount,
}: ConnectButtonsProps) {
return (
<Button
variant="outline"
leftIcon={<Image src={leftIcon} />}
rightIcon={!account ? <Image src={rightIcon} /> : undefined}
onClick={requestAccount}
>
{account ? account.address : "Not connected"}
</Button>
)
}

export default function ConnectWallet() {
const requestBitcoinAccount = useRequestBitcoinAccount()
const requestEthereumAccount = useRequestEthereumAccount()
const { btcAccount } = useContext(LedgerLiveAppContext)

return (
<Box display="flex" alignItems="center" gap="4">
<Box display="flex" gap="2">
<Text>Balance</Text>
<Text as="b">
{!btcAccount ? "0.00" : btcAccount.balance.toString()}
</Text>
<Text>{BITCOIN.token}</Text>
</Box>
<ConnectButton
leftIcon={BitcoinIcon}
rightIcon={InfoIcon}
account={requestBitcoinAccount.account}
requestAccount={async () => {
await requestBitcoinAccount.requestAccount()
}}
/>
<ConnectButton
leftIcon={EthereumIcon}
rightIcon={InfoIcon}
account={requestEthereumAccount.account}
requestAccount={async () => {
await requestEthereumAccount.requestAccount()
}}
/>
</Box>
)
}
11 changes: 11 additions & 0 deletions dapp/src/components/Navbar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react"
import { Box } from "@chakra-ui/react"
import ConnectWallet from "./ConnectWallet"

export default function Navbar() {
return (
<Box p={4} display="flex" justifyContent="end">
<ConnectWallet />
</Box>
)
}
10 changes: 10 additions & 0 deletions dapp/src/constants/chains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const BITCOIN = {
name: "Bitcoin",
token: "BTC",
}

export const CURRENCY_ID_BITCOIN =
import.meta.env.VITE_USE_TESTNET === "true" ? "bitcoin_testnet" : "bitcoin"

export const CURRENCY_ID_ETHEREUM =
import.meta.env.VITE_USE_TESTNET === "true" ? "ethereum_goerli" : "ethereum"
1 change: 1 addition & 0 deletions dapp/src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./chains"
42 changes: 42 additions & 0 deletions dapp/src/contexts/LedgerLiveAppContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Account } from "@ledgerhq/wallet-api-client"
import React, { createContext, useMemo, useState } from "react"

type LedgerLiveAppContextValue = {
btcAccount: Account | undefined
setBtcAccount: React.Dispatch<React.SetStateAction<Account | undefined>>
ethAccount: Account | undefined
setEthAccount: React.Dispatch<React.SetStateAction<Account | undefined>>
}

export const LedgerLiveAppContext = createContext<LedgerLiveAppContextValue>({
btcAccount: undefined,
setBtcAccount: () => {},
ethAccount: undefined,
setEthAccount: () => {},
})

export function LedgerLiveAppProvider({
children,
}: {
children: React.ReactNode
}): React.ReactElement {
const [btcAccount, setBtcAccount] = useState<Account | undefined>(undefined)
const [ethAccount, setEthAccount] = useState<Account | undefined>(undefined)

const contextValue: LedgerLiveAppContextValue =
useMemo<LedgerLiveAppContextValue>(
() => ({
btcAccount,
setBtcAccount,
ethAccount,
setEthAccount,
}),
[btcAccount, setBtcAccount, ethAccount, setEthAccount],
)

return (
<LedgerLiveAppContext.Provider value={contextValue}>
{children}
</LedgerLiveAppContext.Provider>
)
}
2 changes: 2 additions & 0 deletions dapp/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from "./useDetectThemeMode"
export * from "./useRequestBitcoinAccount"
export * from "./useRequestEthereumAccount"
33 changes: 33 additions & 0 deletions dapp/src/hooks/useRequestBitcoinAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client"
import { useRequestAccount } from "@ledgerhq/wallet-api-client-react"
import { useCallback, useContext, useEffect } from "react"
import { LedgerLiveAppContext } from "../contexts/LedgerLiveAppContext"
import { CURRENCY_ID_BITCOIN } from "../constants"

type UseRequestAccount = {
pending: boolean
account: Account | null
error: unknown
}

type RequestAccountParams = Parameters<WalletAPIClient["account"]["request"]>

type UseRequestAccountReturn = {
requestAccount: (...params: RequestAccountParams) => Promise<void>
} & UseRequestAccount

export function useRequestBitcoinAccount(): UseRequestAccountReturn {
const { setBtcAccount } = useContext(LedgerLiveAppContext)
const requestAccountResponse = useRequestAccount()
const { account, requestAccount } = requestAccountResponse

useEffect(() => {
setBtcAccount(account || undefined)
}, [account, setBtcAccount])

const requestBitcoinAccount = useCallback(async () => {
await requestAccount({ currencyIds: [CURRENCY_ID_BITCOIN] })
}, [requestAccount])

return { ...requestAccountResponse, requestAccount: requestBitcoinAccount }
}
33 changes: 33 additions & 0 deletions dapp/src/hooks/useRequestEthereumAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client"
import { useRequestAccount } from "@ledgerhq/wallet-api-client-react"
import { useCallback, useContext, useEffect } from "react"
import { LedgerLiveAppContext } from "../contexts/LedgerLiveAppContext"
import { CURRENCY_ID_ETHEREUM } from "../constants"

type UseRequestAccount = {
pending: boolean
account: Account | null
error: unknown
}

type RequestAccountParams = Parameters<WalletAPIClient["account"]["request"]>

type UseRequestAccountReturn = {
requestAccount: (...params: RequestAccountParams) => Promise<void>
} & UseRequestAccount

export function useRequestEthereumAccount(): UseRequestAccountReturn {
const { setEthAccount } = useContext(LedgerLiveAppContext)
const requestAccountResponse = useRequestAccount()
const { account, requestAccount } = requestAccountResponse

useEffect(() => {
setEthAccount(account || undefined)
}, [account, setEthAccount])

const requestEthereumAccount = useCallback(async () => {
await requestAccount({ currencyIds: [CURRENCY_ID_ETHEREUM] })
}, [requestAccount])

return { ...requestAccountResponse, requestAccount: requestEthereumAccount }
}

0 comments on commit 65ab5ab

Please sign in to comment.