diff --git a/.doc/makeReadme.js b/.doc/makeReadme.js index 309be31..70605d7 100644 --- a/.doc/makeReadme.js +++ b/.doc/makeReadme.js @@ -4,7 +4,7 @@ const fs = require('fs') // const files = path.resolve(__dirname, '../src') + '/*.js' const basePath = path.resolve(__dirname, '../src') -const files = fs.readdirSync(basePath).map(f => { +const files = fs.readdirSync(basePath).filter(f => f.endsWith('.js')).map(f => { return { path: `${basePath}/${f}`, name: titleFromFile(f) } }) const parser = Jsdoc2md({ 'heading-depth': 3 }) diff --git a/.eslintrc.js b/.eslintrc.js index 234e836..2bed13e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,5 +3,8 @@ module.exports = { "env": { "node": true, "mocha": true - } + }, + "plugins": [ + "json" + ] }; diff --git a/package-lock.json b/package-lock.json index 25f7506..a85d9a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2946,6 +2946,16 @@ } } }, + "eslint-plugin-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-json/-/eslint-plugin-json-2.0.1.tgz", + "integrity": "sha512-IxKZIlMyBn0tvxlBj2viW0N/UBuoey1oYpV+SNVuNFmy4xsNuwgeoOjzEeFDnVXL0FpIo7UbQSeZ+lfh2D/nLQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15", + "vscode-json-languageservice": "^3.3.5" + } + }, "eslint-plugin-mocha": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-5.3.0.tgz", @@ -4966,6 +4976,12 @@ "minimist": "^1.2.0" } }, + "jsonc-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.0.tgz", + "integrity": "sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA==", + "dev": true + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -5151,15 +5167,6 @@ "which": "^1.2.1" } }, - "karma-detect-browsers": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/karma-detect-browsers/-/karma-detect-browsers-2.3.3.tgz", - "integrity": "sha512-ltFVyA3ijThv9l9TQ+TKnccoMk6YAWn8OMaccL+n8pO2LGwMOcy6tUWy3Mnv9If29jqvVHDCWntj7wBQpPtv7Q==", - "dev": true, - "requires": { - "which": "^1.2.4" - } - }, "karma-firefox-launcher": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.2.0.tgz", @@ -8138,6 +8145,43 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, + "vscode-json-languageservice": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-3.4.7.tgz", + "integrity": "sha512-y3MN2+/yph3yoIHGmHu4ScYpm285L58XVvfGkd49xTQzLja4apxSbwzsYcP9QsqS0W7KuvoyiPhqksiudoMwjg==", + "dev": true, + "requires": { + "jsonc-parser": "^2.2.0", + "vscode-languageserver-textdocument": "^1.0.0-next.4", + "vscode-languageserver-types": "^3.15.0-next.6", + "vscode-nls": "^4.1.1", + "vscode-uri": "^2.1.0" + } + }, + "vscode-languageserver-textdocument": { + "version": "1.0.0-next.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.0-next.5.tgz", + "integrity": "sha512-1jp/zAidN/bF/sqPimhBX1orH5G4rzRw63k75TesukJDuxm8yW79ECStWbDSy41BHGOwSGN4M69QFvhancSr5A==", + "dev": true + }, + "vscode-languageserver-types": { + "version": "3.15.0-next.9", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.9.tgz", + "integrity": "sha512-Rl/8qJ6932nrHCdPn+9y0x08uLVQaSLRG+U4JzhyKpWU4eJbVaDRoAcz1Llj7CErJGbPr6kdBvShPy5fRfR+Uw==", + "dev": true + }, + "vscode-nls": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz", + "integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==", + "dev": true + }, + "vscode-uri": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.1.tgz", + "integrity": "sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A==", + "dev": true + }, "walk-back": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-3.0.1.tgz", diff --git a/package.json b/package.json index 2d4a656..172b608 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "test:browser": "npx karma start", "test": "npm run test:node && npm run test:browser", "doc": "node ./.doc/makeReadme.js > README.md", - "build": "npm run lint && npm run doc && npx babel src -d dist" + "build": "npm run lint && npm run doc && npx babel src -d dist --copy-files" }, "repository": { "type": "git", @@ -43,6 +43,7 @@ "eslint": "^4.19.1", "eslint-config-standard": "^11.0.0", "eslint-plugin-import": "^2.16.0", + "eslint-plugin-json": "^2.0.1", "eslint-plugin-mocha": "^5.3.0", "eslint-plugin-node": "^6.0.1", "eslint-plugin-promise": "^3.8.0", diff --git a/src/addresses.js b/src/addresses.js index 8f37d31..22c2a43 100644 --- a/src/addresses.js +++ b/src/addresses.js @@ -1,5 +1,6 @@ import { keccak256 } from './hashes' import { stripHexPrefix } from './strings' +import nets from './networks.json' /** * @description Check if a string is an address @@ -33,3 +34,29 @@ export function toChecksumAddress (address, chainId) { export function isValidChecksumAddress (address, chainId) { return isAddress(address) && toChecksumAddress(address, chainId) === address } + +/** + * @description Checks if an address is valid. + * @param {String} address + * @param {Integer|String} chainId + * @returns {Boolean} + */ +export function isValidAddress (address, chainId) { + if (typeof address !== 'string') return false + if (address.match(/[A-F]/)) { + return isValidChecksumAddress(address, chainId) + } + return isAddress(address) +} + +/** + * @description Search network info of checksummed address + * @param {String} address + * @param {Array} [networks], chainId list + * see: https://chainid.network/chains.json + * @returns {Array} + */ +export function searchChecksummedNetworks (address, networks) { + networks = networks || nets + return networks.filter(net => toChecksumAddress(address, net.chainId) === address) +} diff --git a/src/networks.json b/src/networks.json new file mode 100644 index 0000000..153e6fc --- /dev/null +++ b/src/networks.json @@ -0,0 +1,167 @@ +[ + { + "name": "Ethereum Mainnet", + "chainId": 1, + "shortName": "eth", + "chain": "ETH", + "network": "mainnet", + "networkId": 1, + "nativeCurrency": { + "name": "Ether", + "symbol": "ETH", + "decimals": 18 + }, + "rpc": [ + "https://mainnet.infura.io/v3/${INFURA_API_KEY}", + "https://api.mycryptoapi.com/eth" + ], + "faucets": [], + "infoURL": "https://ethereum.org", + "explorers": [ + "https://etherscan.io" + ] + }, + { + "name": "Ethereum Testnet Ropsten", + "chainId": 3, + "shortName": "rop", + "chain": "ETH", + "network": "ropsten", + "networkId": 3, + "nativeCurrency": { + "name": "Ropsten Ether", + "symbol": "ROP", + "decimals": 18 + }, + "rpc": [ + "https://ropsten.infura.io/v3/${INFURA_API_KEY}" + ], + "faucets": [ + "https://faucet.ropsten.be?${ADDRESS}" + ], + "infoURL": "https://github.com/ethereum/ropsten", + "explorers": [ + "https://ropsten.etherscan.io" + ] + }, + { + "name": "RSK Mainnet", + "chainId": 30, + "shortName": "rsk", + "chain": "RSK", + "network": "mainnet", + "networkId": 30, + "nativeCurrency": { + "name": "RSK Mainnet Ether", + "symbol": "RSK", + "decimals": 18 + }, + "rpc": [ + "https://public-node.rsk.co", + "https://mycrypto.rsk.co" + ], + "faucets": [], + "infoURL": "https://rsk.co", + "explorers": [ + "https://explorer.rsk.co", + "https://blockscout.com/rsk/mainnet" + ] + }, + { + "name": "RSK Testnet", + "chainId": 31, + "shortName": "trsk", + "chain": "RSK", + "network": "testnet", + "networkId": 31, + "nativeCurrency": { + "name": "RSK Testnet Ether", + "symbol": "TRSK", + "decimals": 18 + }, + "rpc": [ + "https://public-node.testnet.rsk.co", + "https://mycrypto.testnet.rsk.co" + ], + "faucets": [ + "https://faucet.testnet.rsk.co" + ], + "infoURL": "https://rsk.co", + "explorers": [ + "https://explorer.testnet.rsk.co" + ] + }, + { + "name": "Ethereum Testnet Rinkeby", + "chainId": 4, + "shortName": "rin", + "chain": "ETH", + "network": "rinkeby", + "networkId": 4, + "nativeCurrency": { + "name": "Rinkeby Ether", + "symbol": "RIN", + "decimals": 18 + }, + "rpc": [ + "https://rinkeby.infura.io/v3/${INFURA_API_KEY}" + ], + "faucets": [ + "https://faucet.rinkeby.io" + ], + "infoURL": "https://www.rinkeby.io", + "explorers": [ + "https://rinkeby.etherscan.io" + ] + }, + { + "name": "Ethereum Testnet Kovan", + "chainId": 42, + "shortName": "kov", + "chain": "ETH", + "network": "kovan", + "networkId": 42, + "nativeCurrency": { + "name": "Kovan Ether", + "symbol": "KOV", + "decimals": 18 + }, + "rpc": [ + "https://kovan.infura.io/v3/${INFURA_API_KEY}" + ], + "faucets": [ + "https://faucet.kovan.network", + "https://gitter.im/kovan-testnet/faucet" + ], + "infoURL": "https://kovan-testnet.github.io/website", + "explorers": [ + "https://kovan.etherscan.io" + ] + }, + { + "name": "Ethereum Testnet Görli", + "chainId": 5, + "shortName": "gor", + "chain": "ETH", + "network": "goerli", + "networkId": 5, + "nativeCurrency": { + "name": "Görli Ether", + "symbol": "GOR", + "decimals": 18 + }, + "rpc": [ + "https://rpc.goerli.mudit.blog/", + "https://rpc.slock.it/goerli ", + "https://goerli.prylabs.net/" + ], + "faucets": [ + "https://goerli-faucet.slock.it/?address=${ADDRESS}", + "https://faucet.goerli.mudit.blog" + ], + "infoURL": "https://goerli.net/#about", + "explorers": [ + "https://goerli.etherscan.io" + ] + } +] \ No newline at end of file diff --git a/test/addresses.spec.js b/test/addresses.spec.js index bf5f9b5..d20cb26 100644 --- a/test/addresses.spec.js +++ b/test/addresses.spec.js @@ -1,4 +1,11 @@ -import { isAddress, isValidChecksumAddress, toChecksumAddress } from '../src/addresses' +import { + isAddress, + isValidChecksumAddress, + toChecksumAddress, + isValidAddress, + searchChecksummedNetworks +} from '../src/addresses' + import { assert } from 'chai' const invalidAddresses = [ @@ -116,15 +123,37 @@ const allAddresses = [ describe(`# Addresses`, function () { test({ isAddress }, allAddresses, true) test({ isAddress }, invalidAddresses, false) + test({ isValidAddress }, invalidAddresses, false) + test({ isValidAddress }, plainAddresses, true) + for (let id in netAddresses) { - test({ isValidChecksumAddress }, netAddresses[id].map(a => [a, id]), true) + let addrs = netAddresses[id].map(a => [a, id]) + test({ isValidChecksumAddress }, addrs, true) + test({ isValidAddress }, addrs, true) + // validateAddress with changed ids + test({ isValidAddress }, netAddresses[id].map(a => [a, (parseInt(id) + 2)]), false) } + for (let id in eip1191ChecksummAddresses) { - test({ isValidChecksumAddress }, eip1191ChecksummAddresses[id].map(a => [a, id]), true) + let addrs = eip1191ChecksummAddresses[id].map(a => [a, id]) + test({ isValidChecksumAddress }, addrs, true) + test({ isValidAddress }, addrs, true) } test({ toChecksumAddress }, EIP1191ethMainnet.map(a => [a, undefined]), EIP1191ethMainnet) }) +describe(`searchChecksummedNetworks()`, function () { + for (let id in netAddresses) { + for (let address of netAddresses[id]) { + it(`should return a network info`, () => { + let result = searchChecksummedNetworks(address) + assert.equal(Array.isArray(result), true) + assert.isTrue(result.map(r => r.chainId).includes(parseInt(id))) + }) + } + } +}) + function test (payload, value, expected) { value = (!Array.isArray(value)) ? [value] : value for (let method in payload) {