import bitcoin from 'bitcoinjs-lib' import bech32 from 'lib/utils/bech32' import lightningRequestReq from 'bolt11' export const decodePayReq = (payReq, addDefaults = true) => { const data = lightningRequestReq.decode(payReq) const expiry = data.tags.find(t => t.tagName === 'expire_time') if (addDefaults && !expiry) { data.tags.push({ tagName: 'expire_time', data: 3600 }) data.timeExpireDate = data.timestamp + 3600 data.timeExpireDateString = new Date(data.timeExpireDate * 1000).toISOString() } return data } /** * Turns parsed number into a string. */ export const formatValue = (integer, fractional) => { let value if (fractional && fractional.length > 0) { value = `${integer}.${fractional}` } else { // Empty string means `XYZ.` instead of just plain `XYZ`. if (fractional === '') { value = `${integer}.` } else { value = `${integer}` } } return value } /** * Splits number into integer and fraction. */ export const parseNumber = (_value, precision) => { let value = String(_value || '') if (typeof _value === 'string') { value = _value.replace(/[^0-9.]/g, '') } let integer = null let fractional = null if (value * 1.0 < 0) { value = '0.0' } // pearse integer and fractional value so that we can reproduce the same string value afterwards // [0, 0] === 0.0 // [0, ''] === 0. // [0, null] === 0 if (value.match(/^[0-9]*\.[0-9]*$/)) { ;[integer, fractional] = value.toString().split(/\./) if (!fractional) { fractional = '' } } else { integer = value } // Limit fractional precision to the correct number of decimal places. if (fractional && fractional.length > precision) { fractional = fractional.substring(0, precision) } return [integer, fractional] } /** * Test to see if a string is a valid on-chain address. * @param {String} input string to check. * @param {String} [network='mainnet'] network to check (mainnet, testnet). * @return {Boolean} boolean indicating wether the address is a valid on-chain address. */ export const isOnchain = (input, chain = 'bitcoin', network = 'mainnet') => { if (typeof input !== 'string') { return false } if (chain !== 'bitcoin') { // TODO: Implement address checking for litecoin. return true } try { bitcoin.address.toOutputScript( input, network === 'mainnet' ? bitcoin.networks.bitcoin : bitcoin.networks.testnet ) return true } catch (e) { return false } } /** * Test to see if a string is a valid lightning address. * @param {String} input string to check. * @param {String} [network='bitcoin'] chain to check (bitcoin, litecoin). * @param {String} [network='mainnet'] network to check (mainnet, testnet, regtest). * @return {Boolean} boolean indicating wether the address is a lightning address. */ export const isLn = (input, chain = 'bitcoin', network = 'mainnet') => { if (typeof input !== 'string') { return false } let prefix = 'ln' // Prefixes come from SLIP-0173 // See https://github.com/satoshilabs/slips/blob/master/slip-0173.md if (chain === 'bitcoin') { switch (network) { case 'mainnet': prefix = 'lnbc' break case 'testnet': prefix = 'lntb' break case 'regtest': prefix = 'lnbcrt' break } } else if (chain === 'litecoin') { switch (network) { case 'mainnet': prefix = 'lnltc' break case 'testnet': prefix = 'lntltc' break case 'regtest': prefix = 'lnrltc' break } } if (!input.startsWith(prefix)) { return false } try { bech32.decode(input) return true } catch (e) { return false } } /** * Get a nodes alias. * @param {String} pubkey pubKey of node to fetch alias for. * @param {Array} Node list to search. * @return {String} Node alias, if found */ export const getNodeAlias = (pubkey, nodes = []) => { const node = nodes.find(n => n.pub_key === pubkey) if (node && node.alias.length) { return node.alias } return null } /** * Given a list of routest, find the minimum fee. * @param {QueryRoutesResponse} routes * @return {Number} minimum fee rounded up to the nearest satoshi. */ export const getMinFee = (routes = []) => { if (!routes || !routes.length) { return null } return routes.reduce((min, b) => Math.min(min, b.total_fees), routes[0].total_fees) } /** * Given a list of routest, find the maximum fee. * @param {QueryRoutesResponse} routes * @return {Number} maximum fee. */ export const getMaxFee = routes => { if (!routes || !routes.length) { return null } return routes.reduce((max, b) => Math.max(max, b.total_fees), routes[0].total_fees) } /** * Given a list of routest, find the maximum and maximum fee. * @param {QueryRoutesResponse} routes * @return {Object} object with kets `min` and `max` */ export const getFeeRange = (routes = []) => ({ min: getMinFee(routes), max: getMaxFee(routes) })