Skip to content

Commit

Permalink
feat: Finalize wallet setup for funded zero balance account
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick1904 committed May 20, 2022
1 parent 759ba61 commit 98db72c
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 6 deletions.
7 changes: 4 additions & 3 deletions features/flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@
"lastEditedAt": "2022-05-06T17:30:49.429Z"
},
"testnet": {
"enabled": true,
"enabled": false,
"lastEditedBy": "Patrick Tajima",
"lastEditedAt": "2022-05-06T17:30:49.430Z"
},
Expand All @@ -285,14 +285,15 @@
"enabled": false,
"lastEditedBy": "Patrick Tajima",
"lastEditedAt": "2022-05-06T17:30:49.430Z"

},
"testnet_STAGING": {
"enabled": true,
"enabled": false,
"lastEditedBy": "Patrick Tajima",
"lastEditedAt": "2022-05-06T17:30:49.430Z"
},
"testnet_NEARORG": {
"enabled": true,
"enabled": false,
"lastEditedBy": "esaminu",
"lastEditedAt": "2022-05-09T21:50:59.559Z"
},
Expand Down
5 changes: 5 additions & 0 deletions packages/frontend/src/components/profile/Profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Translate } from 'react-localize-redux';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';

import { IMPORT_ZERO_BALANCE_ACCOUNT } from '../../../../../features';
import { IS_MAINNET, MIN_BALANCE_FOR_GAS } from '../../config';
import { useAccount } from '../../hooks/allAccounts';
import { Mixpanel } from '../../mixpanel/index';
Expand Down Expand Up @@ -47,6 +48,7 @@ import MobileSharingWrapper from './mobile_sharing/MobileSharingWrapper';
import RecoveryContainer from './Recovery/RecoveryContainer';
import RemoveAccountWrapper from './remove_account/RemoveAccountWrapper';
import TwoFactorAuth from './two_factor/TwoFactorAuth';
import { ZeroBalanceAccountWrapper } from './zero_balance/ZeroBalanceAccountWrapper';

const { fetchRecoveryMethods } = recoveryMethodsActions;

Expand Down Expand Up @@ -309,6 +311,9 @@ export function Profile({ match }) {
</div>
}
</div>
{IMPORT_ZERO_BALANCE_ACCOUNT &&
<ZeroBalanceAccountWrapper/>
}
</StyledContainer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import { Translate } from 'react-localize-redux';
import styled from 'styled-components';

import FormButton from '../../common/FormButton';
import Modal from '../../common/modal/Modal';

const Container = styled.div`
&&&&& {
padding: 15px 0 10px 0;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
h3 {
margin: 15px 0;
}
> button {
margin-top: 25px;
width: 100%;
}
}
`;

