Skip to content

Commit

Permalink
Merge branch 'main' of github.com:thesis/acre into connection-alert
Browse files Browse the repository at this point in the history
  • Loading branch information
kkosiorowska committed Nov 28, 2024
2 parents 13659b9 + cb3f90a commit df2b777
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"account.list",
"bitcoin.getAddress",
"bitcoin.getPublicKey",
"bitcoin.getXPub",
"transaction.signAndBroadcast",
"custom.acre.messageSign",
"custom.acre.transactionSignAndBroadcast"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"account.list",
"bitcoin.getAddress",
"bitcoin.getPublicKey",
"bitcoin.getXPub",
"transaction.signAndBroadcast",
"custom.acre.messageSign",
"custom.acre.transactionSignAndBroadcast"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"account.list",
"bitcoin.getAddress",
"bitcoin.getPublicKey",
"bitcoin.getXPub",
"transaction.signAndBroadcast",
"custom.acre.messageSign",
"custom.acre.transactionSignAndBroadcast"
Expand Down
1 change: 1 addition & 0 deletions dapp/manifests/ledger-live/ledger-manifest-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"account.list",
"bitcoin.getAddress",
"bitcoin.getPublicKey",
"bitcoin.getXPub",
"transaction.signAndBroadcast",
"custom.acre.messageSign",
"custom.acre.transactionSignAndBroadcast"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ export default function ConnectWalletButton({
},
onError: (error: OrangeKitError) => {
const errorData = orangeKit.parseOrangeKitConnectionError(error)

if (errorData === ConnectionAlert.Default) {
console.error("Failed to connect", error)
}

setConnectionAlert(errorData)
},
})
Expand Down
124 changes: 85 additions & 39 deletions dapp/src/components/Header/ConnectWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import {
HStack,
Icon,
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
StackDivider,
useClipboard,
useMultiStyleConfig,
} from "@chakra-ui/react"
import { useIsEmbed, useModal, useWallet } from "#/hooks"
import { useIsEmbed, useMobileMode, useModal, useWallet } from "#/hooks"
import { CurrencyBalance } from "#/components/shared/CurrencyBalance"
import { TextMd } from "#/components/shared/Typography"
import { BitcoinIcon } from "#/assets/icons"
Expand All @@ -21,6 +25,8 @@ import {
IconLogout,
IconWallet,
IconUserCode,
IconChevronDown,
IconChevronUp,
} from "@tabler/icons-react"
import { useMatch } from "react-router-dom"
import Tooltip from "../shared/Tooltip"
Expand All @@ -39,6 +45,7 @@ export default function ConnectWallet() {
size: "lg",
})
const isDashboardPage = useMatch("/dashboard")
const isMobile = useMobileMode()

const handleConnectWallet = (isReconnecting: boolean = false) => {
openModal(MODAL_TYPES.CONNECT_WALLET, { isReconnecting })
Expand All @@ -62,8 +69,65 @@ export default function ConnectWallet() {
</Button>
)
}
const options = [
{
id: "Copy",
icon: IconCopy,
label: hasCopied ? "Address copied" : "Copy Address",
onClick: onCopy,
isSupported: true,
closeOnSelect: false,
},
{
id: "Change account",
icon: IconUserCode,
label: "Change account",
onClick: () => handleConnectWallet(true),
isSupported: isChangeAccountFeatureSupported(embeddedApp),
closeOnSelect: true,
},
{
id: "Disconnect",
icon: IconLogout,
label: "Disconnect",
onClick: onDisconnect,
closeOnSelect: true,
isSupported: true,
},
]

