diff --git a/docs/URI.md b/docs/URI.md new file mode 100644 index 0000000..17578eb --- /dev/null +++ b/docs/URI.md @@ -0,0 +1,44 @@ +# URI + +Represents a bitcoin payment uri. Bitcoin URI strings became the most popular +way to share payment request, sometimes as a bitcoin link and others using a QR code. + +URI Examples: +``` +bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu +bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2 +bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2&message=Payment&label=Satoshi&extra=other-param +``` + +The main use that we expect you'll have for the `URI` class in bitcore is +validating and parsing bitcoin URIs. A `URI` instance exposes the address as a +bitcore `Address` object and the amount in Satoshis, if present. + +The code for validating uris looks like this: +```javascript +var uriString = 'bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2'; +var valid = URI.isValid(uriString); +var uri = new URI(uriString); +console.log(uri.address.network, uri.amount); // 'livenet', 120000000 +``` + +All standard parameters can be found as members of the `URI` instance. However +a bitcoin uri may contain other non-standard parameters, all those can be found +under the `extra` namespace. + +See [the official BIP21 spec](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki) +for more information. + +Other usecase important usecase for the `URI` class is creating a bitcoin URI for +sharing a payment request. That can be acomplished by using an Object to create +an instance of URI. + +The code for creating an URI from an Object looks like this: +```javascript +var uriString = new URI({ + address: '12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu', + amount : 10000, // in satoshis + message: 'My payment request' +}); +var uriString = uri.toString(); +``` diff --git a/lib/address.js b/lib/address.js index 957f53c..fe3d6ec 100644 --- a/lib/address.js +++ b/lib/address.js @@ -329,7 +329,7 @@ Address.getValidationError = function(data, network, type) { * @param {String} data - The encoded data * @param {String} network - The network: 'mainnet' or 'testnet' * @param {String} type - The type of address: 'script' or 'pubkey' - * @returns {null|Error} The corresponding error message + * @returns {boolean} The corresponding error message */ Address.isValid = function(data, network, type) { return !Address.getValidationError(data, network, type); diff --git a/lib/uri.js b/lib/uri.js index c8bc3eb..668aa2c 100644 --- a/lib/uri.js +++ b/lib/uri.js @@ -1,34 +1,79 @@ 'use strict'; var _ = require('lodash'); - var URL = require('url'); + var Address = require('./address'); -var URI = function(arg, knownArgs) { +/** + * + * Bitcore URI + * + * Instantiate an URI from a bitcoin URI String or an Object. An URI instance + * can be created with a bitcoin uri string or an object. All instances of + * URI are valid, the static method isValid allows checking before instanciation. + * + * All standard parameters can be found as members of the class, the address + * is represented using an {Address} instance and the amount is represented in + * satoshis. Any other non-standard parameters can be found under the extra member. + * + * @example + * + * var uri = new URI('bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2'); + * console.log(uri.address, uri.amount); + * + * @param {string|Object} data - A bitcoin URI string or an Object + * @param {Array.} [knownParams] - Required non-standard params + * @throws {TypeError} Invalid bitcoin address + * @throws {TypeError} Invalid amount + * @throws {Error} Unknown required argument + * @returns {URI} A new valid and frozen instance of URI + */ +var URI = function(data, knownParams) { this.extras = {}; - this.knownArgs = knownArgs || []; + this.knownParams = knownParams || []; this.address = this.network = this.amount = this.message = null; - if (typeof(arg) == 'string') { - var params = URI.parse(arg); + if (typeof(data) == 'string') { + var params = URI.parse(data); this._fromObject(params); - } else if (typeof(arg) == 'object') { - this._fromObject(arg); + } else if (typeof(data) == 'object') { + this._fromObject(data); } else { throw new TypeError('Unrecognized data format.'); } }; -URI.isValid = function(arg, knownArgs) { +/** + * + * Check if an bitcoin URI string is valid + * + * @example + * + * var valid = URI.isValid('bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu'); + * // true + * + * @param {string|Object} data - A bitcoin URI string or an Object + * @param {Array.} [knownParams] - Required non-standard params + * @returns {boolean} Result of uri validation + */ +URI.isValid = function(arg, knownParams) { try { - var uri = new URI(arg, knownArgs); + var uri = new URI(arg, knownParams); return true; } catch(err) { return false; } }; +/** + * + * Convert a bitcoin URI string into a simple object. + * + * @param {string} uri - A bitcoin URI string + * @throws {TypeError} Invalid bitcoin URI + * @returns {Object} An object with the parsed params + */ URI.parse = function(uri) { var info = URL.parse(uri, true); @@ -43,9 +88,17 @@ URI.parse = function(uri) { return info.query; }; - +/** + * + * Internal function to load the URI instance with an object. + * + * @param {Object} amount - Amount BTC string + * @throws {TypeError} Invalid bitcoin address + * @throws {TypeError} Invalid amount + * @throws {Error} Unknown required argument + */ URI.prototype._fromObject = function(obj) { - var members = ['address', 'amount', 'message', 'label']; + var members = ['address', 'amount', 'message', 'label', 'r']; if (!Address.isValid(obj.address)) throw new TypeError('Invalid bitcoin address'); @@ -57,7 +110,7 @@ URI.prototype._fromObject = function(obj) { for (var key in obj) { if (key === 'address' || key === 'amount') continue; - if (/^req-/.exec(key) && this.knownArgs.indexOf(key) === -1) { + if (/^req-/.exec(key) && this.knownParams.indexOf(key) === -1) { throw Error('Unknown required argument ' + key); } @@ -66,13 +119,26 @@ URI.prototype._fromObject = function(obj) { } }; +/** + * + * Internal function to transform a BTC string amount into satoshis + * + * @param {String} amount - Amount BTC string + * @throws {TypeError} Invalid amount + * @returns {Object} Amount represented in satoshis + */ URI.prototype._parseAmount = function(amount) { var amount = Number(amount); if (isNaN(amount)) throw new TypeError('Invalid amount'); return amount; // TODO: Convert to Satoshis (yemel) }; - +/** + * + * Will return a the string representation of the URI + * + * @returns {String} Bitcoin URI string + */ URI.prototype.toString = function() { var query = _.clone(this.extras); if (this.amount) query.amount = this.amount; // TODO: Convert to BTC (yemel) @@ -85,6 +151,12 @@ URI.prototype.toString = function() { }); }; +/** + * + * Will return a string formatted for the console + * + * @returns {String} Bitcoin URI + */ URI.prototype.inspect = function() { return ''; }