diff --git a/dapp/manifests/ledger-live/ledger-live-manifest-development.json b/dapp/manifests/ledger-live/ledger-live-manifest-development.json index 84e58fcad..bffaf7026 100644 --- a/dapp/manifests/ledger-live/ledger-live-manifest-development.json +++ b/dapp/manifests/ledger-live/ledger-live-manifest-development.json @@ -23,6 +23,7 @@ "account.list", "bitcoin.getAddress", "bitcoin.getPublicKey", + "bitcoin.getXPub", "transaction.signAndBroadcast", "custom.acre.messageSign", "custom.acre.transactionSignAndBroadcast" diff --git a/dapp/manifests/ledger-live/ledger-live-manifest-mainnet.json b/dapp/manifests/ledger-live/ledger-live-manifest-mainnet.json index 281992191..22f74bbc5 100644 --- a/dapp/manifests/ledger-live/ledger-live-manifest-mainnet.json +++ b/dapp/manifests/ledger-live/ledger-live-manifest-mainnet.json @@ -23,6 +23,7 @@ "account.list", "bitcoin.getAddress", "bitcoin.getPublicKey", + "bitcoin.getXPub", "transaction.signAndBroadcast", "custom.acre.messageSign", "custom.acre.transactionSignAndBroadcast" diff --git a/dapp/manifests/ledger-live/ledger-live-manifest-testnet.json b/dapp/manifests/ledger-live/ledger-live-manifest-testnet.json index 32dd4a45f..a6ea2019c 100644 --- a/dapp/manifests/ledger-live/ledger-live-manifest-testnet.json +++ b/dapp/manifests/ledger-live/ledger-live-manifest-testnet.json @@ -23,6 +23,7 @@ "account.list", "bitcoin.getAddress", "bitcoin.getPublicKey", + "bitcoin.getXPub", "transaction.signAndBroadcast", "custom.acre.messageSign", "custom.acre.transactionSignAndBroadcast" diff --git a/dapp/manifests/ledger-live/ledger-manifest-template.json b/dapp/manifests/ledger-live/ledger-manifest-template.json index e999601d2..980c407d2 100644 --- a/dapp/manifests/ledger-live/ledger-manifest-template.json +++ b/dapp/manifests/ledger-live/ledger-manifest-template.json @@ -23,6 +23,7 @@ "account.list", "bitcoin.getAddress", "bitcoin.getPublicKey", + "bitcoin.getXPub", "transaction.signAndBroadcast", "custom.acre.messageSign", "custom.acre.transactionSignAndBroadcast" diff --git a/dapp/src/utils/orangekit/index.ts b/dapp/src/utils/orangekit/index.ts index 18f355df4..af5317b7c 100644 --- a/dapp/src/utils/orangekit/index.ts +++ b/dapp/src/utils/orangekit/index.ts @@ -107,6 +107,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::`. + const xpubFromAccountId = accountId.split(":")[3] + + if (!xpubFromAccountId) return undefined + + return xpubFromAccountId +} + export default { getWalletInfo, isWalletInstalled, @@ -119,4 +146,5 @@ export default { isWalletConnectionRejectedError, verifySignInWithWalletMessage, getOrangeKitLedgerLiveConnector, + findXpubFromUrl, } diff --git a/dapp/src/utils/orangekit/ledger-live/bitcoin-provider.ts b/dapp/src/utils/orangekit/ledger-live/bitcoin-provider.ts index 7dab9c5ad..316e628a4 100644 --- a/dapp/src/utils/orangekit/ledger-live/bitcoin-provider.ts +++ b/dapp/src/utils/orangekit/ledger-live/bitcoin-provider.ts @@ -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. @@ -90,6 +93,7 @@ export default class AcreLedgerLiveBitcoinProvider network: BitcoinNetwork, options: AcreLedgerLiveBitcoinProviderOptions = { tryConnectToAddress: undefined, + tryConnectToAccountByXpub: undefined, }, ) { const windowMessageTransport = new WindowMessageTransport() @@ -115,6 +119,7 @@ export default class AcreLedgerLiveBitcoinProvider walletApiClient: WalletAPIClient, options: AcreLedgerLiveBitcoinProviderOptions = { tryConnectToAddress: undefined, + tryConnectToAccountByXpub: undefined, }, ) { this.#network = network @@ -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 diff --git a/dapp/src/wagmiConfig.ts b/dapp/src/wagmiConfig.ts index 19d1cf4e4..972a38fda 100644 --- a/dapp/src/wagmiConfig.ts +++ b/dapp/src/wagmiConfig.ts @@ -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 @@ -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<