Skip to content

Commit

Permalink
feat: support restoring wallet from OW
Browse files Browse the repository at this point in the history
  • Loading branch information
slient-coder committed Oct 12, 2023
1 parent 1cf4562 commit f37eac8
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 94 deletions.
7 changes: 7 additions & 0 deletions src/shared/constant/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ export const ADDRESS_TYPES: {
}
];

export const OW_HD_PATH = "m/86'/0'/0'";

export const RESTORE_WALLETS: { value: RestoreWalletType; name: string; addressTypes: AddressType[] }[] = [
{
value: RestoreWalletType.UNISAT,
Expand All @@ -185,6 +187,11 @@ export const RESTORE_WALLETS: { value: RestoreWalletType; name: string; addressT
name: 'Xverse Wallet',
addressTypes: [AddressType.P2SH_P2WPKH, AddressType.P2TR]
},
{
value: RestoreWalletType.OW,
name: 'Ordinals Wallet',
addressTypes: [AddressType.P2TR]
},
{
value: RestoreWalletType.OTHERS,
name: 'Other Wallet',
Expand Down
1 change: 1 addition & 0 deletions src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum RestoreWalletType {
UNISAT,
SPARROW,
XVERSE,
OW,
OTHERS
}

Expand Down
214 changes: 126 additions & 88 deletions src/ui/pages/Account/CreateHDWalletScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import bitcore from 'bitcore-lib';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';

import { ADDRESS_TYPES, RESTORE_WALLETS } from '@/shared/constant';
import { ADDRESS_TYPES, OW_HD_PATH, RESTORE_WALLETS } from '@/shared/constant';
import { AddressType, RestoreWalletType } from '@/shared/types';
import { Button, Card, Column, Content, Grid, Header, Input, Layout, Row, Text } from '@/ui/components';
import { useTools } from '@/ui/components/ActionComponent';
Expand Down Expand Up @@ -141,11 +141,22 @@ function Step1_Import({
contextData: ContextData;
updateContextData: (params: UpdateContextDataParams) => void;
}) {
const [keys, setKeys] = useState<Array<string>>(new Array(wordsItems[contextData.wordsType].count).fill(''));
const [curInputIndex, setCurInputIndex] = useState(0);
const [hover, setHover] = useState(999);
const [disabled, setDisabled] = useState(true);

const wordsItems = useMemo(() => {
if (contextData.restoreWalletType === RestoreWalletType.OW) {
return [WORDS_12_ITEM];
} else if (contextData.restoreWalletType === RestoreWalletType.XVERSE) {
return [WORDS_12_ITEM];
} else {
return [WORDS_12_ITEM, WORDS_24_ITEM];
}
}, [contextData]);

const [keys, setKeys] = useState<Array<string>>(new Array(wordsItems[contextData.wordsType].count).fill(''));

const handleEventPaste = (event, index: number) => {
const copyText = event.clipboardData?.getData('text/plain');
const textArr = copyText.trim().split(' ');
Expand Down Expand Up @@ -192,9 +203,21 @@ function Step1_Import({
//todo
}, [hover]);

const onNext = () => {
const mnemonics = keys.join(' ');
updateContextData({ mnemonics, tabType: TabType.STEP3 });
const createAccount = useCreateAccountCallback();
const navigate = useNavigate();
const tools = useTools();
const onNext = async () => {
try {
const mnemonics = keys.join(' ');
if (contextData.restoreWalletType === RestoreWalletType.OW) {
await createAccount(mnemonics, OW_HD_PATH, '', AddressType.P2TR, 1);
navigate('MainScreen');
} else {
updateContextData({ mnemonics, tabType: TabType.STEP3 });
}
} catch (e) {
tools.toastError((e as any).message);
}
};
const handleOnKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (!disabled && 'Enter' == e.key) {
Expand All @@ -207,21 +230,23 @@ function Step1_Import({
<Text text="Secret Recovery Phrase" preset="title-bold" textCenter />
<Text text="Import an existing wallet with your secret recovery phrase" preset="sub" textCenter />

<Row justifyCenter>
<Radio.Group
onChange={(e) => {
const wordsType = e.target.value;
updateContextData({ wordsType });
setKeys(new Array(wordsItems[wordsType].count).fill(''));
}}
value={contextData.wordsType}>
{wordsItems.map((v) => (
<Radio key={v.key} value={v.key}>
{v.label}
</Radio>
))}
</Radio.Group>
</Row>
{wordsItems.length > 1 ? (
<Row justifyCenter>
<Radio.Group
onChange={(e) => {
const wordsType = e.target.value;
updateContextData({ wordsType });
setKeys(new Array(wordsItems[wordsType].count).fill(''));
}}
value={contextData.wordsType}>
{wordsItems.map((v) => (
<Radio key={v.key} value={v.key}>
{v.label}
</Radio>
))}
</Radio.Group>
</Row>
) : null}

<Row justifyCenter>
<Grid columns={2}>
Expand Down Expand Up @@ -320,14 +345,16 @@ function Step2({
}, [contextData]);

const allHdPathOptions = useMemo(() => {
return ADDRESS_TYPES.sort((a, b) => a.displayIndex - b.displayIndex).map((v) => {
return {
label: v.name,
hdPath: v.hdPath,
addressType: v.value,
isUnisatLegacy: v.isUnisatLegacy
};
});
return ADDRESS_TYPES.map((v) => v)
.sort((a, b) => a.displayIndex - b.displayIndex)
.map((v) => {
return {
label: v.name,
hdPath: v.hdPath,
addressType: v.value,
isUnisatLegacy: v.isUnisatLegacy
};
});
}, []);

const [previewAddresses, setPreviewAddresses] = useState<string[]>(hdPathOptions.map((v) => ''));
Expand All @@ -341,6 +368,7 @@ function Step2({
}>({});

const [error, setError] = useState('');
const [pathError, setPathError] = useState('');
const [loading, setLoading] = useState(false);

const createAccount = useCreateAccountCallback();
Expand Down Expand Up @@ -432,25 +460,32 @@ function Step2({
fetchAddressesBalance();
}, [previewAddresses]);

const submitCustomHdPath = () => {
if (contextData.customHdPath === pathText) return;
const isValid = bitcore.HDPrivateKey.isValidPath(pathText);
if (!isValid) {
setError('Invalid derivation path.');
return;
const submitCustomHdPath = (text: string) => {
setPathError('');
setPathText(text);
if (text !== '') {
const isValid = bitcore.HDPrivateKey.isValidPath(text);
if (!isValid) {
setPathError('Invalid derivation path.');
return;
}
updateContextData({
customHdPath: text
});
} else {
updateContextData({
customHdPath: ''
});
}
updateContextData({
customHdPath: pathText
});
};

const resetCustomHdPath = () => {
updateContextData({
customHdPath: ''
});
setError('');
setPathText('');
};
const disabled = useMemo(() => {
if (!error && !pathError) {
return false;
} else {
return true;
}
}, [error, pathError]);

const onNext = async () => {
try {
Expand Down Expand Up @@ -607,23 +642,12 @@ function Step2({
<Input
placeholder={'Custom HD Wallet Derivation Path'}
value={pathText}
onChange={async (e) => {
setError('');
setPathText(e.target.value);
}}
onBlur={(e) => {
submitCustomHdPath();
onChange={(e) => {
submitCustomHdPath(e.target.value);
}}
/>
{contextData.customHdPath && (
<Icon
onClick={() => {
resetCustomHdPath();
}}>
<CloseOutlined />
</Icon>
)}
</Column>
{pathError && <Text text={pathError} color="error" />}
{error && <Text text={error} color="error" />}

<Text text="Phrase (Optional)" preset="bold" mt="lg" />
Expand All @@ -639,7 +663,7 @@ function Step2({
/>

<FooterButtonContainer>
<Button text="Continue" preset="primary" onClick={onNext} />
<Button text="Continue" preset="primary" onClick={onNext} disabled={disabled} />
</FooterButtonContainer>

{loading && (
Expand All @@ -662,18 +686,17 @@ enum WordsType {
WORDS_24
}

const wordsItems = [
{
key: WordsType.WORDS_12,
label: '12 words',
count: 12
},
{
key: WordsType.WORDS_24,
label: '24 words',
count: 24
}
];
const WORDS_12_ITEM = {
key: WordsType.WORDS_12,
label: '12 words',
count: 12
};

const WORDS_24_ITEM = {
key: WordsType.WORDS_24,
label: '24 words',
count: 24
};

interface ContextData {
mnemonics: string;
Expand Down Expand Up @@ -737,23 +760,38 @@ export default function CreateHDWalletScreen() {

const items = useMemo(() => {
if (contextData.isRestore) {
return [
{
key: TabType.STEP1,
label: 'Step 1',
children: <Step0 contextData={contextData} updateContextData={updateContextData} />
},
{
key: TabType.STEP2,
label: 'Step 2',
children: <Step1_Import contextData={contextData} updateContextData={updateContextData} />
},
{
key: TabType.STEP3,
label: 'Step 3',
children: <Step2 contextData={contextData} updateContextData={updateContextData} />
}
];
if (contextData.restoreWalletType === RestoreWalletType.OW) {
return [
{
key: TabType.STEP1,
label: 'Step 1',
children: <Step0 contextData={contextData} updateContextData={updateContextData} />
},
{
key: TabType.STEP2,
label: 'Step 2',
children: <Step1_Import contextData={contextData} updateContextData={updateContextData} />
}
];
} else {
return [
{
key: TabType.STEP1,
label: 'Step 1',
children: <Step0 contextData={contextData} updateContextData={updateContextData} />
},
{
key: TabType.STEP2,
label: 'Step 2',
children: <Step1_Import contextData={contextData} updateContextData={updateContextData} />
},
{
key: TabType.STEP3,
label: 'Step 3',
children: <Step2 contextData={contextData} updateContextData={updateContextData} />
}
];
}
} else {
return [
{
Expand Down
24 changes: 19 additions & 5 deletions src/ui/pages/Main/SettingsTabScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';

import { ADDRESS_TYPES, DISCORD_URL, GITHUB_URL, NETWORK_TYPES, TWITTER_URL } from '@/shared/constant';
import { Card, Column, Content, Footer, Header, Layout, Row, Text } from '@/ui/components';
import { useTools } from '@/ui/components/ActionComponent';
import { Button } from '@/ui/components/Button';
import { Icon } from '@/ui/components/Icon';
import { NavTabBar } from '@/ui/components/NavTabBar';
import { WarningPopver } from '@/ui/components/WarningPopver';
import { useExtensionIsInTab, useOpenExtensionInTab } from '@/ui/features/browser/tabs';
import { getCurrentTab } from '@/ui/features/browser/tabs';
import { useCurrentAccount } from '@/ui/state/accounts/hooks';
Expand Down Expand Up @@ -100,7 +102,6 @@ export default function SettingsTabScreen() {
const currentKeyring = useCurrentKeyring();
const currentAccount = useCurrentAccount();
const versionInfo = useVersionInfo();

const wallet = useWallet();
useEffect(() => {
const run = async () => {
Expand All @@ -114,6 +115,11 @@ export default function SettingsTabScreen() {
run();
}, []);

const isCustomHdPath = useMemo(() => {
const item = ADDRESS_TYPES[currentKeyring.addressType];
return item.hdPath !== currentKeyring.hdPath;
}, [currentKeyring]);

const toRenderSettings = SettingList.filter((v) => {
if (v.action == 'manage-wallet') {
v.value = currentKeyring.alianName;
Expand All @@ -129,7 +135,8 @@ export default function SettingsTabScreen() {

if (v.action == 'addressType') {
const item = ADDRESS_TYPES[currentKeyring.addressType];
v.value = `${item.name} (${item.hdPath}/${currentAccount.index})`;
const hdPath = currentKeyring.hdPath || item.hdPath;
v.value = `${item.name} (${hdPath}/${currentAccount.index})`;
}

if (v.action == 'expand-view') {
Expand All @@ -141,6 +148,7 @@ export default function SettingsTabScreen() {
return true;
});

const tools = useTools();
const openExtensionInTab = useOpenExtensionInTab();

return (
Expand Down Expand Up @@ -176,8 +184,14 @@ export default function SettingsTabScreen() {
key={item.action}
mt="lg"
onClick={(e) => {
if (item.action == 'expand-view') {
openExtensionInTab();
if (item.action == 'addressType') {
if (isCustomHdPath) {
tools.showTip(
'The wallet currently uses a custom HD path and does not support switching address types.'
);
return;
}
navigate('/settings/address-type');
return;
}
navigate(item.route);
Expand Down
Loading

0 comments on commit f37eac8

Please sign in to comment.