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

Split Mnemonic Import #419

Merged
merged 4 commits into from
Jul 7, 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
7 changes: 6 additions & 1 deletion packages/test-project/tests/common/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ function ImportAccount(account) {
await extensionPage.click('#importAccount');
await extensionPage.waitForSelector('#accountName');
await extensionPage.type('#accountName', account.name);
await extensionPage.type('#enterMnemonic', account.mnemonic);
const accountMnemonic = account.mnemonic.split(' ');
for (let i = 0; i < accountMnemonic.length; i++) {
const mnemonicWordId = '#mnemonicWord' + i;
await extensionPage.type(mnemonicWordId, accountMnemonic[i]);
}
await extensionPage.waitForTimeout(200);
await extensionPage.click('#nextStep');
await inputPassword();
await extensionPage.waitForTimeout(2000);
Expand Down
263 changes: 246 additions & 17 deletions packages/ui/src/pages/ImportAccount.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FunctionalComponent } from 'preact';
import { html } from 'htm/preact';
import { useState, useContext } from 'preact/hooks';
import { useState, useContext, useEffect } from 'preact/hooks';
import { route } from 'preact-router';
import { JsonRpcMethod } from '@algosigner/common/messaging/types';

Expand All @@ -14,8 +14,9 @@ import algosdk from 'algosdk';

