Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group wallets by protocol and service #57

Merged
merged 56 commits into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
07b502d
Group wallets by protocol and service
nicomiicro May 25, 2022
ac2ff71
Fix camelCase
nicomiicro May 26, 2022
b694432
Merge branch 'master' into ui/rearrange-multiwalletmodal-to-divide-by…
nicomiicro May 26, 2022
1277d68
Use void instead of catch
nicomiicro May 26, 2022
a3809ba
Prefer null over undefined
nicomiicro May 26, 2022
e0cb57f
Remove unnecessary Fragment
nicomiicro May 26, 2022
9db4edd
Rename addHelpTextToMetaMaskInfo to addMetamaskEcosystemInfo
nicomiicro May 26, 2022
fb26351
Fix wallet icons in MultiConnectButton
nicomiicro May 26, 2022
ab71b61
Remove curried function
nicomiicro May 26, 2022
be9e6b3
Fix curried function implementation
nicomiicro May 26, 2022
4426977
Merge branch 'master' into ui/rearrange-multiwalletmodal-to-divide-by…
nicomiicro May 27, 2022
9ea2223
Merge branch 'ui/rm-wormhole-form' into ui/rearrange-multiwalletmodal…
nicomiicro May 27, 2022
37c382d
Remove chain from EvmWalletAdapter constructor
nicomiicro May 27, 2022
4b96f6c
Add useWalletService for wallet adapters per service/protocol
nicomiicro May 27, 2022
5181495
Merge branch 'master' into ui/rearrange-multiwalletmodal-to-divide-by…
nicomiicro May 27, 2022
aca8a32
Simplify walletService state and move notifications out of the store
nicomiicro May 30, 2022
71429bc
Rename useWalletService to useWalletAdapter
nicomiicro May 30, 2022
a1fa3bf
Add useWalletSevice hook
nicomiicro May 30, 2022
1715741
Disconnect previous adapter if exists
nicomiicro May 30, 2022
a3641c7
Move useWalletsMonitor to its own component
nicomiicro May 30, 2022
817e253
Update EVM logo and protocol names
nicomiicro May 30, 2022
9c5b82e
Lint order of imports
nicomiicro May 30, 2022
2f9da4c
Use EVM instead of Ethereum Virtual Machine
nicomiicro May 30, 2022
35b377e
Revert imports in selectors
nicomiicro May 30, 2022
7cfbc8d
Display wallets count/icons
nicomiicro May 30, 2022
783b8f8
DRY up ecosystems and use filterMap
nicomiicro May 30, 2022
4f0ba66
Remove unnecessary Map
nicomiicro May 30, 2022
e982f53
Remove ecosystem icons from connect button
nicomiicro May 31, 2022
943300c
Add ecosystem icons to home page
nicomiicro May 31, 2022
39c258e
Rename type in useWalletService
nicomiicro May 31, 2022
7318734
Access useWalletAdapter state via selectors
nicomiicro May 31, 2022
d0c0e8f
Update ecosystem icons in HomePage
nicomiicro May 31, 2022
1bd1c22
Add tests for useWalletAdapter
nicomiicro May 31, 2022
af0988a
Add tests for useWalletsMonitor
nicomiicro May 31, 2022
601d4d2
Add tests for useWalletService
nicomiicro May 31, 2022
3c9aada
Revert unnecessary access modifier change
nicomiicro May 31, 2022
732ee0c
Capitalize MetaMask
nicomiicro Jun 2, 2022
5460327
Don't throw error if wallet adapter is missing
nicomiicro Jun 2, 2022
a573c3f
Use root models import
nicomiicro Jun 2, 2022
3ab0e5d
Remove unused useMemo
nicomiicro Jun 2, 2022
03a7bad
Use root models import
nicomiicro Jun 3, 2022
c2fbeb2
Pass adapter to useWalletAdapter.connectService
nicomiicro Jun 3, 2022
9633352
Fix camel case issue
nicomiicro Jun 3, 2022
c3cd50f
Disable eslint "functional/immutable-data" in test files
nicomiicro Jun 3, 2022
93b796c
Remove unused object modification
nicomiicro Jun 3, 2022
c355ccd
Remove unnecessary eslint-disable comment
nicomiicro Jun 3, 2022
87f61d5
Remove shorten address format from test expectation
nicomiicro Jun 3, 2022
e1adce9
DRY up connect buttons
nicomiicro Jun 6, 2022
b2b90fd
List EVM chains in a popover
nicomiicro Jun 6, 2022
656aecc
Remove white EuiPanel from home page chains
nicomiicro Jun 6, 2022
590ec4f
Merge branch 'master' into ui/rearrange-multiwalletmodal-to-divide-by…
nicomiicro Jun 6, 2022
38e7e8f
Remove unused imports
nicomiicro Jun 6, 2022
5b60ec8
Remove unused parameter from useWalletAdapter connectService
nicomiicro Jun 6, 2022
8a0d64f
DRY up promoted ecosystems in home page
nicomiicro Jun 7, 2022
18e8024
Add isEcosystemEnabled helper
nicomiicro Jun 7, 2022
e3d7c31
Filter enabled ecosystems in MultiWalletModal
nicomiicro Jun 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions apps/ui/src/components/MultiWalletModal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.walletServiceButton {
white-space: nowrap;

&__ecosystems {
margin: 10px 0 0 30px;

& > li {
margin-bottom: 15px;

img {
margin-right: 15px;
}
}
}
}
203 changes: 99 additions & 104 deletions apps/ui/src/components/MultiWalletModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,33 @@ import {
import type { ReactElement } from "react";
import { Fragment } from "react";

import { EcosystemId } from "../config";
import { useConfig } from "../contexts";
import type { Ecosystem } from "../config";
import { Protocol, getEcosystemsForProtocol } from "../config";
import { useWallets } from "../hooks";
import AVALANCHE_SVG from "../images/ecosystems/avalanche.svg";
import BSC_SVG from "../images/ecosystems/bsc.svg";
import ETHEREUM_SVG from "../images/ecosystems/ethereum.svg";
import POLYGON_SVG from "../images/ecosystems/polygon.svg";
import SOLANA_SVG from "../images/ecosystems/solana.svg";
import {
AVALANCHE_WALLET_SERVICES,
BSC_WALLET_SERVICES,
ETHEREUM_WALLET_SERVICES,
POLYGON_WALLET_SERVICES,
SOLANA_WALLET_SERVICES,
} from "../models";
import { WALLET_SERVICES } from "../models";
import type { WalletService } from "../models";
import { isUserOnMobileDevice, shortenAddress } from "../utils";
import {
findOrThrow,
groupBy,
isUserOnMobileDevice,
shortenAddress,
} from "../utils";

import { CustomModal } from "./CustomModal";
import { MobileDeviceDisclaimer } from "./MobileDeviceDisclaimer";

import "./ConnectButton.scss";
nicomiicro marked this conversation as resolved.
Show resolved Hide resolved
import "./MultiWalletModal.scss";

interface WalletServiceButtonProps<W extends WalletService = WalletService> {
readonly service: W;
readonly onClick: () => void;
readonly disconnect: () => void;
readonly serviceConnected: boolean;
readonly address: string | null;
readonly ecosystems: ReadonlyArray<Ecosystem>;
}

const WalletServiceButton = <W extends WalletService = WalletService>({
Expand All @@ -49,12 +47,13 @@ const WalletServiceButton = <W extends WalletService = WalletService>({
onClick,
serviceConnected,
address,
ecosystems,
}: WalletServiceButtonProps<W>): ReactElement => {
const {
info: { icon, name, helpText },
} = service;
return (
<span className="eui-textNoWrap">
<span className="walletServiceButton">
wormat marked this conversation as resolved.
Show resolved Hide resolved
<EuiButtonEmpty
className={`connect-button ${
serviceConnected ? "connected connected-service" : ""
Expand All @@ -69,66 +68,108 @@ const WalletServiceButton = <W extends WalletService = WalletService>({
{helpText && <>{helpText}</>}
<EuiIcon className="exit-icon" type="crossInACircleFilled" size="m" />
</EuiButtonEmpty>
{ecosystems.length > 1 && (
wormat marked this conversation as resolved.
Show resolved Hide resolved
<ul className="walletServiceButton__ecosystems">
{ecosystems.map((ecosystem) => (
<li key={ecosystem.displayName}>
<EuiIcon type={ecosystem.logo} size="m" />
{ecosystem.displayName}
</li>
))}
</ul>
)}
</span>
);
};

interface EcosystemWalletOptionsListProps<
W extends WalletService = WalletService,
> {
readonly address: string | null;
readonly connected: boolean;
interface ProtocolWalletOptionsListProps {
readonly icon: string;
readonly ecosystemName: string;
readonly walletServices: readonly W[];
readonly ecosystemId: EcosystemId;
readonly createServiceClickHandler: (service: W) => () => void;
readonly protocol: Protocol;
}

const EcosystemWalletOptionsList = <W extends WalletService = WalletService>({
address,
const ProtocolWalletOptionsList = ({
icon,
connected,
ecosystemName,
walletServices,
ecosystemId,
createServiceClickHandler,
}: EcosystemWalletOptionsListProps<W>): ReactElement => {
// needed for wallet extraction to work
if (ecosystemId === EcosystemId.Terra) {
throw new Error("Unsupported ecosystem");
protocol,
}: ProtocolWalletOptionsListProps): ReactElement => {
if (protocol === Protocol.Cosmos) {
throw new Error("Unsupported protocol");
}

const wallets = useWallets();
const { wallet, service: currentService } = wallets[ecosystemId];
const ecosystemIds = getEcosystemsForProtocol(protocol);
const protocolWalletServices = ecosystemIds.flatMap(
nicomiicro marked this conversation as resolved.
Show resolved Hide resolved
(ecosystemId) => WALLET_SERVICES[ecosystemId],
);
const protocolWalletServicesByServiceId = groupBy(
wormat marked this conversation as resolved.
Show resolved Hide resolved
protocolWalletServices,
(protocolwalletService) => protocolwalletService.id,
nicomiicro marked this conversation as resolved.
Show resolved Hide resolved
);
const protocolWallets = ecosystemIds.map(
(ecosystemId) => wallets[ecosystemId],
);

const connectedWallets = protocolWallets.filter((wallet) => wallet.connected);

const disconnect = (): void => {
void wallet?.disconnect();
const disconnect = (serviceId: string): void => {
protocolWalletServicesByServiceId[serviceId].forEach((walletService) => {
const ecosystemId = walletService.info.ecosystem.id;
const wallet = wallets[ecosystemId];

if (wallet.connected && wallet.service?.id === serviceId) {
wallets[ecosystemId].wallet?.disconnect().catch(console.error);
nicomiicro marked this conversation as resolved.
Show resolved Hide resolved
}
});
};

const connect = (serviceId: string) => {
protocolWalletServicesByServiceId[serviceId].forEach((walletService) => {
const ecosystemId = walletService.info.ecosystem.id;
const wallet = wallets[ecosystemId];

if (wallet.createServiceClickHandler) {
wallet.createServiceClickHandler(serviceId)();
nicomiicro marked this conversation as resolved.
Show resolved Hide resolved
}
});
};

return (
<EuiFlexItem style={{ minWidth: "180px" }}>
<EuiTitle size="xs">
<h2 style={{ whiteSpace: "nowrap" }}>
<EuiIcon type={icon} size="l" style={{ marginRight: "8px" }} />
{ecosystemName}
{protocol}
</h2>
</EuiTitle>
<EuiSpacer size="s" />
{walletServices.map((service) => {
return (
<Fragment key={`${ecosystemName}:${service.info.name}`}>
<WalletServiceButton
service={service}
serviceConnected={
connected && currentService?.info.name === service.info.name
}
address={address}
disconnect={disconnect}
onClick={createServiceClickHandler(service)}
/>
</Fragment>
);
})}
{Object.entries(protocolWalletServicesByServiceId).map(
([serviceId, serviceWalletServices]) => {
const service = findOrThrow(
protocolWalletServices,
(walletService) => walletService.id === serviceId,
);

const ecosystems = serviceWalletServices.map(
(walletService) => walletService.info.ecosystem,
);

const connectedWallet = connectedWallets.find(
(wallet) => wallet.service?.id === serviceId,
);
nicomiicro marked this conversation as resolved.
Show resolved Hide resolved

return (
<Fragment key={`${protocol}:${serviceId}`}>
nicomiicro marked this conversation as resolved.
Show resolved Hide resolved
<WalletServiceButton
service={service}
ecosystems={ecosystems}
serviceConnected={!!connectedWallet}
address={connectedWallet ? connectedWallet.address : null}
disconnect={() => disconnect(service.id)}
onClick={() => connect(service.id)}
/>
</Fragment>
);
},
)}
</EuiFlexItem>
);
};
Expand All @@ -140,15 +181,6 @@ export interface MultiWalletModalProps {
export const MultiWalletModal = ({
handleClose,
}: MultiWalletModalProps): ReactElement => {
const { solana, ethereum, bsc, avalanche, polygon } = useWallets();

const { ecosystems } = useConfig();
const solanaEcosystem = ecosystems[EcosystemId.Solana];
const ethereumEcosystem = ecosystems[EcosystemId.Ethereum];
const bscEcosystem = ecosystems[EcosystemId.Bsc];
const avalancheEcosystem = ecosystems[EcosystemId.Avalanche];
const polygonEcosystem = ecosystems[EcosystemId.Polygon];

return (
<CustomModal onClose={handleClose}>
<EuiModalHeader>
Expand All @@ -161,50 +193,13 @@ export const MultiWalletModal = ({
{isUserOnMobileDevice() ? <MobileDeviceDisclaimer /> : ""}
<EuiSpacer />
<EuiFlexGrid columns={3} gutterSize="xl">
<EcosystemWalletOptionsList
address={solana.address}
connected={solana.connected}
<ProtocolWalletOptionsList
icon={SOLANA_SVG}
ecosystemName={solanaEcosystem.displayName}
walletServices={SOLANA_WALLET_SERVICES}
ecosystemId={EcosystemId.Solana}
createServiceClickHandler={solana.createServiceClickHandler}
/>
<EcosystemWalletOptionsList
address={ethereum.address}
connected={ethereum.connected}
icon={ETHEREUM_SVG}
ecosystemName={ethereumEcosystem.displayName}
walletServices={ETHEREUM_WALLET_SERVICES}
ecosystemId={EcosystemId.Ethereum}
createServiceClickHandler={ethereum.createServiceClickHandler}
/>
<EcosystemWalletOptionsList
address={bsc.address}
connected={bsc.connected}
icon={BSC_SVG}
ecosystemName={bscEcosystem.displayName}
walletServices={BSC_WALLET_SERVICES}
ecosystemId={EcosystemId.Bsc}
createServiceClickHandler={bsc.createServiceClickHandler}
/>
<EcosystemWalletOptionsList
address={avalanche.address}
connected={avalanche.connected}
icon={AVALANCHE_SVG}
ecosystemName={avalancheEcosystem.displayName}
walletServices={AVALANCHE_WALLET_SERVICES}
ecosystemId={EcosystemId.Avalanche}
createServiceClickHandler={avalanche.createServiceClickHandler}
protocol={Protocol.Solana}
/>
<EcosystemWalletOptionsList
address={polygon.address}
connected={polygon.connected}
icon={POLYGON_SVG}
ecosystemName={polygonEcosystem.displayName}
walletServices={POLYGON_WALLET_SERVICES}
ecosystemId={EcosystemId.Polygon}
createServiceClickHandler={polygon.createServiceClickHandler}
<ProtocolWalletOptionsList
icon={ETHEREUM_SVG} // TODO update icon for EVM and display name for Protocols
wormat marked this conversation as resolved.
Show resolved Hide resolved
protocol={Protocol.Evm}
/>
</EuiFlexGrid>
</EuiModalBody>
Expand Down
6 changes: 3 additions & 3 deletions apps/ui/src/components/SingleWalletModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import type { ReactElement } from "react";
import { Fragment } from "react";

import type { WalletService } from "../models";
import type { WalletAdapter, WalletService } from "../models";
import { isUserOnMobileDevice } from "../utils";

import { CustomModal } from "./CustomModal";
Expand All @@ -22,7 +22,7 @@ export interface SingleWalletModalProps<
readonly services: readonly W[];
readonly handleClose: () => void;
readonly createServiceClickHandler: (
service: W,
serviceId: WalletService<WalletAdapter>["id"],
callback?: () => any,
) => () => void;
}
Expand Down Expand Up @@ -51,7 +51,7 @@ export const SingleWalletModal = <W extends WalletService = WalletService>({
<Fragment key={name}>
<EuiButtonEmpty
isSelected={currentService === id}
onClick={createServiceClickHandler(service, handleClose)}
onClick={createServiceClickHandler(service.id, handleClose)}
>
<EuiIcon type={icon} size="l" style={{ marginRight: 8 }} />
{name}
Expand Down
Loading