Skip to content

Commit

Permalink
feat: add desktop download URL and instructions
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
hlopes-ledger authored and DanielSinclair committed Jun 28, 2023
1 parent 88168e1 commit ab6d1f9
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 9 deletions.
42 changes: 42 additions & 0 deletions .changeset/wise-doors-give.md
Original file line number Diff line number Diff line change
@@ -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',
}
]
},
},
}
```
110 changes: 106 additions & 4 deletions packages/rainbowkit/src/components/ConnectOptions/ConnectDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export function GetDetail({
?.filter(
wallet =>
wallet.extensionDownloadUrl ||
wallet.desktopDownloadUrl ||
(wallet.qrCode && wallet.downloadUrls?.qrCode)
)
.map(wallet => {
Expand All @@ -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 (
<Box
Expand Down Expand Up @@ -113,6 +116,8 @@ export function GetDetail({
<Text color="modalTextSecondary" size="14" weight="medium">
{hasMobileAndExtension
? 'Mobile Wallet and Extension'
: hasMobileAndDesktop
? 'Mobile and Desktop Wallets'
: hasMobileCompanionApp
? 'Mobile Wallet'
: hasExtension
Expand Down Expand Up @@ -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: {
Expand All @@ -211,7 +218,7 @@ export function ConnectDetail({
label: 'GET',
onClick: () =>
changeWalletStep(
hasQrCodeAndExtension
hasQrCodeAndExtension || hasQrCodeAndDesktop
? WalletStep.DownloadOptions
: WalletStep.Download
),
Expand Down Expand Up @@ -389,7 +396,7 @@ const DownloadOptionsBox = ({
isCompact: boolean;
iconUrl: string | (() => Promise<string>);
iconBackground?: string;
variant: 'browser' | 'app';
variant: 'browser' | 'app' | 'desktop';
}) => {
const isBrowserCard = variant === 'browser';
const gradientRgbas =
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -622,9 +635,29 @@ export function DownloadOptionsDetail({
variant="browser"
/>
)}
{desktopDownloadUrl && (
<DownloadOptionsBox
actionLabel="Get the desktop app"
description="Use the desktop wallet to explore the world of Ethereum."
iconAccent={wallet.iconAccent}
iconBackground={wallet.iconBackground}
iconUrl={wallet.iconUrl}
isCompact={isCompact}
onAction={() =>
changeWalletStep(
desktop?.instructions
? WalletStep.InstructionsDesktop
: WalletStep.Connect
)
}
title={`${wallet.name} for Desktop`}
url={desktopDownloadUrl}
variant="desktop"
/>
)}
{mobileDownloadUrl && (
<DownloadOptionsBox
actionLabel="Get the app"
actionLabel="Get the mobile app"
description="Use the mobile wallet to explore the world of Ethereum."
iconAccent={wallet.iconAccent}
iconBackground={wallet.iconBackground}
Expand Down Expand Up @@ -707,6 +740,7 @@ const stepIcons: Record<
InstructionStepName,
(wallet: WalletConnector) => ReactNode
> = {
connect: () => <RefreshIcon />,
create: () => <CreateIcon />,
install: wallet => (
<AsyncImage
Expand Down Expand Up @@ -890,3 +924,71 @@ export function InstructionExtensionDetail({
</Box>
);
}

export function InstructionDesktopDetail({
connectWallet,
wallet,
}: {
connectWallet: (wallet: WalletConnector) => void;
wallet: WalletConnector;
}) {
return (
<Box
alignItems="center"
display="flex"
flexDirection="column"
height="full"
width="full"
>
<Box
display="flex"
flexDirection="column"
gap="28"
height="full"
justifyContent="center"
paddingY="32"
style={{ maxWidth: 320 }}
>
{wallet?.desktop?.instructions?.steps.map((d, idx) => (
<Box
alignItems="center"
display="flex"
flexDirection="row"
gap="16"
key={idx}
>
<Box
borderRadius="10"
height="48"
minWidth="48"
overflow="hidden"
position="relative"
width="48"
>
{stepIcons[d.step]?.(wallet)}
</Box>
<Box display="flex" flexDirection="column" gap="4">
<Text color="modalText" size="14" weight="bold">
{d.title}
</Text>
<Text color="modalTextSecondary" size="14" weight="medium">
{d.description}
</Text>
</Box>
</Box>
))}
</Box>

<Box
alignItems="center"
display="flex"
flexDirection="column"
gap="12"
justifyContent="center"
marginBottom="16"
>
<ActionButton label="Connect" onClick={() => connectWallet(wallet)} />
</Box>
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
DownloadDetail,
DownloadOptionsDetail,
GetDetail,
InstructionDesktopDetail,
InstructionExtensionDetail,
InstructionMobileDetail,
} from './ConnectDetails';
Expand All @@ -43,6 +44,7 @@ export enum WalletStep {
DownloadOptions = 'DOWNLOAD_OPTIONS',
Download = 'DOWNLOAD',
InstructionsMobile = 'INSTRUCTIONS_MOBILE',
InstructionsDesktop = 'INSTRUCTIONS_DESKTOP',
InstructionsExtension = 'INSTRUCTIONS_EXTENSION',
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -295,6 +300,22 @@ export function DesktopOptions({ onClose }: { onClose: () => void }) {
}`;
headerBackButtonLink = WalletStep.DownloadOptions;
break;
case WalletStep.InstructionsDesktop:
walletContent = selectedWallet && (
<InstructionDesktopDetail
connectWallet={selectWallet}
wallet={selectedWallet}
/>
);
headerLabel =
selectedWallet &&
`Get started with ${
compactModeEnabled
? selectedWallet.shortName || selectedWallet.name
: selectedWallet.name
}`;
headerBackButtonLink = WalletStep.DownloadOptions;
break;
default:
break;
}
Expand Down
16 changes: 15 additions & 1 deletion packages/rainbowkit/src/wallets/Wallet.ts
Original file line number Diff line number Diff line change
@@ -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<C extends Connector = Connector> = {
connector: C;
Expand All @@ -9,6 +14,14 @@ type RainbowKitConnector<C extends Connector = Connector> = {
};
desktop?: {
getUri?: () => Promise<string>;
instructions?: {
learnMoreUrl: string;
steps: {
step: InstructionStepName;
title: string;
description: string;
}[];
};
};
qrCode?: {
getUri: () => Promise<string>;
Expand Down Expand Up @@ -45,6 +58,7 @@ export type Wallet<C extends Connector = Connector> = {
android?: string;
ios?: string;
mobile?: string;
desktop?: string;
qrCode?: string;
chrome?: string;
edge?: string;
Expand Down
4 changes: 4 additions & 0 deletions packages/rainbowkit/src/wallets/downloadUrls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ export const getMobileDownloadUrl = (wallet?: WalletInstance) => {
wallet?.downloadUrls?.mobile
);
};

export const getDesktopDownloadUrl = (wallet?: WalletInstance) => {
return wallet?.downloadUrls?.desktop;
};
8 changes: 7 additions & 1 deletion packages/rainbowkit/src/wallets/useWalletConnectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -18,6 +22,7 @@ export interface WalletConnector extends WalletInstance {
recent: boolean;
mobileDownloadUrl?: string;
extensionDownloadUrl?: string;
desktopDownloadUrl?: string;
}

export function useWalletConnectors(): WalletConnector[] {
Expand Down Expand Up @@ -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),
Expand Down
Loading

0 comments on commit ab6d1f9

Please sign in to comment.