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

Ledger live implementation #515

Merged
merged 32 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
37d51a7
Add Ledger Live connector
michalsmiarowski May 15, 2023
4da9032
Add LedgerLive button to SelectWallet modal
michalsmiarowski May 15, 2023
42d139b
Create ConnectLedgerLive component
michalsmiarowski May 15, 2023
27053bc
Fix typings in ledger_live connector
michalsmiarowski May 15, 2023
9b1eff2
Add `@ledgerhq/connect-kit-loader` package
michalsmiarowski May 15, 2023
d2a1af9
Add `LEDGER_LIVE` to `WalletType` enum
michalsmiarowski May 15, 2023
f7be6fa
Add proper Ledger Live icon
michalsmiarowski May 17, 2023
131ab84
Add listeners to the provider
michalsmiarowski May 17, 2023
740de82
Fix error then disconnectin the Ledger wallet.
michalsmiarowski May 17, 2023
d7ed01b
Change console.log to console.error
michalsmiarowski May 25, 2023
869f9ac
Add empty line
michalsmiarowski May 25, 2023
f936566
Remove duplicated code
michalsmiarowski May 30, 2023
c235c98
Use correct Ledger Live icon
michalsmiarowski May 30, 2023
1149fa5
Rename ledger_live connector
michalsmiarowski May 30, 2023
f1fac17
Remove unnecessary undefined.
michalsmiarowski May 30, 2023
36b0767
Create `ConnectorNotAcivatedError` error
michalsmiarowski May 30, 2023
6ccab86
Close walletconnection modal for LedgerLive
michalsmiarowski Jun 5, 2023
225800e
Pass rpc to contructor of ledger Live
michalsmiarowski Jun 6, 2023
6f909fc
Use `useColorModeValue` hook
michalsmiarowski Jun 8, 2023
bc69060
Merge branch 'main' into ledger-live-implementation
michalsmiarowski Jun 9, 2023
f6ff7f5
Add alert for unsupported network
michalsmiarowski Jun 12, 2023
eaaa1b6
Merge branch 'main' into ledger-live-implementation
michalsmiarowski Jul 24, 2023
df848ca
Update @ledgerhq/connect-kit-loader lib
michalsmiarowski Jul 24, 2023
66224b1
Adjust `checkSupport` method for new lib version
michalsmiarowski Jul 24, 2023
aaacbe7
Make code more readable
michalsmiarowski Jul 24, 2023
ab114c9
Simplify wallet connection aler useEffect
michalsmiarowski Jul 24, 2023
3bf7da1
Add `shouldForceCloseModal` property
michalsmiarowski Jul 25, 2023
98a6086
Add `shouldForceCloseModal` to dependency array
michalsmiarowski Jul 26, 2023
e3f46f9
Add feature flag for ledger live
michalsmiarowski Jul 27, 2023
0853ce8
Pass project id through a constructor
michalsmiarowski Jul 27, 2023
1f996cd
Update `@ledgerhq/connect-kit-loader`
michalsmiarowski Aug 11, 2023
6cfe182
Fix "Cannot convert undefined or null to object"
michalsmiarowski Aug 14, 2023
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@keep-network/tbtc": "development",
"@keep-network/tbtc-v2": "development",
"@keep-network/tbtc-v2.ts": "development",
"@ledgerhq/connect-kit-loader": "^1.0.2",
"@ledgerhq/hw-app-eth": "^6.9.0",
"@ledgerhq/hw-transport-u2f": "^5.36.0-deprecated",
"@ledgerhq/hw-transport-webhid": "^6.7.0",
Expand Down
55 changes: 55 additions & 0 deletions src/components/Modal/SelectWalletModal/ConnectLedgerLive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { FC, useEffect } from "react"
import { MetaMaskIcon } from "../../../static/icons/MetaMask"
import { useWeb3React } from "@web3-react/core"
import { ledgerLive } from "../../../web3/connectors"
import { WalletConnectionModalBase } from "./components"
import { ConnectionError, WalletType } from "../../../enums"
import doesErrorInclude from "../../../web3/utils/doesErrorInclude"
import { useCapture } from "../../../hooks/posthog"
import { PosthogEvent } from "../../../types/posthog"

