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

sync: fresh and existing wallets skip trial decryption #164

Merged
merged 14 commits into from
Sep 4, 2024
6 changes: 6 additions & 0 deletions .changeset/cuddly-worms-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@repo/context': major
'chrome-extension': major
---

fresh and existing wallets skip trial decryption
16 changes: 8 additions & 8 deletions apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@
"@connectrpc/connect-web": "^1.4.0",
"@penumbra-labs/registry": "11.1.0",
"@penumbra-zone/bech32m": "^7.0.0",
"@penumbra-zone/client": "^18.0.1",
"@penumbra-zone/crypto-web": "^22.0.0",
"@penumbra-zone/client": "^18.1.0",
"@penumbra-zone/crypto-web": "^23.0.0",
"@penumbra-zone/getters": "^16.0.0",
"@penumbra-zone/keys": "^4.2.1",
"@penumbra-zone/perspective": "^28.0.0",
"@penumbra-zone/perspective": "^29.0.0",
"@penumbra-zone/protobuf": "^6.0.0",
"@penumbra-zone/query": "^29.0.0",
"@penumbra-zone/services": "^32.0.0",
"@penumbra-zone/storage": "^28.0.0",
"@penumbra-zone/query": "^30.0.0",
"@penumbra-zone/services": "^33.0.0",
"@penumbra-zone/storage": "^29.0.0",
"@penumbra-zone/transport-chrome": "^8.0.1",
"@penumbra-zone/transport-dom": "^7.5.0",
"@penumbra-zone/types": "^21.0.0",
"@penumbra-zone/wasm": "^26.2.0",
"@penumbra-zone/types": "^22.0.0",
"@penumbra-zone/wasm": "^27.0.0",
"@radix-ui/react-icons": "^1.3.0",
"@repo/context": "workspace:*",
"@repo/ui": "workspace:*",
Expand Down
12 changes: 2 additions & 10 deletions apps/extension/src/hooks/full-sync-height.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { PopupLoaderData } from '../routes/popup/home';
import { useStore } from '../state';
import { networkSelector } from '../state/network';
import { useLoaderData } from 'react-router-dom';
import { TendermintProxyService } from '@penumbra-zone/protobuf';
import { createGrpcWebTransport } from '@connectrpc/connect-web';
import { createPromiseClient } from '@connectrpc/connect';
import { fetchBlockHeight } from '../state/block-height';

const tryGetMax = (a?: number, b?: number): number | undefined => {
// Height can be 0n which is falsy, so should compare to undefined state
Expand Down Expand Up @@ -38,13 +36,7 @@ export const useSyncProgress = () => {
if (!grpcEndpoint) {
return;
}
const tendermintClient = createPromiseClient(
TendermintProxyService,
createGrpcWebTransport({ baseUrl: grpcEndpoint }),
);
const blockHeight = (await tendermintClient.getStatus({}).catch(() => undefined))?.syncInfo
?.latestBlockHeight;
return Number(blockHeight);
return await fetchBlockHeight(grpcEndpoint);
},
enabled: Boolean(grpcEndpoint),
});
Expand Down
12 changes: 10 additions & 2 deletions apps/extension/src/hooks/onboarding.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useStore } from '../state';
// import { existingWalletBlockHeightSelector } from '../state/block-height';
import { passwordSelector } from '../state/password';
import { generateSelector } from '../state/seed-phrase/generate';
import { importSelector } from '../state/seed-phrase/import';
import { walletsSelector } from '../state/wallets';

// Saves hashed password, uses that hash to encrypt the seed phrase
// and then saves that to session + local storage
export const useOnboardingSave = () => {
export const useAddWallet = () => {
const { setPassword } = useStore(passwordSelector);
const { phrase: generatedPhrase } = useStore(generateSelector);
const { phrase: importedPhrase } = useStore(importSelector);
Expand All @@ -16,7 +17,14 @@ export const useOnboardingSave = () => {
// Determine which routes it came through to get here
const seedPhrase = generatedPhrase.length ? generatedPhrase : importedPhrase;
await setPassword(plaintextPassword);

await addWallet({ label: 'Wallet #1', seedPhrase });
};
};

export const useOnboardingSaveOptional = () => {
const { setBlockHeight } = useStore(state => state.walletHeight);

return async (walletBlockHeight: number) => {
await setBlockHeight(walletBlockHeight);
};
};
56 changes: 49 additions & 7 deletions apps/extension/src/routes/page/onboarding/generate.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ExclamationTriangleIcon, LockClosedIcon } from '@radix-ui/react-icons';
import { SeedPhraseLength } from '@penumbra-zone/crypto-web/mnemonic';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Button } from '@repo/ui/components/ui/button';
import { BackIcon } from '@repo/ui/components/ui/icons/back-icon';
import { Card, CardContent, CardHeader, CardTitle } from '@repo/ui/components/ui/card';
Expand All @@ -14,20 +14,38 @@ import { generateSelector } from '../../../state/seed-phrase/generate';
import { usePageNav } from '../../../utils/navigate';
import { PagePath } from '../paths';
import { WordLengthToogles } from '../../../shared/containers/word-length-toogles';
import { walletBlockHeightSelector } from '../../../state/block-height';

