From 25488fb74645c8fc30cbd9bfd05bbcff0554a691 Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" <330911+jurevans@users.noreply.github.com> Date: Tue, 23 May 2023 19:24:22 +0200 Subject: [PATCH] feat/253 - Ledger connection setup page (#274) * Add main dependency * Add basic pages for Ledger connection flow * Continue refactoring routes for Setup pages in extension * Add placeholder route and components for Import Account * Remove BrowserRouter * Update routes to support original flow * Fix back-button issue on AccountCreation * minor styling * Adding test code to launch USB approval screen for Ledger * Add basic Ledger class and connection form with alias * Fix encoding type for public key * Make alias required in Account Creation, fix alias+path formatting * Minor clean up * Complete basic functions of Ledger class * Only return publicKey in hex for now * Provide alternate transport to init method, add user feedback on success * Clean up * Final clean up before updating versions * Refactor styled components to re-use common components * Further updates to make setup processes consistent * Fix improper nesting * buttons on Ledger form should be conditional * Updated to support NamadaApp v0.0.4 * Add back reference to slip-044 doc * clean up --- apps/extension/package.json | 5 + .../App/Accounts/AccountListing.components.ts | 10 + .../src/App/Accounts/AccountListing.tsx | 12 +- .../AccountCreation.components.ts | 46 ----- .../Setup/AccountCreation/AccountCreation.tsx | 193 ++---------------- .../Steps/Completion/Completion.components.ts | 21 -- .../Steps/Completion/Completion.tsx | 14 +- .../Steps/Password/Password.components.ts | 59 +----- .../Steps/Password/Password.tsx | 55 +++-- .../Steps/SeedPhrase/SeedPhrase.components.ts | 41 ---- .../Steps/SeedPhrase/SeedPhrase.tsx | 35 ++-- .../SeedPhraseConfirmation.components.ts | 55 ----- .../SeedPhraseConfirmation.tsx | 80 ++++---- .../AccountCreation/Steps/Start/Start.tsx | 43 ---- .../src/Setup/AccountCreation/Steps/index.ts | 1 - .../src/Setup/AccountCreation/types.ts | 34 --- .../ImportAccount.components.ts} | 4 +- .../src/Setup/ImportAccount/ImportAccount.tsx | 47 +++++ .../src/Setup/ImportAccount/index.ts | 1 + .../src/Setup/Ledger/Ledger.components.ts | 6 + apps/extension/src/Setup/Ledger/Ledger.tsx | 146 +++++++++++++ apps/extension/src/Setup/Ledger/index.ts | 1 + apps/extension/src/Setup/Setup.components.ts | 98 +++++++-- apps/extension/src/Setup/Setup.tsx | 178 ++++++++++++++-- .../src/Setup/Start/Start.components.ts | 10 + apps/extension/src/Setup/Start/Start.tsx | 58 ++++++ .../Steps => }/Start/index.ts | 0 apps/extension/src/Setup/index.tsx | 5 +- apps/extension/src/Setup/types.ts | 23 +++ apps/extension/src/background/ledger/index.ts | 1 + .../extension/src/background/ledger/ledger.ts | 116 +++++++++++ .../src/App/TopNavigation/topNavigation.tsx | 5 - packages/chains/src/chains/namada.ts | 6 +- packages/utils/src/helpers/index.ts | 8 + yarn.lock | 82 +++++++- 35 files changed, 886 insertions(+), 613 deletions(-) delete mode 100644 apps/extension/src/Setup/AccountCreation/Steps/Start/Start.tsx delete mode 100644 apps/extension/src/Setup/AccountCreation/types.ts rename apps/extension/src/Setup/{AccountCreation/Steps/Start/Start.components.ts => ImportAccount/ImportAccount.components.ts} (79%) create mode 100644 apps/extension/src/Setup/ImportAccount/ImportAccount.tsx create mode 100644 apps/extension/src/Setup/ImportAccount/index.ts create mode 100644 apps/extension/src/Setup/Ledger/Ledger.components.ts create mode 100644 apps/extension/src/Setup/Ledger/Ledger.tsx create mode 100644 apps/extension/src/Setup/Ledger/index.ts create mode 100644 apps/extension/src/Setup/Start/Start.components.ts create mode 100644 apps/extension/src/Setup/Start/Start.tsx rename apps/extension/src/Setup/{AccountCreation/Steps => }/Start/index.ts (100%) create mode 100644 apps/extension/src/Setup/types.ts create mode 100644 apps/extension/src/background/ledger/index.ts create mode 100644 apps/extension/src/background/ledger/ledger.ts diff --git a/apps/extension/package.json b/apps/extension/package.json index 48f7219bd10..c9e8e812a35 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -34,6 +34,10 @@ "@anoma/types": "0.1.0", "@anoma/utils": "0.1.0", "@cosmjs/encoding": "^0.29.0", + "@ledgerhq/hw-transport": "^6.28.3", + "@ledgerhq/hw-transport-webhid": "^6.27.14", + "@ledgerhq/hw-transport-webusb": "^6.27.14", + "@zondax/ledger-namada": "^0.0.2", "buffer": "^6.0.3", "dompurify": "^3.0.2", "framer-motion": "6.2.4", @@ -70,6 +74,7 @@ "file-loader": "^6.2.0", "jest": "^29.0.1", "jest-environment-jsdom": "^29.3.1", + "leb128": "^0.0.5", "merge-jsons-webpack-plugin": "^2.0.1", "mockzilla": "^0.14.0", "rimraf": "^3.0.2", diff --git a/apps/extension/src/App/Accounts/AccountListing.components.ts b/apps/extension/src/App/Accounts/AccountListing.components.ts index 87f68c73bf4..a197cc52ef5 100644 --- a/apps/extension/src/App/Accounts/AccountListing.components.ts +++ b/apps/extension/src/App/Accounts/AccountListing.components.ts @@ -22,8 +22,18 @@ export const Details = styled.div` flex: 3; `; +export const DerivationPathContainer = styled.div` + display: flex; + flex-direction: row; +`; + export const DerivationPath = styled.div``; +export const ParentAlias = styled.div` + font-weight: 600; + padding-right: 4px; +`; + export const Address = styled.div` font-family: monospace; font-size: 10px; diff --git a/apps/extension/src/App/Accounts/AccountListing.tsx b/apps/extension/src/App/Accounts/AccountListing.tsx index 1ca7e112ed9..7c7889ad313 100644 --- a/apps/extension/src/App/Accounts/AccountListing.tsx +++ b/apps/extension/src/App/Accounts/AccountListing.tsx @@ -9,6 +9,8 @@ import { Alias, DerivationPath, Button, + ParentAlias, + DerivationPathContainer, } from "./AccountListing.components"; import { shortenAddress } from "@anoma/utils"; @@ -45,10 +47,12 @@ const AccountListing = ({ account, parentAlias }: Props): JSX.Element => { return (
- - {isChildAccount && parentAlias} - {formatDerivationPath(isChildAccount, path, type)} - + + {isChildAccount && {parentAlias}} + + {formatDerivationPath(isChildAccount, path, type)} + + {alias && {alias}}
{shortenAddress(address)}
diff --git a/apps/extension/src/Setup/AccountCreation/AccountCreation.components.ts b/apps/extension/src/Setup/AccountCreation/AccountCreation.components.ts index 8f96e35dccb..f2fbb024efa 100644 --- a/apps/extension/src/Setup/AccountCreation/AccountCreation.components.ts +++ b/apps/extension/src/Setup/AccountCreation/AccountCreation.components.ts @@ -1,50 +1,4 @@ import styled from "styled-components"; -import { motion } from "framer-motion"; - -export const AccountCreationContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: start; - align-items: center; - box-sizing: border-box; - background-color: ${(props) => props.theme.colors.utility1.main80}; - border: 1px solid ${(props) => props.theme.colors.utility1.main80}; - border-radius: 8px; - color: ${(props) => props.theme.colors.utility2.main80}; - padding: 0 36px; - height: 100%; - min-height: 400px; - width: 480px; - transition: background-color 0.3s linear; -`; - -export const MotionContainer = styled(motion.div)` - height: 100%; - box-sizing: border-box; -`; - -export const TopSection = styled.section` - display: flex; - justify-content: start; - align-items: center; - width: 100%; - margin: 32px 0; -`; - -export const TopSectionHeaderContainer = styled.section` - display: flex; - justify-content: center; - width: 100%; - align-items: center; -`; - -export const TopSectionButtonContainer = styled.section` - display: flex; - justify-content: center; - align-items: center; - width: 48px; - min-height: 50px; -`; export const RouteContainer = styled.div` display: flex; diff --git a/apps/extension/src/Setup/AccountCreation/AccountCreation.tsx b/apps/extension/src/Setup/AccountCreation/AccountCreation.tsx index d66006a4d80..df9a1735f89 100644 --- a/apps/extension/src/Setup/AccountCreation/AccountCreation.tsx +++ b/apps/extension/src/Setup/AccountCreation/AccountCreation.tsx @@ -1,207 +1,46 @@ -import React, { useState, useEffect, useContext } from "react"; -import { Routes, Route, useNavigate, useLocation } from "react-router-dom"; +import React, { useContext } from "react"; +import { useNavigate, Outlet } from "react-router-dom"; import { AnimatePresence } from "framer-motion"; import { ThemeContext } from "styled-components"; -import { ExtensionRequester } from "extension"; -import { AccountCreationRoute, accountCreationSteps } from "./types"; - import { Icon, IconName, IconSize } from "@anoma/components"; +import { RouteContainer } from "./AccountCreation.components"; import { - Start, - Password, - SeedPhrase, - SeedPhraseConfirmation, - Completion, -} from "./Steps"; -import { - AccountCreationContainer, TopSection, TopSectionHeaderContainer, TopSectionButtonContainer, - RouteContainer, - MotionContainer, -} from "./AccountCreation.components"; -import { AccountCreationDetails } from "Setup/AccountCreation/types"; - -type AnimatedTransitionProps = { - elementKey: string; - children: JSX.Element; -}; - -/** - * This is a utility to facilitate the animated transitions. - */ -const AnimatedTransition: React.FC = (props) => { - const { children, elementKey } = props; - return ( - - {children} - - ); -}; - -type Props = { - requester: ExtensionRequester; -}; + SubViewContainer, +} from "Setup/Setup.components"; /** * The main purpose of this is to coordinate the flow for creating a new account. * It persists the data between the screens in the flow. */ -const AccountCreation: React.FC = ({ requester }) => { - const [accountCreationDetails, setAccountCreationDetails] = - useState({ - alias: "", - }); - const [seedPhrase, setSeedPhrase] = useState(); - const [stepIndex, setStepIndex] = useState(0); - +const AccountCreation: React.FC = () => { const themeContext = useContext(ThemeContext); const navigate = useNavigate(); - const location = useLocation(); - - // info for disabling the back button in the last step - const isLastIndex = accountCreationSteps.length - 1 === stepIndex; - - useEffect(() => { - // at the load we redirect to the first step - // this way we do not need to expose the flow routes to outside - navigate(AccountCreationRoute.Start); - }, []); - - const navigateToNext = (): void => { - setStepIndex((stepIndex) => stepIndex + 1); - navigate(`${accountCreationSteps[stepIndex + 1]}`); - }; - - const navigateToPrevious = (): void => { - setStepIndex((stepIndex) => { - return stepIndex - 1; - }); - navigate(`${accountCreationSteps[stepIndex - 1]}`); - }; - return ( - + - {!isLastIndex && stepIndex !== 0 && ( - { - navigateToPrevious(); - }} - style={{ cursor: "pointer" }} - > - - - )} + navigate(-1)} style={{ cursor: "pointer" }}> + + - - - { - navigateToNext(); - }} - /> - - } - /> - - - { - setSeedPhrase(seedPhrase); - navigateToNext(); - }} - /> - - } - /> - - navigateToNext()} - /> - - } - /> - - { - setAccountCreationDetails((accountCreationDetails) => { - return { - ...accountCreationDetails, - ...accountCreationDetailsDelta, - }; - }); - }} - onSubmitAccountCreationDetails={( - accountCreationDetails - ) => { - setAccountCreationDetails(accountCreationDetails); - navigateToNext(); - }} - /> - - } - /> - - - - } - /> - + - + ); }; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.components.ts b/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.components.ts index 2040d322b52..e3856a39a23 100644 --- a/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.components.ts +++ b/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.components.ts @@ -8,33 +8,12 @@ export const CompletionViewContainer = styled.div` height: 100%; `; -export const Header1 = styled.h1` - margin: 8px 0; - color: ${(props) => props.theme.colors.utility2.main}; -`; - export const BodyText = styled.p` text-align: center; font-weight: 300; color: ${(props) => props.theme.colors.utility2.main80}; `; -export const CompletionViewUpperPartContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; -`; - -export const ButtonsContainer = styled.div` - display: flex; - justify-content: center; - width: 100%; -`; - -export const ButtonContainer = styled.div` - width: 120px; -`; - export const ImageContainer = styled.div` margin: 0 0 48px; `; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.tsx b/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.tsx index 5d13d8e0b7b..5c62ae56aae 100644 --- a/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.tsx +++ b/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.tsx @@ -9,10 +9,10 @@ import { Ports } from "router"; import { BodyText, ButtonsContainer, - CompletionViewContainer, - CompletionViewUpperPartContainer, Header1, -} from "./Completion.components"; + SubViewContainer, + UpperContentContainer, +} from "Setup/Setup.components"; type Props = { alias: string; @@ -52,8 +52,8 @@ const Completion: React.FC = (props) => { }, []); return ( - - + + Creating your wallet {isComplete && ( @@ -70,7 +70,7 @@ const Completion: React.FC = (props) => { {mnemonicStatus === Status.Pending && Pending...} {mnemonicStatus === Status.Failed && Failed} - + - + ); }; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/Password/Password.components.ts b/apps/extension/src/Setup/AccountCreation/Steps/Password/Password.components.ts index 14bfe0df028..8e26ff1b317 100644 --- a/apps/extension/src/Setup/AccountCreation/Steps/Password/Password.components.ts +++ b/apps/extension/src/Setup/AccountCreation/Steps/Password/Password.components.ts @@ -1,54 +1,9 @@ import styled from "styled-components"; -export const AccountInformationViewContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - height: 100%; -`; - -export const Header1 = styled.h1` - margin: 8px 0; - color: ${(props) => props.theme.colors.utility2.main}; -`; - -export const Header3 = styled.h3` - margin: 8px 0; - color: ${(props) => props.theme.colors.utility2.main}; -`; - -export const Header5 = styled.h5` - margin: 8px 0; - color: ${(props) => props.theme.colors.utility2.main}; -`; - -export const BodyText = styled.p` - font-weight: 300; - color: ${(props) => props.theme.colors.utility2.main80}; -`; - -export const AccountInformationViewUpperPartContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; -`; - -export const AccountInformationForm = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - - width: 100%; -`; - -export const ButtonContainer = styled.div` - display: flex; - justify-content: center; - width: 100%; - padding: 24px 0; -`; - +/** + * TODO: Add onBlur callback to @anoma/components/Input to avoid + * defining it below: + */ export const Input = styled.input` width: 100%; box-sizing: border-box; @@ -61,12 +16,6 @@ export const Input = styled.input` border-radius: 12px; `; -export const InputContainer = styled.div` - width: 100%; - min-height: 92px; - color: ${(props) => props.theme.colors.utility2.main80}; -`; - export const InputFeedback = styled.div` font-size: 12px; margin-top: 4px; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/Password/Password.tsx b/apps/extension/src/Setup/AccountCreation/Steps/Password/Password.tsx index ca4ee24c6cf..1d6c5d2793b 100644 --- a/apps/extension/src/Setup/AccountCreation/Steps/Password/Password.tsx +++ b/apps/extension/src/Setup/AccountCreation/Steps/Password/Password.tsx @@ -1,31 +1,30 @@ -import zxcvbn from "zxcvbn"; import React, { useState } from "react"; +import zxcvbn from "zxcvbn"; + import { Button, ButtonVariant } from "@anoma/components"; + import { - AccountInformationViewContainer, - AccountInformationViewUpperPartContainer, - AccountInformationForm, + BodyText, + ButtonsContainer, + FormContainer, Header1, Header5, - BodyText, - Input, - InputFeedback, + UpperContentContainer, + SubViewContainer, InputContainer, - ButtonContainer, -} from "./Password.components"; -import { AccountCreationDetails } from "Setup/AccountCreation/types"; +} from "Setup/Setup.components"; +import { AccountDetails } from "Setup/types"; +import { Input, InputFeedback } from "./Password.components"; // the data of this form type Props = { // if the user navigates back and forth this might be there - accountCreationDetails?: AccountCreationDetails; + accountCreationDetails?: AccountDetails; onSubmitAccountCreationDetails: ( - accountCreationDetails: AccountCreationDetails + accountCreationDetails: AccountDetails ) => void; - onSetAccountCreationDetails: ( - accountCreationDetails: AccountCreationDetails - ) => void; + onSetAccountCreationDetails: (accountCreationDetails: AccountDetails) => void; }; const validatePassword = ( @@ -48,7 +47,7 @@ const Password: React.FC = (props) => { const [passwordMatchFeedback, setPasswordMatchFeedback] = useState(""); const [zxcvbnFeedback, setZxcvbnFeedback] = useState(zxcvbn("").feedback); const [isSubmitting, setIsSubmitting] = useState(false); - const [alias, setAlias] = useState(""); + const [alias, setAlias] = useState(); const isPasswordValid = validatePassword( zxcvbnFeedback, password, @@ -56,14 +55,14 @@ const Password: React.FC = (props) => { ); return ( - + {/* header */} - + Set a Passcode for your wallet - + {/* form */} - + {/* seed phrase */} {/* description */} @@ -72,7 +71,7 @@ const Password: React.FC = (props) => { - Alias (optional) + Alias setAlias(e.target.value)} /> @@ -127,13 +126,13 @@ const Password: React.FC = (props) => { {/* submit */} - + - - - + + + ); }; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/SeedPhrase/SeedPhrase.components.ts b/apps/extension/src/Setup/AccountCreation/Steps/SeedPhrase/SeedPhrase.components.ts index 974bf4ce0fb..ffa973c1917 100644 --- a/apps/extension/src/Setup/AccountCreation/Steps/SeedPhrase/SeedPhrase.components.ts +++ b/apps/extension/src/Setup/AccountCreation/Steps/SeedPhrase/SeedPhrase.components.ts @@ -1,46 +1,5 @@ import styled from "styled-components"; -export const AccountInformationViewContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - height: 100%; -`; - -export const AccountInformationViewUpperPartContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; -`; - -export const AccountInformationForm = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - - width: 100%; -`; - -export const Header1 = styled.h1` - margin: 8px 0; - color: ${(props) => props.theme.colors.utility2.main}; -`; - -export const BodyText = styled.p` - text-align: center; - width: 100%; - font-weight: 300; - color: ${(props) => props.theme.colors.utility2.main80}; -`; - -export const ButtonContainer = styled.div` - display: flex; - justify-content: center; - width: 100%; - padding: 48px 0; -`; - export const SeedPhraseCard = styled.div` display: flex; align-items: center; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/SeedPhrase/SeedPhrase.tsx b/apps/extension/src/Setup/AccountCreation/Steps/SeedPhrase/SeedPhrase.tsx index e3e64c9a025..4d39a0c8a7e 100644 --- a/apps/extension/src/Setup/AccountCreation/Steps/SeedPhrase/SeedPhrase.tsx +++ b/apps/extension/src/Setup/AccountCreation/Steps/SeedPhrase/SeedPhrase.tsx @@ -1,15 +1,19 @@ import React, { useEffect, useState } from "react"; + import { Button, ButtonVariant, Toggle } from "@anoma/components"; + import { GenerateMnemonicMsg } from "background/keyring"; import { ExtensionRequester } from "extension"; import { Ports } from "router"; import { - AccountInformationViewContainer, - AccountInformationViewUpperPartContainer, - AccountInformationForm, - Header1, BodyText, - ButtonContainer, + ButtonsContainer, + FormContainer, + Header1, + SubViewContainer, + UpperContentContainer, +} from "Setup/Setup.components"; +import { SeedPhraseCard, SeedPhraseContainer, SeedPhraseIndexLabel, @@ -18,15 +22,14 @@ import { ExportSeedPhraseButtonsContainer, CopyToClipboard, } from "./SeedPhrase.components"; - -import { AccountCreationDetails } from "Setup/AccountCreation/types"; +import { AccountDetails } from "Setup/types"; type Props = { requester: ExtensionRequester; // go to next screen onConfirm: (seedPhraseAsArray: string[]) => void; // depending if first load this might or might not be available - accountCreationDetails?: AccountCreationDetails; + accountCreationDetails?: AccountDetails; // depending if first load this might or might not be available defaultSeedPhrase?: string[]; }; @@ -61,14 +64,14 @@ const SeedPhrase: React.FC = (props) => { }, [mnemonicLength]); return ( - + {/* header */} - + Seed Phrase - + {/* form */} - + {/* description */} Write down your seed phrase. @@ -109,7 +112,7 @@ const SeedPhrase: React.FC = (props) => { )} {/* continue */} - + - - - + + + ); }; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.components.ts b/apps/extension/src/Setup/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.components.ts index 0abdf11ef4b..246d9f7bfa0 100644 --- a/apps/extension/src/Setup/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.components.ts +++ b/apps/extension/src/Setup/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.components.ts @@ -1,39 +1,5 @@ import styled from "styled-components"; -export const AccountInformationViewContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - height: 100%; -`; - -export const Header1 = styled.h1` - margin: 8px 0; - color: ${(props) => props.theme.colors.utility2.main}; -`; - -export const Header5 = styled.h5` - margin: 8px 0; - color: ${(props) => props.theme.colors.utility2.main}; -`; -export const BodyText = styled.p` - font-weight: 300; - color: ${(props) => props.theme.colors.utility2.main80}; -`; - -export const AccountInformationViewUpperPartContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; -`; -export const AccountInformationForm = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - width: 100%; -`; - export const DescriptionAndInputContainer = styled.div` display: flex; flex-direction: column; @@ -41,24 +7,3 @@ export const DescriptionAndInputContainer = styled.div` height: 100%; width: 100%; `; - -export const ButtonContainer = styled.div` - display: flex; - justify-content: center; - width: 100%; - padding: 24px 0; -`; - -export const Input = styled.input` - width: 100%; - box-sizing: border-box; - font-size: 16px; - height: 42px; - padding: 0 16px; - border-radius: 999px; -`; -export const InputContainer = styled.div` - width: 100%; - height: 92px; - margin: 0 0 24px; -`; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.tsx b/apps/extension/src/Setup/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.tsx index 06eedb3c209..9752a25c75c 100644 --- a/apps/extension/src/Setup/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.tsx +++ b/apps/extension/src/Setup/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.tsx @@ -1,17 +1,16 @@ import React, { useEffect, useState } from "react"; -import { Button, ButtonVariant } from "@anoma/components"; +import { Button, ButtonVariant, Input, InputVariants } from "@anoma/components"; + import { - AccountInformationViewContainer, - AccountInformationViewUpperPartContainer, - AccountInformationForm, - DescriptionAndInputContainer, - Header1, - Header5, BodyText, + ButtonsContainer, InputContainer, - ButtonContainer, -} from "./SeedPhraseConfirmation.components"; -import { Input } from "../Password/Password.components"; + Header1, + Header5, + SubViewContainer, + UpperContentContainer, + FormContainer, +} from "Setup/Setup.components"; type Props = { seedPhrase: string[]; @@ -29,42 +28,33 @@ const SeedPhraseConfirmation: React.FC = (props) => { }, []); return ( - - {/* header */} - + + Verify Phrase - - - {/* form */} - - - {/* description */} - - - {/* seed verification */} - - Word #{indexToConfirm + 1} - { - setVerificationInput(event.target.value); - }} - /> - - - - - - - - + + + + { + setVerificationInput(event.target.value); + }} + /> + + + + + ); }; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/Start/Start.tsx b/apps/extension/src/Setup/AccountCreation/Steps/Start/Start.tsx deleted file mode 100644 index 2e1c6c4b6de..00000000000 --- a/apps/extension/src/Setup/AccountCreation/Steps/Start/Start.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from "react"; -import { Button, ButtonVariant } from "@anoma/components"; -import { - StartViewContainer, - StartViewUpperPartContainer, - Header1, - BodyText, -} from "./Start.components"; - -type Props = { - onClick: () => void; -}; - -const Start: React.FC = (props) => { - const { onClick } = props; - return ( - - - Create Your Account - Create an initial account for your wallet. - - - - - - ); -}; - -export default Start; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/index.ts b/apps/extension/src/Setup/AccountCreation/Steps/index.ts index f7319290769..bb869054446 100644 --- a/apps/extension/src/Setup/AccountCreation/Steps/index.ts +++ b/apps/extension/src/Setup/AccountCreation/Steps/index.ts @@ -2,4 +2,3 @@ export { Password } from "./Password"; export { Completion } from "./Completion"; export { SeedPhrase } from "./SeedPhrase"; export { SeedPhraseConfirmation } from "./SeedPhraseConfirmation"; -export { Start } from "./Start"; diff --git a/apps/extension/src/Setup/AccountCreation/types.ts b/apps/extension/src/Setup/AccountCreation/types.ts deleted file mode 100644 index a97e41c2fb9..00000000000 --- a/apps/extension/src/Setup/AccountCreation/types.ts +++ /dev/null @@ -1,34 +0,0 @@ -export enum AccountCreationRoute { - // initial screen to explain the flow - Start = "start", - // user is requested to enter account alias and password - //AccountDetails = "account-details", - // phrase is provided to the user - SeedPhrase = "seed-phrase", - // user confirms the saving of the phrase - SeedPhraseConfirmation = "seed-phrase-confirmation", - // user sets a password for a wallet - Password = "password", - // the last screen to confirm the completion of the flow - Completion = "completion", -} - -export const accountCreationSteps = [ - AccountCreationRoute.Start, - AccountCreationRoute.SeedPhrase, - AccountCreationRoute.SeedPhraseConfirmation, - AccountCreationRoute.Password, - AccountCreationRoute.Completion, -]; - -export type Step = { - title: string; - url: string; - next?: Step; - previous?: Step; -}; - -export type AccountCreationDetails = { - alias: string; - password?: string; -}; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/Start/Start.components.ts b/apps/extension/src/Setup/ImportAccount/ImportAccount.components.ts similarity index 79% rename from apps/extension/src/Setup/AccountCreation/Steps/Start/Start.components.ts rename to apps/extension/src/Setup/ImportAccount/ImportAccount.components.ts index cb66db23603..b318833cfc4 100644 --- a/apps/extension/src/Setup/AccountCreation/Steps/Start/Start.components.ts +++ b/apps/extension/src/Setup/ImportAccount/ImportAccount.components.ts @@ -1,13 +1,13 @@ import styled from "styled-components"; -export const StartViewContainer = styled.div` +export const ImportAccountViewContainer = styled.div` display: flex; flex-direction: column; align-items: center; height: 100%; `; -export const StartViewUpperPartContainer = styled.div` +export const ImportAccountViewUpperPartContainer = styled.div` display: flex; flex-direction: column; align-items: center; diff --git a/apps/extension/src/Setup/ImportAccount/ImportAccount.tsx b/apps/extension/src/Setup/ImportAccount/ImportAccount.tsx new file mode 100644 index 00000000000..28a1cc8859e --- /dev/null +++ b/apps/extension/src/Setup/ImportAccount/ImportAccount.tsx @@ -0,0 +1,47 @@ +import React, { useContext } from "react"; +import { useNavigate } from "react-router-dom"; +import { ThemeContext } from "styled-components"; + +import { Icon, IconName, IconSize } from "@anoma/components"; + +import { + SubViewContainer, + TopSection, + TopSectionHeaderContainer, + TopSectionButtonContainer, + UpperContentContainer, + Header1, + BodyText, +} from "Setup/Setup.components"; + +const ImportAccount: React.FC = () => { + const navigate = useNavigate(); + const themeContext = useContext(ThemeContext); + + return ( + + + + navigate(-1)} style={{ cursor: "pointer" }}> + + + + + + + + Import Account + + + + TBD + + + ); +}; + +export default ImportAccount; diff --git a/apps/extension/src/Setup/ImportAccount/index.ts b/apps/extension/src/Setup/ImportAccount/index.ts new file mode 100644 index 00000000000..765b9b024b5 --- /dev/null +++ b/apps/extension/src/Setup/ImportAccount/index.ts @@ -0,0 +1 @@ +export { default as ImportAccount } from "./ImportAccount"; diff --git a/apps/extension/src/Setup/Ledger/Ledger.components.ts b/apps/extension/src/Setup/Ledger/Ledger.components.ts new file mode 100644 index 00000000000..5682f5551d4 --- /dev/null +++ b/apps/extension/src/Setup/Ledger/Ledger.components.ts @@ -0,0 +1,6 @@ +import styled from "styled-components"; + +export const LedgerError = styled.div` + color: ${(props) => props.theme.colors.utility3.highAttention}; + padding: 8px 0; +`; diff --git a/apps/extension/src/Setup/Ledger/Ledger.tsx b/apps/extension/src/Setup/Ledger/Ledger.tsx new file mode 100644 index 00000000000..df313452899 --- /dev/null +++ b/apps/extension/src/Setup/Ledger/Ledger.tsx @@ -0,0 +1,146 @@ +import React, { useContext, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { ThemeContext } from "styled-components"; + +import { + Button, + ButtonVariant, + Icon, + IconName, + IconSize, + Input, + InputVariants, +} from "@anoma/components"; +import { shortenAddress } from "@anoma/utils"; + +import { initLedgerHIDTransport, Ledger as LedgerApp } from "background/ledger"; +import { ExtensionRequester } from "extension"; +import { LedgerError } from "./Ledger.components"; +import { + TopSection, + TopSectionHeaderContainer, + TopSectionButtonContainer, + ButtonsContainer, + SubViewContainer, + UpperContentContainer, + Header1, + BodyText, + FormContainer, +} from "Setup/Setup.components"; + +type Props = { + requester: ExtensionRequester; +}; + +const Ledger: React.FC = ({ requester: _ }) => { + const navigate = useNavigate(); + const themeContext = useContext(ThemeContext); + + const [alias, setAlias] = useState(""); + const [error, setError] = useState(); + const [isConnected, setIsConnected] = useState(false); + const [publicKey, setPublicKey] = useState(); + const [appInfo, setAppInfo] = useState<{ name: string; version: string }>(); + + const queryLedger = async (ledger: LedgerApp): Promise => { + const pk = await ledger.getPublicKey(); + const { appName, appVersion } = ledger.status(); + + setAppInfo({ name: appName, version: appVersion }); + setPublicKey(pk); + setIsConnected(true); + }; + + /** + * Connect using default USB transport + */ + const handleConnectUSB = async (): Promise => { + try { + queryLedger(await LedgerApp.init()); + } catch (e) { + setError(`Failed to connect to Ledger: ${e}`); + } + }; + + /** + * Connect using HID transport + */ + const handleConnectHID = async (): Promise => { + try { + const transport = await initLedgerHIDTransport(); + queryLedger(await LedgerApp.init(transport)); + } catch (e) { + setError(`Failed to connect to Ledger: ${e}`); + } + }; + + return ( + + + + navigate(-1)} style={{ cursor: "pointer" }}> + + + + + + + + Connect Ledger + + + {error && {error}} + {/* TODO: Navigate to next step for adding this account to background service. The following is temporary: */} + {isConnected && ( + <> + + Connection successful for "{alias}"! + + + Public key: {publicKey && shortenAddress(publicKey)} + + {appInfo && ( + <> + Name: {appInfo.name} + Version: {appInfo.version} + + )} + + )} + {!isConnected && ( + <> + + setAlias(e.target.value)} + variant={InputVariants.Text} + /> + + + + + + + )} + + ); +}; + +export default Ledger; diff --git a/apps/extension/src/Setup/Ledger/index.ts b/apps/extension/src/Setup/Ledger/index.ts new file mode 100644 index 00000000000..eba5466af85 --- /dev/null +++ b/apps/extension/src/Setup/Ledger/index.ts @@ -0,0 +1 @@ +export { default as Ledger } from "./Ledger"; diff --git a/apps/extension/src/Setup/Setup.components.ts b/apps/extension/src/Setup/Setup.components.ts index 2fc1f76cefe..cf697fbc829 100644 --- a/apps/extension/src/Setup/Setup.components.ts +++ b/apps/extension/src/Setup/Setup.components.ts @@ -1,4 +1,5 @@ import styled, { createGlobalStyle } from "styled-components"; +import { motion } from "framer-motion"; export const GlobalStyles = createGlobalStyle` html, body { @@ -8,37 +9,108 @@ export const GlobalStyles = createGlobalStyle` export const AppContainer = styled.div` display: flex; - justify-content: space-between; - align-items: top; flex-direction: column; + justify-content: start; + align-items: center; + box-sizing: border-box; + background-color: ${(props) => props.theme.colors.utility1.main80}; + border: 1px solid ${(props) => props.theme.colors.utility1.main80}; + border-radius: 8px; + color: ${(props) => props.theme.colors.utility2.main80}; + padding: 0 36px; height: 100%; + width: 480px; + transition: background-color 0.3s linear; + margin: 36px auto; +`; + +export const SubViewContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + width: 440px; min-height: 400px; - width: 100%; - box-sizing: border-box; +`; - @media screen and (max-width: 860px) { - padding: 0 36px; - min-width: 360px; - } +export const MotionContainer = styled(motion.div)` + height: 100%; + box-sizing: border-box; `; export const TopSection = styled.section` display: flex; - justify-content: center; - align-items: flex-start; + justify-content: start; + align-items: center; width: 100%; + margin: 24px 0; `; -export const BottomSection = styled.section` +export const TopSectionHeaderContainer = styled.section` display: flex; justify-content: center; - align-items: flex-start; width: 100%; + align-items: center; +`; + +export const TopSectionButtonContainer = styled.section` + display: flex; + justify-content: center; + align-items: center; + width: 48px; + min-height: 50px; `; export const ContentContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +export const UpperContentContainer = styled.div` display: flex; flex-direction: column; - justify-content: start; align-items: center; `; + +export const FormContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + margin: 20px 0; + width: 100%; + min-height: 170px; +`; + +export const Header1 = styled.h1` + margin: 8px 0; + color: ${(props) => props.theme.colors.utility2.main}; +`; + +export const Header3 = styled.h3` + margin: 8px 0; + color: ${(props) => props.theme.colors.utility2.main}; +`; + +export const Header5 = styled.h5` + margin: 8px 0; + color: ${(props) => props.theme.colors.utility2.main}; +`; + +export const BodyText = styled.p` + font-weight: 300; + color: ${(props) => props.theme.colors.utility2.main80}; +`; + +export const ButtonsContainer = styled.div` + margin: 20px 0; + display: flex; + justify-content: center; + width: 100%; +`; + +export const InputContainer = styled.div` + width: 100%; + height: 92px; + margin: 0 0 24px; +`; diff --git a/apps/extension/src/Setup/Setup.tsx b/apps/extension/src/Setup/Setup.tsx index 4e19671bce1..7a6cbbc7502 100644 --- a/apps/extension/src/Setup/Setup.tsx +++ b/apps/extension/src/Setup/Setup.tsx @@ -1,21 +1,31 @@ import browser from "webextension-polyfill"; -import React from "react"; -import { HashRouter, Routes, Route } from "react-router-dom"; +import React, { useState } from "react"; +import { Routes, Route, useNavigate } from "react-router-dom"; import { ThemeProvider } from "styled-components"; -import { getTheme } from "@anoma/utils"; -import { ExtensionMessenger, ExtensionRequester } from "extension"; +import { formatRouterPath, getTheme } from "@anoma/utils"; +import { ExtensionKVStore } from "@anoma/storage"; +import { ExtensionMessenger, ExtensionRequester } from "extension"; import { AccountCreation } from "./AccountCreation"; import { AppContainer, - BottomSection, ContentContainer, GlobalStyles, - TopSection, + MotionContainer, } from "./Setup.components"; -import { ExtensionKVStore } from "@anoma/storage"; +import { + Completion, + SeedPhrase, + SeedPhraseConfirmation, + Password, +} from "Setup/AccountCreation/Steps"; import { KVPrefix } from "router"; +import { TopLevelRoute, AccountCreationRoute, AccountDetails } from "./types"; +import { Ledger } from "./Ledger"; +import { Start } from "./Start"; +import { AnimatePresence } from "framer-motion"; +import { ImportAccount } from "./ImportAccount"; const store = new ExtensionKVStore(KVPrefix.LocalStorage, { get: browser.storage.local.get, @@ -24,25 +34,167 @@ const store = new ExtensionKVStore(KVPrefix.LocalStorage, { const messenger = new ExtensionMessenger(); const requester = new ExtensionRequester(messenger, store); +type AnimatedTransitionProps = { + elementKey: string; + children: JSX.Element; +}; + +/** + * This is a utility to animate transitions + */ +const AnimatedTransition: React.FC = (props) => { + const { children, elementKey } = props; + return ( + + {children} + + ); +}; + export const Setup: React.FC = () => { const theme = getTheme("dark"); + const navigate = useNavigate(); + const [accountCreationDetails, setAccountCreationDetails] = + useState({ + alias: "", + }); + const [seedPhrase, setSeedPhrase] = useState(); return ( - Anoma Browser Extension - + } + path={formatRouterPath([TopLevelRoute.Start])} + element={} + /> + + + + } + > + + { + setSeedPhrase(seedPhrase); + navigate( + formatRouterPath([ + TopLevelRoute.AccountCreation, + AccountCreationRoute.SeedPhraseConfirmation, + ]) + ); + }} + /> + + } + /> + + + navigate( + formatRouterPath([ + TopLevelRoute.AccountCreation, + AccountCreationRoute.Password, + ]) + ) + } + /> + + } + /> + + { + setAccountCreationDetails( + (accountCreationDetails) => { + return { + ...accountCreationDetails, + ...accountCreationDetailsDelta, + }; + } + ); + }} + onSubmitAccountCreationDetails={( + accountCreationDetails + ) => { + setAccountCreationDetails(accountCreationDetails); + navigate( + formatRouterPath([ + TopLevelRoute.AccountCreation, + AccountCreationRoute.Completion, + ]) + ); + }} + /> + + } + /> + + + + } + /> + + } + /> + + + + } /> - + - ); diff --git a/apps/extension/src/Setup/Start/Start.components.ts b/apps/extension/src/Setup/Start/Start.components.ts new file mode 100644 index 00000000000..ddd2822a1f5 --- /dev/null +++ b/apps/extension/src/Setup/Start/Start.components.ts @@ -0,0 +1,10 @@ +import styled from "styled-components"; + +export const StartViewContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 440px; + width: 100%; +`; diff --git a/apps/extension/src/Setup/Start/Start.tsx b/apps/extension/src/Setup/Start/Start.tsx new file mode 100644 index 00000000000..09198011a91 --- /dev/null +++ b/apps/extension/src/Setup/Start/Start.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; + +import { Button, ButtonVariant } from "@anoma/components"; +import { formatRouterPath } from "@anoma/utils"; + +import { + BodyText, + Header1, + SubViewContainer, + UpperContentContainer, +} from "Setup/Setup.components"; +import { AccountCreationRoute, TopLevelRoute } from "../types"; +import { StartViewContainer } from "./Start.components"; + +const Start: React.FC = () => { + const navigate = useNavigate(); + + return ( + + + + Create Your Account + + Create an account for your wallet, or connect to Ledger. + + + + + + + + ); +}; + +export default Start; diff --git a/apps/extension/src/Setup/AccountCreation/Steps/Start/index.ts b/apps/extension/src/Setup/Start/index.ts similarity index 100% rename from apps/extension/src/Setup/AccountCreation/Steps/Start/index.ts rename to apps/extension/src/Setup/Start/index.ts diff --git a/apps/extension/src/Setup/index.tsx b/apps/extension/src/Setup/index.tsx index 1c17a2743cd..96f2f763ef6 100644 --- a/apps/extension/src/Setup/index.tsx +++ b/apps/extension/src/Setup/index.tsx @@ -1,12 +1,15 @@ import React from "react"; import ReactDOM from "react-dom"; +import { HashRouter } from "react-router-dom"; import { Setup } from "./Setup"; export default ((): void => { ReactDOM.render( - + + + , document.getElementById("root") ); diff --git a/apps/extension/src/Setup/types.ts b/apps/extension/src/Setup/types.ts new file mode 100644 index 00000000000..a0c6232a5fb --- /dev/null +++ b/apps/extension/src/Setup/types.ts @@ -0,0 +1,23 @@ +export enum TopLevelRoute { + Start = "*", + AccountCreation = "account-creation", + ImportAccount = "restore-account", + Ledger = "ledger", +} + +export enum AccountCreationRoute { + // Generate new seed phrase + SeedPhrase = "seed-phrase", + // Validate seed phrase + SeedPhraseConfirmation = "seed-phrase-confirmation", + // Secure wallet with a password + Password = "password", + // Final screen with confirmation + Completion = "completion", +} + +// Alias and optional password (in the case of Ledger accounts) +export type AccountDetails = { + alias: string; + password?: string; +}; diff --git a/apps/extension/src/background/ledger/index.ts b/apps/extension/src/background/ledger/index.ts new file mode 100644 index 00000000000..d1cd2cc47a3 --- /dev/null +++ b/apps/extension/src/background/ledger/index.ts @@ -0,0 +1 @@ +export * from "./ledger"; diff --git a/apps/extension/src/background/ledger/ledger.ts b/apps/extension/src/background/ledger/ledger.ts new file mode 100644 index 00000000000..13fd0b08e9f --- /dev/null +++ b/apps/extension/src/background/ledger/ledger.ts @@ -0,0 +1,116 @@ +import { + NamadaApp, + ResponseAppInfo, + ResponseSign, + ResponseVersion, +} from "@zondax/ledger-namada"; +import TransportUSB from "@ledgerhq/hw-transport-webusb"; +import TransportHID from "@ledgerhq/hw-transport-webhid"; +import Transport from "@ledgerhq/hw-transport"; + +import { defaultChainId, chains } from "@anoma/chains"; + +const namadaChain = chains[defaultChainId]; +const bip44CoinType = namadaChain.bip44.coinType; + +export const initLedgerUSBTransport = async (): Promise => { + return await TransportHID.create(); +}; + +export const initLedgerHIDTransport = async (): Promise => { + return await TransportUSB.create(); +}; + +export const DEFAULT_LEDGER_BIP44_PATH = `m/44'/${bip44CoinType}'/0'/0/0`; + +export class Ledger { + public version: ResponseVersion | undefined; + public info: ResponseAppInfo | undefined; + + constructor(public readonly namadaApp: NamadaApp | undefined = undefined) {} + + /** + * Returns an initialized Ledger class instance with initialized Transport + */ + static async init(transport?: Transport): Promise { + const initializedTransport = transport ?? (await initLedgerUSBTransport()); + + const namadaApp = new NamadaApp(initializedTransport); + const ledger = new Ledger(namadaApp); + + ledger.version = await namadaApp.getVersion(); + ledger.info = await namadaApp.getAppInfo(); + + return ledger; + } + + /** + * Return status and version info of initialized NamadaApp + */ + public status(): { + appName: string; + appVersion: string; + errorMessage: string; + returnCode: number; + deviceLocked: boolean; + major: number; + minor: number; + patch: number; + targetId: string; + testMode: boolean; + } { + if (!this.version || !this.info) { + throw new Error("NamadaApp is not initialized!"); + } + + const { appName, appVersion, errorMessage, returnCode } = this.info; + const { + major, + minor, + patch, + targetId, + deviceLocked = false, + testMode, + } = this.version; + + return { + appName, + appVersion, + errorMessage, + returnCode, + deviceLocked, + major, + minor, + patch, + targetId, + testMode, + }; + } + + /** + * Get public key associated with optional path, otherwise, use default path + */ + public async getPublicKey(bip44Path?: string): Promise { + if (!this.namadaApp) { + throw new Error("NamadaApp is not initialized!"); + } + + const path = bip44Path || DEFAULT_LEDGER_BIP44_PATH; + const { publicKey } = await this.namadaApp.getAddressAndPubKey(path); + + return publicKey.toString("hex"); + } + + /** + * Sign tx bytes with the key associated with provided (or default) path + */ + public async sign(tx: ArrayBuffer, bip44Path: string): Promise { + if (!this.namadaApp) { + throw new Error("NamadaApp is not initialized!"); + } + + const path = bip44Path || DEFAULT_LEDGER_BIP44_PATH; + + return await this.namadaApp.sign(path, Buffer.from(tx)); + } +} diff --git a/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx b/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx index d3d3c574fd7..3e5cb286578 100644 --- a/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx +++ b/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx @@ -289,7 +289,6 @@ function TopNavigation(props: TopNavigationProps): JSX.Element { @@ -328,7 +327,6 @@ function TopNavigation(props: TopNavigationProps): JSX.Element { @@ -368,7 +366,6 @@ function TopNavigation(props: TopNavigationProps): JSX.Element { { setShowMenu(false); - setColorMode("light"); navigate( `${TopLevelRoute.StakingAndGovernance}${StakingAndGovernanceSubRoute.Staking}` ); @@ -383,7 +380,6 @@ function TopNavigation(props: TopNavigationProps): JSX.Element { { setShowMenu(false); - setColorMode("light"); navigate( `${TopLevelRoute.StakingAndGovernance}${StakingAndGovernanceSubRoute.Governance}` ); @@ -398,7 +394,6 @@ function TopNavigation(props: TopNavigationProps): JSX.Element { { setShowMenu(false); - setColorMode("light"); navigate( `${TopLevelRoute.StakingAndGovernance}${StakingAndGovernanceSubRoute.PublicGoodsFunding}` ); diff --git a/packages/chains/src/chains/namada.ts b/packages/chains/src/chains/namada.ts index e58bca6646a..b0a76003a19 100644 --- a/packages/chains/src/chains/namada.ts +++ b/packages/chains/src/chains/namada.ts @@ -17,10 +17,8 @@ const namada: Chain = { alias, bech32Prefix, bip44: { - // TODO: Update this when coinType for NAM token is registered with a SLIP-0044 coin-type. - // For now, use coin-type for testnet (all coins). - // See: https://github.com/satoshilabs/slips/blob/master/slip-0044.md - coinType: 1, + // See Namada coin type at https://github.com/satoshilabs/slips/blob/master/slip-0044.md + coinType: 877, }, rpc, chainId, diff --git a/packages/utils/src/helpers/index.ts b/packages/utils/src/helpers/index.ts index e62692816a9..9f9be60ff38 100644 --- a/packages/utils/src/helpers/index.ts +++ b/packages/utils/src/helpers/index.ts @@ -49,6 +49,14 @@ export const formatRoute = ( return formatted; }; +/** + * Format absolute React Router paths + * @param {string[]} routes + * @returns {string} + */ +export const formatRouterPath = (routes: string[]): string => + `/${routes.join("/")}`; + /** * Format a date-time string from a timestamp */ diff --git a/yarn.lock b/yarn.lock index d7a0f1ec1cd..b831ffe3802 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4108,6 +4108,55 @@ long "^4.0.0" secretjs "^0.17.0" +"@ledgerhq/devices@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.0.2.tgz#8086e9be0185f4925fa611d4d71dc9c141b9b089" + integrity sha512-Qnc9hgGae4YNr/4NUU/5l3xGc5fx6t2k1su6ASu4wsV/p49xmaU/iBO6PtFHqb3QCwsrkieXASU932Ac2WTw0g== + dependencies: + "@ledgerhq/errors" "^6.12.5" + "@ledgerhq/logs" "^6.10.1" + rxjs "6" + semver "^7.3.5" + +"@ledgerhq/errors@^6.12.5": + version "6.12.5" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.12.5.tgz#f112b548ab3c4f21bb7fbe77e6cb62998a050992" + integrity sha512-wQlDyKD2lG4hiFmSPvWfuzhbH8wxWG4ugesM17HdZgxUt8g0SluwaBfFJ7Nx0Ym44VIhbsGMUzyBp0hHyCkVqA== + +"@ledgerhq/hw-transport-webhid@^6.27.14": + version "6.27.14" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webhid/-/hw-transport-webhid-6.27.14.tgz#ce68a591296f77f20b371b99cfe8cd50387a350b" + integrity sha512-oSfGLJE9ztBd5pNoywi6fcSa/LGRlCe06Zre6de/S8hzZ6DIlJG99EBKt3+JYhbYiFjYK79NFJW5TGUKbbP0sQ== + dependencies: + "@ledgerhq/devices" "^8.0.2" + "@ledgerhq/errors" "^6.12.5" + "@ledgerhq/hw-transport" "^6.28.3" + "@ledgerhq/logs" "^6.10.1" + +"@ledgerhq/hw-transport-webusb@^6.27.14": + version "6.27.14" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.27.14.tgz#b32387bfd7039afccfa1ae5fffc6afd6b68a19fb" + integrity sha512-uSpjyiR0FhNXNtXxWqbmatyfCPcjeyADm8E+czuCCM7Wwf0S05AeD+2qLiEa0U1DspBZvz65WgkhPfeSmUsbZA== + dependencies: + "@ledgerhq/devices" "^8.0.2" + "@ledgerhq/errors" "^6.12.5" + "@ledgerhq/hw-transport" "^6.28.3" + "@ledgerhq/logs" "^6.10.1" + +"@ledgerhq/hw-transport@^6.28.2", "@ledgerhq/hw-transport@^6.28.3": + version "6.28.3" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.28.3.tgz#1108ceb8bfb9dc949f6178b17dd1bcb76dba11f0" + integrity sha512-YFPh9n51V4TfPZov7iAUbtez0cyNEVR1+49RG8tYvmsmk8ihvya2rR90U8KO2MnrT2jR4k2rlgQ3IcZJO9dBcw== + dependencies: + "@ledgerhq/devices" "^8.0.2" + "@ledgerhq/errors" "^6.12.5" + events "^3.3.0" + +"@ledgerhq/logs@^6.10.1": + version "6.10.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.10.1.tgz#5bd16082261d7364eabb511c788f00937dac588d" + integrity sha512-z+ILK8Q3y+nfUl43ctCPuR4Y2bIxk/ooCQFwZxhtci1EhAtMDzMAx2W25qx8G1PPL9UUOdnUax19+F0OjXoj4w== + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -5743,6 +5792,13 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@zondax/ledger-namada@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@zondax/ledger-namada/-/ledger-namada-0.0.2.tgz#17d9fc66c8eb5d1436a8fa6b030732219af3d4c0" + integrity sha512-F/rsDAsOclrIPFbGMDFIxD+1gDbGyIKZXtZ9yJD9DMYA8d7rkWzmGdHo/uxXCnzJDHzsM04BI1tvH0qDhmKuaw== + dependencies: + "@ledgerhq/hw-transport" "^6.28.2" + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -6759,6 +6815,13 @@ buffer-indexof@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== +buffer-pipe@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/buffer-pipe/-/buffer-pipe-0.0.3.tgz#242197681d4591e7feda213336af6c07a5ce2409" + integrity sha512-GlxfuD/NrKvCNs0Ut+7b1IHjylfdegMBxQIlZHj7bObKVQBxB5S84gtm2yu1mQ8/sSggceWBDPY0cPXgvX2MuA== + dependencies: + safe-buffer "^5.1.2" + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -8878,7 +8941,7 @@ eventemitter3@^4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.2.0: +events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -12853,6 +12916,14 @@ language-tags@^1.0.5: dependencies: language-subtag-registry "~0.3.2" +leb128@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/leb128/-/leb128-0.0.5.tgz#84524a86ef7799fb3933ce41345f6490e27ac948" + integrity sha512-elbNtfmu3GndZbesVF6+iQAfVjOXW9bM/aax9WwMlABZW+oK9sbAZEXoewaPHmL34sxa8kVwWsru8cNE/yn2gg== + dependencies: + bn.js "^5.0.0" + buffer-pipe "0.0.3" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -15288,6 +15359,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rxjs@6: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -16467,7 +16545,7 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1: +tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==