Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] Block explorer crash #4780

Merged
merged 14 commits into from
Aug 5, 2022
Merged
23 changes: 18 additions & 5 deletions app/components/UI/Transactions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
getNetworkTypeById,
findBlockExplorerForRpc,
getBlockExplorerName,
isMainnetByChainId,
} from '../../../util/networks';
import {
getEtherscanAddressUrl,
Expand Down Expand Up @@ -172,6 +173,7 @@ class Transactions extends PureComponent {
*/
thirdPartyApiMode: PropTypes.bool,
isSigningQRObject: PropTypes.bool,
chainId: PropTypes.string,
};

static defaultProps = {
Expand Down Expand Up @@ -367,18 +369,29 @@ class Transactions extends PureComponent {
const colors = this.context.colors || mockTheme.colors;
const styles = createStyles(colors);

const { chainId } = this.props;
const blockExplorerText = () => {
if (isMainnetByChainId(chainId)) {
return strings('transactions.view_full_history_on_etherscan');
}

if (NO_RPC_BLOCK_EXPLORER !== this.state.rpcBlockExplorer) {
return `${strings(
'transactions.view_full_history_on',
)} ${getBlockExplorerName(this.state.rpcBlockExplorer)}`;
}

return null;
};

return (
<View style={styles.viewMoreBody}>
<TouchableOpacity
onPress={this.viewOnBlockExplore}
style={styles.touchableViewOnEtherscan}
>
<Text reset style={styles.viewOnEtherscan}>
{(this.state.rpcBlockExplorer &&
`${strings(
'transactions.view_full_history_on',
)} ${getBlockExplorerName(this.state.rpcBlockExplorer)}`) ||
strings('transactions.view_full_history_on_etherscan')}
{blockExplorerText()}
</Text>
</TouchableOpacity>
</View>
Expand Down
26 changes: 23 additions & 3 deletions app/components/Views/Asset/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import { safeToChecksumAddress } from '../../../util/address';
import { addAccountTimeFlagFilter } from '../../../util/transactions';
import { toLowerCaseEquals } from '../../../util/general';
import { ThemeContext, mockTheme } from '../../../util/theme';
import {
findBlockExplorerForRpc,
isMainnetByChainId,
} from '../../../util/networks';

const createStyles = (colors) =>
StyleSheet.create({
Expand Down Expand Up @@ -91,6 +95,8 @@ class Asset extends PureComponent {
* Object that represents the current route info like params passed to it
*/
route: PropTypes.object,
rpcTarget: PropTypes.string,
frequentRpcList: PropTypes.array,
};

state = {
Expand All @@ -111,17 +117,28 @@ class Asset extends PureComponent {
navAddress = undefined;

updateNavBar = () => {
const { navigation, route } = this.props;
const { navigation, route, chainId, rpcTarget, frequentRpcList } =
this.props;
const colors = this.context.colors || mockTheme.colors;
const { isETH } = route.params;
const isNativeToken = route.params.isETH;
const isMainnet = isMainnetByChainId(chainId);
const blockExplorer = findBlockExplorerForRpc(rpcTarget, frequentRpcList);

const shouldShowMoreOptionsInNavBar =
isMainnet || !isNativeToken || (isNativeToken && blockExplorer);

navigation.setOptions(
getNetworkNavbarOptions(
route.params?.symbol ?? '',
false,
navigation,
colors,
() => navigation.navigate('AssetOptions', { isNativeCurrency: isETH }),
shouldShowMoreOptionsInNavBar
? () =>
navigation.navigate('AssetOptions', {
isNativeCurrency: isNativeToken,
})
: undefined,
true,
),
);
Expand Down Expand Up @@ -407,6 +424,9 @@ const mapStateToProps = (state) => ({
tokens: state.engine.backgroundState.TokensController.tokens,
transactions: state.engine.backgroundState.TransactionController.transactions,
thirdPartyApiMode: state.privacy.thirdPartyApiMode,
rpcTarget: state.engine.backgroundState.NetworkController.provider.rpcTarget,
frequentRpcList:
state.engine.backgroundState.PreferencesController.frequentRpcList,
});

export default connect(mapStateToProps)(Asset);
12 changes: 6 additions & 6 deletions app/components/Views/AssetOptions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const AssetOptions = (props: Props) => {
});
};

const openOnEtherscan = () => {
const openOnBlockExplorer = () => {
let url = '';
const title = new URL(explorer.baseUrl).hostname;
if (isNativeCurrency) {
Expand All @@ -104,12 +104,12 @@ const AssetOptions = (props: Props) => {
};

const renderOptions = () => {
const options: Option[] = [
{
const options: Option[] = [];
Boolean(explorer.baseUrl) &&
options.push({
label: strings('asset_details.options.view_on_block'),
onPress: openOnEtherscan,
},
];
onPress: openOnBlockExplorer,
});
!isNativeCurrency &&
options.push({
label: strings('asset_details.options.token_details'),
Expand Down
24 changes: 20 additions & 4 deletions app/util/networks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,19 +165,35 @@ export function isprivateConnection(hostname) {
/**
* Returns custom block explorer for specific rpcTarget
*
* @param {string} rpcTarget
* @param {string} rpcTargetUrl
* @param {array<object>} frequentRpcList
*/
export function findBlockExplorerForRpc(rpcTarget, frequentRpcList) {
const frequentRpc = frequentRpcList.find(
({ rpcUrl }) => rpcTarget === rpcUrl,
export function findBlockExplorerForRpc(rpcTargetUrl, frequentRpcList) {
const frequentRpc = frequentRpcList.find(({ rpcUrl }) =>
compareRpcUrls(rpcUrl, rpcTargetUrl),
);
if (frequentRpc) {
return frequentRpc.rpcPrefs && frequentRpc.rpcPrefs.blockExplorerUrl;
}
return undefined;
}

/**
* Returns a boolean indicating if both URLs have the same host
*
* @param {string} rpcOne
* @param {string} rpcTwo
*/
export function compareRpcUrls(rpcOne, rpcTwo) {
// First check that both objects are of the type string
if (typeof rpcOne === 'string' && typeof rpcTwo === 'string') {
const rpcUrlOne = new URL(rpcOne);
const rpcUrlTwo = new URL(rpcTwo);
return rpcUrlOne.host === rpcUrlTwo.host;
}
return false;
}

/**
* From block explorer url, get rendereable name or undefined
*
Expand Down
72 changes: 68 additions & 4 deletions app/util/networks/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
getNetworkName,
getAllNetworks,
getNetworkTypeById,
findBlockExplorerForRpc,
compareRpcUrls,
} from '.';
import {
MAINNET,
Expand All @@ -16,7 +18,7 @@ import {
NETWORK_ERROR_UNKNOWN_NETWORK_ID,
} from '../../../app/constants/error';

describe('getAllNetworks', () => {
describe('NetworkUtils::getAllNetworks', () => {
const allNetworks = getAllNetworks();
it('should get all networks', () => {
expect(allNetworks.includes(MAINNET)).toEqual(true);
Expand All @@ -28,7 +30,7 @@ describe('getAllNetworks', () => {
});
});

describe('isMainNet', () => {
describe('NetworkUtils::isMainNet', () => {
it(`should return true if the selected network is ${MAINNET}`, () => {
expect(isMainNet('1')).toEqual(true);
expect(
Expand All @@ -53,7 +55,7 @@ describe('isMainNet', () => {
});
});

describe('getNetworkName', () => {
describe('NetworkUtils::getNetworkName', () => {
it(`should get network name for ${MAINNET} id`, () => {
const main = getNetworkName(String(1));
expect(main).toEqual(MAINNET);
Expand All @@ -75,7 +77,7 @@ describe('getNetworkName', () => {
});
});

describe('getNetworkTypeById', () => {
describe('NetworkUtils::getNetworkTypeById', () => {
it('should get network type by Id', () => {
const type = getNetworkTypeById(42);
expect(type).toEqual(KOVAN);
Expand All @@ -98,3 +100,65 @@ describe('getNetworkTypeById', () => {
}
});
});

describe('NetworkUtils::findBlockExplorerForRpc', () => {
const frequentRpcListMock = [
{
chainId: '137',
nickname: 'Polygon Mainnet',
rpcPrefs: {
blockExplorerUrl: 'https://polygonscan.com',
},
rpcUrl: 'https://polygon-mainnet.infura.io/v3',
ticker: 'MATIC',
},
{
chainId: '56',
nickname: 'Binance Smart Chain',
rpcPrefs: {},
rpcUrl: 'https://bsc-dataseed.binance.org/',
ticker: 'BNB',
},
{
chainId: '10',
nickname: 'Optimism',
rpcPrefs: { blockExplorerUrl: 'https://optimistic.ethereum.io' },
rpcUrl: 'https://mainnet.optimism.io/',
ticker: 'ETH',
},
];

it('should find the block explorer is it exists', () => {
const mockRpcUrl = frequentRpcListMock[2].rpcUrl;
const expectedBlockExplorer =
frequentRpcListMock[2].rpcPrefs.blockExplorerUrl;
expect(findBlockExplorerForRpc(mockRpcUrl, frequentRpcListMock)).toBe(
expectedBlockExplorer,
);
});
it('should return undefined if the block explorer does not exist', () => {
const mockRpcUrl = frequentRpcListMock[1].rpcUrl;
expect(findBlockExplorerForRpc(mockRpcUrl, frequentRpcListMock)).toBe(
undefined,
);
});
it('should return undefined if the RPC does not exist', () => {
const mockRpcUrl = 'https://arb1.arbitrum.io/rpc';
expect(findBlockExplorerForRpc(mockRpcUrl, frequentRpcListMock)).toBe(
undefined,
);
});
});

describe('NetworkUtils::compareRpcUrls', () => {
it('should return true if both URLs have the same host', () => {
const mockRpcOne = 'https://mainnet.optimism.io/';
const mockRpcTwo = 'https://mainnet.optimism.io/d03910331458';
expect(compareRpcUrls(mockRpcOne, mockRpcTwo)).toBe(true);
});
it('should return false if both URLs have the same host', () => {
const mockRpcOne = 'https://bsc-dataseed.binance.org/';
const mockRpcTwo = 'https://mainnet.optimism.io/d03910331458';
expect(compareRpcUrls(mockRpcOne, mockRpcTwo)).toBe(false);
});
});