Skip to content

Commit

Permalink
feat: remove selectIdentities in favour of selectInternalAccounts (
Browse files Browse the repository at this point in the history
…#9724)

## **Description**

1. What is the reason for the change?
In an effort to make MetaMask multi chain we are migrating from an
address based accounts to account id based account. In
#8759 previous PR we
integrated the accounts controller which stores metadata like the
account name, address and supported methods. This pr migrates all the
use of `selectIdentities` to use the accounts controller as the source
of truth for all accounts information (address, name etc).
2. What is the improvement/solution?
- find all use of `selectIdentities` and replace it with
`selectInternalAccounts` and/or
`selectSelectedInternalAccountChecksummedAddress`
- These selectors are already in use in `main` and are defined
[here](https://github.com/MetaMask/metamask-mobile/blob/main/app/selectors/accountsController.ts#L11-L32).
- tests for these utils can be found
[here](https://github.com/MetaMask/metamask-mobile/blob/main/app/selectors/accountsController.test.ts#L86).
- Note that ENS resolution in the send flow is not working but has been
broken in production for a while. ENS resolution in the accounts list
and dapp transactions are working as expected.


## **Related issues**

Fixes: MetaMask/accounts-planning#410

## Q.A/Review check list
- [QA
builds](https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/a4fca9c3-11d5-4654-80ce-4cdedd638fe6?tab=artifacts)
- [ ] Verify that there is no use of `selectIdentities` in the app
- [ ] Verify that no functions are expecting `identities` as params

## **Manual testing steps**

#### Fresh install
1. Launch wallet
2. create a new account
4. once on the wallet view, add a new account
5. create a custom name for your account(s)
6. swipe away the app and re open
7. login
8. the selected account should be same one that was previously selected
9. all addresses should be in [checksum
format](https://coincodex.com/article/2078/ethereum-address-checksum-explained/)
10. connect the portfolio dapp via the portfolio button on the home
screen.
11. Click connect on the portfolio dapp
12. The first address in the address picker should be match the selected
address on the wallet view and be in checksum format

#### Upgrade path

1. Use an existing wallet that has multiple accounts created/imported
2. Change the names of your accounts so that they have custom names
3. "upgrade" your wallet to this branch
4. the selected account should be the same as it was on the previous
branch
5. all of the account data should remain the same (names and balances)

#### Send flow

1. Start a send transaction
2. Verify that the account names and addresses are consistent in the
relevant screens of the flow

#### Hardware wallet connection and confirmation

1. Connect a hardware wallet device
2. Go to the MetaMask test dapp and try to connect an account
3. Verify that the account names and addresses are consistent

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **After**

#### Basic usage

https://github.com/MetaMask/metamask-mobile/assets/22918444/c5df59e1-05e6-43de-9efd-9a479aa342f7


#### Hardware account name preserved when connecting and confirming with
dapps

https://github.com/MetaMask/metamask-mobile/assets/22918444/2931b041-7e69-43ff-acb1-c4405e5b526b


#### Complex usage
- Ledger account connected
- Imported account with ens (orangefox.eth)
- custom account name
- derived account
- Tested....
    - send flow has the correct names ✅ 
- wallet list has the correct names, even after a force reload (and
correct selected account) ✅
- dapp connection request has the correct names for all accounts
including hardware and ENS account ✅
    - dapp transactions have correct names for all account types


https://github.com/MetaMask/metamask-mobile/assets/22918444/254aeb44-6301-463e-86b6-01a12d56d967


## **Pre-merge author checklist**

- [ ] I’ve followed [MetaMask Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
  • Loading branch information
owencraston authored Jul 3, 2024
1 parent 25e9079 commit 466f57c
Show file tree
Hide file tree
Showing 56 changed files with 1,141 additions and 1,127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ exports[`AccountApproval should render correctly 1`] = `
}
}
>
0xC496...a756
Account 2
</Text>
<Text
accessibilityRole="text"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,6 @@ const mockInitialState = {
'0x326836cc6cd09B5aa59B81A7F72F25FcC0136b95': '0x5',
},
},
PreferencesController: {
selectedAddress: MOCK_ADDRESS_1,
identities: {
[MOCK_ADDRESS_1]: {
address: MOCK_ADDRESS_1,
name: 'Account 1',
},
[MOCK_ADDRESS_2]: {
address: MOCK_ADDRESS_2,
name: 'Account 2',
},
},
},
AccountsController: MOCK_ACCOUNTS_CONTROLLER_STATE,
},
},
Expand Down
109 changes: 73 additions & 36 deletions app/components/UI/AccountFromToInfoCard/AccountFromToInfoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
selectChainId,
selectTicker,
} from '../../../selectors/networkController';
import { selectIdentities } from '../../../selectors/preferencesController';
import { collectConfusables } from '../../../util/confusables';
import { decodeTransferData } from '../../../util/transactions';
import { doENSReverseLookup } from '../../../util/ENSUtils';
Expand All @@ -20,10 +19,13 @@ import useExistingAddress from '../../hooks/useExistingAddress';
import { AddressFrom, AddressTo } from '../AddressInputs';
import createStyles from './AccountFromToInfoCard.styles';
import { AccountFromToInfoCardProps } from './AccountFromToInfoCard.types';
import { selectInternalAccounts } from '../../../selectors/accountsController';
import { toLowerCaseEquals } from '../../../util/general';
import { RootState } from '../../../reducers';

const AccountFromToInfoCard = (props: AccountFromToInfoCardProps) => {
const {
identities,
internalAccounts,
chainId,
onPressFromAddressIcon,
ticker,
Expand All @@ -33,7 +35,6 @@ const AccountFromToInfoCard = (props: AccountFromToInfoCardProps) => {
const {
transaction: { from: rawFromAddress, data, to },
transactionTo,
transactionToName,
transactionFromName,
selectedAsset,
ensRecipient,
Expand All @@ -56,50 +57,88 @@ const AccountFromToInfoCard = (props: AccountFromToInfoCardProps) => {
);

useEffect(() => {
if (!fromAddress) {
return;
}
if (transactionFromName) {
setFromAccountName(transactionFromName);
return;
}
(async () => {
const fetchFromAccountDetails = async () => {
if (!fromAddress) {
return;
}

if (transactionFromName) {
if (fromAccountName !== transactionFromName) {
setFromAccountName(transactionFromName);
}
return;
}

const fromEns = await doENSReverseLookup(fromAddress, chainId);
if (fromEns) {
setFromAccountName(fromEns);
if (fromAccountName !== fromEns) {
setFromAccountName(fromEns);
}
} else {
const { name: fromName } = identities[fromAddress];
setFromAccountName(fromName);
const accountWithMatchingFromAddress = internalAccounts.find(
(account) => toLowerCaseEquals(account.address, fromAddress),
);

const newName = accountWithMatchingFromAddress
? accountWithMatchingFromAddress.metadata.name
: fromAddress;

if (fromAccountName !== newName) {
setFromAccountName(newName);
}
}
})();
}, [fromAddress, identities, transactionFromName, chainId]);
};

fetchFromAccountDetails();
}, [
fromAddress,
transactionFromName,
chainId,
internalAccounts,
fromAccountName,
]);

useEffect(() => {
if (existingToAddress) {
setToAccountName(existingToAddress?.name);
return;
}
(async () => {
const fetchAccountDetails = async () => {
if (existingToAddress) {
if (toAccountName !== existingToAddress.name) {
setToAccountName(existingToAddress.name);
}
return;
}

const toEns = await doENSReverseLookup(toAddress, chainId);
if (toEns) {
setToAccountName(toEns);
} else if (identities[toAddress]) {
const { name: toName } = identities[toAddress];
setToAccountName(toName);
if (toAccountName !== toEns) {
setToAccountName(toEns);
}
} else {
const accountWithMatchingToAddress = internalAccounts.find((account) =>
toLowerCaseEquals(account.address, toAddress),
);

const newName = accountWithMatchingToAddress
? accountWithMatchingToAddress.metadata.name
: toAddress;

if (toAccountName !== newName) {
setToAccountName(newName);
}
}
})();
}, [existingToAddress, identities, chainId, toAddress, transactionToName]);
};

fetchAccountDetails();
}, [existingToAddress, chainId, toAddress, internalAccounts, toAccountName]);

useEffect(() => {
const accountNames =
(identities &&
Object.keys(identities).map((hash) => identities[hash].name)) ||
[];
const accountNames = internalAccounts.map(
(account) => account.metadata.name,
);
const isOwnAccount = ensRecipient && accountNames.includes(ensRecipient);
if (ensRecipient && !isOwnAccount) {
setConfusableCollection(collectConfusables(ensRecipient));
}
}, [identities, ensRecipient]);
}, [internalAccounts, ensRecipient]);

useEffect(() => {
let toAddr;
Expand Down Expand Up @@ -172,10 +211,8 @@ const AccountFromToInfoCard = (props: AccountFromToInfoCardProps) => {
);
};

// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mapStateToProps = (state: any) => ({
identities: selectIdentities(state),
const mapStateToProps = (state: RootState) => ({
internalAccounts: selectInternalAccounts(state),
chainId: selectChainId(state),
ticker: selectTicker(state),
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
interface Identity {
address: string;
name: string;
}

type Identities = Record<string, Identity>;
import { InternalAccount } from '@metamask/keyring-api';

interface SelectedAsset {
isETH: boolean;
Expand All @@ -26,7 +21,7 @@ export interface Transaction {
}

export interface AccountFromToInfoCardProps {
identities: Identities;
internalAccounts: InternalAccount[];
chainId: string;
onPressFromAddressIcon?: () => void;
ticker?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ exports[`AccountInfoCard should match snapshot 1`] = `
}
}
>
0xC495...D272
Account 1
</Text>
<Text
accessibilityRole="text"
Expand Down
12 changes: 6 additions & 6 deletions app/components/UI/AccountInfoCard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
selectCurrentCurrency,
} from '../../../selectors/currencyRateController';
import { selectTicker } from '../../../selectors/networkController';
import { selectIdentities } from '../../../selectors/preferencesController';
import { fontStyles } from '../../../styles/common';
import {
getLabelTextByAddress,
Expand All @@ -32,6 +31,7 @@ import {
} from '../../../util/transactions';
import ApproveTransactionHeader from '../../Views/confirmations/components/ApproveTransactionHeader';
import Identicon from '../Identicon';
import { selectInternalAccounts } from '../../../selectors/accountsController';

const createStyles = (colors) =>
StyleSheet.create({
Expand Down Expand Up @@ -111,9 +111,9 @@ class AccountInfoCard extends PureComponent {
*/
accounts: PropTypes.object,
/**
* List of accounts from the PreferencesController
* List of accounts from the AccountsController
*/
identities: PropTypes.object,
internalAccounts: PropTypes.array,
/**
* A number that specifies the ETH/USD conversion rate
*/
Expand Down Expand Up @@ -142,7 +142,7 @@ class AccountInfoCard extends PureComponent {
render() {
const {
accounts,
identities,
internalAccounts,
conversionRate,
currentCurrency,
operation,
Expand All @@ -162,7 +162,7 @@ class AccountInfoCard extends PureComponent {
? hexToBN(accounts[fromAddress].balance)
: 0;
const balance = `${renderFromWei(weiBalance)} ${getTicker(ticker)}`;
const accountLabel = renderAccountName(fromAddress, identities);
const accountLabel = renderAccountName(fromAddress, internalAccounts);
const address = renderShortAddress(fromAddress);
const dollarBalance = showFiatBalance
? weiToFiat(weiBalance, conversionRate, currentCurrency, 2)?.toUpperCase()
Expand Down Expand Up @@ -248,7 +248,7 @@ class AccountInfoCard extends PureComponent {

const mapStateToProps = (state) => ({
accounts: selectAccounts(state),
identities: selectIdentities(state),
internalAccounts: selectInternalAccounts(state),
conversionRate: selectConversionRate(state),
currentCurrency: selectCurrentCurrency(state),
ticker: selectTicker(state),
Expand Down
33 changes: 19 additions & 14 deletions app/components/UI/AccountOverview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,17 @@ import AppConstants from '../../../core/AppConstants';
import Engine from '../../../core/Engine';
import { selectChainId } from '../../../selectors/networkController';
import { selectCurrentCurrency } from '../../../selectors/currencyRateController';
import { selectIdentities } from '../../../selectors/preferencesController';
import { selectSelectedInternalAccountChecksummedAddress } from '../../../selectors/accountsController';
import {
selectInternalAccounts,
selectSelectedInternalAccountChecksummedAddress,
} from '../../../selectors/accountsController';
import { createAccountSelectorNavDetails } from '../../Views/AccountSelector';
import Text, {
TextVariant,
} from '../../../component-library/components/Texts/Text';
import { withMetricsAwareness } from '../../../components/hooks/useMetrics';
import { isPortfolioUrl } from '../../../util/url';
import { toLowerCaseEquals } from '../../../util/general';

const createStyles = (colors) =>
StyleSheet.create({
Expand Down Expand Up @@ -157,9 +160,9 @@ class AccountOverview extends PureComponent {
*/
selectedAddress: PropTypes.string,
/**
/* Identities object required to get account name
/* InternalAccounts object required to get account name
*/
identities: PropTypes.object,
internalAccounts: PropTypes.object,
/**
* Object that represents the selected account
*/
Expand Down Expand Up @@ -234,8 +237,8 @@ class AccountOverview extends PureComponent {
input = React.createRef();

componentDidMount = () => {
const { identities, selectedAddress, onRef } = this.props;
const accountLabel = renderAccountName(selectedAddress, identities);
const { internalAccounts, selectedAddress, onRef } = this.props;
const accountLabel = renderAccountName(selectedAddress, internalAccounts);
this.setState({ accountLabel });
onRef && onRef(this);
InteractionManager.runAfterInteractions(() => {
Expand All @@ -259,16 +262,18 @@ class AccountOverview extends PureComponent {
}

setAccountLabel = () => {
const { selectedAddress, identities } = this.props;
const { selectedAddress, internalAccounts } = this.props;
const { accountLabel } = this.state;

const lastAccountLabel = identities[selectedAddress].name;
const accountWithMatchingToAddress = internalAccounts.find((account) =>
toLowerCaseEquals(account.address, selectedAddress),
);

Engine.setAccountLabel(
selectedAddress,
this.isAccountLabelDefined(accountLabel)
? accountLabel
: lastAccountLabel,
: accountWithMatchingToAddress.metadata.name,
);
this.setState({ accountLabelEditable: false });
};
Expand All @@ -278,17 +283,17 @@ class AccountOverview extends PureComponent {
};

setAccountLabelEditable = () => {
const { identities, selectedAddress } = this.props;
const accountLabel = renderAccountName(selectedAddress, identities);
const { internalAccounts, selectedAddress } = this.props;
const accountLabel = renderAccountName(selectedAddress, internalAccounts);
this.setState({ accountLabelEditable: true, accountLabel });
setTimeout(() => {
this.input && this.input.current && this.input.current.focus();
}, 100);
};

cancelAccountLabelEdition = () => {
const { identities, selectedAddress } = this.props;
const accountLabel = renderAccountName(selectedAddress, identities);
const { internalAccounts, selectedAddress } = this.props;
const accountLabel = renderAccountName(selectedAddress, internalAccounts);
this.setState({ accountLabelEditable: false, accountLabel });
};

Expand Down Expand Up @@ -461,7 +466,7 @@ class AccountOverview extends PureComponent {

const mapStateToProps = (state) => ({
selectedAddress: selectSelectedInternalAccountChecksummedAddress(state),
identities: selectIdentities(state),
internalAccounts: selectInternalAccounts(state),
currentCurrency: selectCurrentCurrency(state),
chainId: selectChainId(state),
browserTabs: state.browser.tabs,
Expand Down
Loading

0 comments on commit 466f57c

Please sign in to comment.