diff --git a/lib/btc/amount.js b/lib/btc/amount.js index 1d319e91..e4eada22 100644 --- a/lib/btc/amount.js +++ b/lib/btc/amount.js @@ -10,348 +10,353 @@ const assert = require('assert'); const fixed = require('../utils/fixed'); /** + * Amount * Represents a bitcoin amount (satoshis internally). * @alias module:btc.Amount - * @constructor - * @param {(String|Number)?} value - * @param {String?} unit * @property {Amount} value */ -function Amount(value, unit) { - if (!(this instanceof Amount)) - return new Amount(value, unit); +class Amount { + /** + * Create an amount. + * @constructor + * @param {(String|Number)?} value + * @param {String?} unit + */ - this.value = 0; + constructor(value, unit) { + this.value = 0; - if (value != null) - this.fromOptions(value, unit); -} + if (value != null) + this.fromOptions(value, unit); + } -/** - * Inject properties from options. - * @private - * @param {(String|Number)?} value - * @param {String?} unit - * @returns {Amount} - */ + /** + * Inject properties from options. + * @private + * @param {(String|Number)?} value + * @param {String?} unit + * @returns {Amount} + */ -Amount.prototype.fromOptions = function fromOptions(value, unit) { - if (typeof unit === 'string') - return this.from(unit, value); + fromOptions(value, unit) { + if (typeof unit === 'string') + return this.from(unit, value); - if (typeof value === 'number') - return this.fromValue(value); + if (typeof value === 'number') + return this.fromValue(value); - return this.fromBTC(value); -}; + return this.fromBTC(value); + } -/** - * Get satoshi value. - * @returns {Amount} - */ + /** + * Get satoshi value. + * @returns {Amount} + */ -Amount.prototype.toValue = function toValue() { - return this.value; -}; - -/** - * Get satoshi string or value. - * @param {Boolean?} num - * @returns {String|Amount} - */ - -Amount.prototype.toSatoshis = function toSatoshis(num) { - if (num) + toValue() { return this.value; - - return this.value.toString(10); -}; - -/** - * Get bits string or value. - * @param {Boolean?} num - * @returns {String|Amount} - */ - -Amount.prototype.toBits = function toBits(num) { - return Amount.encode(this.value, 2, num); -}; - -/** - * Get mbtc string or value. - * @param {Boolean?} num - * @returns {String|Amount} - */ - -Amount.prototype.toMBTC = function toMBTC(num) { - return Amount.encode(this.value, 5, num); -}; - -/** - * Get btc string or value. - * @param {Boolean?} num - * @returns {String|Amount} - */ - -Amount.prototype.toBTC = function toBTC(num) { - return Amount.encode(this.value, 8, num); -}; - -/** - * Get unit string or value. - * @param {String} unit - Can be `sat`, - * `ubtc`, `bits`, `mbtc`, or `btc`. - * @param {Boolean?} num - * @returns {String|Amount} - */ - -Amount.prototype.to = function to(unit, num) { - switch (unit) { - case 'sat': - return this.toSatoshis(num); - case 'ubtc': - case 'bits': - return this.toBits(num); - case 'mbtc': - return this.toMBTC(num); - case 'btc': - return this.toBTC(num); } - throw new Error(`Unknown unit "${unit}".`); -}; -/** - * Convert amount to bitcoin string. - * @returns {String} - */ + /** + * Get satoshi string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ -Amount.prototype.toString = function toString() { - return this.toBTC(); -}; + toSatoshis(num) { + if (num) + return this.value; -/** - * Inject properties from value. - * @private - * @param {Amount} value - * @returns {Amount} - */ - -Amount.prototype.fromValue = function fromValue(value) { - assert(Number.isSafeInteger(value) && value >= 0, 'Value must be an int64.'); - this.value = value; - return this; -}; - -/** - * Inject properties from satoshis. - * @private - * @param {Number|String} value - * @returns {Amount} - */ - -Amount.prototype.fromSatoshis = function fromSatoshis(value) { - this.value = Amount.decode(value, 0); - return this; -}; - -/** - * Inject properties from bits. - * @private - * @param {Number|String} value - * @returns {Amount} - */ - -Amount.prototype.fromBits = function fromBits(value) { - this.value = Amount.decode(value, 2); - return this; -}; - -/** - * Inject properties from mbtc. - * @private - * @param {Number|String} value - * @returns {Amount} - */ - -Amount.prototype.fromMBTC = function fromMBTC(value) { - this.value = Amount.decode(value, 5); - return this; -}; - -/** - * Inject properties from btc. - * @private - * @param {Number|String} value - * @returns {Amount} - */ - -Amount.prototype.fromBTC = function fromBTC(value) { - this.value = Amount.decode(value, 8); - return this; -}; - -/** - * Inject properties from unit. - * @private - * @param {String} unit - * @param {Number|String} value - * @returns {Amount} - */ - -Amount.prototype.from = function from(unit, value) { - switch (unit) { - case 'sat': - return this.fromSatoshis(value); - case 'ubtc': - case 'bits': - return this.fromBits(value); - case 'mbtc': - return this.fromMBTC(value); - case 'btc': - return this.fromBTC(value); + return this.value.toString(10); } - throw new Error(`Unknown unit "${unit}".`); -}; -/** - * Instantiate amount from options. - * @param {(String|Number)?} value - * @param {String?} unit - * @returns {Amount} - */ + /** + * Get bits string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ -Amount.fromOptions = function fromOptions(value, unit) { - return new Amount().fromOptions(value, unit); -}; + toBits(num) { + return Amount.encode(this.value, 2, num); + } -/** - * Instantiate amount from value. - * @private - * @param {Amount} value - * @returns {Amount} - */ + /** + * Get mbtc string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ -Amount.fromValue = function fromValue(value) { - return new Amount().fromValue(value); -}; + toMBTC(num) { + return Amount.encode(this.value, 5, num); + } -/** - * Instantiate amount from satoshis. - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Get btc string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ -Amount.fromSatoshis = function fromSatoshis(value) { - return new Amount().fromSatoshis(value); -}; + toBTC(num) { + return Amount.encode(this.value, 8, num); + } -/** - * Instantiate amount from bits. - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Get unit string or value. + * @param {String} unit - Can be `sat`, + * `ubtc`, `bits`, `mbtc`, or `btc`. + * @param {Boolean?} num + * @returns {String|Amount} + */ -Amount.fromBits = function fromBits(value) { - return new Amount().fromBits(value); -}; + to(unit, num) { + switch (unit) { + case 'sat': + return this.toSatoshis(num); + case 'ubtc': + case 'bits': + return this.toBits(num); + case 'mbtc': + return this.toMBTC(num); + case 'btc': + return this.toBTC(num); + } + throw new Error(`Unknown unit "${unit}".`); + } -/** - * Instantiate amount from mbtc. - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Convert amount to bitcoin string. + * @returns {String} + */ -Amount.fromMBTC = function fromMBTC(value) { - return new Amount().fromMBTC(value); -}; + toString() { + return this.toBTC(); + } -/** - * Instantiate amount from btc. - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Inject properties from value. + * @private + * @param {Amount} value + * @returns {Amount} + */ -Amount.fromBTC = function fromBTC(value) { - return new Amount().fromBTC(value); -}; + fromValue(value) { + assert(Number.isSafeInteger(value) && value >= 0, + 'Value must be an int64.'); + this.value = value; + return this; + } -/** - * Instantiate amount from unit. - * @param {String} unit - * @param {Number|String} value - * @returns {Amount} - */ + /** + * Inject properties from satoshis. + * @private + * @param {Number|String} value + * @returns {Amount} + */ -Amount.from = function from(unit, value) { - return new Amount().from(unit, value); -}; + fromSatoshis(value) { + this.value = Amount.decode(value, 0); + return this; + } -/** - * Inspect amount. - * @returns {String} - */ + /** + * Inject properties from bits. + * @private + * @param {Number|String} value + * @returns {Amount} + */ -Amount.prototype.inspect = function inspect() { - return ``; -}; + fromBits(value) { + this.value = Amount.decode(value, 2); + return this; + } -/** - * Safely convert satoshis to a BTC string. - * This function explicitly avoids any - * floating point arithmetic. - * @param {Amount} value - Satoshis. - * @returns {String} BTC string. - */ + /** + * Inject properties from mbtc. + * @private + * @param {Number|String} value + * @returns {Amount} + */ -Amount.btc = function btc(value, num) { - if (typeof value === 'string') - return value; + fromMBTC(value) { + this.value = Amount.decode(value, 5); + return this; + } - return Amount.encode(value, 8, num); -}; + /** + * Inject properties from btc. + * @private + * @param {Number|String} value + * @returns {Amount} + */ -/** - * Safely convert a BTC string to satoshis. - * @param {String} str - BTC - * @returns {Amount} Satoshis. - * @throws on parse error - */ + fromBTC(value) { + this.value = Amount.decode(value, 8); + return this; + } -Amount.value = function value(str) { - if (typeof str === 'number') - return str; + /** + * Inject properties from unit. + * @private + * @param {String} unit + * @param {Number|String} value + * @returns {Amount} + */ - return Amount.decode(str, 8); -}; + from(unit, value) { + switch (unit) { + case 'sat': + return this.fromSatoshis(value); + case 'ubtc': + case 'bits': + return this.fromBits(value); + case 'mbtc': + return this.fromMBTC(value); + case 'btc': + return this.fromBTC(value); + } + throw new Error(`Unknown unit "${unit}".`); + } -/** - * Safely convert satoshis to a BTC string. - * @param {Amount} value - * @param {Number} exp - Exponent. - * @param {Boolean} num - Return a number. - * @returns {String|Number} - */ + /** + * Instantiate amount from options. + * @param {(String|Number)?} value + * @param {String?} unit + * @returns {Amount} + */ -Amount.encode = function encode(value, exp, num) { - if (num) - return fixed.toFloat(value, exp); - return fixed.encode(value, exp); -}; + static fromOptions(value, unit) { + return new this().fromOptions(value, unit); + } -/** - * Safely convert a BTC string to satoshis. - * @param {String|Number} value - BTC - * @param {Number} exp - Exponent. - * @returns {Amount} Satoshis. - * @throws on parse error - */ + /** + * Instantiate amount from value. + * @private + * @param {Amount} value + * @returns {Amount} + */ -Amount.decode = function decode(value, exp) { - if (typeof value === 'number') - return fixed.fromFloat(value, exp); - return fixed.decode(value, exp); -}; + static fromValue(value) { + return new this().fromValue(value); + } + + /** + * Instantiate amount from satoshis. + * @param {Number|String} value + * @returns {Amount} + */ + + static fromSatoshis(value) { + return new this().fromSatoshis(value); + } + + /** + * Instantiate amount from bits. + * @param {Number|String} value + * @returns {Amount} + */ + + static fromBits(value) { + return new this().fromBits(value); + } + + /** + * Instantiate amount from mbtc. + * @param {Number|String} value + * @returns {Amount} + */ + + static fromMBTC(value) { + return new this().fromMBTC(value); + } + + /** + * Instantiate amount from btc. + * @param {Number|String} value + * @returns {Amount} + */ + + static fromBTC(value) { + return new this().fromBTC(value); + } + + /** + * Instantiate amount from unit. + * @param {String} unit + * @param {Number|String} value + * @returns {Amount} + */ + + static from(unit, value) { + return new this().from(unit, value); + } + + /** + * Inspect amount. + * @returns {String} + */ + + inspect() { + return ``; + } + + /** + * Safely convert satoshis to a BTC string. + * This function explicitly avoids any + * floating point arithmetic. + * @param {Amount} value - Satoshis. + * @returns {String} BTC string. + */ + + static btc(value, num) { + if (typeof value === 'string') + return value; + + return Amount.encode(value, 8, num); + } + + /** + * Safely convert a BTC string to satoshis. + * @param {String} str - BTC + * @returns {Amount} Satoshis. + * @throws on parse error + */ + + static value(str) { + if (typeof str === 'number') + return str; + + return Amount.decode(str, 8); + } + + /** + * Safely convert satoshis to a BTC string. + * @param {Amount} value + * @param {Number} exp - Exponent. + * @param {Boolean} num - Return a number. + * @returns {String|Number} + */ + + static encode(value, exp, num) { + if (num) + return fixed.toFloat(value, exp); + return fixed.encode(value, exp); + } + + /** + * Safely convert a BTC string to satoshis. + * @param {String|Number} value - BTC + * @param {Number} exp - Exponent. + * @returns {Amount} Satoshis. + * @throws on parse error + */ + + static decode(value, exp) { + if (typeof value === 'number') + return fixed.fromFloat(value, exp); + return fixed.decode(value, exp); + } +} /* * Expose diff --git a/lib/btc/uri.js b/lib/btc/uri.js index b4e98d71..34b3d831 100644 --- a/lib/btc/uri.js +++ b/lib/btc/uri.js @@ -11,10 +11,9 @@ const Address = require('../primitives/address'); const Amount = require('./amount'); /** + * URI * Represents a bitcoin URI. * @alias module:btc.URI - * @constructor - * @param {Object|String} options * @property {Address} address * @property {Amount} amount * @property {String|null} label @@ -22,185 +21,194 @@ const Amount = require('./amount'); * @property {String|null} request */ -function URI(options) { - if (!(this instanceof URI)) - return new URI(options); +class URI { + /** + * Create a bitcoin URI. + * @alias module:btc.URI + * @constructor + * @param {Object|String} options + */ - this.address = new Address(); - this.amount = -1; - this.label = null; - this.message = null; - this.request = null; + constructor(options) { + this.address = new Address(); + this.amount = -1; + this.label = null; + this.message = null; + this.request = null; - if (options) - this.fromOptions(options); -} - -/** - * Inject properties from options object. - * @private - * @param {Object|String} options - * @returns {URI} - */ - -URI.prototype.fromOptions = function fromOptions(options) { - if (typeof options === 'string') - return this.fromString(options); - - if (options.address) - this.address.fromOptions(options.address); - - if (options.amount != null) { - assert(Number.isSafeInteger(options.amount) && options.amount >= 0, - 'Amount must be a uint64.'); - this.amount = options.amount; + if (options) + this.fromOptions(options); } - if (options.label) { - assert(typeof options.label === 'string', 'Label must be a string.'); - this.label = options.label; - } + /** + * Inject properties from options object. + * @private + * @param {Object|String} options + * @returns {URI} + */ - if (options.message) { - assert(typeof options.message === 'string', 'Message must be a string.'); - this.message = options.message; - } + fromOptions(options) { + if (typeof options === 'string') + return this.fromString(options); - if (options.request) { - assert(typeof options.request === 'string', 'Request must be a string.'); - this.request = options.request; - } + if (options.address) + this.address.fromOptions(options.address); - return this; -}; + if (options.amount != null) { + assert(Number.isSafeInteger(options.amount) && options.amount >= 0, + 'Amount must be a uint64.'); + this.amount = options.amount; + } -/** - * Instantiate URI from options. - * @param {Object|String} options - * @returns {URI} - */ + if (options.label) { + assert(typeof options.label === 'string', 'Label must be a string.'); + this.label = options.label; + } -URI.fromOptions = function fromOptions(options) { - return new URI().fromOptions(options); -}; + if (options.message) { + assert(typeof options.message === 'string', 'Message must be a string.'); + this.message = options.message; + } -/** - * Parse and inject properties from string. - * @private - * @param {String} str - * @param {Network?} network - * @returns {URI} - */ + if (options.request) { + assert(typeof options.request === 'string', 'Request must be a string.'); + this.request = options.request; + } -URI.prototype.fromString = function fromString(str, network) { - assert(typeof str === 'string'); - assert(str.length > 8, 'Not a bitcoin URI.'); - - const prefix = str.substring(0, 8); - - assert(prefix === 'bitcoin:', 'Not a bitcoin URI.'); - - str = str.substring(8); - - const index = str.indexOf('?'); - - let addr, qs; - if (index === -1) { - addr = str; - } else { - addr = str.substring(0, index); - qs = str.substring(index + 1); - } - - this.address.fromString(addr, network); - - if (!qs) return this; - - const query = parsePairs(qs); - - if (query.amount) { - assert(query.amount.length > 0, 'Value is empty.'); - assert(query.amount[0] !== '-', 'Value is negative.'); - this.amount = Amount.value(query.amount); } - if (query.label) - this.label = query.label; + /** + * Instantiate URI from options. + * @param {Object|String} options + * @returns {URI} + */ - if (query.message) - this.message = query.message; + static fromOptions(options) { + return new this().fromOptions(options); + } - if (query.r) - this.request = query.r; + /** + * Parse and inject properties from string. + * @private + * @param {String} str + * @param {Network?} network + * @returns {URI} + */ - return this; -}; + fromString(str, network) { + assert(typeof str === 'string'); + assert(str.length > 8, 'Not a bitcoin URI.'); -/** - * Instantiate uri from string. - * @param {String} str - * @param {Network?} network - * @returns {URI} - */ + const prefix = str.substring(0, 8); -URI.fromString = function fromString(str, network) { - return new URI().fromString(str, network); -}; + assert(prefix === 'bitcoin:', 'Not a bitcoin URI.'); -/** - * Serialize uri to a string. - * @returns {String} - */ + str = str.substring(8); -URI.prototype.toString = function toString() { - let str = 'bitcoin:'; + const index = str.indexOf('?'); - str += this.address.toString(); + let addr, qs; + if (index === -1) { + addr = str; + } else { + addr = str.substring(0, index); + qs = str.substring(index + 1); + } - const query = []; + this.address.fromString(addr, network); - if (this.amount !== -1) - query.push(`amount=${Amount.btc(this.amount)}`); + if (!qs) + return this; - if (this.label) - query.push(`label=${escape(this.label)}`); + const query = parsePairs(qs); - if (this.message) - query.push(`message=${escape(this.message)}`); + if (query.amount) { + assert(query.amount.length > 0, 'Value is empty.'); + assert(query.amount[0] !== '-', 'Value is negative.'); + this.amount = Amount.value(query.amount); + } - if (this.request) - query.push(`r=${escape(this.request)}`); + if (query.label) + this.label = query.label; - if (query.length > 0) - str += '?' + query.join('&'); + if (query.message) + this.message = query.message; - return str; -}; + if (query.r) + this.request = query.r; -/** - * Inspect bitcoin uri. - * @returns {String} - */ + return this; + } -URI.prototype.inspect = function inspect() { - return ``; -}; + /** + * Instantiate uri from string. + * @param {String} str + * @param {Network?} network + * @returns {URI} + */ + + static fromString(str, network) { + return new this().fromString(str, network); + } + + /** + * Serialize uri to a string. + * @returns {String} + */ + + toString() { + let str = 'bitcoin:'; + + str += this.address.toString(); + + const query = []; + + if (this.amount !== -1) + query.push(`amount=${Amount.btc(this.amount)}`); + + if (this.label) + query.push(`label=${escape(this.label)}`); + + if (this.message) + query.push(`message=${escape(this.message)}`); + + if (this.request) + query.push(`r=${escape(this.request)}`); + + if (query.length > 0) + str += '?' + query.join('&'); + + return str; + } + + /** + * Inspect bitcoin uri. + * @returns {String} + */ + + inspect() { + return ``; + } +} /* * Helpers */ -function BitcoinQuery() { - this.amount = null; - this.label = null; - this.message = null; - this.r = null; +class BitcoinQuery { + constructor() { + this.amount = null; + this.label = null; + this.message = null; + this.r = null; + } } function parsePairs(str) { const parts = str.split('&'); const data = new BitcoinQuery(); + let size = 0; for (const pair of parts) { @@ -244,7 +252,7 @@ function parsePairs(str) { break; } - size++; + size += 1; } return data;