Skip to content
This repository has been archived by the owner on Nov 5, 2023. It is now read-only.

Wallet recovery #342

Merged
merged 17 commits into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"fp-ts": "^2.12.1",
"io-ts": "^2.2.16",
"lodash-es": "^4.17.21",
"mcl-wasm": "^1.0.3",
"phosphor-react": "^1.4.0",
"qrcode.react": "^3.1.0",
"react": "^17.0.2",
Expand Down
108 changes: 108 additions & 0 deletions extension/source/Home/Wallet/Wallets/Recovery/RecoverWalletModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { FunctionComponent, useState } from 'react';
import Modal from 'react-modal';
import Button from '../../../../components/Button';
import Range from '../../../../helpers/Range';
import StepOneInfo from './StepOneInfo';
import StepThreeRecover from './StepThreeRecover';
import StepTwoWalletCreation from './StepTwoWalletCreation';

const WorkflowNumbers: FunctionComponent<{
max: number;
current: number;
}> = ({ max, current }) => {
return (
<div className="flex justify-center space-x-10">
{Range(max).map((i) => (
<div
key={i}
className={[
'icon-lg',
'rounded-full',
'text-center',
'leading-8',
'cursor-pointer',
...(i <= current ? ['bg-blue-500', 'text-white'] : ['text-black']),
].join(' ')}
>
{i + 1}
</div>
))}
</div>
);
};

const RecoverWalletModal = () => {
const [modalIsOpen, setIsOpen] = useState<boolean>(false);
const [pageIndex, setPageIndex] = useState<number>(0);
const [walletPrivateKey, setWalletPrivateKey] = useState<string>('');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to explicitly add the type here? Can see that we're letting typescript infer the type for the above useState's

Copy link
Contributor

Choose a reason for hiding this comment

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

My personal preference is to have explicit typing everywhere. Copied the above 2 lines from somewhere else in the codebase so missed adding type to it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I may lean towards letting typescript infer the type but don't have a strong opinion either way, so happy with either option as long as we're being consistent :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

@kautukkundan Are you sure that's your preference in this case where TypeScript is correctly inferring the type? Would you also add <boolean> and <number> to the previous lines?

Copy link
Contributor

@kautukkundan kautukkundan Nov 3, 2022

Choose a reason for hiding this comment

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

@voltrevo Fine with me as is in this particular case.

The problem starts happening when using complex types. eg - let's say a new Address type is defined as a string that always starts with "0x". In this case initializing with default '' would also work as it'll infer type string, but may break code later when things get complex. This had been the case earlier when using sol.G1 and sol.G2 types in the HubbleBLS library.

So looking at broader picture and anticipating future I prefer being explicit even with simpler types to maintain consistency, If that makes sense.

const [walletAddress, setWalletAddress] = useState<string>('');

const onRecoverComplete = () => {
setWalletPrivateKey('');
setWalletAddress('');
setIsOpen(false);
};

return (
<div>
<Button onPress={() => setIsOpen(true)} className="btn-secondary">
Import
</Button>
<Modal
isOpen={modalIsOpen}
onRequestClose={() => setIsOpen(false)}
style={{
content: {
width: '35rem',
margin: 'auto',
fontFamily: 'montserrat',
padding: 0,
border: 'none',
height: '25rem',
},
overlay: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
}}
>
<div className="p-8 h-full w-100 flex flex-col">
<WorkflowNumbers max={3} current={pageIndex} />
<div className="mt-8 h-100 flex-grow">
{
[
<StepOneInfo
key={1}
onComplete={() => {
kautukkundan marked this conversation as resolved.
Show resolved Hide resolved
setPageIndex(1);
}}
/>,
<StepTwoWalletCreation
key={2}
setWalletPkToParent={setWalletPrivateKey}
setWalletAddressToParent={setWalletAddress}
walletAddress={walletAddress}
onBack={() => {
setPageIndex(0);
}}
onComplete={() => {
setPageIndex(2);
}}
/>,
<StepThreeRecover
key={3}
walletPrivateKey={walletPrivateKey}
onBack={() => {
setPageIndex(1);
}}
onComplete={() => onRecoverComplete()}
/>,
][pageIndex]
}
</div>
</div>
</Modal>
</div>
);
};

