import { ethPlorer, ETHTokenExplorer, SecureWalletName, InsecureWalletName } from './data'; import { EtherscanNode, InfuraNode, RPCNode, Web3Node } from 'libs/nodes'; import { networkIdToName } from 'libs/values'; import { ETH_DEFAULT, ETH_TREZOR, ETH_LEDGER, ETC_LEDGER, ETC_TREZOR, ETH_TESTNET, EXP_DEFAULT, UBQ_DEFAULT, DPath } from 'config/dpaths'; export interface BlockExplorerConfig { origin: string; txUrl(txHash: string): string; addressUrl(address: string): string; } export interface Token { address: string; symbol: string; decimal: number; error?: string | null; } export interface NetworkContract { name: NetworkKeys; address?: string; abi: string; } export interface DPathFormats { trezor: DPath; ledgerNanoS: DPath; mnemonicPhrase: DPath; } export interface NetworkConfig { // TODO really try not to allow strings due to custom networks name: NetworkKeys; unit: string; color?: string; blockExplorer?: BlockExplorerConfig; tokenExplorer?: { name: string; address(address: string): string; }; chainId: number; tokens: Token[]; contracts: NetworkContract[] | null; dPathFormats: DPathFormats; isTestnet?: boolean; } export interface CustomNetworkConfig { name: string; unit: string; chainId: number; dPathFormats: DPathFormats | null; } export interface NodeConfig { network: NetworkKeys; lib: RPCNode | Web3Node; service: string; estimateGas?: boolean; hidden?: boolean; } export interface CustomNodeConfig { name: string; url: string; port: number; network: string; auth?: { username: string; password: string; }; } // Must be a website that follows the ethplorer convention of /tx/[hash] and // address/[address] to generate the correct functions. function makeExplorer(origin: string): BlockExplorerConfig { return { origin, txUrl: hash => `${origin}/tx/${hash}`, addressUrl: address => `${origin}/address/${address}` }; } const ETH: NetworkConfig = { name: 'ETH', unit: 'ETH', chainId: 1, color: '#0e97c0', blockExplorer: makeExplorer('https://etherscan.io'), tokenExplorer: { name: ethPlorer, address: ETHTokenExplorer }, tokens: require('./tokens/eth.json'), contracts: require('./contracts/eth.json'), dPathFormats: { [SecureWalletName.TREZOR]: ETH_TREZOR, [SecureWalletName.LEDGER_NANO_S]: ETH_LEDGER, [InsecureWalletName.MNEMONIC_PHRASE]: ETH_DEFAULT } }; const Ropsten: NetworkConfig = { name: 'Ropsten', unit: 'ETH', chainId: 3, color: '#adc101', blockExplorer: makeExplorer('https://ropsten.etherscan.io'), tokens: require('./tokens/ropsten.json'), contracts: require('./contracts/ropsten.json'), isTestnet: true, dPathFormats: { [SecureWalletName.TREZOR]: ETH_TESTNET, [SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET, [InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET } }; const Kovan: NetworkConfig = { name: 'Kovan', unit: 'ETH', chainId: 42, color: '#adc101', blockExplorer: makeExplorer('https://kovan.etherscan.io'), tokens: require('./tokens/ropsten.json'), contracts: require('./contracts/ropsten.json'), isTestnet: true, dPathFormats: { [SecureWalletName.TREZOR]: ETH_TESTNET, [SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET, [InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET } }; const Rinkeby: NetworkConfig = { name: 'Rinkeby', unit: 'ETH', chainId: 4, color: '#adc101', blockExplorer: makeExplorer('https://rinkeby.etherscan.io'), tokens: require('./tokens/rinkeby.json'), contracts: require('./contracts/rinkeby.json'), isTestnet: true, dPathFormats: { [SecureWalletName.TREZOR]: ETH_TESTNET, [SecureWalletName.LEDGER_NANO_S]: ETH_TESTNET, [InsecureWalletName.MNEMONIC_PHRASE]: ETH_TESTNET } }; const ETC: NetworkConfig = { name: 'ETC', unit: 'ETC', chainId: 61, color: '#669073', blockExplorer: makeExplorer('https://gastracker.io'), tokens: require('./tokens/etc.json'), contracts: require('./contracts/etc.json'), dPathFormats: { [SecureWalletName.TREZOR]: ETC_TREZOR, [SecureWalletName.LEDGER_NANO_S]: ETC_LEDGER, [InsecureWalletName.MNEMONIC_PHRASE]: ETC_TREZOR } }; const UBQ: NetworkConfig = { name: 'UBQ', unit: 'UBQ', chainId: 8, color: '#b37aff', blockExplorer: makeExplorer('https://ubiqscan.io/en'), tokens: require('./tokens/ubq.json'), contracts: require('./contracts/ubq.json'), dPathFormats: { [SecureWalletName.TREZOR]: UBQ_DEFAULT, [SecureWalletName.LEDGER_NANO_S]: UBQ_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: UBQ_DEFAULT } }; const EXP: NetworkConfig = { name: 'EXP', unit: 'EXP', chainId: 2, color: '#673ab7', // tslint:disable:no-http-string - Unavailable behind HTTPS right now blockExplorer: makeExplorer('http://www.gander.tech'), // tslint:enable:no-http-string tokens: require('./tokens/exp.json'), contracts: require('./contracts/exp.json'), dPathFormats: { [SecureWalletName.TREZOR]: EXP_DEFAULT, [SecureWalletName.LEDGER_NANO_S]: EXP_DEFAULT, [InsecureWalletName.MNEMONIC_PHRASE]: EXP_DEFAULT } }; export const NETWORKS = { ETH, Ropsten, Kovan, Rinkeby, ETC, UBQ, EXP }; export type NetworkKeys = keyof typeof NETWORKS; enum NodeName { ETH_MEW = 'eth_mew', ETH_MYCRYPTO = 'eth_mycrypto', ETH_ETHSCAN = 'eth_ethscan', ETH_INFURA = 'eth_infura', ROP_MEW = 'rop_mew', ROP_INFURA = 'rop_infura', KOV_ETHSCAN = 'kov_ethscan', RIN_ETHSCAN = 'rin_ethscan', RIN_INFURA = 'rin_infura', ETC_EPOOL = 'etc_epool', UBQ = 'ubq', EXP_TECH = 'exp_tech' } type NonWeb3NodeConfigs = { [key in NodeName]: NodeConfig }; interface Web3NodeConfig { web3?: NodeConfig; } type NodeConfigs = NonWeb3NodeConfigs & Web3NodeConfig; export const NODES: NodeConfigs = { eth_mew: { network: 'ETH', lib: new RPCNode('https://api.myetherapi.com/eth'), service: 'MyEtherWallet', estimateGas: true }, eth_mycrypto: { network: 'ETH', lib: new RPCNode('https://api.mycryptoapi.com/eth'), service: 'MyCrypto', estimateGas: true }, eth_ethscan: { network: 'ETH', service: 'Etherscan.io', lib: new EtherscanNode('https://api.etherscan.io/api'), estimateGas: false }, eth_infura: { network: 'ETH', service: 'infura.io', lib: new InfuraNode('https://mainnet.infura.io/mew'), estimateGas: false }, rop_mew: { network: 'Ropsten', service: 'MyEtherWallet', lib: new RPCNode('https://api.myetherapi.com/rop'), estimateGas: false }, rop_infura: { network: 'Ropsten', service: 'infura.io', lib: new InfuraNode('https://ropsten.infura.io/mew'), estimateGas: false }, kov_ethscan: { network: 'Kovan', service: 'Etherscan.io', lib: new EtherscanNode('https://kovan.etherscan.io/api'), estimateGas: false }, rin_ethscan: { network: 'Rinkeby', service: 'Etherscan.io', lib: new EtherscanNode('https://rinkeby.etherscan.io/api'), estimateGas: false }, rin_infura: { network: 'Rinkeby', service: 'infura.io', lib: new InfuraNode('https://rinkeby.infura.io/mew'), estimateGas: false }, etc_epool: { network: 'ETC', service: 'Epool.io', lib: new RPCNode('https://mewapi.epool.io'), estimateGas: false }, ubq: { network: 'UBQ', service: 'ubiqscan.io', lib: new RPCNode('https://pyrus2.ubiqscan.io'), estimateGas: true }, exp_tech: { network: 'EXP', service: 'Expanse.tech', lib: new RPCNode('https://node.expanse.tech/'), estimateGas: true } }; interface Web3NodeInfo { networkId: string; lib: Web3Node; } export async function setupWeb3Node(): Promise { const { web3 } = window as any; if (!web3 || !web3.currentProvider || !web3.currentProvider.sendAsync) { throw new Error( 'Web3 not found. Please check that MetaMask is installed, or that MyCrypto is open in Mist.' ); } const lib = new Web3Node(); const networkId = await lib.getNetVersion(); const accounts = await lib.getAccounts(); if (!accounts.length) { throw new Error('No accounts found in MetaMask / Mist.'); } if (networkId === 'loading') { throw new Error('MetaMask / Mist is still loading. Please refresh the page and try again.'); } return { networkId, lib }; } export async function isWeb3NodeAvailable(): Promise { try { await setupWeb3Node(); return true; } catch (e) { return false; } } export const Web3Service = 'MetaMask / Mist'; export interface NodeConfigOverride extends NodeConfig { network: any; } export async function initWeb3Node(): Promise { const { networkId, lib } = await setupWeb3Node(); const web3: NodeConfigOverride = { network: networkIdToName(networkId), service: Web3Service, lib, estimateGas: false, hidden: true }; NODES.web3 = web3; }