Skip to content

Commit

Permalink
Update the dApp to work in Ledger Live (#708)
Browse files Browse the repository at this point in the history
This is an integration PR that adds the necessary changes for the dapp
to run on Ledger Live.

Refs: #730 #742 #763 #759, #760, #797, #780,
mezo-org/passport#143,
mezo-org/passport#144

### Important note
Changes can only be tested in a Ledger Live app built from source. See
the [Ledger Live repo](https://github.com/LedgerHQ/ledger-live), how to
build from source.
  • Loading branch information
nkuba authored Nov 6, 2024
2 parents a843eae + 5f0d436 commit d1ef7d6
Show file tree
Hide file tree
Showing 87 changed files with 2,173 additions and 1,158 deletions.
5 changes: 5 additions & 0 deletions dapp/config/development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "acre-dev",
"name": "Acre [dev]",
"url": "http://localhost:5173/"
}
5 changes: 5 additions & 0 deletions dapp/config/mainnet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "acre",
"name": "Acre",
"url": "https://bitcoin.acre.fi/"
}
5 changes: 5 additions & 0 deletions dapp/config/testnet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "acre-testnet",
"name": "Acre [testnet]",
"url": "https://bitcoin.test.acre.fi/"
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"id": "acre-dev",
"name": "Acre [dev]",
"url": "http://localhost:5173/",
"homepageUrl": "http://localhost:5173/",
"url": "http://localhost:5173/?embed=ledger-live",
"homepageUrl": "https:/acre.fi/",
"icon": "http://localhost:5173/acre.svg",
"platforms": "desktop",
"apiVersion": "^2.0.0",
Expand All @@ -21,10 +21,10 @@
"permissions": [
"account.request",
"account.list",
"message.sign",
"transaction.sign",
"transaction.signAndBroadcast",
"bitcoin.getXPub"
"bitcoin.getAddress",
"bitcoin.getPublicKey",
"custom.acre.messageSign",
"custom.acre.transactionSignAndBroadcast"
],
"domains": ["http://*"],
"type": "walletApp"
Expand Down
31 changes: 31 additions & 0 deletions dapp/manifests/ledger-live/ledger-live-manifest-mainnet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"id": "acre",
"name": "Acre",
"url": "https://bitcoin.acre.fi/?embed=ledger-live",
"homepageUrl": "https:/acre.fi/",
"icon": "https://bitcoin.acre.fi/acre.svg",
"platforms": "desktop",
"apiVersion": "^2.0.0",
"manifestVersion": "2",
"branch": "stable",
"categories": ["staking"],
"currencies": ["bitcoin", "bitcoin_testnet"],
"content": {
"shortDescription": {
"en": "Bitcoin Liquid Staking"
},
"description": {
"en": "Bitcoin Liquid Staking"
}
},
"permissions": [
"account.request",
"account.list",
"bitcoin.getAddress",
"bitcoin.getPublicKey",
"custom.acre.messageSign",
"custom.acre.transactionSignAndBroadcast"
],
"domains": ["http://*"],
"type": "walletApp"
}
31 changes: 31 additions & 0 deletions dapp/manifests/ledger-live/ledger-live-manifest-testnet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"id": "acre-testnet",
"name": "Acre [testnet]",
"url": "https://bitcoin.test.acre.fi/?embed=ledger-live",
"homepageUrl": "https:/acre.fi/",
"icon": "https://bitcoin.test.acre.fi/acre.svg",
"platforms": "desktop",
"apiVersion": "^2.0.0",
"manifestVersion": "2",
"branch": "stable",
"categories": ["staking"],
"currencies": ["bitcoin", "bitcoin_testnet"],
"content": {
"shortDescription": {
"en": "Bitcoin Liquid Staking"
},
"description": {
"en": "Bitcoin Liquid Staking"
}
},
"permissions": [
"account.request",
"account.list",
"bitcoin.getAddress",
"bitcoin.getPublicKey",
"custom.acre.messageSign",
"custom.acre.transactionSignAndBroadcast"
],
"domains": ["http://*"],
"type": "walletApp"
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"id": "acre-test",
"name": "Acre [test]",
"url": "https://dapp.test.acre.fi/",
"id": "{{id}}",
"name": "{{name}}",
"url": "{{{url}}}?embed=ledger-live",
"homepageUrl": "https:/acre.fi/",
"icon": "https://dapp.test.acre.fi/acre.svg",
"icon": "{{{url}}}acre.svg",
"platforms": "desktop",
"apiVersion": "^2.0.0",
"manifestVersion": "2",
Expand All @@ -21,10 +21,10 @@
"permissions": [
"account.request",
"account.list",
"message.sign",
"transaction.sign",
"transaction.signAndBroadcast",
"bitcoin.getXPub"
"bitcoin.getAddress",
"bitcoin.getPublicKey",
"custom.acre.messageSign",
"custom.acre.transactionSignAndBroadcast"
],
"domains": ["http://*"],
"type": "walletApp"
Expand Down
9 changes: 7 additions & 2 deletions dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@
"lint:js:fix": "eslint . --fix",
"lint:config": "prettier -c '**/*.@(json|yaml|toml)'",
"lint:config:fix": "prettier -w '**/*.@(json|yaml|toml)'",
"test": "vitest"
"test": "vitest --reporter=verbose",
"generate:manifests": "for env in development testnet mainnet; do mustache config/$env.json ./manifests/ledger-live/ledger-manifest-template.json > ./manifests/ledger-live/ledger-live-manifest-$env.json; done"
},
"dependencies": {
"@acre-btc/sdk": "workspace:*",
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@orangekit/react": "1.0.0-beta.32",
"@ledgerhq/wallet-api-acre-module": "0.1.0",
"@ledgerhq/wallet-api-client": "1.6.0",
"@orangekit/react": "1.0.0-beta.33",
"@orangekit/sign-in-with-wallet": "1.0.0-beta.6",
"@reduxjs/toolkit": "^2.2.0",
"@rehooks/local-storage": "^2.4.5",
Expand All @@ -31,9 +34,11 @@
"@tanstack/react-query": "^5.40.1",
"@tanstack/react-query-devtools": "^5.49.2",
"axios": "^1.6.7",
"bignumber.js": "^9.1.2",
"ethers": "^6.10.0",
"formik": "^2.4.5",
"framer-motion": "^10.16.5",
"mustache": "^4.2.0",
"react": "^18.2.0",
"react-confetti-explosion": "^2.1.2",
"react-dom": "^18.2.0",
Expand Down
8 changes: 4 additions & 4 deletions dapp/src/acre-react/contexts/AcreSdkContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useCallback, useMemo, useState } from "react"
import { Acre } from "@acre-btc/sdk"
import { BitcoinProvider } from "@acre-btc/sdk/dist/src/lib/bitcoin/providers"
import { Acre, AcreBitcoinProvider } from "@acre-btc/sdk"
import { BITCOIN_NETWORK, env } from "#/constants"