export function AddLedgerKeyModal({
isOpen,
onClickAddLedgerKey,
onClose,
finishingLedgerKeySetup
}) {
return (
<Modal
id='add-ledger-key-modal'
isOpen={isOpen}
onClose={onClose}
modalSize='sm'
>
<Container>
<h3><Translate id='zeroBalance.ledgerModal.title' /></h3>
{
finishingLedgerKeySetup
? <p><Translate id='zeroBalance.ledgerModal.confirmOnLedger' /></p>
: <p><Translate id='zeroBalance.ledgerModal.desc' /></p>
}
<FormButton
onClick={onClickAddLedgerKey}
sending={finishingLedgerKeySetup}
disabled={finishingLedgerKeySetup}
>
<Translate id='zeroBalance.ledgerModal.addLedgerKey' />
</FormButton>
</Container>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';

import { refreshAccount } from '../../../redux/actions/account';
import { showCustomAlert } from '../../../redux/actions/status';
import { selectAccountExists, selectAccountFullAccessKeys, selectAccountId } from '../../../redux/slices/account';
import { finishLocalSetupForZeroBalanceAccount } from '../../../redux/slices/account/createAccountThunks';
import { actions as ledgerActions, selectLedgerConnectionAvailable } from '../../../redux/slices/ledger';
import { wallet } from '../../../utils/wallet';
import { AddLedgerKeyModal } from './AddLedgerKeyModal';


const { handleShowConnectModal } = ledgerActions;

export function ZeroBalanceAccountWrapper() {
const dispatch = useDispatch();

const [showAddLedgerKeyModal, setShowAddLedgerKeyModal] = useState(false);
const [finishingLedgerKeySetup, setFinishingLedgerKeySetup] = useState(false);

const ledgerConnectionAvailable = useSelector(selectLedgerConnectionAvailable);
const accountId = useSelector(selectAccountId);
const accountExists = useSelector(selectAccountExists);
const accountFullAccessKeys = useSelector(selectAccountFullAccessKeys);

const isLedgerKey = accountFullAccessKeys[0]?.meta.type === 'ledger';

useEffect(() => {
if (accountExists && accountFullAccessKeys.length === 1) {
if (isLedgerKey) {
handleCheckLedgerStatus();
} else {
handleAddLocalAccessKeyPhrase();
}
}
}, [accountExists]);

const handleCheckLedgerStatus = async () => {
const localKey = await wallet.getLocalSecretKey(accountId);
if (!localKey) {
setShowAddLedgerKeyModal(true);
}
};

const handleAddLocalAccessKeyLedger = async () => {
try {
await dispatch(finishLocalSetupForZeroBalanceAccount({
implicitAccountId: accountId,
recoveryMethod: 'ledger',
}));
dispatch(showCustomAlert({
success: true,
messageCodeHeader: 'zeroBalance.addLedgerKey.success.header',
messageCode: 'zeroBalance.addLedgerKey.success.message'
}));
} catch (e) {
dispatch(showCustomAlert({
success: false,
messageCodeHeader: 'zeroBalance.addLedgerKey.error.header',
messageCode: 'zeroBalance.addLedgerKey.error.message',
}));
}

dispatch(refreshAccount());
};

const handleAddLocalAccessKeyPhrase = async () => {
try {
await dispatch(finishLocalSetupForZeroBalanceAccount({
implicitAccountId: accountId,
recoveryMethod: 'phrase',
}));
dispatch(showCustomAlert({
success: true,
messageCodeHeader: 'zeroBalance.addPhraseKey.success.header',
messageCode: 'zeroBalance.addPhraseKey.success.message'
}));
} catch (e) {
dispatch(showCustomAlert({
success: false,
messageCodeHeader: 'zeroBalance.addPhraseKey.error.header',
messageCode: 'zeroBalance.addPhraseKey.error.message',
}));
}

dispatch(refreshAccount());
};

if (showAddLedgerKeyModal) {
return (
<AddLedgerKeyModal
isOpen={showAddLedgerKeyModal}
onClose={() => setShowAddLedgerKeyModal(false)}
finishingLedgerKeySetup={finishingLedgerKeySetup}
onClickAddLedgerKey={async () => {
if (!ledgerConnectionAvailable) {
dispatch(handleShowConnectModal());
} else {
setFinishingLedgerKeySetup(true);
await handleAddLocalAccessKeyLedger();
setShowAddLedgerKeyModal(false);
setFinishingLedgerKeySetup(false);
}
}}
/>
);
}
return null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import { PublicKey } from 'near-api-js/lib/utils';
import { KeyType } from 'near-api-js/lib/utils/key_pair';

import * as Config from '../../../config';
import { actions as ledgerActions } from '../../../redux/slices/ledger';
import sendJson from '../../../tmp_fetch_send_json';
import { setReleaseNotesClosed } from '../../../utils/localStorage';
import { setReleaseNotesClosed, getLedgerHDPath } from '../../../utils/localStorage';
import { CONTRACT_CREATE_ACCOUNT_URL, FUNDED_ACCOUNT_CREATE_URL, IDENTITY_FUNDED_ACCOUNT_CREATE_URL, RELEASE_NOTES_MODAL_VERSION, wallet } from '../../../utils/wallet';
import { WalletError } from '../../../utils/walletError';
import { finishAccountSetup } from '../../actions/account';
import { SLICE_NAME } from './';

const {
signInWithLedger
} = ledgerActions;

const {
RECAPTCHA_ENTERPRISE_SITE_KEY,
Expand Down Expand Up @@ -187,3 +191,31 @@ export const finishSetupImplicitAccount = createAsyncThunk(
await dispatch(addLocalKeyAndFinishSetup({ accountId: implicitAccountId, recoveryMethod, publicKey })).unwrap();
}
);

export const finishLocalSetupForZeroBalanceAccount = createAsyncThunk(
`${SLICE_NAME}/finishLocalSetupForZeroBalanceAccount`,
async ({
implicitAccountId,
recoveryMethod
}, { dispatch }) => {
try {
if (recoveryMethod === 'ledger') {
const ledgerHDPath = getLedgerHDPath(implicitAccountId);
await dispatch(signInWithLedger({ path: ledgerHDPath })).unwrap();
} else {
const account = await wallet.getAccount(implicitAccountId);
const accessKeys = await account.getAccessKeys();
const fullAccessKeys = accessKeys.filter((it) => it.access_key && it.access_key.permission === 'FullAccess');
if (fullAccessKeys.length === 1) {
const newKeyPair = KeyPair.fromRandom('ed25519');
const newPublicKey = newKeyPair.publicKey;
await wallet.addNewAccessKeyToAccount(implicitAccountId, newPublicKey);
await wallet.saveAccount(implicitAccountId, newKeyPair);
}
}
} catch (e) {
throw new WalletError(e, 'addAccessKeyZeroBalanceAccountSetup.error');
}

}
);
33 changes: 32 additions & 1 deletion packages/frontend/src/translations/en.global.json
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,9 @@
"addAccessKey": {
"error": "An error has occurred.<br />To access your account, please enter the passphrase from the previous step below."
},
"addAccessKeyZeroBalanceAccountSetup": {
"error": "Something went wrong while finishing import of your now active account. Please re-import your account in the wallet."
},
"addAccessKeySeedPhrase": {
"errorSecond": "An error has occurred.<br />The passphrases was not added to your account. Please try again."
},
Expand Down Expand Up @@ -1738,5 +1741,33 @@
"noWithdraw": "Unable to withdraw pending balance from validator"
}
},
"warning": "Warning"
"warning": "Warning",
"zeroBalance": {
"ledgerModal": {
"desc": "Add a Ledger limited access key to the wallet to manage multiple recovery methods.",
"confirmOnLedger": "Confirm the action on your Ledger device.",
"title": "Your account is now active!",
"addLedgerKey": "Add Ledger Key"
},
"addLedgerKey": {
"success": {
"header": "Ledger access key added successfully!",
"message": "A new Ledger key was added to your account. You can now manage your accounts recovery methods using your Ledger device."
},
"error": {
"header": "Something went wrong",
"message": "We were unable to add an additional Ledger key to your account. Please try again or re-import your Ledger account."
}
},
"addPhraseKey": {
"success": {
"header": "Your account has been successfully imported!",
"message": "Now that your account is active, a new access key has been added to your account to sign transactions in the wallet."
},
"error": {
"header": "Something went wrong",
"message": "We were unable to add a new access key to your account. Please re-import your account to add a new wallet key."
}
}
}
}
2 changes: 1 addition & 1 deletion packages/frontend/src/utils/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class Wallet {

async getLocalSecretKey(accountId) {
const localKeyPair = await this.keyStore.getKey(NETWORK_ID, accountId);
return localKeyPair.toString();
return localKeyPair ? localKeyPair.toString() : null;
}

async getLedgerKey(accountId) {
Expand Down

0 comments on commit 98db72c

Please sign in to comment.