1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Additional incoming transactions support (#14219)

This commit is contained in:
tgmichel 2023-02-14 19:35:42 +01:00 committed by GitHub
parent 3ec042fa68
commit 3a00b5ab54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 136 additions and 45 deletions

View File

@ -9,11 +9,7 @@ import {
TransactionType,
TransactionStatus,
} from '../../../shared/constants/transaction';
import {
CHAIN_IDS,
CHAIN_ID_TO_NETWORK_ID_MAP,
CHAIN_ID_TO_TYPE_MAP,
} from '../../../shared/constants/network';
import { ETHERSCAN_SUPPORTED_NETWORKS } from '../../../shared/constants/network';
import { bnToHex } from '../../../shared/modules/conversion.utils';
const fetchWithTimeout = getFetchWithTimeout();
@ -46,15 +42,9 @@ const fetchWithTimeout = getFetchWithTimeout();
* This controller is responsible for retrieving incoming transactions. Etherscan is polled once every block to check
* for new incoming transactions for the current selected account on the current network
*
* Note that only the built-in Infura networks are supported (i.e. anything in `INFURA_PROVIDER_TYPES`). We will not
* attempt to retrieve incoming transactions on any custom RPC endpoints.
* Note that only Etherscan-compatible networks are supported. We will not attempt to retrieve incoming transactions
* on non-compatible custom RPC endpoints.
*/
const etherscanSupportedNetworks = [
CHAIN_IDS.GOERLI,
CHAIN_IDS.MAINNET,
CHAIN_IDS.SEPOLIA,
];
export default class IncomingTransactionsController {
constructor(opts = {}) {
const {
@ -75,13 +65,16 @@ export default class IncomingTransactionsController {
await this._update(selectedAddress, newBlockNumberDec);
};
const incomingTxLastFetchedBlockByChainId = Object.keys(
ETHERSCAN_SUPPORTED_NETWORKS,
).reduce((network, chainId) => {
network[chainId] = null;
return network;
}, {});
const initState = {
incomingTransactions: {},
incomingTxLastFetchedBlockByChainId: {
[CHAIN_IDS.GOERLI]: null,
[CHAIN_IDS.MAINNET]: null,
[CHAIN_IDS.SEPOLIA]: null,
},
incomingTxLastFetchedBlockByChainId,
...opts.initState,
};
this.store = new ObservableStore(initState);
@ -171,7 +164,7 @@ export default class IncomingTransactionsController {
const { completedOnboarding } = this.onboardingController.store.getState();
const chainId = this.getCurrentChainId();
if (
!etherscanSupportedNetworks.includes(chainId) ||
!Object.hasOwnProperty.call(ETHERSCAN_SUPPORTED_NETWORKS, chainId) ||
!address ||
!completedOnboarding
) {
@ -235,12 +228,10 @@ export default class IncomingTransactionsController {
* @returns {TransactionMeta[]}
*/
async _getNewIncomingTransactions(address, fromBlock, chainId) {
const etherscanSubdomain =
chainId === CHAIN_IDS.MAINNET
? 'api'
: `api-${CHAIN_ID_TO_TYPE_MAP[chainId]}`;
const etherscanDomain = ETHERSCAN_SUPPORTED_NETWORKS[chainId].domain;
const etherscanSubdomain = ETHERSCAN_SUPPORTED_NETWORKS[chainId].subdomain;
const apiUrl = `https://${etherscanSubdomain}.etherscan.io`;
const apiUrl = `https://${etherscanSubdomain}.${etherscanDomain}`;
let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1`;
if (fromBlock) {
@ -303,7 +294,7 @@ export default class IncomingTransactionsController {
blockNumber: etherscanTransaction.blockNumber,
id: createId(),
chainId,
metamaskNetworkId: CHAIN_ID_TO_NETWORK_ID_MAP[chainId],
metamaskNetworkId: ETHERSCAN_SUPPORTED_NETWORKS[chainId].networkId,
status,
time,
txParams,

View File

@ -6,7 +6,7 @@ import { cloneDeep } from 'lodash';
import waitUntilCalled from '../../../test/lib/wait-until-called';
import {
CHAIN_ID_TO_TYPE_MAP,
ETHERSCAN_SUPPORTED_NETWORKS,
CHAIN_IDS,
NETWORK_TYPES,
NETWORK_IDS,
@ -34,11 +34,12 @@ const PREPOPULATED_BLOCKS_BY_NETWORK = {
[CHAIN_IDS.MAINNET]: 3,
[CHAIN_IDS.SEPOLIA]: 6,
};
const EMPTY_BLOCKS_BY_NETWORK = {
[CHAIN_IDS.GOERLI]: null,
[CHAIN_IDS.MAINNET]: null,
[CHAIN_IDS.SEPOLIA]: null,
};
const EMPTY_BLOCKS_BY_NETWORK = Object.keys(
ETHERSCAN_SUPPORTED_NETWORKS,
).reduce((network, chainId) => {
network[chainId] = null;
return network;
}, {});
function getEmptyInitState() {
return {
@ -150,20 +151,13 @@ const getFakeEtherscanTransaction = ({
};
function nockEtherscanApiForAllChains(mockResponse) {
for (const chainId of [
CHAIN_IDS.GOERLI,
CHAIN_IDS.MAINNET,
CHAIN_IDS.SEPOLIA,
'undefined',
]) {
nock(
`https://api${
chainId === CHAIN_IDS.MAINNET ? '' : `-${CHAIN_ID_TO_TYPE_MAP[chainId]}`
}.etherscan.io`,
)
.get(/api.+/u)
.reply(200, JSON.stringify(mockResponse));
}
Object.values(ETHERSCAN_SUPPORTED_NETWORKS).forEach(
({ domain, subdomain }) => {
nock(`https://${domain}.${subdomain}`)
.get(/api.+/u)
.reply(200, JSON.stringify(mockResponse));
},
);
}
describe('IncomingTransactionsController', function () {

View File

@ -201,17 +201,24 @@ export const CHAIN_IDS = {
GOERLI: '0x5',
LOCALHOST: '0x539',
BSC: '0x38',
BSC_TESTNET: '0x61',
OPTIMISM: '0xa',
OPTIMISM_TESTNET: '0x1a4',
POLYGON: '0x89',
POLYGON_TESTNET: '0x13881',
AVALANCHE: '0xa86a',
AVALANCHE_TESTNET: '0xa869',
FANTOM: '0xfa',
FANTOM_TESTNET: '0xfa2',
CELO: '0xa4ec',
ARBITRUM: '0xa4b1',
HARMONY: '0x63564c40',
PALM: '0x2a15c308d',
SEPOLIA: '0xaa36a7',
AURORA: '0x4e454152',
MOONBEAM: '0x504',
MOONBEAM_TESTNET: '0x507',
MOONRIVER: '0x505',
} as const;
/**
@ -542,6 +549,98 @@ export const NATIVE_CURRENCY_TOKEN_IMAGE_MAP = {
export const INFURA_BLOCKED_KEY = 'countryBlocked';
const defaultEtherscanDomain = 'etherscan.io';
const defaultEtherscanSubdomainPrefix = 'api';
/**
* Map of all Etherscan supported networks.
*/
export const ETHERSCAN_SUPPORTED_NETWORKS = {
[CHAIN_IDS.GOERLI]: {
domain: defaultEtherscanDomain,
subdomain: `${defaultEtherscanSubdomainPrefix}-${
CHAIN_ID_TO_TYPE_MAP[CHAIN_IDS.GOERLI]
}`,
networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.GOERLI],
},
[CHAIN_IDS.MAINNET]: {
domain: defaultEtherscanDomain,
subdomain: defaultEtherscanSubdomainPrefix,
networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.MAINNET],
},
[CHAIN_IDS.SEPOLIA]: {
domain: defaultEtherscanDomain,
subdomain: `${defaultEtherscanSubdomainPrefix}-${
CHAIN_ID_TO_TYPE_MAP[CHAIN_IDS.SEPOLIA]
}`,
networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.SEPOLIA],
},
[CHAIN_IDS.BSC]: {
domain: 'bscscan.com',
subdomain: defaultEtherscanSubdomainPrefix,
networkId: parseInt(CHAIN_IDS.BSC, 16).toString(),
},
[CHAIN_IDS.BSC_TESTNET]: {
domain: 'bscscan.com',
subdomain: `${defaultEtherscanSubdomainPrefix}-testnet`,
networkId: parseInt(CHAIN_IDS.BSC_TESTNET, 16).toString(),
},
[CHAIN_IDS.OPTIMISM]: {
domain: defaultEtherscanDomain,
subdomain: `${defaultEtherscanSubdomainPrefix}-optimistic`,
networkId: parseInt(CHAIN_IDS.OPTIMISM, 16).toString(),
},
[CHAIN_IDS.OPTIMISM_TESTNET]: {
domain: defaultEtherscanDomain,
subdomain: `${defaultEtherscanSubdomainPrefix}-goerli-optimistic`,
networkId: parseInt(CHAIN_IDS.OPTIMISM_TESTNET, 16).toString(),
},
[CHAIN_IDS.POLYGON]: {
domain: 'polygonscan.com',
subdomain: defaultEtherscanSubdomainPrefix,
networkId: parseInt(CHAIN_IDS.POLYGON, 16).toString(),
},
[CHAIN_IDS.POLYGON_TESTNET]: {
domain: 'polygonscan.com',
subdomain: `${defaultEtherscanSubdomainPrefix}-mumbai`,
networkId: parseInt(CHAIN_IDS.POLYGON_TESTNET, 16).toString(),
},
[CHAIN_IDS.AVALANCHE]: {
domain: 'snowtrace.io',
subdomain: defaultEtherscanSubdomainPrefix,
networkId: parseInt(CHAIN_IDS.AVALANCHE, 16).toString(),
},
[CHAIN_IDS.AVALANCHE_TESTNET]: {
domain: 'snowtrace.io',
subdomain: `${defaultEtherscanSubdomainPrefix}-testnet`,
networkId: parseInt(CHAIN_IDS.AVALANCHE_TESTNET, 16).toString(),
},
[CHAIN_IDS.FANTOM]: {
domain: 'ftmscan.com',
subdomain: defaultEtherscanSubdomainPrefix,
networkId: parseInt(CHAIN_IDS.FANTOM, 16).toString(),
},
[CHAIN_IDS.FANTOM_TESTNET]: {
domain: 'ftmscan.com',
subdomain: `${defaultEtherscanSubdomainPrefix}-testnet`,
networkId: parseInt(CHAIN_IDS.FANTOM_TESTNET, 16).toString(),
},
[CHAIN_IDS.MOONBEAM]: {
domain: 'moonscan.io',
subdomain: `${defaultEtherscanSubdomainPrefix}-moonbeam`,
networkId: parseInt(CHAIN_IDS.MOONBEAM, 16).toString(),
},
[CHAIN_IDS.MOONBEAM_TESTNET]: {
domain: 'moonscan.io',
subdomain: `${defaultEtherscanSubdomainPrefix}-moonbase`,
networkId: parseInt(CHAIN_IDS.MOONBEAM_TESTNET, 16).toString(),
},
[CHAIN_IDS.MOONRIVER]: {
domain: 'moonscan.io',
subdomain: `${defaultEtherscanSubdomainPrefix}-moonriver`,
networkId: parseInt(CHAIN_IDS.MOONRIVER, 16).toString(),
},
};
/**
* Hardforks are points in the chain where logic is changed significantly
* enough where there is a fork and the new fork becomes the active chain.
@ -591,6 +690,13 @@ export const BUYABLE_CHAINS_MAP: {
| typeof CHAIN_IDS.PALM
| typeof CHAIN_IDS.HARMONY
| typeof CHAIN_IDS.OPTIMISM_TESTNET
| typeof CHAIN_IDS.BSC_TESTNET
| typeof CHAIN_IDS.POLYGON_TESTNET
| typeof CHAIN_IDS.AVALANCHE_TESTNET
| typeof CHAIN_IDS.FANTOM_TESTNET
| typeof CHAIN_IDS.MOONBEAM
| typeof CHAIN_IDS.MOONBEAM_TESTNET
| typeof CHAIN_IDS.MOONRIVER
>]: BuyableChainSettings;
} = {
[CHAIN_IDS.MAINNET]: {