const ConnectLedgerLive: FC<{ goBack: () => void; closeModal: () => void }> = ({
goBack,
closeModal,
}) => {
const { activate, error } = useWeb3React()

const connectionRejected = doesErrorInclude(
error,
ConnectionError.RejectedMetamaskConnection
)

const connector = ledgerLive
const walletType = WalletType.LedgerLive
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
const captureWalletConnected = useCapture(PosthogEvent.WalletConnected)

const onError = (error: unknown) => {
console.log("error:", error)
}
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
useEffect(() => {
if (!connector) return

captureWalletConnected({ walletType })
activate(connector, onError)
closeModal()
}, [activate, connector, captureWalletConnected, walletType])
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved

return (
<WalletConnectionModalBase
connector={ledgerLive}
goBack={goBack}
closeModal={closeModal}
WalletIcon={MetaMaskIcon}
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
title="Ledger Live"
subTitle={
!error
? "The Ledger Live extension will open in an external window."
: ""
}
tryAgain={connectionRejected ? () => activate(ledgerLive) : undefined}
walletType={walletType}
></WalletConnectionModalBase>
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
)
}

export default ConnectLedgerLive
46 changes: 26 additions & 20 deletions src/components/Modal/SelectWalletModal/InitialSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,46 @@ import {
Icon,
Stack,
StackDivider,
useColorMode,
useColorModeValue,
VStack,
} from "@chakra-ui/react"
import { BiRightArrowAlt } from "react-icons/all"
import { H4 } from "@threshold-network/components"
import { WalletOption } from "../../../types"
import { WalletType } from "../../../enums"
import { ColorMode, WalletType } from "../../../enums"

const InitialWalletSelection: FC<{
walletOptions: WalletOption[]
onSelect: (walletType: WalletType) => void
}> = ({ walletOptions, onSelect }) => {
const { colorMode } = useColorMode()
return (
<VStack divider={<StackDivider margin="0 40px !important" />}>
{walletOptions.map((opt) => (
<Button
key={opt.id}
variant="unstyled"
w="100%"
h="100px"
_hover={{ bg: useColorModeValue("gray.100", "gray.500") }}
_active={{ bg: "gray.300" }}
borderRadius={0}
onClick={() => onSelect(opt.id)}
>
<Stack justify="space-between" direction="row" px="40px">
<Stack direction="row">
<Icon as={opt.icon} h="40px" w="40px" mr="32px" />
<H4>{opt.title}</H4>
{walletOptions.map((opt) => {
const icon =
colorMode === ColorMode.DARK ? opt.icon.dark : opt.icon.light
return (
<Button
key={opt.id}
variant="unstyled"
w="100%"
h="100px"
_hover={{ bg: useColorModeValue("gray.100", "gray.500") }}
_active={{ bg: "gray.300" }}
borderRadius={0}
onClick={() => onSelect(opt.id)}
>
<Stack justify="space-between" direction="row" px="40px">
<Stack direction="row">
<Icon as={icon} h="40px" w="40px" mr="32px" />
<H4>{opt.title}</H4>
</Stack>
<Icon as={BiRightArrowAlt} h="40px" w="40px" />
</Stack>
<Icon as={BiRightArrowAlt} h="40px" w="40px" />
</Stack>
</Button>
))}
</Button>
)
})}
</VStack>
)
}
Expand Down
34 changes: 30 additions & 4 deletions src/components/Modal/SelectWalletModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,51 @@ import { CoinbaseWallet } from "../../../static/icons/CoinbaseWallet"
import { useModal } from "../../../hooks/useModal"
import ModalCloseButton from "../ModalCloseButton"
import ConnectTaho from "./ConnectTaho"
import ConnectLedgerLive from "./ConnectLedgerLive"
import { Ledger } from "../../../static/icons/Ledger"
import { LedgerLight } from "../../../static/icons/LedgerLight"
import { LedgerDark } from "../../../static/icons/LedgerDark"

const walletOptions: WalletOption[] = [
{
id: WalletType.TAHO,
title: "Taho",
icon: Taho,
icon: {
light: Taho,
dark: Taho,
},
},
{
id: WalletType.Metamask,
title: "MetaMask",
icon: MetaMaskIcon,
icon: {
light: MetaMaskIcon,
dark: MetaMaskIcon,
},
},
{
id: WalletType.LedgerLive,
title: "Ledger Live",
icon: {
light: LedgerLight,
dark: LedgerDark,
},
},
{
id: WalletType.WalletConnect,
title: "WalletConnect",
icon: WalletConnectIcon,
icon: {
light: WalletConnectIcon,
dark: WalletConnectIcon,
},
},
{
id: WalletType.Coinbase,
title: "Coinbase Wallet",
icon: CoinbaseWallet,
icon: {
light: CoinbaseWallet,
dark: CoinbaseWallet,
},
},
]