const { TBTC_API_ENDPOINT, SUBGRAPH_API_KEY } = env
Expand All @@ -9,7 +8,7 @@ const GELATO_API_KEY = env.GELATO_RELAY_API_KEY

type AcreSdkContextValue = {
acre?: Acre
init: (bitcoinProvider?: BitcoinProvider) => Promise<void>
init: (bitcoinProvider?: AcreBitcoinProvider) => Promise<void>
isInitialized: boolean
isConnected: boolean
}
Expand All @@ -27,7 +26,7 @@ export function AcreSdkProvider({ children }: { children: React.ReactNode }) {
const [isConnected, setIsConnected] = useState(false)

const init = useCallback<AcreSdkContextValue["init"]>(
async (bitcoinProvider?: BitcoinProvider) => {
async (bitcoinProvider?: AcreBitcoinProvider) => {
let sdk = await Acre.initialize(
BITCOIN_NETWORK,
TBTC_API_ENDPOINT,
Expand All @@ -37,6 +36,7 @@ export function AcreSdkProvider({ children }: { children: React.ReactNode }) {
)

if (bitcoinProvider) {
setIsConnected(false)
sdk = await sdk.connect(bitcoinProvider)
setIsConnected(true)
} else {
Expand Down
39 changes: 29 additions & 10 deletions dapp/src/components/ConnectWalletModal/ConnectWalletButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useCallback, useState } from "react"
import React, { useCallback, useEffect, useRef, useState } from "react"
import { CONNECTION_ERRORS, ONE_SEC_IN_MILLISECONDS } from "#/constants"
import {
useAppDispatch,
useIsEmbed,
useModal,
useSignMessageAndCreateSession,
useWallet,
Expand Down Expand Up @@ -34,6 +35,7 @@ type ConnectWalletButtonProps = {
isSelected: boolean
connector: OrangeKitConnector & { isDisabled: boolean }
onSuccess?: OnSuccessCallback
isReconnecting?: boolean
}

const iconStyles: Record<string, ImageProps> = {
Expand All @@ -53,7 +55,9 @@ export default function ConnectWalletButton({
isSelected,
connector,
onSuccess,
isReconnecting,
}: ConnectWalletButtonProps) {
const { isEmbed } = useIsEmbed()
const {
address,
onConnect,
Expand All @@ -66,13 +70,14 @@ export default function ConnectWalletButton({
useWalletConnectionError()
const { closeModal } = useModal()
const dispatch = useAppDispatch()
const isMounted = useRef(false)

const [isLoading, setIsLoading] = useState<boolean>(false)

const hasConnectionError = connectionError || connectionStatus === "error"
const hasSignMessageStatus = signMessageStatus === "error"
const showStatuses = isSelected && !hasConnectionError
const showRetryButton = address && hasSignMessageStatus
const hasSignMessageErrorStatus = signMessageStatus === "error"
const shouldShowStatuses = isSelected && !hasConnectionError
const shouldShowRetryButton = address && hasSignMessageErrorStatus

const onSuccessSignMessage = useCallback(() => {
closeModal()
Expand Down Expand Up @@ -118,6 +123,7 @@ export default function ConnectWalletButton({

const handleConnection = useCallback(() => {
onConnect(connector, {
isReconnecting,
onSuccess: () => {
logPromiseFailure(onSuccessConnection(connector))
},
Expand All @@ -126,7 +132,13 @@ export default function ConnectWalletButton({
setConnectionError(errorData)
},
})
}, [onConnect, connector, onSuccessConnection, setConnectionError])
}, [
onConnect,
connector,
onSuccessConnection,
setConnectionError,
isReconnecting,
])

const handleRedirectUser = useCallback(() => {
setIsLoading(true)
Expand All @@ -144,9 +156,9 @@ export default function ConnectWalletButton({

const handleButtonClick = () => {
// Do not trigger action again when wallet connection is in progress
if (showStatuses) return
if (shouldShowStatuses) return

onDisconnect()
if (!isReconnecting) onDisconnect()
resetConnectionError()
resetMessageStatus()

Expand All @@ -161,6 +173,13 @@ export default function ConnectWalletButton({
handleConnection()
}

useEffect(() => {
if (!isMounted.current && isEmbed && isSelected) {
isMounted.current = true
handleConnection()
}
}, [handleConnection, isEmbed, isSelected])

return (
<Card
key={connector.id}
Expand Down Expand Up @@ -206,7 +225,7 @@ export default function ConnectWalletButton({
</CardHeader>

<AnimatePresence initial={false}>
{showStatuses && (
{shouldShowStatuses && (
<CardBody
as={motion.div}
variants={collapseVariants}
Expand All @@ -231,13 +250,13 @@ export default function ConnectWalletButton({
</TextMd>
<ConnectWalletStatusLabel
status={connectionStatus}
label="Connect wallet"
label={`Connect ${isEmbed ? "account" : "wallet"}`}
/>
<ConnectWalletStatusLabel
status={signMessageStatus}
label="Sign message"
/>
{showRetryButton && (
{shouldShowRetryButton && (
<Button
mt={4}
size="lg"
Expand Down
34 changes: 30 additions & 4 deletions dapp/src/components/ConnectWalletModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import React, { useState } from "react"
import React, { useEffect, useState } from "react"
import { ModalBody, ModalHeader, ModalCloseButton } from "@chakra-ui/react"
import { useConnectors, useWalletConnectionError } from "#/hooks"
import {
useConnectors,
useIsEmbed,
useIsSignedMessage,
useWallet,
useWalletConnectionError,
} from "#/hooks"
import { OrangeKitConnector, BaseModalProps, OnSuccessCallback } from "#/types"
import { wallets } from "#/constants"
import withBaseModal from "../ModalRoot/withBaseModal"
Expand All @@ -10,9 +16,13 @@ import ConnectWalletErrorAlert from "./ConnectWalletErrorAlert"
export function ConnectWalletModalBase({
onSuccess,
withCloseButton = true,
isReconnecting,
}: {
onSuccess?: OnSuccessCallback
isReconnecting?: boolean
} & BaseModalProps) {
const { isEmbed } = useIsEmbed()
const { onDisconnect } = useWallet()
const connectors = useConnectors()
const enabledConnectors = connectors.map((connector) => ({
...connector,
Expand All @@ -21,17 +31,32 @@ export function ConnectWalletModalBase({

const [selectedConnectorId, setSelectedConnectorId] = useState<string>()
const { connectionError, resetConnectionError } = useWalletConnectionError()
const isSignedMessage = useIsSignedMessage()

const handleButtonOnClick = (connector: OrangeKitConnector) => {
setSelectedConnectorId(connector.id)
}

useEffect(() => {
if (!isEmbed) return

setSelectedConnectorId(enabledConnectors[0].id)
}, [enabledConnectors, isEmbed])

return (
<>
{withCloseButton && (
<ModalCloseButton onClick={() => resetConnectionError()} />
<ModalCloseButton
onClick={() => {
resetConnectionError()

if (!isSignedMessage) {
onDisconnect()
}
}}
/>
)}
<ModalHeader>Select your wallet</ModalHeader>
<ModalHeader>{`Select your ${isEmbed ? "account" : "wallet"}`}</ModalHeader>

<ModalBody gap={0}>
<ConnectWalletErrorAlert {...connectionError} />
Expand All @@ -44,6 +69,7 @@ export function ConnectWalletModalBase({
onClick={() => handleButtonOnClick(connector)}
isSelected={selectedConnectorId === connector.id}
onSuccess={onSuccess}
isReconnecting={isReconnecting}
/>
))}
</ModalBody>
Expand Down
Loading

0 comments on commit d1ef7d6

Please sign in to comment.