diff --git a/.changeset/wise-doors-give.md b/.changeset/wise-doors-give.md new file mode 100644 index 0000000000..6eefefc8d0 --- /dev/null +++ b/.changeset/wise-doors-give.md @@ -0,0 +1,42 @@ +--- +'@rainbow-me/rainbowkit': patch +--- + +**Improved desktop app download support** + +RainbowKit wallet connectors now support a desktop download link and desktop +app instructions. + +dApps that utilize the Custom Wallets API can reference the updated docs +[here](https://www.rainbowkit.com/docs/custom-wallets). + +```ts +{ + downloadUrls: { + desktop: 'https://my-wallet/desktop-app', + } +} +``` + +There is a new 'connect' step on the instructions that shows a connect button +you can use after installing the app. + +```ts +return { + connector, + desktop: { + getUri, + instructions: { + learnMoreUrl: 'https://my-wallet/learn-more', + steps: [ + // ... + { + description: 'Connect to wallet' + step: 'connect', + title: 'Connect', + } + ] + }, + }, +} +``` diff --git a/packages/rainbowkit/src/components/ConnectOptions/ConnectDetails.tsx b/packages/rainbowkit/src/components/ConnectOptions/ConnectDetails.tsx index 89bcdba6ed..bb7e5bee3b 100644 --- a/packages/rainbowkit/src/components/ConnectOptions/ConnectDetails.tsx +++ b/packages/rainbowkit/src/components/ConnectOptions/ConnectDetails.tsx @@ -74,6 +74,7 @@ export function GetDetail({ ?.filter( wallet => wallet.extensionDownloadUrl || + wallet.desktopDownloadUrl || (wallet.qrCode && wallet.downloadUrls?.qrCode) ) .map(wallet => { @@ -82,6 +83,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 ? 'Mobile Wallet and Extension' + : hasMobileAndDesktop + ? 'Mobile and Desktop Wallets' : hasMobileCompanionApp ? 'Mobile Wallet' : hasExtension @@ -187,6 +192,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: { @@ -211,7 +218,7 @@ export function ConnectDetail({ label: 'GET', onClick: () => changeWalletStep( - hasQrCodeAndExtension + hasQrCodeAndExtension || hasQrCodeAndDesktop ? WalletStep.DownloadOptions : WalletStep.Download ), @@ -389,7 +396,7 @@ const DownloadOptionsBox = ({ isCompact: boolean; iconUrl: string | (() => Promise); iconBackground?: string; - variant: 'browser' | 'app'; + variant: 'browser' | 'app' | 'desktop'; }) => { const isBrowserCard = variant === 'browser'; const gradientRgbas = @@ -575,7 +582,13 @@ export function DownloadOptionsDetail({ const browser = getBrowser(); const modalSize = useContext(ModalSizeContext); const isCompact = modalSize === 'compact'; - const { extension, extensionDownloadUrl, mobileDownloadUrl } = wallet; + const { + desktop, + desktopDownloadUrl, + extension, + extensionDownloadUrl, + mobileDownloadUrl, + } = wallet; useEffect(() => { // Preload icons used on next screen @@ -622,9 +635,29 @@ export function DownloadOptionsDetail({ variant="browser" /> )} + {desktopDownloadUrl && ( + + changeWalletStep( + desktop?.instructions + ? WalletStep.InstructionsDesktop + : WalletStep.Connect + ) + } + title={`${wallet.name} for Desktop`} + url={desktopDownloadUrl} + variant="desktop" + /> + )} {mobileDownloadUrl && ( ReactNode > = { + connect: () => , create: () => , install: wallet => ( ); } + +export function InstructionDesktopDetail({ + connectWallet, + wallet, +}: { + connectWallet: (wallet: WalletConnector) => void; + wallet: WalletConnector; +}) { + return ( + + + {wallet?.desktop?.instructions?.steps.map((d, idx) => ( + + + {stepIcons[d.step]?.(wallet)} + + + + {d.title} + + + {d.description} + + + + ))} + + + + connectWallet(wallet)} /> + + + ); +} diff --git a/packages/rainbowkit/src/components/ConnectOptions/DesktopOptions.tsx b/packages/rainbowkit/src/components/ConnectOptions/DesktopOptions.tsx index ab729edf8f..271d4f2511 100644 --- a/packages/rainbowkit/src/components/ConnectOptions/DesktopOptions.tsx +++ b/packages/rainbowkit/src/components/ConnectOptions/DesktopOptions.tsx @@ -26,6 +26,7 @@ import { DownloadDetail, DownloadOptionsDetail, GetDetail, + InstructionDesktopDetail, InstructionExtensionDetail, InstructionMobileDetail, } from './ConnectDetails'; @@ -43,6 +44,7 @@ export enum WalletStep { DownloadOptions = 'DOWNLOAD_OPTIONS', Download = 'DOWNLOAD', InstructionsMobile = 'INSTRUCTIONS_MOBILE', + InstructionsDesktop = 'INSTRUCTIONS_DESKTOP', InstructionsExtension = 'INSTRUCTIONS_EXTENSION', } @@ -144,12 +146,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); } @@ -295,6 +300,22 @@ export function DesktopOptions({ onClose }: { onClose: () => void }) { }`; headerBackButtonLink = WalletStep.DownloadOptions; break; + case WalletStep.InstructionsDesktop: + walletContent = selectedWallet && ( + + ); + headerLabel = + selectedWallet && + `Get started with ${ + compactModeEnabled + ? selectedWallet.shortName || selectedWallet.name + : selectedWallet.name + }`; + headerBackButtonLink = WalletStep.DownloadOptions; + break; default: break; } diff --git a/packages/rainbowkit/src/wallets/Wallet.ts b/packages/rainbowkit/src/wallets/Wallet.ts index 816b6d99ea..9ee9bddaf7 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; @@ -45,6 +58,7 @@ export type Wallet = { android?: string; ios?: string; mobile?: string; + desktop?: string; qrCode?: string; chrome?: string; edge?: string; diff --git a/packages/rainbowkit/src/wallets/downloadUrls.ts b/packages/rainbowkit/src/wallets/downloadUrls.ts index 6a48a50b2f..7fc89b9f96 100644 --- a/packages/rainbowkit/src/wallets/downloadUrls.ts +++ b/packages/rainbowkit/src/wallets/downloadUrls.ts @@ -27,3 +27,7 @@ export const getMobileDownloadUrl = (wallet?: WalletInstance) => { wallet?.downloadUrls?.mobile ); }; + +export const getDesktopDownloadUrl = (wallet?: WalletInstance) => { + return wallet?.downloadUrls?.desktop; +}; diff --git a/packages/rainbowkit/src/wallets/useWalletConnectors.ts b/packages/rainbowkit/src/wallets/useWalletConnectors.ts index d0177f00af..494649b280 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 bf1f12452f..c9432e1c87 100644 --- a/packages/rainbowkit/src/wallets/walletConnectors/ledgerWallet/ledgerWallet.ts +++ b/packages/rainbowkit/src/wallets/walletConnectors/ledgerWallet/ledgerWallet.ts @@ -9,6 +9,8 @@ import type { WalletConnectLegacyConnectorOptions, } from '../../getWalletConnectConnector'; +const LEDGERLIVE_WEB_URL = 'https://www.ledger.com/ledger-live'; + export interface LedgerWalletLegacyOptions { projectId?: string; chains: Chain[]; @@ -31,13 +33,15 @@ export const ledgerWallet = ({ }: LedgerWalletLegacyOptions | LedgerWalletOptions): Wallet => ({ id: 'ledger', iconBackground: '#000', + iconAccent: '#000', name: 'Ledger Live', 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', + mobile: LEDGERLIVE_WEB_URL, + desktop: LEDGERLIVE_WEB_URL, + qrCode: LEDGERLIVE_WEB_URL, }, createConnector: () => { const connector = getWalletConnectConnector({ @@ -68,6 +72,57 @@ export const ledgerWallet = ({ ); return `ledgerlive://wc?uri=${encodeURIComponent(uri)}`; }, + instructions: { + learnMoreUrl: LEDGERLIVE_WEB_URL, + steps: [ + { + description: + 'We recommend putting Ledger Live on your home screen for quicker', + step: 'install', + title: 'Open the Ledger Live app', + }, + { + description: 'Set up a new Ledger or connect to an existing one.', + step: 'create', + title: 'Set up your Ledger', + }, + { + description: + 'A connection prompt will appear for you to connect your wallet.', + step: 'connect', + title: 'Connect', + }, + ], + }, + }, + qrCode: { + getUri: async () => { + const { uri } = (await connector.getProvider()).connector; + return `ledgerlive://wc?uri=${encodeURIComponent(uri)}`; + }, + instructions: { + learnMoreUrl: LEDGERLIVE_WEB_URL, + steps: [ + { + description: + 'We recommend putting Ledger Live on your home screen for quicker access.', + step: 'install', + title: 'Open the Ledger Live app', + }, + { + description: + 'You can either sync with the desktop app or connect your Ledger.', + step: 'create', + title: 'Set up your Ledger', + }, + { + description: + 'Tap WalletConnect then Switch to Scanner. After you scan, a connection prompt will appear for you to connect your wallet.', + step: 'scan', + title: 'Scan the code', + }, + ], + }, }, }; }, diff --git a/site/data/docs/custom-wallets.mdx b/site/data/docs/custom-wallets.mdx index 440ef6a1a3..215bd7b4b9 100644 --- a/site/data/docs/custom-wallets.mdx +++ b/site/data/docs/custom-wallets.mdx @@ -120,6 +120,12 @@ The `Wallet` function type is provided to help you define your own custom wallet description: 'Landing page for users that scan the mobile download QR Code', }, + { + name: 'desktop', + required: false, + type: 'string', + description: 'Landing page for desktop users', + }, { name: 'chrome', required: false,