Expand Down Expand Up @@ -94,6 +118,8 @@ const ConnectWallet: FC<{
return <ConnectWalletConnect goBack={goBack} closeModal={onClose} />
case WalletType.Coinbase:
return <ConnectCoinbase goBack={goBack} closeModal={onClose} />
case WalletType.LedgerLive:
return <ConnectLedgerLive goBack={goBack} closeModal={onClose} />
default:
return <></>
}
Expand Down
3 changes: 2 additions & 1 deletion src/enums/web3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ export enum WalletType {
WalletConnect = "WALLET_CONNECT",
Coinbase = "COINBASE",
Trezor = "TREZOR",
Ledger = "Ledger",
Ledger = "LEDGER",
LedgerLive = "LEDGER_LIVE",
}
21 changes: 21 additions & 0 deletions src/static/icons/LedgerDark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createIcon } from "@chakra-ui/icons"

export const LedgerDark = createIcon({
displayName: "Ledger",
viewBox: "0 0 160 160",
path: (
<svg
width="160"
height="160"
viewBox="0 0 160 160"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="160" height="160" rx="16" fill="#00000D" />
<path
d="M93.1482 119.207V125H135V98.8769H128.902V119.207H93.1482ZM93.1482 33V38.792H128.902V59.1231H135V33H93.1482ZM74.0104 59.1231H67.9125V98.8769H95.4153V93.6539H74.0104V59.1231ZM26 98.8769V125H67.8518V119.207H32.0979V98.8769H26ZM26 33V59.1231H32.0979V38.792H67.8518V33H26Z"
fill="white"
/>
</svg>
),
})
21 changes: 21 additions & 0 deletions src/static/icons/LedgerLight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createIcon } from "@chakra-ui/icons"

export const LedgerLight = createIcon({
displayName: "Ledger",
viewBox: "0 0 160 160",
path: (
<svg
width="160"
height="160"
viewBox="0 0 160 160"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="160" height="160" rx="16" fill="white" />
<path
d="M93.1482 119.207V125H135V98.8769H128.902V119.207H93.1482ZM93.1482 33V38.792H128.902V59.1231H135V33H93.1482ZM74.0104 59.1231H67.9125V98.8769H95.4153V93.6539H74.0104V59.1231ZM26 98.8769V125H67.8518V119.207H32.0979V98.8769H26ZM26 33V59.1231H32.0979V38.792H67.8518V33H26Z"
fill="#00000D"
/>
</svg>
),
})
Comment on lines +1 to +21
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the svg is the same we only swap colors in <rect fill={} and <path fill={} />. So we can create one file
LedgerLive.tsx and create an custom icon using Icon component see https://chakra-ui.com/docs/components/icon#using-the-icon-component:

const LedgerLive = ({ bgColor, iconColor }) => {
 <Icon viewBox >
        <rect width="160" height="160" rx="16" fill={bgColor} />
      <path
        d="M93.1482 119.207V125H135V98.8769H128.902V119.207H93.1482ZM93.1482 33V38.792H128.902V59.1231H135V33H93.1482ZM74.0104 59.1231H67.9125V98.8769H95.4153V93.6539H74.0104V59.1231ZM26 98.8769V125H67.8518V119.207H32.0979V98.8769H26ZM26 33V59.1231H32.0979V38.792H67.8518V33H26Z"
        fill="{iconColor}"
      />
 </Icon>
}