export const GenerateSeedPhrase = () => {
const navigate = usePageNav();
const { phrase, generateRandomSeedPhrase } = useStore(generateSelector);
const [count, { startCountdown }] = useCountdown({ countStart: 3 });
const [reveal, setReveal] = useState(false);
const [error, setError] = useState<string | null>(null);
const blockHeight = useStore(walletBlockHeightSelector);
const setBlockHeight = useStore(state => state.walletHeight.setBlockHeight);

// On render, generate a new seed phrase
// Track if the block height has been initialized to avoid multiple fetch attempts
const isInitialized = useRef(false);

// On render, asynchronously generate a new seed phrase and initialize the wallet creation block height
useEffect(() => {
if (!phrase.length) {
generateRandomSeedPhrase(SeedPhraseLength.TWELVE_WORDS);
}
startCountdown();
}, [generateRandomSeedPhrase, phrase.length, startCountdown]);
void (async () => {
try {
if (!phrase.length) {
generateRandomSeedPhrase(SeedPhraseLength.TWELVE_WORDS);
}
startCountdown();

if (!isInitialized.current && blockHeight === 0) {
await setBlockHeight(0, true);
isInitialized.current = true;
}
} catch (error) {
setError('Failed to fetch block height. Please try again later');
}
})();
grod220 marked this conversation as resolved.
Show resolved Hide resolved
}, [generateRandomSeedPhrase, phrase.length, startCountdown, blockHeight, setBlockHeight]);

