mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Additional incoming transactions support (#14219)
This commit is contained in:
parent
3ec042fa68
commit
3a00b5ab54
@ -9,11 +9,7 @@ import {
|
|||||||
TransactionType,
|
TransactionType,
|
||||||
TransactionStatus,
|
TransactionStatus,
|
||||||
} from '../../../shared/constants/transaction';
|
} from '../../../shared/constants/transaction';
|
||||||
import {
|
import { ETHERSCAN_SUPPORTED_NETWORKS } from '../../../shared/constants/network';
|
||||||
CHAIN_IDS,
|
|
||||||
CHAIN_ID_TO_NETWORK_ID_MAP,
|
|
||||||
CHAIN_ID_TO_TYPE_MAP,
|
|
||||||
} from '../../../shared/constants/network';
|
|
||||||
import { bnToHex } from '../../../shared/modules/conversion.utils';
|
import { bnToHex } from '../../../shared/modules/conversion.utils';
|
||||||
|
|
||||||
const fetchWithTimeout = getFetchWithTimeout();
|
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
|
* 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
|
* 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
|
* Note that only Etherscan-compatible networks are supported. We will not attempt to retrieve incoming transactions
|
||||||
* attempt to retrieve incoming transactions on any custom RPC endpoints.
|
* on non-compatible custom RPC endpoints.
|
||||||
*/
|
*/
|
||||||
const etherscanSupportedNetworks = [
|
|
||||||
CHAIN_IDS.GOERLI,
|
|
||||||
CHAIN_IDS.MAINNET,
|
|
||||||
CHAIN_IDS.SEPOLIA,
|
|
||||||
];
|
|
||||||
|
|
||||||
export default class IncomingTransactionsController {
|
export default class IncomingTransactionsController {
|
||||||
constructor(opts = {}) {
|
constructor(opts = {}) {
|
||||||
const {
|
const {
|
||||||
@ -75,13 +65,16 @@ export default class IncomingTransactionsController {
|
|||||||
await this._update(selectedAddress, newBlockNumberDec);
|
await this._update(selectedAddress, newBlockNumberDec);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const incomingTxLastFetchedBlockByChainId = Object.keys(
|
||||||
|
ETHERSCAN_SUPPORTED_NETWORKS,
|
||||||
|
).reduce((network, chainId) => {
|
||||||
|
network[chainId] = null;
|
||||||
|
return network;
|
||||||
|
}, {});
|
||||||
|
|
||||||
const initState = {
|
const initState = {
|
||||||
incomingTransactions: {},
|
incomingTransactions: {},
|
||||||
incomingTxLastFetchedBlockByChainId: {
|
incomingTxLastFetchedBlockByChainId,
|
||||||
[CHAIN_IDS.GOERLI]: null,
|
|
||||||
[CHAIN_IDS.MAINNET]: null,
|
|
||||||
[CHAIN_IDS.SEPOLIA]: null,
|
|
||||||
},
|
|
||||||
...opts.initState,
|
...opts.initState,
|
||||||
};
|
};
|
||||||
this.store = new ObservableStore(initState);
|
this.store = new ObservableStore(initState);
|
||||||
@ -171,7 +164,7 @@ export default class IncomingTransactionsController {
|
|||||||
const { completedOnboarding } = this.onboardingController.store.getState();
|
const { completedOnboarding } = this.onboardingController.store.getState();
|
||||||
const chainId = this.getCurrentChainId();
|
const chainId = this.getCurrentChainId();
|
||||||
if (
|
if (
|
||||||
!etherscanSupportedNetworks.includes(chainId) ||
|
!Object.hasOwnProperty.call(ETHERSCAN_SUPPORTED_NETWORKS, chainId) ||
|
||||||
!address ||
|
!address ||
|
||||||
!completedOnboarding
|
!completedOnboarding
|
||||||
) {
|
) {
|
||||||
@ -235,12 +228,10 @@ export default class IncomingTransactionsController {
|
|||||||
* @returns {TransactionMeta[]}
|
* @returns {TransactionMeta[]}
|
||||||
*/
|
*/
|
||||||
async _getNewIncomingTransactions(address, fromBlock, chainId) {
|
async _getNewIncomingTransactions(address, fromBlock, chainId) {
|
||||||
const etherscanSubdomain =
|
const etherscanDomain = ETHERSCAN_SUPPORTED_NETWORKS[chainId].domain;
|
||||||
chainId === CHAIN_IDS.MAINNET
|
const etherscanSubdomain = ETHERSCAN_SUPPORTED_NETWORKS[chainId].subdomain;
|
||||||
? 'api'
|
|
||||||
: `api-${CHAIN_ID_TO_TYPE_MAP[chainId]}`;
|
|
||||||
|
|
||||||
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`;
|
let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1`;
|
||||||
|
|
||||||
if (fromBlock) {
|
if (fromBlock) {
|
||||||
@ -303,7 +294,7 @@ export default class IncomingTransactionsController {
|
|||||||
blockNumber: etherscanTransaction.blockNumber,
|
blockNumber: etherscanTransaction.blockNumber,
|
||||||
id: createId(),
|
id: createId(),
|
||||||
chainId,
|
chainId,
|
||||||
metamaskNetworkId: CHAIN_ID_TO_NETWORK_ID_MAP[chainId],
|
metamaskNetworkId: ETHERSCAN_SUPPORTED_NETWORKS[chainId].networkId,
|
||||||
status,
|
status,
|
||||||
time,
|
time,
|
||||||
txParams,
|
txParams,
|
||||||
|
@ -6,7 +6,7 @@ import { cloneDeep } from 'lodash';
|
|||||||
|
|
||||||
import waitUntilCalled from '../../../test/lib/wait-until-called';
|
import waitUntilCalled from '../../../test/lib/wait-until-called';
|
||||||
import {
|
import {
|
||||||
CHAIN_ID_TO_TYPE_MAP,
|
ETHERSCAN_SUPPORTED_NETWORKS,
|
||||||
CHAIN_IDS,
|
CHAIN_IDS,
|
||||||
NETWORK_TYPES,
|
NETWORK_TYPES,
|
||||||
NETWORK_IDS,
|
NETWORK_IDS,
|
||||||
@ -34,11 +34,12 @@ const PREPOPULATED_BLOCKS_BY_NETWORK = {
|
|||||||
[CHAIN_IDS.MAINNET]: 3,
|
[CHAIN_IDS.MAINNET]: 3,
|
||||||
[CHAIN_IDS.SEPOLIA]: 6,
|
[CHAIN_IDS.SEPOLIA]: 6,
|
||||||
};
|
};
|
||||||
const EMPTY_BLOCKS_BY_NETWORK = {
|
const EMPTY_BLOCKS_BY_NETWORK = Object.keys(
|
||||||
[CHAIN_IDS.GOERLI]: null,
|
ETHERSCAN_SUPPORTED_NETWORKS,
|
||||||
[CHAIN_IDS.MAINNET]: null,
|
).reduce((network, chainId) => {
|
||||||
[CHAIN_IDS.SEPOLIA]: null,
|
network[chainId] = null;
|
||||||
};
|
return network;
|
||||||
|
}, {});
|
||||||
|
|
||||||
function getEmptyInitState() {
|
function getEmptyInitState() {
|
||||||
return {
|
return {
|
||||||
@ -150,20 +151,13 @@ const getFakeEtherscanTransaction = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
function nockEtherscanApiForAllChains(mockResponse) {
|
function nockEtherscanApiForAllChains(mockResponse) {
|
||||||
for (const chainId of [
|
Object.values(ETHERSCAN_SUPPORTED_NETWORKS).forEach(
|
||||||
CHAIN_IDS.GOERLI,
|
({ domain, subdomain }) => {
|
||||||
CHAIN_IDS.MAINNET,
|
nock(`https://${domain}.${subdomain}`)
|
||||||
CHAIN_IDS.SEPOLIA,
|
.get(/api.+/u)
|
||||||
'undefined',
|
.reply(200, JSON.stringify(mockResponse));
|
||||||
]) {
|
},
|
||||||
nock(
|
);
|
||||||
`https://api${
|
|
||||||
chainId === CHAIN_IDS.MAINNET ? '' : `-${CHAIN_ID_TO_TYPE_MAP[chainId]}`
|
|
||||||
}.etherscan.io`,
|
|
||||||
)
|
|
||||||
.get(/api.+/u)
|
|
||||||
.reply(200, JSON.stringify(mockResponse));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('IncomingTransactionsController', function () {
|
describe('IncomingTransactionsController', function () {
|
||||||
|
@ -201,17 +201,24 @@ export const CHAIN_IDS = {
|
|||||||
GOERLI: '0x5',
|
GOERLI: '0x5',
|
||||||
LOCALHOST: '0x539',
|
LOCALHOST: '0x539',
|
||||||
BSC: '0x38',
|
BSC: '0x38',
|
||||||
|
BSC_TESTNET: '0x61',
|
||||||
OPTIMISM: '0xa',
|
OPTIMISM: '0xa',
|
||||||
OPTIMISM_TESTNET: '0x1a4',
|
OPTIMISM_TESTNET: '0x1a4',
|
||||||
POLYGON: '0x89',
|
POLYGON: '0x89',
|
||||||
|
POLYGON_TESTNET: '0x13881',
|
||||||
AVALANCHE: '0xa86a',
|
AVALANCHE: '0xa86a',
|
||||||
|
AVALANCHE_TESTNET: '0xa869',
|
||||||
FANTOM: '0xfa',
|
FANTOM: '0xfa',
|
||||||
|
FANTOM_TESTNET: '0xfa2',
|
||||||
CELO: '0xa4ec',
|
CELO: '0xa4ec',
|
||||||
ARBITRUM: '0xa4b1',
|
ARBITRUM: '0xa4b1',
|
||||||
HARMONY: '0x63564c40',
|
HARMONY: '0x63564c40',
|
||||||
PALM: '0x2a15c308d',
|
PALM: '0x2a15c308d',
|
||||||
SEPOLIA: '0xaa36a7',
|
SEPOLIA: '0xaa36a7',
|
||||||
AURORA: '0x4e454152',
|
AURORA: '0x4e454152',
|
||||||
|
MOONBEAM: '0x504',
|
||||||
|
MOONBEAM_TESTNET: '0x507',
|
||||||
|
MOONRIVER: '0x505',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -542,6 +549,98 @@ export const NATIVE_CURRENCY_TOKEN_IMAGE_MAP = {
|
|||||||
|
|
||||||
export const INFURA_BLOCKED_KEY = 'countryBlocked';
|
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
|
* 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.
|
* 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.PALM
|
||||||
| typeof CHAIN_IDS.HARMONY
|
| typeof CHAIN_IDS.HARMONY
|
||||||
| typeof CHAIN_IDS.OPTIMISM_TESTNET
|
| 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;
|
>]: BuyableChainSettings;
|
||||||
} = {
|
} = {
|
||||||
[CHAIN_IDS.MAINNET]: {
|
[CHAIN_IDS.MAINNET]: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user