Skip to content

Commit

Permalink
Merge pull request #2338 from getAlby/feat/mnemonic
Browse files Browse the repository at this point in the history
feat: mnemonic and webbtc.getAddress
  • Loading branch information
rolznz authored Jun 30, 2023
2 parents f0d2fa9 + 2ae38b0 commit aa79930
Show file tree
Hide file tree
Showing 69 changed files with 2,205 additions and 487 deletions.
2 changes: 1 addition & 1 deletion doc/CONTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ We welcome and appreciate new contributions.

### Testnet/testing-accounts for development use Alby testnet

We set up our own internal testnet, which can be used for your development.
We have setup some testnet nodes, which can be used for your development.
If this is not reachable please let us know.

- [Test-setup](https://github.com/getAlby/lightning-browser-extension/wiki/Test-setup) for different connectors (i.e. LND)
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
"@headlessui/react": "^1.7.14",
"@lightninglabs/lnc-web": "^0.2.4-alpha",
"@noble/secp256k1": "^1.7.1",
"@scure/bip32": "^1.3.0",
"@scure/bip39": "^1.2.0",
"@scure/btc-signer": "^0.5.1",
"@tailwindcss/forms": "^0.5.3",
"@vespaiach/axios-fetch-adapter": "^0.3.0",
"axios": "^0.27.2",
Expand Down
22 changes: 22 additions & 0 deletions src/app/components/Alert/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { classNames } from "~/app/utils";

type Props = {
type: "warn" | "info";
children: React.ReactNode;
};

export default function Alert({ type, children }: Props) {
return (
<div
className={classNames(
"rounded-md font-medium p-4",
type == "warn" &&
"text-orange-700 bg-orange-50 dark:text-orange-200 dark:bg-orange-900",
type == "info" &&
"text-blue-700 bg-blue-50 dark:text-blue-200 dark:bg-blue-900"
)}
>
<p>{children}</p>
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/components/Badge/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";

type Props = {
label: "active" | "auth";
label: "active" | "auth" | "imported";
color: string;
textColor: string;
small?: boolean;
Expand Down
6 changes: 6 additions & 0 deletions src/app/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type Props = React.ButtonHTMLAttributes<HTMLButtonElement> & {
label: string;
icon?: React.ReactNode;
primary?: boolean;
error?: boolean;
outline?: boolean;
loading?: boolean;
disabled?: boolean;
Expand All @@ -30,13 +31,16 @@ const Button = forwardRef(
primary = false,
outline = false,
loading = false,
error = false,
flex = false,
className,
...otherProps
}: Props,
ref: Ref<HTMLButtonElement>
) => {
return (
<button
{...otherProps}
ref={ref}
type={type}
className={classNames(
Expand All @@ -48,6 +52,8 @@ const Button = forwardRef(
? "bg-primary-gradient border-2 border-transparent text-black"
: outline
? "bg-white text-gray-700 border-2 border-primary dark:text-primary dark:bg-surface-02dp"
: error
? "bg-white text-gray-700 border-2 border-red-500 dark:text-red-500 dark:bg-surface-02dp"
: `bg-white text-gray-700 dark:bg-surface-02dp dark:text-neutral-200 dark:border-neutral-800`,
primary && !disabled && "hover:bg-primary-gradient-hover",
!primary &&
Expand Down
9 changes: 9 additions & 0 deletions src/app/components/ContentBox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";

export function ContentBox({ children }: React.PropsWithChildren<object>) {
return (
<div className="mt-12 shadow bg-white rounded-md p-10 divide-black/10 dark:divide-white/10 dark:bg-surface-02dp flex flex-col gap-4">
{children}
</div>
);
}
41 changes: 41 additions & 0 deletions src/app/components/InputCopyButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CopyIcon as CopyFilledIcon } from "@bitcoin-design/bitcoin-icons-react/filled";
import { CopyIcon } from "@bitcoin-design/bitcoin-icons-react/outline";
import { useState } from "react";
import { toast } from "react-toastify";
import { classNames } from "~/app/utils";

type Props = {
value: string;
className?: string;
};

function InputCopyButton({ value, className }: Props) {
const [copied, setCopied] = useState(false);
const CurrentIcon = copied ? CopyFilledIcon : CopyIcon;
return (
<button
type="button"
tabIndex={-1}
className={classNames(
"flex justify-center items-center h-8 w-10",
!!className && className
)}
onClick={async () => {
try {
navigator.clipboard.writeText(value);
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1000);
} catch (e) {
if (e instanceof Error) {
toast.error(e.message);
}
}
}}
>
<CurrentIcon className="w-6 h-6" />
</button>
);
}
export default InputCopyButton;
2 changes: 0 additions & 2 deletions src/app/components/PasswordForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export default function PasswordForm<
type={passwordView ? "text" : "password"}
required
onChange={handleChange}
tabIndex={0}
minLength={minLength}
pattern={minLength ? `.{${minLength},}` : undefined}
title={
Expand Down Expand Up @@ -140,7 +139,6 @@ export default function PasswordForm<
required
onChange={handleChange}
onBlur={validate}
tabIndex={1}
endAdornment={
<button
type="button"
Expand Down
14 changes: 14 additions & 0 deletions src/app/components/ScrollToTop/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useEffect } from "react";
import { useLocation } from "react-router-dom";

function ScrollToTop() {
const location = useLocation();
// Scroll to top if path changes
useEffect(() => {
window.scrollTo(0, 0);
}, [location.pathname]);

return null;
}

export default ScrollToTop;
2 changes: 1 addition & 1 deletion src/app/components/Tips/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function Tips() {
border: "border-purple-500",
arrow: "text-purple-500",
backgroundIcon: <MnemonicTipCardIcon />,
link: `/accounts/${accountId}`,
link: `/accounts/${accountId}/secret-key/generate`,
},
} as const),
[accountId]
Expand Down
23 changes: 18 additions & 5 deletions src/app/components/form/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { classNames } from "../../../utils";
type Props = {
suffix?: string;
endAdornment?: React.ReactNode;
block?: boolean;
};

export default function Input({
Expand All @@ -26,25 +27,31 @@ export default function Input({
max,
suffix,
endAdornment,
block = true,
className,
...otherProps
}: React.InputHTMLAttributes<HTMLInputElement> & Props) {
const inputEl = useRef<HTMLInputElement>(null);
const outerStyles =
"rounded-md border border-gray-300 dark:border-neutral-800 transition duration-300";
"rounded-md border border-gray-300 dark:border-neutral-800 transition duration-300 flex-1";

const inputNode = (
<input
{...otherProps}
ref={inputEl}
type={type}
name={name}
id={id}
className={classNames(
"block w-full placeholder-gray-500 dark:placeholder-neutral-600",
"placeholder-gray-500 dark:placeholder-neutral-600",
block && "block w-full",
!suffix && !endAdornment
? `${outerStyles} focus:ring-primary focus:border-primary focus:ring-1`
: "pr-0 border-0 focus:ring-0",
disabled
? "bg-gray-50 dark:bg-surface-01dp text-gray-500 dark:text-neutral-500"
: "bg-white dark:bg-black dark:text-white"
: "bg-white dark:bg-black dark:text-white",
!!className && className
)}
placeholder={placeholder}
required={required}
Expand All @@ -68,7 +75,8 @@ export default function Input({
<div
className={classNames(
"flex items-stretch overflow-hidden",
"focus-within:ring-primary focus-within:border-primary focus-within:dark:border-primary focus-within:ring-1",
!disabled &&
"focus-within:ring-primary focus-within:border-primary focus-within:dark:border-primary focus-within:ring-1",
outerStyles
)}
>
Expand All @@ -84,7 +92,12 @@ export default function Input({
</span>
)}
{endAdornment && (
<span className="flex items-center bg-white dark:bg-black dark:text-neutral-400">
<span
className={classNames(
"flex items-center bg-white dark:bg-black dark:text-neutral-400",
!!disabled && "bg-gray-50 dark:bg-surface-01dp"
)}
>
{endAdornment}
</span>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/form/TextField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const TextField = ({
{label}
</label>

<div className="mt-1">
<div className="mt-1 flex flex-col flex-1">
<Input
autoComplete={autoComplete}
autoFocus={autoFocus}
Expand Down
99 changes: 99 additions & 0 deletions src/app/components/mnemonic/MnemonicInputs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
HiddenIcon,
VisibleIcon,
} from "@bitcoin-design/bitcoin-icons-react/filled";
import { wordlist } from "@scure/bip39/wordlists/english";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import Input from "~/app/components/form/Input";

type MnemonicInputsProps = {
mnemonic?: string;
setMnemonic?(mnemonic: string): void;
readOnly?: boolean;
};

export default function MnemonicInputs({
mnemonic,
setMnemonic,
readOnly,
children,
}: React.PropsWithChildren<MnemonicInputsProps>) {
const { t } = useTranslation("translation", {
keyPrefix: "accounts.account_view.mnemonic",
});
const [revealedIndex, setRevealedIndex] = useState<number | undefined>(
undefined
);

const words = mnemonic?.split(" ") || [];
while (words.length < 12) {
words.push("");
}

return (
<div className="border-[1px] border-gray-200 rounded-lg py-8 px-4 flex flex-col gap-8 items-center justify-center w-[580px] self-center">
<h3 className="font-semibold dark:text-white">{t("inputs.title")}</h3>
<div className="flex flex-wrap gap-4 justify-center items-center">
{words.map((word, i) => {
const isRevealed = revealedIndex === i;
const inputId = `mnemonic-word-${i}`;
return (
<div key={i} className="flex justify-center items-center">
<span className="w-7 text-gray-500 slashed-zero dark:text-neutral-500">
{i + 1}.
</span>
<div className="relative">
<Input
id={inputId}
autoFocus={!readOnly && i === 0}
onFocus={() => setRevealedIndex(i)}
onBlur={() => setRevealedIndex(undefined)}
readOnly={readOnly}
block={false}
className="w-24 text-center"
list={readOnly ? undefined : "wordlist"}
value={isRevealed ? word : word.length ? "•••••" : ""}
onChange={(e) => {
if (revealedIndex !== i) {
return;
}
words[i] = e.target.value;
setMnemonic?.(
words
.map((word) => word.trim())
.join(" ")
.trim()
);
}}
endAdornment={
<button
type="button"
tabIndex={-1}
className="mr-2"
onClick={() => document.getElementById(inputId)?.focus()}
>
{isRevealed ? (
<VisibleIcon className="h-6 w-6" />
) : (
<HiddenIcon className="h-6 w-6" />
)}
</button>
}
/>
</div>
</div>
);
})}
</div>
{!readOnly && (
<datalist id="wordlist">
{wordlist.map((word) => (
<option key={word} value={word} />
))}
</datalist>
)}
{children}
</div>
);
}
39 changes: 39 additions & 0 deletions src/app/components/mnemonic/SecretKeyDescription/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useTranslation } from "react-i18next";
import NostrIcon from "~/app/icons/NostrIcon";

function SecretKeyDescription() {
const { t } = useTranslation("translation", {
keyPrefix: "accounts.account_view.mnemonic",
});

return (
<>
<p className="text-gray-500 dark:text-neutral-500">
{t("backup.description1")}
</p>
<div className="flex flex-col gap-4">
<ProtocolListItem
icon={<NostrIcon className="text-gray-500 dark:text-neutral-500" />}
title={t("backup.protocols.nostr")}
/>
</div>

<p className="mb-8 text-gray-500 dark:text-neutral-500">
{t("backup.description2")}
</p>
</>
);
}

export default SecretKeyDescription;

type ProtocolListItemProps = { icon: React.ReactNode; title: string };

function ProtocolListItem({ icon, title }: ProtocolListItemProps) {
return (
<div className="flex gap-2">
{icon}
<span className="text-gray-500 dark:text-neutral-500">{title}</span>
</div>
);
}
Loading

0 comments on commit aa79930

Please sign in to comment.