export default RecoverWalletModal;
38 changes: 38 additions & 0 deletions extension/source/Home/Wallet/Wallets/Recovery/StepOneInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { CaretRight } from 'phosphor-react';
import { FunctionComponent } from 'react';
import Button from '../../../../components/Button';

const StepOneInfo: FunctionComponent<{
onComplete: () => void;
}> = ({ onComplete }) => {
return (
<div className="flex flex-col gap-4 h-full">
<div className="flex-grow">
<div className="text-[14pt]">Recover existing wallet in Quill</div>
<div className="text-[10pt] text-grey-700 leading-loose">
You can recover existing instant BLS wallets into Quill. This is a
simple 2 step process which requires you to copy-paste two values from
Quill to the instant wallet and then again from instant wallet to
Quill
</div>
<br />
<div className="text-[10pt] text-grey-700 leading-loose font-bold mt-2">
Do not close this modal until you have completed all the steps that
follow, else you will lose access to your original keys!
</div>
</div>

<div className="flex justify-end">
<Button
onPress={() => onComplete()}
className="btn-primary h-10 text-[10pt] w-1/3"
icon={<CaretRight size={15} />}
>
Continue
</Button>
</div>
</div>
);
};

export default StepOneInfo;
101 changes: 101 additions & 0 deletions extension/source/Home/Wallet/Wallets/Recovery/StepThreeRecover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ethers } from 'ethers';
import { Download, CaretLeft } from 'phosphor-react';
import { FunctionComponent, useState } from 'react';
import Button from '../../../../components/Button';
import { useQuill } from '../../../../QuillContext';

const StepThreeRecover: FunctionComponent<{
onBack: () => void;
onComplete: () => void;
walletPrivateKey: string;
}> = ({ onBack, onComplete, walletPrivateKey }) => {
const { rpc } = useQuill();

const [salt, setSalt] = useState<string>('');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similar point here and line below, do we want to explicitly state the type?

const [instantWalletAddress, setInstantWalletAddress] = useState<string>('');

const handleRecover = async () => {
await rpc.addRecoveryWallet(
instantWalletAddress,
ethers.utils.formatBytes32String(salt),
walletPrivateKey,
);
jacque006 marked this conversation as resolved.
Show resolved Hide resolved
onComplete();
};

return (
<div className="flex flex-col gap-4 h-full">
<div className="flex-grow">
<div className="text-[14pt]">Almost There</div>
<div className="text-[10pt] text-grey-700 leading-loose">
Your instant wallet recovery is in process. Once finished the same
wallet will be visible inside Quill.
<br />
<div
className={[
'text-[10pt]',
'text-grey-700',
'leading-loose',
'font-bold',
'mt-2',
].join(' ')}
kautukkundan marked this conversation as resolved.
Show resolved Hide resolved
>
Copy back the instant wallet address and salt entered
</div>
</div>

<div className="mt-4 flex flex-col gap-2">
<input
type="text"
className={[
'bg-opacity-5',
'border-opacity-45',
'focus:border-opacity-85',
'h-10',
'text-[10pt]',
].join(' ')}
placeholder="Instant Wallet Address"
onChange={(e) => {
setInstantWalletAddress(e.target.value);
}}
/>

<input
type="text"
className={[
'bg-opacity-5',
'border-opacity-45',
'focus:border-opacity-85',
'h-10',
'text-[10pt]',
].join(' ')}
placeholder="Recovery Salt"
onChange={(e) => {
setSalt(e.target.value);
}}
/>
</div>
</div>

<div className="flex justify-between">
<Button
onPress={() => onBack()}
className="btn-primary h-10 text-[10pt] w-1/3"
iconLeft={<CaretLeft size={15} />}
>
Back
</Button>

<Button
onPress={() => handleRecover()}
className="btn-primary h-10 text-[10pt] w-1/3"
icon={<Download size={15} />}
>
Recover Wallet
</Button>
</div>
</div>
);
};

