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

feat: mnemonic #2338

Merged
merged 135 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
135 commits
Select commit Hold shift + click to select a range
7aa9a22
feat: first steps
reneaaron Apr 2, 2023
f0d3544
feat: untie webbtc from lightning
reneaaron Apr 4, 2023
c7599ea
feat: webbtc & prompts for signPsbt
reneaaron Apr 4, 2023
fe0a2aa
fix: remove bracket
reneaaron Apr 4, 2023
b573f9c
feat: first steps towards psbt
reneaaron Apr 11, 2023
7ff1d5d
fix: impelemented some basic functionality & tests (wip)
reneaaron Apr 12, 2023
914b1aa
Merge branch 'master' into feat/mnemonic
reneaaron Apr 15, 2023
b38eee4
Merge branch 'master' into feat/mnemonic
reneaaron Apr 17, 2023
6d9c0ba
fix: remove lib from lockfile
reneaaron Apr 17, 2023
8297383
fix: webbtc
reneaaron Apr 17, 2023
99c9b2a
fix: cleanup test
reneaaron Apr 18, 2023
d848511
Merge branch 'master' into feat/mnemonic
reneaaron Apr 20, 2023
e12139f
feat: mnemonic / secret key UI WIP
rolznz Apr 22, 2023
eb1536b
feat: account secret key section styling and copy WIP
rolznz Apr 25, 2023
e9e59d0
feat: backup secret key component WIP
rolznz Apr 25, 2023
be6abfd
feat: add copy and confirmation to backup secret key component
rolznz Apr 25, 2023
8871fb1
feat: scroll to top on options navigation
rolznz Apr 25, 2023
90fe854
feat: improve styling in BackupSecretKey screen
rolznz Apr 25, 2023
96a6a20
feat: import secret key screen
rolznz Apr 25, 2023
6e558a3
chore: display nostr key origin WIP
rolznz Apr 26, 2023
8f6fa22
feat: new advanced nostr settings screen
rolznz Apr 26, 2023
1d1b1ae
feat: store mnemonic and generate nostr derived key WIP
rolznz Apr 26, 2023
941df57
fix: show saved mnemonic in backup screen
rolznz Apr 27, 2023
4b20966
chore: reduce duplication in deriving nostr secret key
rolznz Apr 27, 2023
3b2ca07
chore: update nostr key not replaced warning
rolznz Apr 27, 2023
96cfcb4
chore: mnemonic code restructure
rolznz May 1, 2023
c6230c2
chore: display existing nostr key alert in backup and import secret k…
rolznz May 1, 2023
0c5c746
chore: backup secret key translations WIP
rolznz May 1, 2023
91fcb74
chore: add translations for nostr advanced settings
rolznz May 2, 2023
5a59456
chore: secret key dark mode styling
rolznz May 2, 2023
fb8fe75
Merge remote-tracking branch 'origin/master' into feat/mnemonic-ui
rolznz May 2, 2023
ce341e7
Merge branch 'master' into feat/mnemonic-ui
rolznz May 3, 2023
a46f9ad
fix: onboarding tab behaviour
rolznz May 3, 2023
64aad55
feat: new tip cards
rolznz May 3, 2023
d049541
chore: minor card styling improvements
rolznz May 3, 2023
4cd039b
chore: fix webbtc postmessage
rolznz May 3, 2023
bf7da6b
feat: use mnemonic from account in signPsbt
rolznz May 3, 2023
35edbf8
fix: textfield flex direction
rolznz May 4, 2023
a545a70
Merge remote-tracking branch 'origin/master' into feat/mnemonic-ui
rolznz May 4, 2023
5b0a27e
fix: signpsbt
rolznz May 12, 2023
f7a288f
feat: sign psbt preview (wip)
rolznz May 12, 2023
c3f8b59
feat: add webbtc functions to get addresses or single address
rolznz May 15, 2023
671f07d
chore: add tests for webbtc getAddresses
rolznz May 15, 2023
39012c4
chore: fix broken tests
rolznz May 16, 2023
dd0b6a6
chore: remove hardcoded network type in confirm sign psbt dialog
rolznz May 16, 2023
4acb8b7
feat: use derivation path from account instead of passing parameter i…
rolznz May 17, 2023
c88e686
chore: replace getDerivationPath with bitcoin network option
rolznz May 17, 2023
f09b13d
chore: use getAddress instead of getAddresses function
rolznz May 17, 2023
25530d9
feat: use taproot with signpsbt and getaddress
rolznz May 17, 2023
6865847
fix: signPsbt in service worker
rolznz May 18, 2023
3219591
chore: improve sign psbt modal ui
rolznz May 18, 2023
fb48b65
Merge remote-tracking branch 'origin/master' into feat/mnemonic
rolznz May 18, 2023
881ed18
chore: remove placeholder in mnemonic inputs
rolznz May 18, 2023
4cf1eab
fix: updated box paddings
reneaaron May 30, 2023
0f9dfbc
fix: added hover states for onboarding cards
reneaaron May 30, 2023
0689051
fix: remove color from copy button
reneaaron May 30, 2023
8482285
Merge branch 'master' into feat/mnemonic
reneaaron May 30, 2023
ebc3924
fix: improve spacing on advanced nostr settings
reneaaron May 30, 2023
00dff56
fix: account / secret key removal, prompts
reneaaron May 30, 2023
5ad55cb
fix: re-encrypt mnemonic on password change
reneaaron Jun 14, 2023
9bbbc4a
Merge branch 'master' into feat/mnemonic
reneaaron Jun 14, 2023
815c452
fix: sign psbt warning text
im-adithya Jun 14, 2023
ec7ebf9
fix: tests
reneaaron Jun 14, 2023
22e7ab7
fix: test
reneaaron Jun 14, 2023
55a251c
fix: cleanup
reneaaron Jun 14, 2023
07e20df
Merge branch 'master' into feat/mnemonic
reneaaron Jun 14, 2023
adf54f1
Merge branch 'master' into feat/mnemonic
reneaaron Jun 15, 2023
e6f1f80
feat: improve secret key security
rolznz Jun 16, 2023
fbd499b
chore: remove signPsbt
rolznz Jun 16, 2023
a1b68eb
fix: useTips unit tests
rolznz Jun 16, 2023
658d7aa
feat: add alert component
reneaaron Jun 16, 2023
1b7db17
fix: rename nostr to webbtc
reneaaron Jun 16, 2023
1c3b0fc
fix: update usages of alert component
reneaaron Jun 16, 2023
bf22e69
chore: remove ordinals from secret key backup screen
rolznz Jun 19, 2023
78b2369
chore: remove unnecessary try-catch
rolznz Jun 19, 2023
7bcbc66
chore: revert tsconfig target
rolznz Jun 19, 2023
4aa3b22
Merge remote-tracking branch 'origin/master' into feat/mnemonic
rolznz Jun 19, 2023
034f7d7
Merge branch 'feat/mnemonic' into feat/secret-key-security
rolznz Jun 19, 2023
7d115ff
chore: remove insecure copy secret key button
rolznz Jun 19, 2023
becdf95
chore: remove remaining psbt references
rolznz Jun 19, 2023
8ddc6cf
Merge branch 'feat/mnemonic' into feat/secret-key-security
rolznz Jun 19, 2023
8d27b83
chore: minor translation improvements
rolznz Jun 19, 2023
4b0ac78
chore: add comment for disabling webkit-calendar-picker-indicator
rolznz Jun 20, 2023
0e59e28
fix: write await result to variable
rolznz Jun 20, 2023
9f6d20b
fix: make webbtc imports consistent
rolznz Jun 20, 2023
b6436d5
Merge pull request #2490 from getAlby/feat/secret-key-security
rolznz Jun 20, 2023
f9babf4
chore: move clipboard read to separate line
rolznz Jun 20, 2023
dc992d2
chore: remove log leaking invalid mnemonic
rolznz Jun 20, 2023
d92e2b8
chore: remove paste button in import secret key screen
rolznz Jun 21, 2023
c3f176f
chore: mnemonic inputs improvements
rolznz Jun 21, 2023
7b1fc7d
fix: mnemonic inputs firefox autocomplete
rolznz Jun 21, 2023
a0dc581
chore: update TODOs
rolznz Jun 21, 2023
cc52e79
chore: remove unnecessary id check
rolznz Jun 21, 2023
bdc5ace
chore: do not access nostr private key in mnemonic backup/import screens
rolznz Jun 21, 2023
e7d89ea
chore: check if account has secret key before injecting webbtc
rolznz Jun 21, 2023
6c0e600
chore: add TODO to not get nostr private key in account detail page
rolznz Jun 21, 2023
7146f12
chore: simplify imported nostr key functionality
rolznz Jun 21, 2023
e64da1b
chore: split backup page into generate and backup pages
rolznz Jun 21, 2023
f17c907
fix: replace history back with navigate
rolznz Jun 21, 2023
7d261b9
chore: add nostr getPublicKey function
rolznz Jun 21, 2023
e4a9c13
fix: do not get nostr private key in account detail screen
rolznz Jun 21, 2023
7b94714
chore: move bitcoin network setting to account settings
rolznz Jun 21, 2023
46f7737
chore: move saveMnemonic and saveNostrPrivateKey utility functions to…
rolznz Jun 22, 2023
2c8e5b6
chore: move bitcoin lib to background script
rolznz Jun 22, 2023
83987a8
chore: move mnemonic generation to background script
rolznz Jun 22, 2023
0d772ba
chore: minor cleanup
rolznz Jun 22, 2023
31f19d0
fix: do not request mnemonic in ImportSecretKey
rolznz Jun 22, 2023
441ac7a
fix: use mnemonic from correct account
rolznz Jun 22, 2023
d1f661d
Merge branch 'master' into feat/mnemonic
reneaaron Jun 23, 2023
d0c78af
fix: generalize warning copy
reneaaron Jun 23, 2023
b5f1ad1
fix: spacing on account details
reneaaron Jun 23, 2023
c718174
fix: improve warning copy
reneaaron Jun 23, 2023
ad61944
fix: colors for alert
reneaaron Jun 27, 2023
8746d55
chore: remove unused types
rolznz Jun 28, 2023
0620f84
chore: do not export nostr derivation path
rolznz Jun 28, 2023
efa0605
fix: only show secret key import subsection if account does not have …
rolznz Jun 28, 2023
0cad988
fix: get correct account, navigate to account settings if mnemonic al…
rolznz Jun 28, 2023
d5ae8ce
chore: move mnemonic components into subfolder
rolznz Jun 28, 2023
f3acf75
chore: rename nostr advanced settings to nostr settings
rolznz Jun 28, 2023
905ea53
fix: rename generateMnemonic function
rolznz Jun 28, 2023
f852ce5
chore: add TODOs to state object
rolznz Jun 28, 2023
bbb1d28
chore: update nostrPrivateKey TODO in state object
rolznz Jun 28, 2023
9538488
chore: add testnet bitcoin network
rolznz Jun 28, 2023
1ec9a78
chore: update TODO
rolznz Jun 28, 2023
034bdae
chore: wrap nostr key validation in try-catch
rolznz Jun 28, 2023
f08c90b
fix: make memoized bitcoin object consistent with other memoized stat…
rolznz Jun 28, 2023
e9d99db
chore: simplify mnemonic key derivation functions
rolznz Jun 28, 2023
f5a35f8
chore: rename nostr generatePublicKey to derivePublicKey
rolznz Jun 28, 2023
2373c8d
chore: add TODO
rolznz Jun 28, 2023
b2c66ee
fix: make nostr getPublicKey id mandatory
rolznz Jun 28, 2023
d84a2a8
chore: use api for nostr and mnemonic actions
rolznz Jun 28, 2023
f233b5c
fix: make nostr generatePrivateKey action return key rather than savi…
rolznz Jun 28, 2023
d458a87
chore: use api method instead of msg.request to get account
rolznz Jun 28, 2023
9cbc89b
Merge remote-tracking branch 'origin/master' into feat/mnemonic
rolznz Jun 30, 2023
2ae38b0
feat: add delete key button in nostr settings screen
rolznz Jun 30, 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
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`,
rolznz marked this conversation as resolved.
Show resolved Hide resolved
},
} 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
Loading