btc: classify.

This commit is contained in:
Christopher Jeffrey 2017-11-16 18:49:59 -08:00
parent 51e3341252
commit 32b81f0d95
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 451 additions and 438 deletions

View File

@ -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 `<Amount: ${this.toString()}>`;
};
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 `<Amount: ${this.toString()}>`;
}
/**
* 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

View File

@ -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 `<URI: ${this.toString()}>`;
};
/**
* 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 `<URI: ${this.toString()}>`;
}
}
/*
* 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;