export const LedgerLiveDark = () => <LedgerLive bgColor={#00000D} iconColor={white} />

export const LedgerLiveLight = () => <LedgerLive bgColor={white} iconColor={#00000D} />

5 changes: 4 additions & 1 deletion src/types/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export interface WalletConnectionModalProps {

export interface WalletOption {
id: WalletType
icon: FC
title: string
icon: {
light: FC
dark: FC
}
}
1 change: 1 addition & 0 deletions src/web3/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export * from "./taho"
export * from "./metamask"
export * from "./walletConnect"
export * from "./coinbaseWallet"
export * from "./ledger_live"
export { AbstractConnector } from "@web3-react/abstract-connector"
export { UserRejectedRequestError } from "@web3-react/injected-connector"
100 changes: 100 additions & 0 deletions src/web3/connectors/ledger_live.ts
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { AbstractConnector } from "@web3-react/abstract-connector"
import { AbstractConnectorArguments, ConnectorUpdate } from "@web3-react/types"
import { getEnvVariable, supportedChainId } from "../../utils/getEnvVariable"
import {
LedgerConnectKit,
SupportedProviders,
loadConnectKit,
EthereumProvider,
} from "@ledgerhq/connect-kit-loader"
import { EnvVariable } from "../../enums"

export class LedgerLiveConnector extends AbstractConnector {
private provider?: EthereumProvider | undefined
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
private connectKitPromise: Promise<LedgerConnectKit>

constructor(args: Required<AbstractConnectorArguments>) {
super(args)

this.handleNetworkChanged = this.handleNetworkChanged.bind(this)
this.handleChainChanged = this.handleChainChanged.bind(this)
this.handleAccountsChanged = this.handleAccountsChanged.bind(this)
this.handleClose = this.handleClose.bind(this)

this.connectKitPromise = loadConnectKit()
}

private handleNetworkChanged(networkId: string): void {
this.emitUpdate({ provider: this.provider, chainId: networkId })
}

private handleChainChanged(chainId: string): void {
this.emitUpdate({ chainId })
}

private handleAccountsChanged(accounts: string[]): void {
this.emitUpdate({ account: accounts.length === 0 ? null : accounts[0] })
}

private handleClose(): void {
this.emitDeactivate()
}

public async activate(): Promise<ConnectorUpdate> {
let account = ""
try {
const connectKit = await this.connectKitPromise
const checkSupportResult = connectKit.checkSupport({
chainId: 1,
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
providerType: SupportedProviders.Ethereum,
rpc: {
[Number(supportedChainId)]: rpcUrl as string,
},
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
})

this.provider = (await connectKit.getProvider()) as EthereumProvider

this.provider.on("networkChanged", this.handleNetworkChanged)
this.provider.on("chainChanged", this.handleChainChanged)
this.provider.on("accountsChanged", this.handleAccountsChanged)
this.provider.on("close", this.handleClose)

const accounts = (await this.provider.request({
method: "eth_requestAccounts",
})) as string[]
account = accounts[0]
} catch (err) {
console.log("Error: ", err)
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
}
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved

return { provider: this.provider, account }
}

public async getProvider(): Promise<EthereumProvider | undefined> {
return this.provider
}

public async getChainId(): Promise<number> {
return this.provider!.request({ method: "eth_chainId" })
}

public async getAccount(): Promise<string> {
const accounts = (await this.provider!.request({
method: "eth_requestAccounts",
})) as string[]
return accounts[0]
}

public deactivate() {
this.provider!.removeListener("networkChanged", this.handleNetworkChanged)
this.provider!.removeListener("chainChanged", this.handleChainChanged)
this.provider!.removeListener("accountsChanged", this.handleAccountsChanged)
this.provider!.removeListener("close", this.handleClose)
}
}
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
const rpcUrl = getEnvVariable(EnvVariable.ETH_HOSTNAME_HTTP)
r-czajkowski marked this conversation as resolved.
Show resolved Hide resolved
const chainId = +supportedChainId

export const ledgerLive = new LedgerLiveConnector({
supportedChainIds: [chainId],
})
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3588,6 +3588,11 @@
"@summa-tx/relay-sol" "^2.0.2"
openzeppelin-solidity "2.3.0"

"@ledgerhq/connect-kit-loader@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@ledgerhq/connect-kit-loader/-/connect-kit-loader-1.0.2.tgz#8554e16943f86cc2a5f6348a14dfe6e5bd0c572a"
integrity sha512-TQ21IjcZOw/scqypaVFY3jHVqI7X7Hta3qN/us6FvTol3AY06UmrhhXGww0E9xHmAbdX241ddwXEiMBSQZFr9g==

"@ledgerhq/cryptoassets@^5.27.2", "@ledgerhq/cryptoassets@^5.53.0":
version "5.53.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/cryptoassets/-/cryptoassets-5.53.0.tgz#11dcc93211960c6fd6620392e4dd91896aaabe58"
Expand Down