From d978160ddb9a92f52290ca1c1470d8115798cf9c Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Mon, 1 Dec 2014 12:33:45 -0300 Subject: [PATCH] Utility for unit conversion --- docs/Unit.md | 59 +++++++++++++++++ index.js | 1 + lib/unit.js | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/unit.js | 111 ++++++++++++++++++++++++++++++++ 4 files changed, 346 insertions(+) create mode 100644 docs/Unit.md create mode 100644 lib/unit.js create mode 100644 test/unit.js diff --git a/docs/Unit.md b/docs/Unit.md new file mode 100644 index 000000000..a6fec8f9f --- /dev/null +++ b/docs/Unit.md @@ -0,0 +1,59 @@ +# Unit + +Unit it's a utility for handling and converting bitcoins units. We strongly +recommend you to always use satoshis to represent amount inside your application +and only convert them to other units in the front-end. + +The supported units are BTC, mBTC, bits and satoshis. The codes for each unit +can be found as members of the Unit class. + +```javascript +var btcCode = Unit.BTC; +var mbtcCode = Unit.mBTC; +var bitsCode = Unit.bits; +var satsCode = Unit.satoshis; +``` + +There are two ways for creating a unit instance. You can instanciate the class +using a value and a unit code; alternatively if the unit it's fixed you could +you some of the static methods. Check some examples below: + +```javascript +var unit; +var amount = 100; + +// using a unit code +var unitPreference = Unit.BTC; +unit = new Unit(amount, unitPreference); + +// using a known unit +unit = Unit.fromBTC(amount); +unit = Unit.fromMilis(amount); +unit = Unit.fromBits(amount); +unit = Unit.fromSatoshis(amount); +``` + +Once you have a unit instance, you can check it's representantion in all the +available units. For your convinience the classes expose three ways to acomplish +this. Using the `.to(unitCode)` method, using a fixed unit like `.toSatoshis()` +or by using the accessors. + +```javascript +var unit; + +// using a unit code +var unitPreference = Unit.BTC; +value = Unit.fromSatoshis(amount).to(unitPreference); + +// using a known unit +value = Unit.fromBTC(amount).toBTC(); +value = Unit.fromBTC(amount).toMilis(); +value = Unit.fromBTC(amount).toBits(); +value = Unit.fromBTC(amount).toSatoshis(); + +// using accessors +value = Unit.fromBTC(amount).BTC; +value = Unit.fromBTC(amount).mBTC; +value = Unit.fromBTC(amount).bits; +value = Unit.fromBTC(amount).satoshis; +``` diff --git a/index.js b/index.js index 9fadddf87..98a43924f 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,7 @@ bitcore.encoding.BufferWriter = require('./lib/encoding/bufferwriter'); bitcore.encoding.Varint = require('./lib/encoding/varint'); // main bitcoin library +bitcore.Unit = require('./lib/unit'); bitcore.Address = require('./lib/address'); bitcore.BIP32 = require('./lib/bip32'); bitcore.Block = require('./lib/block'); diff --git a/lib/unit.js b/lib/unit.js new file mode 100644 index 000000000..983c818aa --- /dev/null +++ b/lib/unit.js @@ -0,0 +1,175 @@ +'use strict'; + +/** + * + * Bitcore Unit + * + * Utility for handling and converting bitcoins units. The supported units are + * BTC, mBTC, bits and satoshis. A unit instance can be created with an + * amount and a unit code, or alternatively using static methods like {fromBTC}. + * You can consult for different representation of a unit instance using it's + * {to} method, the fixed unit methods like {toSatoshis} or alternatively using + * the unit accessors. + * + * @example + * + * var sats = Unit.fromBTC(1.3).toSatoshis(); + * var mili = Unit.fromBits(1.3).to(Unit.mBTC); + * var btc = new Unit(1.3, Unit.bits).BTC; + * + * @param {Number} amount - The amount to be represented + * @param {String} code - The unit of the amount + * @returns {Unit} A new instance of an Unit + */ + +function Unit(amount, code) { + this._value = this._from(amount, code); + + var self = this; + var defineAccesor = function(key) { + Object.defineProperty(self, key, { + get: function() { return self.to(key); }, + enumerable: true, + }); + }; + + Object.keys(UNITS).forEach(defineAccesor); +}; + +var UNITS = { + "BTC" : [1e8, 8], + "mBTC" : [1e5, 5], + "bits" : [1e2, 2], + "satoshis" : [1, 0] +}; + +Object.keys(UNITS).forEach(function(key) { + Unit[key] = key; +}); + + +/** + * + * Will return a Unit instance created from an amount in BTC + * + * @param {Number} amount - The amount in BTC + * @returns {Unit} A Unit instance + */ +Unit.fromBTC = function(amount) { + return new Unit(amount, Unit.BTC); +}; + +/** + * + * Will return a Unit instance created from an amount in mBTC + * + * @param {Number} amount - The amount in mBTC + * @returns {Unit} A Unit instance + */ +Unit.fromMilis = function(amount) { + return new Unit(amount, Unit.mBTC); +}; + +/** + * + * Will return a Unit instance created from an amount in bits + * + * @param {Number} amount - The amount in bits + * @returns {Unit} A Unit instance + */ +Unit.fromBits = function(amount) { + return new Unit(amount, Unit.bits); +}; + +/** + * + * Will return a Unit instance created from an amount in satoshis + * + * @param {Number} amount - The amount in satoshis + * @returns {Unit} A Unit instance + */ +Unit.fromSatoshis = function(amount) { + return new Unit(amount, Unit.satoshis); +}; + +Unit.prototype._from = function(amount, code) { + if (!UNITS[code]) throw Error('Unknown unit code'); + + return parseInt((amount * UNITS[code][0]).toFixed()); +}; + +/** + * + * Will return the value represented in the specified unit + * + * @param {string} code - The unit code + * @returns {Number} The converted value + */ +Unit.prototype.to = function(code) { + if (!UNITS[code]) throw Error('Unknown unit code'); + + var value = this._value / UNITS[code][0]; + return parseFloat(value.toFixed(UNITS[code][1])); +}; + +/** + * + * Will return the value represented in BTC + * + * @returns {Number} The value converted to BTC + */ +Unit.prototype.toBTC = function() { + return this.to(Unit.BTC); +}; + +/** + * + * Will return the value represented in mBTC + * + * @returns {Number} The value converted to mBTC + */ +Unit.prototype.toMilis = function(code) { + return this.to(Unit.mBTC); +}; + +/** + * + * Will return the value represented in bits + * + * @returns {Number} The value converted to bits + */ +Unit.prototype.toBits = function(code) { + return this.to(Unit.bits); +}; + +/** + * + * Will return the value represented in satoshis + * + * @returns {Number} The value converted to satoshis + */ +Unit.prototype.toSatoshis = function() { + return this.to(Unit.satoshis); +}; + +/** + * + * Will return a the string representation of the value in satoshis + * + * @returns {String} the value in satoshis + */ +Unit.prototype.toString = function() { + return this.satoshis + ' satoshis'; +}; + +/** + * + * Will return a string formatted for the console + * + * @returns {String} the value in satoshis + */ +Unit.prototype.inspect = function() { + return ''; +}; + +module.exports = Unit; diff --git a/test/unit.js b/test/unit.js new file mode 100644 index 000000000..d9f1ecddb --- /dev/null +++ b/test/unit.js @@ -0,0 +1,111 @@ +'use strict'; + +var should = require('chai').should(); +var bitcore = require('..'); +var Unit = bitcore.Unit; + +describe('Unit', function() { + + it('should create an instance', function() { + var unit = new Unit(1.2, "BTC"); + should.exist(unit); + }); + + it('should have property accesors', function() { + var unit = new Unit(1.2, "BTC"); + should.exist(unit.BTC); + should.exist(unit.mBTC); + should.exist(unit.bits); + should.exist(unit.satoshis); + }); + + it('should have constructor helpers', function() { + var unit; + + unit = Unit.fromBTC(1.00001); + unit.BTC.should.equal(1.00001); + + unit = Unit.fromMilis(1.00001); + unit.mBTC.should.equal(1.00001); + + unit = Unit.fromBits(100); + unit.bits.should.equal(100); + + unit = Unit.fromSatoshis(8999); + unit.satoshis.should.equal(8999); + }); + + it('should convert to satoshis correctly', function() { + var unit; + + unit = Unit.fromBTC(1.3); + unit.mBTC.should.equal(1300); + unit.bits.should.equal(1300000); + unit.satoshis.should.equal(130000000); + + unit = Unit.fromMilis(1.3); + unit.BTC.should.equal(0.0013); + unit.bits.should.equal(1300); + unit.satoshis.should.equal(130000); + + unit = Unit.fromBits(1.3); + unit.BTC.should.equal(0.0000013); + unit.mBTC.should.equal(0.0013); + unit.satoshis.should.equal(130); + + unit = Unit.fromSatoshis(3); + unit.BTC.should.equal(0.00000003); + unit.mBTC.should.equal(0.00003); + unit.bits.should.equal(0.03); + }); + + it('should take in count floating point problems', function() { + var unit = Unit.fromBTC(0.00000003); + unit.mBTC.should.equal(0.00003); + unit.bits.should.equal(0.03); + unit.satoshis.should.equal(3); + }); + + it('should expose unit codes', function() { + should.exist(Unit.BTC); + Unit.BTC.should.equal('BTC'); + + should.exist(Unit.mBTC); + Unit.mBTC.should.equal('mBTC'); + + should.exist(Unit.bits); + Unit.bits.should.equal('bits'); + + should.exist(Unit.satoshis); + Unit.satoshis.should.equal('satoshis'); + }); + + it('should expose shorthand conversion methods', function() { + var unit = new Unit(1.3, "BTC"); + unit.toBTC().should.equal(unit.BTC); + unit.toMilis().should.equal(unit.mBTC); + unit.toBits().should.equal(unit.bits); + unit.toSatoshis().should.equal(unit.satoshis); + }); + + it('should expose a general conversion method', function() { + var unit = new Unit(1.3, "BTC"); + unit.to(Unit.BTC).should.equal(unit.BTC); + unit.to(Unit.mBTC).should.equal(unit.mBTC); + unit.to(Unit.bits).should.equal(unit.bits); + unit.to(Unit.satoshis).should.equal(unit.satoshis); + }); + + it('should have a toString method', function() { + var unit = new Unit(1.3, "BTC"); + should.exist(unit.toString); + unit.toString().should.be.a('string'); + }); + + it('should have an inspect method', function() { + var unit = new Unit(1.3, "BTC"); + should.exist(unit.inspect); + unit.inspect().should.be.a('string'); + }); + +});