diff --git a/src/coins.js b/src/coins.js index e8298d8f..31edbf6d 100644 --- a/src/coins.js +++ b/src/coins.js @@ -3,8 +3,103 @@ const typeforce = require('typeforce') const networks = require('./networks') +/** + * @param network + * @returns {string} the name of the network. Returns undefined if network is not a value + * of `networks` + */ +function getNetworkName (network) { + return Object.keys(networks).find(n => networks[n] === network) +} + +/** + * @param network + * @returns {Object} the mainnet corresponding to a testnet + */ +function getMainnet (network) { + switch (network) { + case networks.bitcoin: + case networks.testnet: + return networks.bitcoin + + case networks.bitcoincash: + case networks.bitcoincashTestnet: + return networks.bitcoincash + + case networks.bitcoingold: + // FIXME(https://github.com/BitGo/bitgo-utxo-lib/issues/50): define bitcoingoldTest + return networks.bitcoingold + + case networks.bitcoinsv: + case networks.bitcoinsvTestnet: + return networks.bitcoinsv + + case networks.dash: + case networks.dashTest: + return networks.dash + + case networks.litecoin: + case networks.litecoinTest: + return networks.litecoin + + case networks.zcash: + case networks.zcashTest: + return networks.zcash + } + throw new TypeError(`invalid network`) +} + +/** + * @param network + * @returns {boolean} true iff network is a mainnet + */ +function isMainnet (network) { + return getMainnet(network) === network +} + +/** + * @param network + * @returns {boolean} true iff network is a testnet + */ +function isTestnet (network) { + return getMainnet(network) !== network +} + +const networksArray = Object.keys(networks).map(name => networks[name]) +const mainnets = networksArray.filter(isMainnet) +const testnets = networksArray.filter(isTestnet) +const mainnetTestnetPairs = new Map( + mainnets.map(m => [m, testnets.filter(t => getMainnet(t) === m)]) +) + +/** + * @param network + * @returns {Object|undefined} - The testnet corresponding to a mainnet. + * Returns undefined if a network has no testnet. + */ +function getTestnet (network) { + if (isTestnet(network)) { + return network + } + const testnets = mainnetTestnetPairs.get(network) + if (testnets === undefined) { + throw new Error(`invalid argument`) + } + if (testnets.length === 0) { + return + } + if (testnets.length === 1) { + return testnets[0] + } + throw new Error(`more than one testnet for ${getNetworkName(network)}`) +} + +/** + * @param network + * @returns {boolean} true iff network bitcoin or testnet + */ function isBitcoin (network) { - return typeforce.value(networks.bitcoin.coin)(network.coin) + return getMainnet(network) === networks.bitcoin } /** @@ -12,7 +107,7 @@ function isBitcoin (network) { * @returns {boolean} true iff network is bitcoincash or bitcoincashTestnet */ function isBitcoinCash (network) { - return typeforce.value(networks.bitcoincash.coin)(network.coin) + return getMainnet(network) === networks.bitcoincash } /** @@ -20,7 +115,7 @@ function isBitcoinCash (network) { * @returns {boolean} true iff network is bitcoingold */ function isBitcoinGold (network) { - return typeforce.value(networks.bitcoingold.coin)(network.coin) + return getMainnet(network) === networks.bitcoingold } /** @@ -28,7 +123,7 @@ function isBitcoinGold (network) { * @returns {boolean} true iff network is bitcoinsv or bitcoinsvTestnet */ function isBitcoinSV (network) { - return typeforce.value(networks.bitcoinsv.coin)(network.coin) + return getMainnet(network) === networks.bitcoinsv } /** @@ -36,7 +131,7 @@ function isBitcoinSV (network) { * @returns {boolean} true iff network is dash or dashTest */ function isDash (network) { - return typeforce.value(networks.dash.coin)(network.coin) + return getMainnet(network) === networks.dash } /** @@ -44,7 +139,7 @@ function isDash (network) { * @returns {boolean} true iff network is litecoin or litecoinTest */ function isLitecoin (network) { - return typeforce.value(networks.litecoin.coin)(network.coin) + return getMainnet(network) === networks.litecoin } /** @@ -52,7 +147,7 @@ function isLitecoin (network) { * @returns {boolean} true iff network is zcash or zcashTest */ function isZcash (network) { - return typeforce.value(networks.zcash.coin)(network.coin) + return getMainnet(network) === networks.zcash } /** @@ -78,6 +173,8 @@ module.exports = { LTC: networks.litecoin.coin, ZEC: networks.zcash.coin, + getNetworkName, + getMainnet, isMainnet, getTestnet, @@ -96,4 +193,5 @@ module.exports = { * @deprecated: use isValidNetwork */ isValidCoin: isValidNetwork + } diff --git a/src/networks.js b/src/networks.js index 3ecf97b4..c9717149 100644 --- a/src/networks.js +++ b/src/networks.js @@ -153,6 +153,7 @@ module.exports = { messagePrefix: '\x19Litecoin Signed Message:\n', bech32: 'ltc', bip32: { + // FIXME(BG-16466): these are incorrect public: 0x019da462, private: 0x019d9cfe }, @@ -165,6 +166,7 @@ module.exports = { messagePrefix: '\x19Litecoin Signed Message:\n', bech32: 'tltc', bip32: { + // FIXME(BG-16466): these are incorrect public: 0x0488b21e, private: 0x0488ade4 }, diff --git a/test/networks.js b/test/networks.js index 21d38897..f3f4d8c2 100644 --- a/test/networks.js +++ b/test/networks.js @@ -71,6 +71,26 @@ describe('networks', function () { assert(coins.isValidNetwork(network)) }) + it('getNetworkName() returns network name', function () { + assert.strictEqual(name, coins.getNetworkName(network)) + }) + + it('has corresponding testnet/mainnet', function () { + if (coins.isMainnet(network)) { + assert.strictEqual(coins.isTestnet(network), false) + assert.strictEqual(coins.getMainnet(network), network) + assert.strictEqual( + typeof coins.getTestnet(network), + (network === networks.bitcoingold) ? 'undefined' : 'object' + ) + } else { + assert.strictEqual(coins.isMainnet(network), false) + assert.strictEqual(coins.getTestnet(network), network) + assert.notStrictEqual(coins.getMainnet(network), network) + assert.strictEqual(typeof coins.getMainnet(network), 'object') + } + }) + it('has expected properties', function () { assert.strictEqual(typeof network, 'object') assert.strictEqual(typeof network.messagePrefix, 'string') @@ -80,6 +100,25 @@ describe('networks', function () { assert.strictEqual(typeof network.scriptHash, 'number') assert.strictEqual(typeof network.wif, 'number') assert.strictEqual(typeof network.coin, 'string') + + // FIXME(BG-16466): litecoin should not be a special case here -- all forks have the same bip32 values + const isLitecoin = coins.getMainnet(network) === networks.litecoin + + if (coins.isMainnet(network)) { + assert.strictEqual( + (network.bip32.public === networks.bitcoin.bip32.public), !isLitecoin + ) + assert.strictEqual( + (network.bip32.private === networks.bitcoin.bip32.private), !isLitecoin + ) + } else { + assert.strictEqual( + (network.bip32.public === networks.testnet.bip32.public), !isLitecoin + ) + assert.strictEqual( + (network.bip32.private === networks.testnet.bip32.private), !isLitecoin + ) + } }) for (const otherName in networks) {