return (
<FadeTransition>
Expand Down Expand Up @@ -60,6 +78,29 @@ export const GenerateSeedPhrase = () => {
isSuccessCopyText
/>
</div>

{reveal && (
<div className='mt-4 rounded-lg border border-gray-500 bg-gray-800 p-4 shadow-sm'>
<h4 className='text-center text-lg font-semibold text-gray-200'>Wallet Birthday</h4>
<p className='mt-2 text-center text-gray-300'>
<span className='font-bold text-gray-100'>
{error ? (
<span className='text-red-500'>{error}</span>
) : isInitialized.current ? (
Number(blockHeight).toLocaleString()
) : (
'Loading...'
)}
</span>
</p>
<p className='mt-2 text-sm text-gray-400'>
This is the block height at the time your wallet was created. Please save the block
height along with your recovery passphrase. It&apos;s not required, but will help
you restore your wallet quicker on a fresh Prax install next time.
</p>
</div>
)}

<div className='mt-2 flex flex-col justify-center gap-4'>
<div className='flex flex-col gap-1'>
<p className='flex items-center gap-2 text-rust'>
Expand All @@ -80,6 +121,7 @@ export const GenerateSeedPhrase = () => {
</p>
</div>
</div>

{reveal ? (
<Button
className='mt-4'
Expand Down
63 changes: 63 additions & 0 deletions apps/extension/src/routes/page/onboarding/height.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { BackIcon } from '@repo/ui/components/ui/icons/back-icon';
import { Button } from '@repo/ui/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@repo/ui/components/ui/card';
import { FadeTransition } from '@repo/ui/components/ui/fade-transition';
import { usePageNav } from '../../../utils/navigate';
import { PagePath } from '../paths';
import { FormEvent, useState } from 'react';
import { Input } from '@repo/ui/components/ui/input';
import { useOnboardingSaveOptional } from '../../../hooks/onboarding';

export const ImportWalletCreationHeight = () => {
grod220 marked this conversation as resolved.
Show resolved Hide resolved
const navigate = usePageNav();
const [blockHeight, setBlockHeight] = useState('');
const onboardingSave = useOnboardingSaveOptional();

const handleSubmit = (event: FormEvent) => {
event.preventDefault();

void (async () => {
await onboardingSave(Number(blockHeight));
navigate(PagePath.SET_PASSWORD);
})();
};

return (
<FadeTransition>
<BackIcon className='float-left mb-4' onClick={() => navigate(-1)} />
<Card className='w-[600px] p-8' gradient>
<CardHeader className='items-center text-center'>
<CardTitle className='text-xl font-semibold'>
Enter your wallet&apos;s birthday (Optional)
</CardTitle>
<CardDescription className='mt-2 text-sm'>
This is the block height at the time your wallet was created. Providing your
wallet&apos;s block creation height can help speed up the synchronization process, but
it&apos;s not required. If you don&apos;t have this information, you can safely skip
this step.
</CardDescription>
</CardHeader>
<CardContent className='mt-8'>
<form className='grid gap-6' onSubmit={handleSubmit}>
<Input
type='number'
placeholder='Enter block height'
value={blockHeight}
onChange={e => setBlockHeight(e.target.value)}
className='rounded-md border border-gray-700 p-3 text-[16px] font-normal leading-[24px]'
/>
<Button className='mt-6 w-full' variant='gradient' onClick={handleSubmit}>
Continue
</Button>
</form>
</CardContent>
</Card>
</FadeTransition>
);
};
2 changes: 1 addition & 1 deletion apps/extension/src/routes/page/onboarding/import.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const ImportSeedPhrase = () => {

const handleSubmit = (event: MouseEvent | FormEvent) => {
event.preventDefault();
navigate(PagePath.SET_PASSWORD);
navigate(PagePath.IMPORT_WALLET_CREATION_HEIGHT);
};

return (
Expand Down
5 changes: 5 additions & 0 deletions apps/extension/src/routes/page/onboarding/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { pageIndexLoader } from '..';
import { SetGrpcEndpoint } from './set-grpc-endpoint';
import { SetDefaultFrontendPage } from './default-frontend';
import { SetNumerairesPage } from './set-numeraire';
import { ImportWalletCreationHeight } from './height';

export const onboardingRoutes = [
{
Expand All @@ -27,6 +28,10 @@ export const onboardingRoutes = [
path: PagePath.IMPORT_SEED_PHRASE,
element: <ImportSeedPhrase />,
},
{
path: PagePath.IMPORT_WALLET_CREATION_HEIGHT,
element: <ImportWalletCreationHeight />,
},
{
path: PagePath.SET_PASSWORD,
element: <SetPassword />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export const SetGrpcEndpoint = () => {
<Card className='w-[400px]' gradient>
<CardHeader>
<CardTitle>Select your preferred RPC endpoint</CardTitle>

<CardDescription>
The requests you make may reveal your intentions about transactions you wish to make, so
select an RPC node that you trust. If you&apos;re unsure which one to choose, leave this
Expand Down
4 changes: 2 additions & 2 deletions apps/extension/src/routes/page/onboarding/set-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import {
CardTitle,
} from '@repo/ui/components/ui/card';
import { FadeTransition } from '@repo/ui/components/ui/fade-transition';
import { useOnboardingSave } from '../../../hooks/onboarding';
import { useAddWallet } from '../../../hooks/onboarding';
import { usePageNav } from '../../../utils/navigate';
import { PagePath } from '../paths';
import { PasswordInput } from '../../../shared/components/password-input';

export const SetPassword = () => {
const navigate = usePageNav();
const onboardingSave = useOnboardingSave();
const onboardingSave = useAddWallet();
const [password, setPassword] = useState('');
const [confirmation, setConfirmation] = useState('');

Expand Down
1 change: 1 addition & 0 deletions apps/extension/src/routes/page/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export enum PagePath {
GENERATE_SEED_PHRASE = '/welcome/generate',
CONFIRM_BACKUP = '/welcome/confirm-backup',
IMPORT_SEED_PHRASE = '/welcome/import',
IMPORT_WALLET_CREATION_HEIGHT = '/welcome/set-wallet-creation-height',
ONBOARDING_SUCCESS = '/welcome/success',
SET_PASSWORD = '/welcome/set-password',
SET_GRPC_ENDPOINT = '/welcome/set-grpc-endpoint',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import {
CardTitle,
} from '@repo/ui/components/ui/card';
import { FadeTransition } from '@repo/ui/components/ui/fade-transition';
import { useOnboardingSave } from '../../../hooks/onboarding';
import { useAddWallet } from '../../../hooks/onboarding';
import { usePageNav } from '../../../utils/navigate';
import { PagePath } from '../paths';
import { PasswordInput } from '../../../shared/components/password-input';

export const SetPassword = () => {
const navigate = usePageNav();
const finalOnboardingSave = useOnboardingSave();
const finalOnboardingSave = useAddWallet();
const [password, setPassword] = useState('');
const [confirmation, setConfirmation] = useState('');

Expand Down
Loading