From ef64a229a7826351ca8d13106e20f78f6a73734b Mon Sep 17 00:00:00 2001 From: Hugo Lopes <106091551+hlopes-ledger@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:34:21 +0000 Subject: [PATCH] feat: add desktop download URL and instructions (#1233) * feat: add desktop download URL and instructions - add a downloadUrls.desktop property - add a wallet.desktop.instructions property - set these on the Ledger connector to present desktop and mobile app download buttons for Ledger Live * fix: getWalletConnectUri in qrCode getUri * feat: platform detection util * feat: platforms util type enum * feat: platform specific desktop download urls * feat: ledger connector download urls and learn mores * chore: amend changeset * feat: connect instruction step icon * feat: desktop flow learn more button * revert: mobile action label * feat: platform label in desktop download details * feat: desktop connect details platform icons * fix: rename it Ledger * fix: linting * fix: dependency * fix: i18n string for mobile and desktop wallet * fix: i18n connector strings * chore: update changeset * chore: update macos icon to sonoma * fix: missing i18n strings --------- Co-authored-by: Daniel Sinclair --- .changeset/wise-doors-give.md | 43 +++++ packages/rainbowkit/package.json | 6 +- .../ConnectOptions/ConnectDetails.tsx | 153 +++++++++++++++++- .../ConnectOptions/DesktopOptions.tsx | 23 ++- .../src/components/Icons/Connect.tsx | 18 +++ .../rainbowkit/src/components/Icons/Linux.svg | 1 + .../rainbowkit/src/components/Icons/Macos.svg | 1 + .../src/components/Icons/Windows.svg | 1 + .../src/components/Icons/connect.svg | 1 + packages/rainbowkit/src/locales/en_US.json | 49 ++++++ packages/rainbowkit/src/utils/platforms.ts | 32 ++++ packages/rainbowkit/src/wallets/Wallet.ts | 19 ++- .../rainbowkit/src/wallets/downloadUrls.ts | 13 ++ .../src/wallets/useWalletConnectors.ts | 8 +- .../ledgerWallet/ledgerWallet.ts | 60 ++++++- pnpm-lock.yaml | 40 ++--- site/data/docs/custom-wallets.mdx | 25 +++ 17 files changed, 457 insertions(+), 36 deletions(-) create mode 100644 .changeset/wise-doors-give.md create mode 100644 packages/rainbowkit/src/components/Icons/Connect.tsx create mode 100644 packages/rainbowkit/src/components/Icons/Linux.svg create mode 100644 packages/rainbowkit/src/components/Icons/Macos.svg create mode 100644 packages/rainbowkit/src/components/Icons/Windows.svg create mode 100644 packages/rainbowkit/src/components/Icons/connect.svg create mode 100644 packages/rainbowkit/src/utils/platforms.ts diff --git a/.changeset/wise-doors-give.md b/.changeset/wise-doors-give.md new file mode 100644 index 0000000000..b0b97b5520 --- /dev/null +++ b/.changeset/wise-doors-give.md @@ -0,0 +1,43 @@ +--- +'@rainbow-me/rainbowkit': minor +--- + +**Improved desktop wallet download support** + +RainbowKit wallet connectors now support desktop download links and desktop +wallet instructions. + +Dapps that utilize the Custom Wallets API can reference the updated docs [here](https://www.rainbowkit.com/docs/custom-wallets). + +```ts +{ + downloadUrls: { + windows: 'https://my-wallet/windows-app', + macos: 'https://my-wallet/macos-app', + linux: 'https://my-wallet/linux-app', + desktop: 'https://my-wallet/desktop-app', + } +} +``` + +We've also introduced a new 'connect' `InstructionStepName` type in the `instructions` API to provide wallet connection instructions. + +```ts +return { + connector, + desktop: { + getUri, + instructions: { + learnMoreUrl: 'https://my-wallet/learn-more', + steps: [ + // ... + { + description: 'A prompt will appear for you to approve the connection to My Wallet.' + step: 'connect', + title: 'Connect', + } + ] + }, + }, +} +``` diff --git a/packages/rainbowkit/package.json b/packages/rainbowkit/package.json index e2f3e6eb3d..14047c47e5 100644 --- a/packages/rainbowkit/package.json +++ b/packages/rainbowkit/package.json @@ -63,7 +63,8 @@ "vitest": "^0.33.0", "wagmi": "~1.4.3", "@wagmi/core": "~1.4.3", - "@types/i18n-js": "^3.8.5" + "@types/i18n-js": "^3.8.5", + "@types/ua-parser-js": "^0.7.36" }, "dependencies": { "@vanilla-extract/css": "1.9.1", @@ -72,7 +73,8 @@ "clsx": "1.1.1", "qrcode": "1.5.0", "react-remove-scroll": "2.5.4", - "i18n-js": "^4.3.2" + "i18n-js": "^4.3.2", + "ua-parser-js": "^1.0.35" }, "repository": { "type": "git", diff --git a/packages/rainbowkit/src/components/ConnectOptions/ConnectDetails.tsx b/packages/rainbowkit/src/components/ConnectOptions/ConnectDetails.tsx index f8f1d3b757..3f9d6857ac 100644 --- a/packages/rainbowkit/src/components/ConnectOptions/ConnectDetails.tsx +++ b/packages/rainbowkit/src/components/ConnectOptions/ConnectDetails.tsx @@ -3,6 +3,7 @@ import { touchableStyles } from '../../css/touchableStyles'; import { useWindowSize } from '../../hooks/useWindowSize'; import { BrowserType, getBrowser, isSafari } from '../../utils/browsers'; import { getGradientRGBAs } from '../../utils/colors'; +import { PlatformType, getPlatform } from '../../utils/platforms'; import { InstructionStepName } from '../../wallets/Wallet'; import { WalletConnector, @@ -12,6 +13,7 @@ import { AsyncImage } from '../AsyncImage/AsyncImage'; import { loadImages } from '../AsyncImage/useAsyncImage'; import { Box, BoxProps } from '../Box/Box'; import { ActionButton } from '../Button/ActionButton'; +import { ConnectIcon, preloadConnectIcon } from '../Icons/Connect'; import { CreateIcon, preloadCreateIcon } from '../Icons/Create'; import { RefreshIcon, preloadRefreshIcon } from '../Icons/Refresh'; import { ScanIcon, preloadScanIcon } from '../Icons/Scan'; @@ -46,6 +48,22 @@ const getBrowserSrc: () => Promise = async () => { const preloadBrowserIcon = () => loadImages(getBrowserSrc); +const getPlatformSrc: () => Promise = async () => { + const platform = getPlatform(); + switch (platform) { + case PlatformType.Windows: + return (await import('../Icons/Windows.svg')).default; + case PlatformType.MacOS: + return (await import('../Icons/Macos.svg')).default; + case PlatformType.Linux: + return (await import('../Icons/Linux.svg')).default; + default: + return (await import('../Icons/Linux.svg')).default; + } +}; + +const preloadPlatformIcon = () => loadImages(getPlatformSrc); + export function GetDetail({ getWalletDownload, compactModeEnabled, @@ -79,6 +97,7 @@ export function GetDetail({ ?.filter( (wallet) => wallet.extensionDownloadUrl || + wallet.desktopDownloadUrl || (wallet.qrCode && wallet.downloadUrls?.qrCode), ) .map((wallet) => { @@ -87,6 +106,8 @@ export function GetDetail({ const hasMobileCompanionApp = downloadUrls?.qrCode && qrCode; const hasExtension = !!wallet.extensionDownloadUrl; const hasMobileAndExtension = downloadUrls?.qrCode && hasExtension; + const hasMobileAndDesktop = + downloadUrls?.qrCode && !!wallet.desktopDownloadUrl; return ( {hasMobileAndExtension ? i18n.t('get.mobile_and_extension.description') + : hasMobileAndDesktop + ? i18n.t('get.mobile_and_desktop.description') : hasMobileCompanionApp ? i18n.t('get.mobile.description') : hasExtension @@ -195,6 +218,8 @@ export function ConnectDetail({ const hasExtension = !!wallet.extensionDownloadUrl; const hasQrCodeAndExtension = downloadUrls?.qrCode && hasExtension; + const hasQrCodeAndDesktop = + downloadUrls?.qrCode && !!wallet.desktopDownloadUrl; const hasQrCode = qrCode && qrCodeUri; const secondaryAction: { @@ -221,7 +246,7 @@ export function ConnectDetail({ label: i18n.t('connect.secondary_action.get.label'), onClick: () => changeWalletStep( - hasQrCodeAndExtension + hasQrCodeAndExtension || hasQrCodeAndDesktop ? WalletStep.DownloadOptions : WalletStep.Download, ), @@ -234,6 +259,7 @@ export function ConnectDetail({ useEffect(() => { // Preload icon used on next screen preloadBrowserIcon(); + preloadPlatformIcon(); }, []); return ( @@ -404,7 +430,7 @@ const DownloadOptionsBox = ({ isCompact: boolean; iconUrl: string | (() => Promise); iconBackground?: string; - variant: 'browser' | 'app'; + variant: 'browser' | 'app' | 'desktop'; }) => { const isBrowserCard = variant === 'browser'; const gradientRgbas = @@ -588,9 +614,16 @@ export function DownloadOptionsDetail({ wallet: WalletConnector; }) { const browser = getBrowser(); + const platform = getPlatform(); const modalSize = useContext(ModalSizeContext); const isCompact = modalSize === 'compact'; - const { extension, extensionDownloadUrl, mobileDownloadUrl } = wallet; + const { + desktop, + desktopDownloadUrl, + extension, + extensionDownloadUrl, + mobileDownloadUrl, + } = wallet; const i18n = useContext(I18nContext); @@ -599,6 +632,7 @@ export function DownloadOptionsDetail({ preloadCreateIcon(); preloadScanIcon(); preloadRefreshIcon(); + preloadConnectIcon(); }, []); return ( @@ -644,6 +678,29 @@ export function DownloadOptionsDetail({ variant="browser" /> )} + {desktopDownloadUrl && ( + + changeWalletStep( + desktop?.instructions + ? WalletStep.InstructionsDesktop + : WalletStep.Connect, + ) + } + title={i18n.t('get_options.desktop.title', { + wallet: wallet.name, + platform, + })} + url={desktopDownloadUrl} + variant="desktop" + /> + )} {mobileDownloadUrl && ( ReactNode > = { + connect: () => , create: () => , install: (wallet) => ( ); } + +export function InstructionDesktopDetail({ + connectWallet, + wallet, +}: { + connectWallet: (wallet: WalletConnector) => void; + wallet: WalletConnector; +}) { + const i18n = useContext(I18nContext); + + return ( + + + {wallet?.desktop?.instructions?.steps.map((d, idx) => ( + + + {stepIcons[d.step]?.(wallet)} + + + + {i18n.t(d.title)} + + + {i18n.t(d.description)} + + + + ))} + + + + connectWallet(wallet)} + /> + + + {i18n.t('get_instructions.desktop.learn_more.label')} + + + + + ); +} diff --git a/packages/rainbowkit/src/components/ConnectOptions/DesktopOptions.tsx b/packages/rainbowkit/src/components/ConnectOptions/DesktopOptions.tsx index 57e596e6a0..7f97e3d7e3 100644 --- a/packages/rainbowkit/src/components/ConnectOptions/DesktopOptions.tsx +++ b/packages/rainbowkit/src/components/ConnectOptions/DesktopOptions.tsx @@ -27,6 +27,7 @@ import { DownloadDetail, DownloadOptionsDetail, GetDetail, + InstructionDesktopDetail, InstructionExtensionDetail, InstructionMobileDetail, } from './ConnectDetails'; @@ -44,6 +45,7 @@ export enum WalletStep { DownloadOptions = 'DOWNLOAD_OPTIONS', Download = 'DOWNLOAD', InstructionsMobile = 'INSTRUCTIONS_MOBILE', + InstructionsDesktop = 'INSTRUCTIONS_DESKTOP', InstructionsExtension = 'INSTRUCTIONS_EXTENSION', } @@ -154,12 +156,15 @@ export function DesktopOptions({ onClose }: { onClose: () => void }) { setSelectedOptionId(id); const sWallet = wallets.find((w) => id === w.id); const isMobile = sWallet?.downloadUrls?.qrCode; + const isDesktop = !!sWallet?.desktopDownloadUrl; const isExtension = !!sWallet?.extensionDownloadUrl; setSelectedWallet(sWallet); - if (isMobile && isExtension) { + if (isMobile && (isExtension || isDesktop)) { changeWalletStep(WalletStep.DownloadOptions); } else if (isMobile) { changeWalletStep(WalletStep.Download); + } else if (isDesktop) { + changeWalletStep(WalletStep.InstructionsDesktop); } else { changeWalletStep(WalletStep.InstructionsExtension); } @@ -312,6 +317,22 @@ export function DesktopOptions({ onClose }: { onClose: () => void }) { }); headerBackButtonLink = WalletStep.DownloadOptions; break; + case WalletStep.InstructionsDesktop: + walletContent = selectedWallet && ( + + ); + headerLabel = + selectedWallet && + i18n.t('get_options.title', { + wallet: compactModeEnabled + ? selectedWallet.shortName || selectedWallet.name + : selectedWallet.name, + }); + headerBackButtonLink = WalletStep.DownloadOptions; + break; default: break; } diff --git a/packages/rainbowkit/src/components/Icons/Connect.tsx b/packages/rainbowkit/src/components/Icons/Connect.tsx new file mode 100644 index 0000000000..9c1a2dd7fc --- /dev/null +++ b/packages/rainbowkit/src/components/Icons/Connect.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { AsyncImage } from '../AsyncImage/AsyncImage'; +import { loadImages } from '../AsyncImage/useAsyncImage'; + +const src = async () => (await import('./connect.svg')).default; + +export const preloadConnectIcon = () => loadImages(src); + +export const ConnectIcon = () => ( + +); diff --git a/packages/rainbowkit/src/components/Icons/Linux.svg b/packages/rainbowkit/src/components/Icons/Linux.svg new file mode 100644 index 0000000000..41e5c4ad67 --- /dev/null +++ b/packages/rainbowkit/src/components/Icons/Linux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/rainbowkit/src/components/Icons/Macos.svg b/packages/rainbowkit/src/components/Icons/Macos.svg new file mode 100644 index 0000000000..12269a238e --- /dev/null +++ b/packages/rainbowkit/src/components/Icons/Macos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/rainbowkit/src/components/Icons/Windows.svg b/packages/rainbowkit/src/components/Icons/Windows.svg new file mode 100644 index 0000000000..0212a36fe2 --- /dev/null +++ b/packages/rainbowkit/src/components/Icons/Windows.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/rainbowkit/src/components/Icons/connect.svg b/packages/rainbowkit/src/components/Icons/connect.svg new file mode 100644 index 0000000000..c75077cce2 --- /dev/null +++ b/packages/rainbowkit/src/components/Icons/connect.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/rainbowkit/src/locales/en_US.json b/packages/rainbowkit/src/locales/en_US.json index c3c990447d..5b06f78893 100644 --- a/packages/rainbowkit/src/locales/en_US.json +++ b/packages/rainbowkit/src/locales/en_US.json @@ -109,6 +109,9 @@ "mobile_and_extension": { "description": "Mobile Wallet and Extension" }, + "mobile_and_desktop": { + "description": "Mobile and Desktop Wallet" + }, "looking_for": { "title": "Not what you're looking for?", "mobile": { @@ -137,6 +140,13 @@ "download": { "label": "Add to %{browser}" } + }, + "desktop": { + "title": "%{wallet} for %{platform}", + "description": "Access your wallet natively from your powerful desktop.", + "download": { + "label": "Add to %{platform}" + } } }, @@ -164,6 +174,14 @@ "learn_more": { "label": "Learn More" } + }, + "desktop": { + "connect": { + "label": "Connect" + }, + "learn_more": { + "label": "Learn More" + } } }, @@ -952,6 +970,37 @@ "description": "Tap the Scan QR icon at the top right and confirm the prompt to connect." } } + }, + + "ledger": { + "desktop": { + "step1": { + "title": "Open the Ledger Live app", + "description": "We recommend putting Ledger Live on your home screen for quicker access." + }, + "step2": { + "title": "Set up your Ledger", + "description": "Set up a new Ledger or connect to an existing one." + }, + "step3": { + "title": "Connect", + "description": "A connection prompt will appear for you to connect your wallet." + } + }, + "qr_code": { + "step1": { + "title": "Open the Ledger Live app", + "description": "We recommend putting Ledger Live on your home screen for quicker access." + }, + "step2": { + "title": "Set up your Ledger", + "description": "You can either sync with the desktop app or connect your Ledger." + }, + "step3": { + "title": "Scan the code", + "description": "Tap WalletConnect then Switch to Scanner. After you scan, a connection prompt will appear for you to connect your wallet." + } + } } } } diff --git a/packages/rainbowkit/src/utils/platforms.ts b/packages/rainbowkit/src/utils/platforms.ts new file mode 100644 index 0000000000..eb503d079c --- /dev/null +++ b/packages/rainbowkit/src/utils/platforms.ts @@ -0,0 +1,32 @@ +import { UAParser } from 'ua-parser-js'; + +const ua = UAParser(); +const { os } = ua; + +export enum PlatformType { + Windows = 'Windows', + MacOS = 'macOS', + Linux = 'Linux', + Desktop = 'Desktop', +} + +export function isWindows(): boolean { + return os.name === 'Windows'; +} + +export function isMacOS(): boolean { + return os.name === 'Mac OS'; +} + +export function isLinux(): boolean { + return ['Ubuntu', 'Mint', 'Fedora', 'Debian', 'Arch', 'Linux'].includes( + os.name!, + ); +} + +export function getPlatform(): PlatformType { + if (isWindows()) return PlatformType.Windows; + else if (isMacOS()) return PlatformType.MacOS; + else if (isLinux()) return PlatformType.Linux; + return PlatformType.Desktop; +} diff --git a/packages/rainbowkit/src/wallets/Wallet.ts b/packages/rainbowkit/src/wallets/Wallet.ts index 816b6d99ea..ee46333010 100644 --- a/packages/rainbowkit/src/wallets/Wallet.ts +++ b/packages/rainbowkit/src/wallets/Wallet.ts @@ -1,6 +1,11 @@ import { Connector } from 'wagmi'; -export type InstructionStepName = 'install' | 'create' | 'scan' | 'refresh'; +export type InstructionStepName = + | 'install' + | 'create' + | 'scan' + | 'connect' + | 'refresh'; type RainbowKitConnector = { connector: C; @@ -9,6 +14,14 @@ type RainbowKitConnector = { }; desktop?: { getUri?: () => Promise; + instructions?: { + learnMoreUrl: string; + steps: { + step: InstructionStepName; + title: string; + description: string; + }[]; + }; }; qrCode?: { getUri: () => Promise; @@ -52,6 +65,10 @@ export type Wallet = { opera?: string; safari?: string; browserExtension?: string; + macos?: string; + windows?: string; + linux?: string; + desktop?: string; }; hidden?: (args: { wallets: { diff --git a/packages/rainbowkit/src/wallets/downloadUrls.ts b/packages/rainbowkit/src/wallets/downloadUrls.ts index 6a48a50b2f..dbacf08c29 100644 --- a/packages/rainbowkit/src/wallets/downloadUrls.ts +++ b/packages/rainbowkit/src/wallets/downloadUrls.ts @@ -1,5 +1,6 @@ import { BrowserType, getBrowser } from '../utils/browsers'; import { isIOS } from '../utils/isMobile'; +import { PlatformType, getPlatform } from '../utils/platforms'; import { WalletInstance } from './Wallet'; export const getExtensionDownloadUrl = (wallet?: WalletInstance) => { @@ -27,3 +28,15 @@ export const getMobileDownloadUrl = (wallet?: WalletInstance) => { wallet?.downloadUrls?.mobile ); }; + +export const getDesktopDownloadUrl = (wallet?: WalletInstance) => { + const platform = getPlatform(); + return ( + { + [PlatformType.Windows]: wallet?.downloadUrls?.windows, + [PlatformType.MacOS]: wallet?.downloadUrls?.macos, + [PlatformType.Linux]: wallet?.downloadUrls?.linux, + [PlatformType.Desktop]: wallet?.downloadUrls?.desktop, + }[platform] ?? wallet?.downloadUrls?.desktop + ); +}; diff --git a/packages/rainbowkit/src/wallets/useWalletConnectors.ts b/packages/rainbowkit/src/wallets/useWalletConnectors.ts index cf8b384fd2..3c9053a96e 100644 --- a/packages/rainbowkit/src/wallets/useWalletConnectors.ts +++ b/packages/rainbowkit/src/wallets/useWalletConnectors.ts @@ -7,7 +7,11 @@ import { useRainbowKitChains, } from './../components/RainbowKitProvider/RainbowKitChainContext'; import { WalletInstance } from './Wallet'; -import { getExtensionDownloadUrl, getMobileDownloadUrl } from './downloadUrls'; +import { + getDesktopDownloadUrl, + getExtensionDownloadUrl, + getMobileDownloadUrl, +} from './downloadUrls'; import { addRecentWalletId, getRecentWalletIds } from './recentWalletIds'; export interface WalletConnector extends WalletInstance { @@ -18,6 +22,7 @@ export interface WalletConnector extends WalletInstance { recent: boolean; mobileDownloadUrl?: string; extensionDownloadUrl?: string; + desktopDownloadUrl?: string; } export function useWalletConnectors(): WalletConnector[] { @@ -106,6 +111,7 @@ export function useWalletConnectors(): WalletConnector[] { wallet.connector.showQrModal ? connectToWalletConnectModal(wallet.id, wallet.connector) : connectWallet(wallet.id, wallet.connector), + desktopDownloadUrl: getDesktopDownloadUrl(wallet), extensionDownloadUrl: getExtensionDownloadUrl(wallet), groupName: wallet.groupName, mobileDownloadUrl: getMobileDownloadUrl(wallet), diff --git a/packages/rainbowkit/src/wallets/walletConnectors/ledgerWallet/ledgerWallet.ts b/packages/rainbowkit/src/wallets/walletConnectors/ledgerWallet/ledgerWallet.ts index 485760ac73..9fa58a7c89 100644 --- a/packages/rainbowkit/src/wallets/walletConnectors/ledgerWallet/ledgerWallet.ts +++ b/packages/rainbowkit/src/wallets/walletConnectors/ledgerWallet/ledgerWallet.ts @@ -30,13 +30,18 @@ export const ledgerWallet = ({ }: LedgerWalletLegacyOptions | LedgerWalletOptions): Wallet => ({ id: 'ledger', iconBackground: '#000', - name: 'Ledger Live', + iconAccent: '#000', + name: 'Ledger', iconUrl: async () => (await import('./ledgerWallet.svg')).default, downloadUrls: { android: 'https://play.google.com/store/apps/details?id=com.ledger.live', ios: 'https://apps.apple.com/us/app/ledger-live-web3-wallet/id1361671700', mobile: 'https://www.ledger.com/ledger-live', - qrCode: 'https://ledger.com/ledger-live', + qrCode: 'https://r354.adj.st/?adj_t=t2esmlk', + windows: 'https://www.ledger.com/ledger-live/download', + macos: 'https://www.ledger.com/ledger-live/download', + linux: 'https://www.ledger.com/ledger-live/download', + desktop: 'https://www.ledger.com/ledger-live', }, createConnector: () => { const connector = getWalletConnectConnector({ @@ -67,6 +72,57 @@ export const ledgerWallet = ({ ); return `ledgerlive://wc?uri=${encodeURIComponent(uri)}`; }, + instructions: { + learnMoreUrl: + 'https://support.ledger.com/hc/en-us/articles/4404389503889-Getting-started-with-Ledger-Live', + steps: [ + { + description: 'wallet_connectors.ledger.desktop.step1.description', + step: 'install', + title: 'wallet_connectors.ledger.desktop.step1.title', + }, + { + description: 'wallet_connectors.ledger.desktop.step2.description', + step: 'create', + title: 'wallet_connectors.ledger.desktop.step2.title', + }, + { + description: 'wallet_connectors.ledger.desktop.step3.description', + step: 'connect', + title: 'wallet_connectors.ledger.desktop.step3.title', + }, + ], + }, + }, + qrCode: { + getUri: async () => { + const uri = await getWalletConnectUri( + connector, + walletConnectVersion, + ); + return `ledgerlive://wc?uri=${encodeURIComponent(uri)}`; + }, + instructions: { + learnMoreUrl: + 'https://support.ledger.com/hc/en-us/articles/4404389503889-Getting-started-with-Ledger-Live', + steps: [ + { + description: 'wallet_connectors.ledger.qr_code.step1.description', + step: 'install', + title: 'wallet_connectors.ledger.qr_code.step1.title', + }, + { + description: 'wallet_connectors.ledger.qr_code.step2.description', + step: 'create', + title: 'wallet_connectors.ledger.qr_code.step2.title', + }, + { + description: 'wallet_connectors.ledger.qr_code.step3.description', + step: 'scan', + title: 'wallet_connectors.ledger.qr_code.step3.title', + }, + ], + }, }, }; }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5349c78594..6558bfa2cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -410,6 +410,9 @@ importers: react-remove-scroll: specifier: 2.5.4 version: 2.5.4(@types/react@18.2.21)(react@18.2.0) + ua-parser-js: + specifier: ^1.0.35 + version: 1.0.36 devDependencies: '@testing-library/jest-dom': specifier: ^6.1.3 @@ -426,6 +429,9 @@ importers: '@types/qrcode': specifier: ^1.4.2 version: 1.4.2 + '@types/ua-parser-js': + specifier: ^0.7.36 + version: 0.7.38 '@vanilla-extract/css-utils': specifier: 0.1.2 version: 0.1.2 @@ -8811,6 +8817,10 @@ packages: /@types/trusted-types@2.0.3: resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==} + /@types/ua-parser-js@0.7.38: + resolution: {integrity: sha512-59CA5oavBEWSNLtS/BChj9xntiWMsIf9IytjxmBo9OuZEYuRzRf3K1ARzFPlXTOz5Zm2wXI38AP9RlLqDYMToQ==} + dev: true + /@types/unist@2.0.6: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} @@ -9809,7 +9819,6 @@ packages: /acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} - hasBin: true dev: false /acorn@8.10.0: @@ -10198,7 +10207,6 @@ packages: /autoprefixer@10.4.15(postcss@8.4.29): resolution: {integrity: sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==} engines: {node: ^10 || ^12 || >=14} - hasBin: true peerDependencies: postcss: ^8.1.0 dependencies: @@ -10214,7 +10222,6 @@ packages: /autoprefixer@10.4.15(postcss@8.4.6): resolution: {integrity: sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==} engines: {node: ^10 || ^12 || >=14} - hasBin: true peerDependencies: postcss: ^8.1.0 dependencies: @@ -11582,7 +11589,6 @@ packages: /css-blank-pseudo@3.0.3(postcss@8.4.6): resolution: {integrity: sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==} engines: {node: ^12 || ^14 || >=16} - hasBin: true peerDependencies: postcss: ^8.4 dependencies: @@ -11602,7 +11608,6 @@ packages: /css-has-pseudo@3.0.4(postcss@8.4.6): resolution: {integrity: sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==} engines: {node: ^12 || ^14 || >=16} - hasBin: true peerDependencies: postcss: ^8.4 dependencies: @@ -11659,7 +11664,6 @@ packages: /css-prefers-color-scheme@6.0.3(postcss@8.4.6): resolution: {integrity: sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==} engines: {node: ^12 || ^14 || >=16} - hasBin: true peerDependencies: postcss: ^8.4 dependencies: @@ -15670,7 +15674,6 @@ packages: /jayson@3.7.0: resolution: {integrity: sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==} engines: {node: '>=8'} - hasBin: true dependencies: '@types/connect': 3.4.35 '@types/node': 12.20.55 @@ -15728,7 +15731,6 @@ packages: /jest-cli@27.5.1(ts-node@10.9.1): resolution: {integrity: sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - hasBin: true peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: @@ -16270,7 +16272,6 @@ packages: /jest@27.5.1(ts-node@10.9.1): resolution: {integrity: sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - hasBin: true peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: @@ -16303,7 +16304,6 @@ packages: /js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true dependencies: argparse: 1.0.10 esprima: 4.0.1 @@ -16319,7 +16319,6 @@ packages: /jscodeshift@0.13.1(@babel/preset-env@7.16.4): resolution: {integrity: sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==} - hasBin: true peerDependencies: '@babel/preset-env': ^7.1.6 dependencies: @@ -17782,7 +17781,6 @@ packages: /next@13.4.19(@babel/core@7.21.8)(@opentelemetry/api@1.1.0)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==} engines: {node: '>=16.8.0'} - hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 react: ^18.2.0 @@ -17895,7 +17893,6 @@ packages: /node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} - hasBin: true /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -18473,7 +18470,6 @@ packages: /pino@7.11.0: resolution: {integrity: sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==} - hasBin: true dependencies: atomic-sleep: 1.0.0 fast-redact: 3.1.2 @@ -19607,7 +19603,6 @@ packages: /qrcode@1.5.3: resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} engines: {node: '>=10.13.0'} - hasBin: true dependencies: dijkstrajs: 1.0.3 encode-utf8: 1.0.3 @@ -19883,7 +19878,6 @@ packages: /react-scripts@5.0.1(@babel/plugin-syntax-flow@7.21.4)(@babel/plugin-transform-react-jsx@7.21.5)(esbuild@0.14.39)(eslint@8.15.0)(react@18.2.0)(ts-node@10.9.1)(typescript@5.0.4): resolution: {integrity: sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==} engines: {node: '>=14.0.0'} - hasBin: true peerDependencies: eslint: '*' react: '>= 16' @@ -20801,7 +20795,6 @@ packages: /sha.js@2.4.11: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} - hasBin: true dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 @@ -21823,7 +21816,6 @@ packages: /ts-node@10.9.1(@types/node@18.16.12)(typescript@5.0.4): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} - hasBin: true peerDependencies: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' @@ -21986,6 +21978,10 @@ packages: resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} engines: {node: '>=12.20'} + /ua-parser-js@1.0.36: + resolution: {integrity: sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw==} + dev: false + /ufo@1.1.2: resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} @@ -22154,7 +22150,6 @@ packages: /update-browserslist-db@1.0.11(browserslist@4.21.10): resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} - hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: @@ -22164,7 +22159,6 @@ packages: /update-browserslist-db@1.0.11(browserslist@4.21.5): resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} - hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: @@ -22175,7 +22169,6 @@ packages: /update-browserslist-db@1.0.11(browserslist@4.21.9): resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} - hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: @@ -22455,7 +22448,6 @@ packages: /vite@4.4.7(@types/node@18.16.12): resolution: {integrity: sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==} engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true peerDependencies: '@types/node': '>= 14' less: '*' @@ -22490,7 +22482,6 @@ packages: /vitest@0.33.0(jsdom@22.1.0): resolution: {integrity: sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==} engines: {node: '>=v14.18.0'} - hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@vitest/browser': '*' @@ -22699,7 +22690,6 @@ packages: /webpack-dev-server@4.15.0(webpack@5.82.0): resolution: {integrity: sha512-HmNB5QeSl1KpulTBQ8UT4FPrByYyaLxpJoQ0+s7EvUrMc16m0ZS1sgb1XGqzmgCPk0c9y+aaXxn11tbLzuM7NQ==} engines: {node: '>= 12.13.0'} - hasBin: true peerDependencies: webpack: ^4.37.0 || ^5.0.0 webpack-cli: '*' @@ -22781,7 +22771,6 @@ packages: /webpack@5.82.0(esbuild@0.14.39): resolution: {integrity: sha512-iGNA2fHhnDcV1bONdUu554eZx+XeldsaeQ8T67H6KKHl2nUSwX8Zm7cmzOA46ox/X1ARxf7Bjv8wQ/HsB5fxBg==} engines: {node: '>=10.13.0'} - hasBin: true peerDependencies: webpack-cli: '*' peerDependenciesMeta: @@ -22931,7 +22920,6 @@ packages: /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true dependencies: isexe: 2.0.0 diff --git a/site/data/docs/custom-wallets.mdx b/site/data/docs/custom-wallets.mdx index 4fb5a79223..b360d07d5b 100644 --- a/site/data/docs/custom-wallets.mdx +++ b/site/data/docs/custom-wallets.mdx @@ -157,6 +157,31 @@ The `Wallet` function type is provided to help you define your own custom wallet description: 'Landing page for desktop extension users when browser-compatible URLs are unavailable', }, + { + name: 'macos', + required: false, + type: 'string', + description: 'Download page URL for macOS users', + }, + { + name: 'windows', + required: false, + type: 'string', + description: 'Download page URL for Windows users', + }, + { + name: 'linux', + required: false, + type: 'string', + description: 'Download page URL for Linux users', + }, + { + name: 'desktop', + required: false, + type: 'string', + description: + 'Landing page for desktop app users when system-compatible URLs are unavailable', + }, ]} />