export default StepThreeRecover;
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { CaretRight, CaretLeft, CircleNotch, CopySimple } from 'phosphor-react';
import { FunctionComponent, useEffect, useState } from 'react';
import Button from '../../../../components/Button';
import { useQuill } from '../../../../QuillContext';

const StepTwoWalletCreation: FunctionComponent<{
onBack: () => void;
onComplete: () => void;
setWalletPkToParent: (address: string) => void;
setWalletAddressToParent: (address: string) => void;
walletAddress: string;
}> = ({
onBack,
onComplete,
setWalletPkToParent,
walletAddress,
setWalletAddressToParent,
}) => {
const [loading, setLoading] = useState<boolean>(!walletAddress);

const { rpc } = useQuill();

useEffect(() => {
const createWallet = async () => {
setLoading(true);
const { address, privateKey } = await rpc.createTempAccount();
setWalletAddressToParent(address);
setWalletPkToParent(privateKey);
setLoading(false);
};

if (!walletAddress) {
createWallet();
}
}, [walletAddress, rpc, setWalletPkToParent, setWalletAddressToParent]);

return (
<div className="flex flex-col gap-4 h-full">
<div className="flex-grow">
<div className="text-[14pt]">Recovery Wallet</div>
<div className="text-[10pt] text-grey-700 leading-loose">
This is the wallet address which will be used to recover your instant
wallet.
</div>
<br />
<div className="text-[10pt] text-grey-700 leading-loose font-bold mt-2">
Copy the address and paste it in the instant wallet.
</div>
<div className="mt-4 bg-grey-900 bg-opacity-25 rounded-md p-5">
{loading ? (
<div className="flex gap-4 text-[12pt] items-center">
Generating wallet
<div className="animate-spin relative">
<CircleNotch size={30} />
</div>
</div>
) : (
<div className="flex justify-between">
<div className="font-mono text-[11pt]">{walletAddress}</div>
{/* eslint-disable-next-line */}
Copy link
Collaborator

Choose a reason for hiding this comment

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

What rule was disabled here? Better to disable a specific rule than all linting rules.

Copy link
Contributor

Choose a reason for hiding this comment

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

it wanted to also have a "keyboard input" for the copy button which didn't make much sense to me.

<div
className="cursor-pointer"
onClick={() => navigator.clipboard.writeText(walletAddress)}
>
<CopySimple size={20} />
</div>
</div>
)}
</div>
</div>

<div className="flex justify-between">
<Button
onPress={() => onBack()}
className="btn-primary h-10 text-[10pt] w-1/3"
iconLeft={<CaretLeft size={15} />}
>
Back
</Button>

<Button
onPress={() => onComplete()}
className="btn-primary h-10 text-[10pt] w-1/3"
icon={<CaretRight size={15} />}
>
Continue
</Button>
</div>
</div>
);
};

export default StepTwoWalletCreation;
10 changes: 7 additions & 3 deletions extension/source/Home/Wallet/Wallets/WalletWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import useCell from '../../../cells/useCell';
import Button from '../../../components/Button';
import Loading from '../../../components/Loading';
import { useQuill } from '../../../QuillContext';
import RecoverWalletModal from './Recovery/RecoverWalletModal';
/* eslint import/no-cycle: "warn" -- TODO (merge-ok) Fix import cycle */
import { WalletSummary } from './WalletSummary';

Expand All @@ -25,9 +26,12 @@ export const WalletsWrapper: FunctionComponent = () => {
<div className="">
<div className="flex justify-between place-items-center">
<div className="text-body">Wallets</div>
<Button onPress={rpc.addHDAccount} className="btn-secondary">
Add
</Button>
<div className="flex gap-2">
<RecoverWalletModal />
<Button onPress={rpc.addHDAccount} className="btn-primary">
Add
</Button>
</div>
</div>

{!ethAccounts && <Loading />}
Expand Down
Loading