return (
return isMobile ? (
<Menu>
{({ isOpen }) => (
<>
<MenuButton
as={Button}
variant="card"
leftIcon={<Icon as={BitcoinIcon} boxSize={6} color="brand.400" />}
rightIcon={isOpen ? <IconChevronUp /> : <IconChevronDown />}
>
<TextMd color="brand.400">{truncateAddress(address)}</TextMd>
</MenuButton>
<MenuList bg="gold.200">
{options.map(
(option) =>
option.isSupported && (
<MenuItem
key={option.id}
closeOnSelect={option.closeOnSelect}
{...styles}
icon={<Icon as={option.icon} boxSize={5} />}
onClick={option.onClick}
>
{option.label}
</MenuItem>
),
)}
</MenuList>
</>
)}
</Menu>
) : (
<HStack spacing={4}>
<HStack display={{ base: "none", md: "flex" }}>
<CurrencyBalance currency="bitcoin" amount={balance} />
Expand Down Expand Up @@ -100,44 +164,26 @@ export default function ConnectWallet() {
spacing={1}
divider={<StackDivider borderColor="gold.500" />}
>
<Tooltip
size="xs"
label={hasCopied ? "Address copied" : "Copy"}
closeOnClick={false}
>
<IconButton
variant="ghost"
aria-label="Copy"
icon={<Icon as={IconCopy} boxSize={5} />}
px={2}
boxSize={5}
onClick={onCopy}
/>
</Tooltip>

{isChangeAccountFeatureSupported(embeddedApp) && (
<Tooltip size="xs" label="Change account">
<IconButton
variant="ghost"
aria-label="Change account"
icon={<Icon as={IconUserCode} boxSize={5} />}
px={2}
boxSize={5}
onClick={() => handleConnectWallet(true)}
/>
</Tooltip>
{options.map(
(option) =>
option.isSupported && (
<Tooltip
key={option.id}
size="xs"
label={option.label}
closeOnClick={false}
>
<IconButton
variant="ghost"
aria-label={option.id}
icon={<Icon as={option.icon} boxSize={5} />}
px={2}
boxSize={5}
onClick={option.onClick}
/>
</Tooltip>
),
)}

<Tooltip size="xs" label="Disconnect">
<IconButton
variant="ghost"
aria-label="Disconnect"
icon={<Icon as={IconLogout} boxSize={5} />}
px={2}
boxSize={5}
onClick={onDisconnect}
/>
</Tooltip>
</HStack>
</Flex>
</HStack>
Expand Down
28 changes: 28 additions & 0 deletions dapp/src/utils/orangekit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,33 @@ async function verifySignInWithWalletMessage(
return result.data
}

/**
* Finds the extended public key (xpub) of the user's account from URL. Users
* can be redirected to the exact app in the Ledger Live application. One of the
* parameters passed via URL is `accountId` - the ID of the user's account in
* Ledger Live.
* @see https://developers.ledger.com/docs/ledger-live/exchange/earn/liveapp#url-parameters-for-direct-navigation
*
* @param {string} url Request url
* @returns The extended public key (xpub) of the user's account if the search
* parameter `accountId` exists in the URL. Otherwise `undefined`.
*/
function findXpubFromUrl(url: string): string | undefined {
const parsedUrl = new URL(url)

const accountId = parsedUrl.searchParams.get("accountId")

if (!accountId) return undefined

// The fourth value separated by `:` is extended public key. See the
// account ID template: `js:2:bitcoin_testnet:<xpub>:<address_type>`.
const xpubFromAccountId = accountId.split(":")[3]

if (!xpubFromAccountId) return undefined

return xpubFromAccountId
}

export default {
getWalletInfo,
isWalletInstalled,
Expand All @@ -112,4 +139,5 @@ export default {
isWalletConnectionRejectedError,
verifySignInWithWalletMessage,
getOrangeKitLedgerLiveConnector,
findXpubFromUrl,
}
27 changes: 22 additions & 5 deletions dapp/src/utils/orangekit/ledger-live/bitcoin-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ function numberToValidHexString(value: number): string {
return `0x${hex}`
}

export type AcreLedgerLiveBitcoinProviderOptions = {
tryConnectToAddress: string | undefined
}
export type AcreLedgerLiveBitcoinProviderOptions =
| {
tryConnectToAddress?: string
tryConnectToAccountByXpub?: never
}
| { tryConnectToAddress?: never; tryConnectToAccountByXpub?: string }

/**
* Ledger Live Wallet API Bitcoin Provider.
Expand Down Expand Up @@ -90,6 +93,7 @@ export default class AcreLedgerLiveBitcoinProvider
network: BitcoinNetwork,
options: AcreLedgerLiveBitcoinProviderOptions = {
tryConnectToAddress: undefined,
tryConnectToAccountByXpub: undefined,
},
) {
const windowMessageTransport = new WindowMessageTransport()
Expand All @@ -115,6 +119,7 @@ export default class AcreLedgerLiveBitcoinProvider
walletApiClient: WalletAPIClient,
options: AcreLedgerLiveBitcoinProviderOptions = {
tryConnectToAddress: undefined,
tryConnectToAccountByXpub: undefined,
},
) {
this.#network = network
Expand All @@ -140,12 +145,24 @@ export default class AcreLedgerLiveBitcoinProvider
currencyIds,
})

if (this.#options.tryConnectToAddress) {
if (
this.#options.tryConnectToAddress ||
this.#options.tryConnectToAccountByXpub
) {
for (let i = 0; i < accounts.length; i += 1) {
const acc = accounts[i]
if (
this.#options.tryConnectToAccountByXpub &&
// eslint-disable-next-line no-await-in-loop
(await this.#walletApiClient.bitcoin.getXPub(acc.id)) ===
this.#options.tryConnectToAccountByXpub
) {
this.#account = acc
break
}

// eslint-disable-next-line no-await-in-loop
const address = await this.#getAddress(acc.id)

if (address === this.#options.tryConnectToAddress) {
this.#account = acc
break
Expand Down
14 changes: 11 additions & 3 deletions dapp/src/wagmiConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { env } from "./constants"
import { getLastUsedBtcAddress } from "./hooks/useLastUsedBtcAddress"
import referralProgram, { EmbedApp } from "./utils/referralProgram"
import { orangeKit } from "./utils"
import { AcreLedgerLiveBitcoinProviderOptions } from "./utils/orangekit/ledger-live/bitcoin-provider"

const isTestnet = env.USE_TESTNET
const CHAIN_ID = isTestnet ? sepolia.id : mainnet.id
Expand Down Expand Up @@ -34,12 +35,19 @@ async function getWagmiConfig() {
let createEmbedConnectorFn
const embeddedApp = referralProgram.getEmbeddedApp()
if (referralProgram.isEmbedApp(embeddedApp)) {
const lastUsedBtcAddress = getLastUsedBtcAddress()
const xpub = orangeKit.findXpubFromUrl(window.location.href)
const ledgerLiveConnectorOptions: AcreLedgerLiveBitcoinProviderOptions =
xpub
? { tryConnectToAccountByXpub: xpub }
: {
tryConnectToAddress: lastUsedBtcAddress,
}

const orangeKitLedgerLiveConnector =
orangeKit.getOrangeKitLedgerLiveConnector({
...connectorConfig,
options: {
tryConnectToAddress: getLastUsedBtcAddress(),
},
options: ledgerLiveConnectorOptions,
})

const embedConnectorsMap: Record<
Expand Down

0 comments on commit df2b777

Please sign in to comment.