const ImportAccount: FunctionalComponent = (props: any) => {
const store: any = useContext(StoreContext);
const wordsNumber = 25;
const { ledger } = props;
const [mnemonic, setMnemonic] = useState<string>('');
const [mnemonicArray, setMnemonicArray] = useState<Array<string>>(new Array<string>(wordsNumber));
const [address, setAddress] = useState<string>('');
const [isRef, setIsRef] = useState<boolean>(false);
const [name, setName] = useState<string>('');
Expand All @@ -24,16 +25,20 @@ const ImportAccount: FunctionalComponent = (props: any) => {
const [authError, setAuthError] = useState<string>('');
const [error, setError] = useState<string>('');

const matches = mnemonic.trim().split(/[\s\t\r\n]+/) || [];
const disabled =
name.trim().length === 0 ||
(!isRef && matches.length !== 25) ||
(!isRef && (mnemonicArray.length !== 25 || mnemonicArray.some((e) => e === ''))) ||
(isRef && !algosdk.isValidAddress(address));

const importAccount = (pwd: string) => {
// Trim mnemonic words
for (let i = 0; i < mnemonicArray.length; i++) {
mnemonicArray[i] = mnemonicArray[i].trim();
}

const params = {
passphrase: pwd,
mnemonic: matches.join(' '),
mnemonic: mnemonicArray.join(' '),
address: address,
isRef: isRef,
name: name.trim(),
Expand Down Expand Up @@ -64,13 +69,61 @@ const ImportAccount: FunctionalComponent = (props: any) => {
};

const handleMnemonicInput = (e) => {
setMnemonic(e.target.value);
const localMnemonicArray = mnemonicArray;
const inputText = e.target.value.toString();
const inputArray = inputText.split(/[\s\t\r\n,]+/);

// If it is a single word then update that word placement in the array
if (inputArray.length === 1) {
localMnemonicArray[e.target.id.toString().replace('mnemonicWord', '')] = inputText;
}
// If it is multiple words then verify there are 25 and split
else if (inputArray.length === wordsNumber) {
for(let i = 0; i < wordsNumber; i++) {
localMnemonicArray[i.toString()] = inputArray[i];
}

// For the case the user is typing the full mnemonic
// after the split the last element will be empty
// so we must focust that element now to continue typing
const element = document.getElementById('mnemonicWord24');
if (element) {
element.focus();
}

}
// If it is multiple words but they are not 25 then warn, but allow for typing out 25.
else {
console.log('[WARNING] - Mnemonic words must be a single word or the entire 25 mnemonic.')
localMnemonicArray[e.target.id.toString().replace('mnemonicWord', '')] = inputText;
}

setMnemonicArray([]);
setMnemonicArray(localMnemonicArray);
};

const handleAddressInput = (e) => {
setAddress(e.target.value);
};

const handleMnemonicIconClick = (iconNumber) => {
// Mnemonic boxes are prefixed with "mnemonicWord"
const wordId = 'mnemonicWord' + iconNumber;
const element = document.getElementById(wordId);

// As long as we can locate the element just swap the show/hide icon and text/password
if (element !== null) {
if (element['type'] === 'password') {
element['type'] = 'text';
element.parentElement?.querySelectorAll('svg')[0]?.setAttribute('data-icon','eye');
}
else {
element['type'] = 'password';
element.parentElement?.querySelectorAll('svg')[0]?.setAttribute('data-icon','eye-slash');
}
}
}

return html`
<div class="main-view" style="flex-direction: column; justify-content: space-between;">
<${HeaderView} action="${() => route('/wallet')}" title="Import ${ledger} account" />
Expand Down Expand Up @@ -122,18 +175,194 @@ const ImportAccount: FunctionalComponent = (props: any) => {
`}
${!isRef &&
html`
<p class="my-3">Insert the 25 word mnemonic of the account:</p>
<textarea
id="enterMnemonic"
class="textarea has-fixed-size"
placeholder="apples butter king monkey nuts ..."
rows="5"
onInput=${handleMnemonicInput}
value=${mnemonic}
/>
<p class="my-3">
<div>
Insert the 25 word mnemonic of the account
</div>
<div>
(Entire mnemonic may be pasted into a single field):
</div>
</p>
<div id="mnemonicBlock" style="display: flex; flex-wrap: wrap; text-align: right; padding-right: 30px;">
<div style="display: block; width: 33%;">
<label for="mnemonicWord0" style="margin-right: 5px;">1:</label>
<input id="mnemonicWord0" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[0]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('0')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord1" style="margin-right: 5px;">2:</label>
<input id="mnemonicWord1" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[1]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('1')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord2" style="margin-right: 5px;">3:</label>
<input id="mnemonicWord2" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[2]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('2')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord3" style="margin-right: 5px;">4:</label>
<input id="mnemonicWord3" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[3]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('3')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord4" style="margin-right: 5px;">5:</label>
<input id="mnemonicWord4" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[4]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('4')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord5" style="margin-right: 5px;">6:</label>
<input id="mnemonicWord5" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[5]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('5')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord6" style="margin-right: 5px;">7:</label>
<input id="mnemonicWord6" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[6]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('6')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord7" style="margin-right: 5px;">8:</label>
<input id="mnemonicWord7" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[7]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('7')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord8" style="margin-right: 5px;">9:</label>
<input id="mnemonicWord8" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[8]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('8')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord9" style="margin-right: 5px;">10:</label>
<input id="mnemonicWord9" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[9]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('9')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord10" style="margin-right: 5px;">11:</label>
<input id="mnemonicWord10" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[10]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('10')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord11" style="margin-right: 5px;">12:</label>
<input id="mnemonicWord11" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[11]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('11')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord12" style="margin-right: 5px;">13:</label>
<input id="mnemonicWord12" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[12]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('12')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord13" style="margin-right: 5px;">14:</label>
<input id="mnemonicWord13" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[13]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('13')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord14" style="margin-right: 5px;">15:</label>
<input id="mnemonicWord14" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[14]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('14')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord15" style="margin-right: 5px;">16:</label>
<input id="mnemonicWord15" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[15]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('15')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord16" style="margin-right: 5px;">17:</label>
<input id="mnemonicWord16" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[16]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('16')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord17" style="margin-right: 5px;">18:</label>
<input id="mnemonicWord17" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[17]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('17')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord18" style="margin-right: 5px;">19:</label>
<input id="mnemonicWord18" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[18]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('18')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord19" style="margin-right: 5px;">20:</label>
<input id="mnemonicWord19" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[19]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('19')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord20" style="margin-right: 5px;">21:</label>
<input id="mnemonicWord20" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[20]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('20')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord21" style="margin-right: 5px;">22:</label>
<input id="mnemonicWord21" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[21]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('21')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord22" style="margin-right: 5px;">23:</label>
<input id="mnemonicWord22" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[22]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('22')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord23" style="margin-right: 5px;">24:</label>
<input id="mnemonicWord23" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[23]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('23')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
<div style="display: block; width: 33%;">
<label for="mnemonicWord24" style="margin-right: 5px;">25:</label>
<input id="mnemonicWord24" type="password" style="width: 55%" onInput=${handleMnemonicInput} value=${mnemonicArray[24]} />
<span class="icon ml-1" onClick="${() => handleMnemonicIconClick('24')}">
<i class="fas fa-eye-slash" aria-hidden="true" />
</span>
</div>
</div>
`}

<p class="mt-3 has-text-danger" style="height: 1.5em;">
<p class="pt-2 has-text-danger">
${error !== undefined && error.length > 0 && error}
</p>
</div>
Expand Down