2023-06-09 22:48:48 +02:00
|
|
|
import { hasProperty, Hex, isObject, isStrictHexString } from '@metamask/utils';
|
|
|
|
import { BN } from 'ethereumjs-util';
|
|
|
|
import { cloneDeep, mapKeys } from 'lodash';
|
2023-08-16 21:26:20 +02:00
|
|
|
import log from 'loglevel';
|
2023-06-09 22:48:48 +02:00
|
|
|
|
|
|
|
type VersionedData = {
|
|
|
|
meta: { version: number };
|
|
|
|
data: Record<string, unknown>;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const version = 88;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This migration does a few things:
|
|
|
|
*
|
|
|
|
* - Rebuilds `allNftContracts` and `allNfts` in NftController state to be keyed
|
|
|
|
* by a hex chain ID rather than a decimal chain ID.
|
|
|
|
* - Rebuilds `tokensChainsCache` in TokenListController to be keyed by a hex
|
|
|
|
* chain ID rather than a decimal chain ID.
|
2023-08-02 00:54:02 +02:00
|
|
|
* - Rebuilds `allTokens`, `allDetectedTokens`, and `allIgnoredTokens` in
|
|
|
|
* TokensController to be keyed by a hex chain ID rather than a decimal chain ID.
|
|
|
|
* - removes any entries in `allNftContracts`, `allNfts`, `tokensChainsCache`,
|
|
|
|
* `allTokens`, `allIgnoredTokens` or `allDetectedTokens` that are keyed by the
|
|
|
|
* string 'undefined'
|
2023-06-09 22:48:48 +02:00
|
|
|
*
|
|
|
|
* @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist.
|
|
|
|
* @param originalVersionedData.meta - State metadata.
|
|
|
|
* @param originalVersionedData.meta.version - The current state version.
|
|
|
|
* @param originalVersionedData.data - The persisted MetaMask state, keyed by controller.
|
|
|
|
* @returns Updated versioned MetaMask extension state.
|
|
|
|
*/
|
|
|
|
export async function migrate(
|
|
|
|
originalVersionedData: VersionedData,
|
|
|
|
): Promise<VersionedData> {
|
|
|
|
const versionedData = cloneDeep(originalVersionedData);
|
|
|
|
versionedData.meta.version = version;
|
|
|
|
migrateData(versionedData.data);
|
|
|
|
return versionedData;
|
|
|
|
}
|
|
|
|
|
|
|
|
function migrateData(state: Record<string, unknown>): void {
|
|
|
|
if (hasProperty(state, 'NftController') && isObject(state.NftController)) {
|
|
|
|
const nftControllerState = state.NftController;
|
|
|
|
|
|
|
|
// Migrate NftController.allNftContracts
|
|
|
|
if (
|
|
|
|
hasProperty(nftControllerState, 'allNftContracts') &&
|
|
|
|
isObject(nftControllerState.allNftContracts)
|
|
|
|
) {
|
|
|
|
const { allNftContracts } = nftControllerState;
|
|
|
|
|
|
|
|
if (
|
|
|
|
Object.keys(allNftContracts).every((address) =>
|
|
|
|
isObject(allNftContracts[address]),
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
Object.keys(allNftContracts).forEach((address) => {
|
|
|
|
const nftContractsByChainId = allNftContracts[address];
|
|
|
|
|
|
|
|
if (isObject(nftContractsByChainId)) {
|
2023-08-02 00:54:02 +02:00
|
|
|
for (const chainId of Object.keys(nftContractsByChainId)) {
|
|
|
|
if (chainId === 'undefined' || chainId === undefined) {
|
|
|
|
delete nftContractsByChainId[chainId];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-09 22:48:48 +02:00
|
|
|
allNftContracts[address] = mapKeys(
|
|
|
|
nftContractsByChainId,
|
|
|
|
(_, chainId: string) => toHex(chainId),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2023-08-16 21:26:20 +02:00
|
|
|
} else if (hasProperty(nftControllerState, 'allNftContracts')) {
|
|
|
|
global.sentry?.captureException?.(
|
|
|
|
new Error(
|
|
|
|
`typeof state.NftController.allNftContracts is ${typeof nftControllerState.allNftContracts}`,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log.warn(
|
|
|
|
`typeof state.NftController.allNftContracts is ${typeof nftControllerState.allNftContracts}`,
|
|
|
|
);
|
2023-06-09 22:48:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Migrate NftController.allNfts
|
|
|
|
if (
|
|
|
|
hasProperty(nftControllerState, 'allNfts') &&
|
|
|
|
isObject(nftControllerState.allNfts)
|
|
|
|
) {
|
|
|
|
const { allNfts } = nftControllerState;
|
|
|
|
|
|
|
|
if (Object.keys(allNfts).every((address) => isObject(allNfts[address]))) {
|
|
|
|
Object.keys(allNfts).forEach((address) => {
|
|
|
|
const nftsByChainId = allNfts[address];
|
|
|
|
|
|
|
|
if (isObject(nftsByChainId)) {
|
2023-08-02 00:54:02 +02:00
|
|
|
for (const chainId of Object.keys(nftsByChainId)) {
|
|
|
|
if (chainId === 'undefined' || chainId === undefined) {
|
|
|
|
delete nftsByChainId[chainId];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-09 22:48:48 +02:00
|
|
|
allNfts[address] = mapKeys(nftsByChainId, (_, chainId: string) =>
|
|
|
|
toHex(chainId),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2023-08-16 21:26:20 +02:00
|
|
|
} else if (hasProperty(nftControllerState, 'allNfts')) {
|
|
|
|
global.sentry?.captureException?.(
|
|
|
|
new Error(
|
|
|
|
`typeof state.NftController.allNfts is ${typeof nftControllerState.allNfts}`,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log.warn(
|
|
|
|
`typeof state.NftController.allNfts is ${typeof nftControllerState.allNfts}`,
|
|
|
|
);
|
2023-06-09 22:48:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
state.NftController = nftControllerState;
|
2023-08-16 21:26:20 +02:00
|
|
|
} else if (hasProperty(state, 'NftController')) {
|
|
|
|
global.sentry?.captureException?.(
|
|
|
|
new Error(`typeof state.NftController is ${typeof state.NftController}`),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log.warn(`typeof state.NftController is undefined`);
|
2023-06-09 22:48:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
hasProperty(state, 'TokenListController') &&
|
|
|
|
isObject(state.TokenListController)
|
|
|
|
) {
|
|
|
|
const tokenListControllerState = state.TokenListController;
|
|
|
|
|
|
|
|
// Migrate TokenListController.tokensChainsCache
|
|
|
|
if (
|
|
|
|
hasProperty(tokenListControllerState, 'tokensChainsCache') &&
|
|
|
|
isObject(tokenListControllerState.tokensChainsCache)
|
|
|
|
) {
|
2023-08-02 00:54:02 +02:00
|
|
|
for (const chainId of Object.keys(
|
|
|
|
tokenListControllerState.tokensChainsCache,
|
|
|
|
)) {
|
|
|
|
if (chainId === 'undefined' || chainId === undefined) {
|
|
|
|
delete tokenListControllerState.tokensChainsCache[chainId];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-09 22:48:48 +02:00
|
|
|
tokenListControllerState.tokensChainsCache = mapKeys(
|
|
|
|
tokenListControllerState.tokensChainsCache,
|
|
|
|
(_, chainId: string) => toHex(chainId),
|
|
|
|
);
|
2023-08-16 21:26:20 +02:00
|
|
|
} else if (hasProperty(tokenListControllerState, 'tokensChainsCache')) {
|
|
|
|
global.sentry?.captureException?.(
|
|
|
|
new Error(
|
|
|
|
`typeof state.TokenListController.tokensChainsCache is ${typeof state
|
|
|
|
.TokenListController.tokensChainsCache}`,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log.warn(
|
|
|
|
`typeof state.TokenListController.tokensChainsCache is undefined`,
|
|
|
|
);
|
2023-06-09 22:48:48 +02:00
|
|
|
}
|
2023-08-16 21:26:20 +02:00
|
|
|
} else {
|
2023-08-22 16:06:18 +02:00
|
|
|
console.warn(
|
2023-08-16 21:26:20 +02:00
|
|
|
new Error(
|
|
|
|
`typeof state.TokenListController is ${typeof state.TokenListController}`,
|
|
|
|
),
|
|
|
|
);
|
2023-06-09 22:48:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
hasProperty(state, 'TokensController') &&
|
|
|
|
isObject(state.TokensController)
|
|
|
|
) {
|
|
|
|
const tokensControllerState = state.TokensController;
|
|
|
|
|
|
|
|
// Migrate TokensController.allTokens
|
|
|
|
if (
|
|
|
|
hasProperty(tokensControllerState, 'allTokens') &&
|
|
|
|
isObject(tokensControllerState.allTokens)
|
|
|
|
) {
|
|
|
|
const { allTokens } = tokensControllerState;
|
|
|
|
|
2023-08-02 00:54:02 +02:00
|
|
|
for (const chainId of Object.keys(allTokens)) {
|
|
|
|
if (chainId === 'undefined' || chainId === undefined) {
|
|
|
|
delete allTokens[chainId];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-09 22:48:48 +02:00
|
|
|
tokensControllerState.allTokens = mapKeys(
|
|
|
|
allTokens,
|
|
|
|
(_, chainId: string) => toHex(chainId),
|
|
|
|
);
|
2023-08-16 21:26:20 +02:00
|
|
|
} else if (hasProperty(tokensControllerState, 'allTokens')) {
|
|
|
|
global.sentry?.captureException?.(
|
|
|
|
new Error(
|
|
|
|
`typeof state.TokensController.allTokens is ${typeof tokensControllerState.allTokens}`,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log.warn(
|
|
|
|
`typeof state.TokensController.allTokens is ${typeof tokensControllerState.allTokens}`,
|
|
|
|
);
|
2023-06-09 22:48:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Migrate TokensController.allIgnoredTokens
|
|
|
|
if (
|
|
|
|
hasProperty(tokensControllerState, 'allIgnoredTokens') &&
|
|
|
|
isObject(tokensControllerState.allIgnoredTokens)
|
|
|
|
) {
|
|
|
|
const { allIgnoredTokens } = tokensControllerState;
|
|
|
|
|
2023-08-02 00:54:02 +02:00
|
|
|
for (const chainId of Object.keys(allIgnoredTokens)) {
|
|
|
|
if (chainId === 'undefined' || chainId === undefined) {
|
|
|
|
delete allIgnoredTokens[chainId];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-09 22:48:48 +02:00
|
|
|
tokensControllerState.allIgnoredTokens = mapKeys(
|
|
|
|
allIgnoredTokens,
|
|
|
|
(_, chainId: string) => toHex(chainId),
|
|
|
|
);
|
2023-08-16 21:26:20 +02:00
|
|
|
} else if (hasProperty(tokensControllerState, 'allIgnoredTokens')) {
|
|
|
|
global.sentry?.captureException?.(
|
|
|
|
new Error(
|
|
|
|
`typeof state.TokensController.allIgnoredTokens is ${typeof tokensControllerState.allIgnoredTokens}`,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log.warn(
|
|
|
|
`typeof state.TokensController.allIgnoredTokens is ${typeof tokensControllerState.allIgnoredTokens}`,
|
|
|
|
);
|
2023-06-09 22:48:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Migrate TokensController.allDetectedTokens
|
|
|
|
if (
|
|
|
|
hasProperty(tokensControllerState, 'allDetectedTokens') &&
|
|
|
|
isObject(tokensControllerState.allDetectedTokens)
|
|
|
|
) {
|
|
|
|
const { allDetectedTokens } = tokensControllerState;
|
|
|
|
|
2023-08-02 00:54:02 +02:00
|
|
|
for (const chainId of Object.keys(allDetectedTokens)) {
|
|
|
|
if (chainId === 'undefined' || chainId === undefined) {
|
|
|
|
delete allDetectedTokens[chainId];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-09 22:48:48 +02:00
|
|
|
tokensControllerState.allDetectedTokens = mapKeys(
|
|
|
|
allDetectedTokens,
|
|
|
|
(_, chainId: string) => toHex(chainId),
|
|
|
|
);
|
2023-08-16 21:26:20 +02:00
|
|
|
} else if (hasProperty(tokensControllerState, 'allDetectedTokens')) {
|
|
|
|
global.sentry?.captureException?.(
|
|
|
|
new Error(
|
|
|
|
`typeof state.TokensController.allDetectedTokens is ${typeof tokensControllerState.allDetectedTokens}`,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log.warn(
|
|
|
|
`typeof state.TokensController.allDetectedTokens is ${typeof tokensControllerState.allDetectedTokens}`,
|
|
|
|
);
|
2023-06-09 22:48:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
state.TokensController = tokensControllerState;
|
2023-08-16 21:26:20 +02:00
|
|
|
} else {
|
|
|
|
global.sentry?.captureException?.(
|
|
|
|
new Error(
|
|
|
|
`typeof state.TokensController is ${typeof state.TokensController}`,
|
|
|
|
),
|
|
|
|
);
|
2023-06-09 22:48:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function toHex(value: number | string | BN): Hex {
|
|
|
|
if (typeof value === 'string' && isStrictHexString(value)) {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
const hexString = BN.isBN(value)
|
|
|
|
? value.toString(16)
|
|
|
|
: new BN(value.toString(), 10).toString(16);
|
|
|
|
return `0x${hexString}`;
|
|
|
|
}
|