diff --git a/README.md b/README.md index 572a4ab4..b972c951 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ pool.open().then(function() { wallet.on('balance', function(balance) { console.log('Balance updated.'); - console.log(bcoin.utils.btc(balance.unconfirmed)); + console.log(bcoin.amount.btc(balance.unconfirmed)); }); }); ``` diff --git a/bin/cli b/bin/cli index c49e936d..d339fd10 100755 --- a/bin/cli +++ b/bin/cli @@ -7,6 +7,7 @@ var utils = require('../lib/utils/utils'); var co = require('../lib/utils/co'); var Client = require('../lib/http/client'); var Wallet = require('../lib/http/wallet'); +var Amount = require('../lib/utils/amount'); var main; function CLI() { @@ -286,10 +287,10 @@ CLI.prototype.sendTX = co(function* sendTX() { if (this.config.script) { output.script = this.config.script; - output.value = utils.satoshi(this.config.value || this.argv[0]); + output.value = Amount.value(this.config.value || this.argv[0]); } else { output.address = this.config.address || this.argv[0]; - output.value = utils.satoshi(this.config.value || this.argv[1]); + output.value = Amount.value(this.config.value || this.argv[1]); } options = { @@ -309,10 +310,10 @@ CLI.prototype.createTX = co(function* createTX() { if (this.config.script) { output.script = this.config.script; - output.value = utils.satoshi(this.config.value || this.argv[0]); + output.value = Amount.value(this.config.value || this.argv[0]); } else { output.address = this.config.address || this.argv[0]; - output.value = utils.satoshi(this.config.value || this.argv[1]); + output.value = Amount.value(this.config.value || this.argv[1]); } options = { diff --git a/browser/index.js b/browser/index.js index 9e1b6b0c..78c59901 100644 --- a/browser/index.js +++ b/browser/index.js @@ -82,7 +82,7 @@ send.onsubmit = function(ev) { options = { outputs: [{ address: address, - value: utils.satoshi(value) + value: bcoin.amount.value(value) }] }; @@ -148,7 +148,7 @@ function addItem(tx) { chainState.innerHTML = '' + 'tx=' + node.chain.db.state.tx + ' coin=' + node.chain.db.state.coin - + ' value=' + utils.btc(node.chain.db.state.value); + + ' value=' + bcoin.amount.btc(node.chain.db.state.value); } function setMouseup(el, obj) { @@ -182,11 +182,11 @@ function formatWallet(wallet) { wallet.getBalance().then(function(balance) { html += 'Confirmed Balance: ' - + utils.btc(balance.confirmed) + + bcoin.amount.btc(balance.confirmed) + '
'; html += 'Unconfirmed Balance: ' - + utils.btc(balance.unconfirmed) + + bcoin.amount.btc(balance.unconfirmed) + '
'; return wallet.getHistory(); diff --git a/lib/chain/chaindb.js b/lib/chain/chaindb.js index 08886c95..f027005d 100644 --- a/lib/chain/chaindb.js +++ b/lib/chain/chaindb.js @@ -13,6 +13,7 @@ var utils = require('../utils/utils'); var assert = require('assert'); var BufferWriter = require('../utils/writer'); var BufferReader = require('../utils/reader'); +var Amount = require('../utils/amount'); var encoding = require('../utils/encoding'); var co = require('../utils/co'); var Network = require('../protocol/network'); @@ -145,7 +146,7 @@ ChainDB.prototype._open = co(function* open() { this.state.rhash, this.state.tx, this.state.coin, - utils.btc(this.state.value)); + Amount.btc(this.state.value)); }); /** diff --git a/lib/http/client.js b/lib/http/client.js index fdc1a97b..17020196 100644 --- a/lib/http/client.js +++ b/lib/http/client.js @@ -10,6 +10,7 @@ var Network = require('../protocol/network'); var AsyncObject = require('../utils/async'); var RPCClient = require('./rpcclient'); +var Amount = require('../utils/amount'); var utils = require('../utils/utils'); var co = require('../utils/co'); var request = require('./request').promise; @@ -620,11 +621,11 @@ HTTPClient.prototype.send = function send(id, options) { options.outputs = options.outputs || []; if (options.rate) - options.rate = utils.btc(options.rate); + options.rate = Amount.btc(options.rate); options.outputs = options.outputs.map(function(output) { return { - value: utils.btc(output.value), + value: Amount.btc(output.value), address: output.address, script: toHex(output.script) }; @@ -668,11 +669,11 @@ HTTPClient.prototype.createTX = function createTX(id, options) { options = utils.merge({}, options); if (options.rate) - options.rate = utils.btc(options.rate); + options.rate = Amount.btc(options.rate); options.outputs = options.outputs.map(function(output) { return { - value: utils.btc(output.value), + value: Amount.btc(output.value), address: output.address, script: toHex(output.script) }; diff --git a/lib/http/rpc.js b/lib/http/rpc.js index bf4f10ff..2560daa4 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -13,6 +13,7 @@ var assert = require('assert'); var constants = require('../protocol/constants'); var ec = require('../crypto/ec'); var time = require('../net/time'); +var Amount = require('../utils/amount'); var NetworkAddress = require('../primitives/netaddress'); var Script = require('../script/script'); var Address = require('../primitives/address'); @@ -322,7 +323,7 @@ RPC.prototype.getinfo = co(function* getinfo(args) { version: constants.USER_VERSION, protocolversion: constants.VERSION, walletversion: 0, - balance: +utils.btc(balance.unconfirmed), + balance: Amount.btc(balance.unconfirmed, true), blocks: this.chain.height, timeoffset: time.offset, connections: this.pool.peers.all.length, @@ -332,8 +333,8 @@ RPC.prototype.getinfo = co(function* getinfo(args) { keypoololdest: 0, keypoolsize: 0, unlocked_until: this.wallet.master.until, - paytxfee: +utils.btc(this.network.feeRate), - relayfee: +utils.btc(this.network.minRelay), + paytxfee: Amount.btc(this.network.feeRate, true), + relayfee: Amount.btc(this.network.minRelay, true), errors: '' }; }); @@ -379,7 +380,7 @@ RPC.prototype.getnetworkinfo = function getnetworkinfo(args) { timeoffset: time.offset, connections: this.pool.peers.all.length, networks: [], - relayfee: +utils.btc(this.network.getMinRelay()), + relayfee: Amount.btc(this.network.getMinRelay(), true), localaddresses: [], warnings: '' }); @@ -794,7 +795,7 @@ RPC.prototype._txToJSON = function _txToJSON(tx) { }), vout: tx.outputs.map(function(output, i) { return { - value: +utils.btc(output.value), + value: Amount.btc(output.value, true), n: i, scriptPubKey: self._scriptToJSON(output.script, true) }; @@ -984,7 +985,7 @@ RPC.prototype.getmempoolinfo = function getmempoolinfo(args) { bytes: this.mempool.getSize(), usage: this.mempool.getSize(), maxmempool: constants.mempool.MAX_MEMPOOL_SIZE, - mempoolminfee: +utils.btc(this.mempool.minRelay) + mempoolminfee: Amount.btc(this.mempool.minRelay, true) }); }; @@ -1123,18 +1124,18 @@ RPC.prototype._entryToJSON = function _entryToJSON(entry) { var tx = entry.tx; return { size: entry.size, - fee: +utils.btc(entry.fee), - modifiedfee: +utils.btc(entry.fees), + fee: Amount.btc(entry.fee, true), + modifiedfee: Amount.btc(entry.fees, true), time: entry.ts, height: entry.height, startingpriority: entry.priority, currentpriority: entry.getPriority(this.chain.height), descendantcount: this.mempool.countDescendants(tx), descendantsize: entry.sizes, - descendantfees: +utils.btc(entry.fees), + descendantfees: Amount.btc(entry.fees, true), ancestorcount: this.mempool.countAncestors(tx), ancestorsize: entry.sizes, - ancestorfees: +utils.btc(entry.fees), + ancestorfees: Amount.btc(entry.fees, true), depends: this.mempool.getDepends(tx).map(utils.revHex) }; }; @@ -1172,7 +1173,7 @@ RPC.prototype.gettxout = co(function* gettxout(args) { return { bestblock: this.chain.tip.rhash, confirmations: coin.getConfirmations(this.chain.height), - value: +utils.btc(coin.value), + value: Amount.btc(coin.value, true), scriptPubKey: this._scriptToJSON(coin.script, true), version: coin.version, coinbase: coin.coinbase @@ -1283,7 +1284,7 @@ RPC.prototype.gettxoutsetinfo = function gettxoutsetinfo(args) { txouts: this.chain.db.state.coin, bytes_serialized: 0, hash_serialized: 0, - total_amount: +utils.btc(this.chain.db.state.value) + total_amount: Amount.btc(this.chain.db.state.value, true) }); }; @@ -2333,7 +2334,7 @@ RPC.prototype.fundrawtransaction = co(function* fundrawtransaction(args) { return { hex: tx.toRaw().toString('hex'), changepos: tx.changeIndex, - fee: +utils.btc(tx.getFee()) + fee: Amount.btc(tx.getFee(), true) }; }); @@ -2545,7 +2546,7 @@ RPC.prototype.estimatefee = function estimatefee(args) { if (fee === 0) fee = -1; else - fee = +utils.btc(fee); + fee = Amount.btc(fee, true); return Promise.resolve(fee); }; @@ -2588,7 +2589,7 @@ RPC.prototype.estimatesmartfee = function estimatesmartfee(args) { if (fee === 0) fee = -1; else - fee = +utils.btc(fee); + fee = Amount.btc(fee, true); return Promise.resolve({ fee: fee, @@ -2901,7 +2902,7 @@ RPC.prototype.getbalance = co(function* getbalance(args) { else value = balance.unconfirmed; - return +utils.btc(value); + return Amount.btc(value, true); }); RPC.prototype.getnewaddress = co(function* getnewaddress(args) { @@ -2982,7 +2983,7 @@ RPC.prototype.getreceivedbyaccount = co(function* getreceivedbyaccount(args) { } } - return +utils.btc(total); + return Amount.btc(total, true); }); RPC.prototype.getreceivedbyaddress = co(function* getreceivedbyaddress(args) { @@ -3019,7 +3020,7 @@ RPC.prototype.getreceivedbyaddress = co(function* getreceivedbyaddress(args) { } } - return +utils.btc(total); + return Amount.btc(total, true); }); RPC.prototype._toWalletTX = co(function* _toWalletTX(tx) { @@ -3054,7 +3055,7 @@ RPC.prototype._toWalletTX = co(function* _toWalletTX(tx) { account: member.path.name, address: member.address.toBase58(this.network), category: 'receive', - amount: +utils.btc(member.value), + amount: Amount.btc(member.value, true), label: member.path.name, vout: i }); @@ -3073,8 +3074,8 @@ RPC.prototype._toWalletTX = co(function* _toWalletTX(tx) { ? member.address.toBase58(this.network) : null, category: 'send', - amount: -(+utils.btc(member.value)), - fee: -(+utils.btc(details.fee)), + amount: -(Amount.btc(member.value, true)), + fee: -(Amount.btc(details.fee, true)), vout: i }); @@ -3082,7 +3083,7 @@ RPC.prototype._toWalletTX = co(function* _toWalletTX(tx) { } json = { - amount: +utils.btc(receive ? received : -sent), + amount: Amount.btc(receive ? received : -sent, true), confirmations: details.confirmations, blockhash: details.block ? utils.revHex(details.block) : null, blockindex: details.index, @@ -3145,7 +3146,7 @@ RPC.prototype.getunconfirmedbalance = co(function* getunconfirmedbalance(args) { balance = yield this.wallet.getBalance(); - return +utils.btc(balance.unconfirmed); + return Amount.btc(balance.unconfirmed, true); }); RPC.prototype.getwalletinfo = co(function* getwalletinfo(args) { @@ -3159,15 +3160,15 @@ RPC.prototype.getwalletinfo = co(function* getwalletinfo(args) { return { walletid: this.wallet.id, walletversion: 6, - balance: +utils.btc(balance.unconfirmed), - unconfirmed_balance: +utils.btc(balance.unconfirmed), + balance: Amount.btc(balance.unconfirmed, true), + unconfirmed_balance: Amount.btc(balance.unconfirmed, true), txcount: this.wallet.state.tx, keypoololdest: 0, keypoolsize: 0, unlocked_until: this.wallet.master.until, paytxfee: this.feeRate != null - ? +utils.btc(this.feeRate) - : +utils.btc(0) + ? Amount.btc(this.feeRate, true) + : 0 }; }); @@ -3312,7 +3313,7 @@ RPC.prototype.listaccounts = co(function* listaccounts(args) { for (i = 0; i < accounts.length; i++) { account = accounts[i]; balance = yield this.wallet.getBalance(account); - map[account] = +utils.btc(balance.unconfirmed); + map[account] = Amount.btc(balance.unconfirmed, true); } return map; @@ -3465,7 +3466,7 @@ RPC.prototype._listReceived = co(function* _listReceived(minconf, empty, account continue; if (entry.confirmations === -1) entry.confirmations = 0; - entry.amount = +utils.btc(entry.amount); + entry.amount = Amount.btc(entry.amount, true); result.push(entry); } @@ -3587,7 +3588,7 @@ RPC.prototype._toListTX = co(function* _toListTX(tx) { ? member.address.toBase58(this.network) : null, category: receive ? 'receive' : 'send', - amount: +utils.btc(receive ? received : -sent), + amount: Amount.btc(receive ? received : -sent, true), label: member.path ? member.path.name : undefined, vout: index, confirmations: details.confirmations, @@ -3714,7 +3715,7 @@ RPC.prototype.listunspent = co(function* listunspent(args) { ? ring.script.toJSON() : undefined, scriptPubKey: coin.script.toJSON(), - amount: +utils.btc(coin.value), + amount: Amount.btc(coin.value, true), confirmations: depth, spendable: !this.wallet.isLocked(coin), solvable: true @@ -4179,7 +4180,7 @@ function isHash(obj) { function toSatoshi(obj) { if (typeof obj !== 'number') throw new RPCError('Bad BTC amount.'); - return utils.satoshi(obj + ''); + return Amount.value(obj, true); } function reverseEndian(data) { diff --git a/lib/http/server.js b/lib/http/server.js index 3de68b17..e9f213ba 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -17,6 +17,7 @@ var HTTPBase = require('./base'); var utils = require('../utils/utils'); var co = require('../utils/co'); var base58 = require('../utils/base58'); +var Amount = require('../utils/amount'); var Address = require('../primitives/address'); var Bloom = require('../utils/bloom'); var TX = require('../primitives/tx'); @@ -304,16 +305,16 @@ HTTPServer.prototype._init = function _init() { } if (params.fee) - options.fee = utils.satoshi(params.fee); + options.fee = Amount.value(params.fee); if (params.hardFee) - options.hardFee = utils.satoshi(params.hardFee); + options.hardFee = Amount.value(params.hardFee); if (params.maxFee) - options.maxFee = utils.satoshi(params.maxFee); + options.maxFee = Amount.value(params.maxFee); if (params.rate) - options.rate = utils.satoshi(params.rate); + options.rate = Amount.value(params.rate); if (params.m != null) { options.m = Number(params.m); @@ -382,7 +383,7 @@ HTTPServer.prototype._init = function _init() { script: output.script ? Script.fromRaw(output.script, 'hex') : null, - value: utils.satoshi(output.value) + value: Amount.value(output.value) }); } } @@ -732,11 +733,11 @@ HTTPServer.prototype._init = function _init() { var fee; if (!this.fees) - return send(200, { rate: utils.btc(this.network.feeRate) }); + return send(200, { rate: Amount.btc(this.network.feeRate) }); fee = this.fees.estimateFee(req.options.blocks); - send(200, { rate: utils.btc(fee) }); + send(200, { rate: Amount.btc(fee) }); }); // Reset chain @@ -1358,12 +1359,12 @@ HTTPServer.prototype._initIO = function _initIO() { if (!self.fees) { rate = self.network.feeRate; - rate = utils.btc(rate); + rate = Amount.btc(rate); return callback(null, rate); } rate = self.fees.estimateFee(blocks); - rate = utils.btc(rate); + rate = Amount.btc(rate); return callback(null, rate); }); diff --git a/lib/primitives/coin.js b/lib/primitives/coin.js index 9378fdae..2f7cad3f 100644 --- a/lib/primitives/coin.js +++ b/lib/primitives/coin.js @@ -7,10 +7,11 @@ 'use strict'; +var assert = require('assert'); var utils = require('../utils/utils'); var constants = require('../protocol/constants'); var Network = require('../protocol/network'); -var assert = require('assert'); +var Amount = require('../utils/amount'); var Output = require('./output'); var Script = require('../script/script'); var Network = require('../protocol/network'); @@ -122,7 +123,7 @@ Coin.prototype.inspect = function inspect() { type: this.getType(), version: this.version, height: this.height, - value: utils.btc(this.value), + value: Amount.btc(this.value), script: this.script, coinbase: this.coinbase, hash: this.hash ? utils.revHex(this.hash) : null, @@ -150,7 +151,7 @@ Coin.prototype.toJSON = function toJSON(network) { return { version: this.version, height: this.height, - value: utils.btc(this.value), + value: Amount.btc(this.value), script: this.script.toJSON(), address: address, coinbase: this.coinbase, @@ -186,7 +187,7 @@ Coin.prototype.fromJSON = function fromJSON(json) { this.version = json.version; this.height = json.height; - this.value = utils.satoshi(json.value); + this.value = Amount.value(json.value); this.script.fromJSON(json.script); this.coinbase = json.coinbase; this.hash = json.hash ? utils.revHex(json.hash) : null; diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js index 4e72d261..00ff5f77 100644 --- a/lib/primitives/mtx.js +++ b/lib/primitives/mtx.js @@ -175,8 +175,8 @@ MTX.prototype.addInput = function addInput(options, index) { * Add an output. * @example * tx.addOutput({ address: ..., value: 100000 }); - * tx.addOutput({ address: ..., value: utils.satoshi('0.1') }); - * tx.addOutput(receivingWallet, utils.satoshi('0.1')); + * tx.addOutput({ address: ..., value: Amount.value('0.1') }); + * tx.addOutput(receivingWallet, Amount.value('0.1')); * @param {Wallet|KeyRing|Object} obj - Wallet, Address, * or options (see {@link Script.createOutputScript} for options). * @param {Amount?} value - Only needs to be present for non-options. diff --git a/lib/primitives/output.js b/lib/primitives/output.js index 7856043c..915a2f96 100644 --- a/lib/primitives/output.js +++ b/lib/primitives/output.js @@ -9,6 +9,7 @@ var utils = require('../utils/utils'); var constants = require('../protocol/constants'); +var Amount = require('../utils/amount'); var Network = require('../protocol/network'); var Script = require('../script/script'); var BufferWriter = require('../utils/writer'); @@ -117,7 +118,7 @@ Output.prototype.getHash = function getHash(enc) { Output.prototype.inspect = function inspect() { return { type: this.getType(), - value: utils.btc(this.value), + value: Amount.btc(this.value), script: this.script, address: this.getAddress() }; @@ -138,7 +139,7 @@ Output.prototype.toJSON = function toJSON(network) { address = address.toBase58(network); return { - value: utils.btc(this.value), + value: Amount.btc(this.value), script: this.script.toJSON(), address: address }; @@ -205,7 +206,7 @@ Output.prototype.isDust = function isDust(rate) { Output.prototype.fromJSON = function fromJSON(json) { assert(typeof json.value === 'string'); - this.value = utils.satoshi(json.value); + this.value = Amount.value(json.value); this.script.fromJSON(json.script); return this; }; diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index 0d4154ee..343a773a 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -11,6 +11,7 @@ var assert = require('assert'); var utils = require('../utils/utils'); var crypto = require('../crypto/crypto'); var btcutils = require('../utils/btcutils'); +var Amount = require('../utils/amount'); var constants = require('../protocol/constants'); var Network = require('../protocol/network'); var Script = require('../script/script'); @@ -2041,10 +2042,10 @@ TX.prototype.inspect = function inspect() { size: this.getSize(), virtualSize: this.maxSize(), height: this.height, - value: utils.btc(this.getOutputValue()), - fee: utils.btc(this.getFee()), - minFee: utils.btc(this.getMinFee()), - rate: utils.btc(rate), + value: Amount.btc(this.getOutputValue()), + fee: Amount.btc(this.getFee()), + minFee: Amount.btc(this.getMinFee()), + rate: Amount.btc(rate), date: utils.date(this.ts || this.ps), block: this.block ? utils.revHex(this.block) : null, ts: this.ts, @@ -2084,8 +2085,8 @@ TX.prototype.toJSON = function toJSON(network) { ps: this.ps, date: utils.date(this.ts || this.ps), index: this.index, - fee: utils.btc(this.getFee()), - rate: utils.btc(rate), + fee: Amount.btc(this.getFee()), + rate: Amount.btc(rate), version: this.version, flag: this.flag, inputs: this.inputs.map(function(input) { diff --git a/lib/utils/amount.js b/lib/utils/amount.js index 6457938a..362cbc27 100644 --- a/lib/utils/amount.js +++ b/lib/utils/amount.js @@ -1,5 +1,19 @@ +/*! + * amount.js - amount object for bcoin + * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var assert = require('assert'); var utils = require('./utils'); +/** + * Amount + * @constructor + */ + function Amount(value, unit, num) { if (!(this instanceof Amount)) return new Amount(value, unit, num); @@ -47,6 +61,7 @@ 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': @@ -91,6 +106,7 @@ Amount.prototype.from = function from(unit, value, num) { switch (unit) { case 'sat': return this.fromSatoshis(value, num); + case 'ubtc': case 'bits': return this.fromBits(value, num); case 'mbtc': @@ -101,7 +117,7 @@ Amount.prototype.from = function from(unit, value, num) { throw new Error('Unknown unit "' + unit + '".'); }; -Amount.fromOptions = function fromOptions(value) { +Amount.fromOptions = function fromOptions(value, unit, num) { return new Amount().fromOptions(value); }; @@ -141,19 +157,21 @@ Amount.prototype.inspect = function inspect() { * @returns {String} BTC string. */ -Amount.btc = function btc(value) { +Amount.btc = function btc(value, num) { if (utils.isFloat(value)) return value; - return Amount.serialize(value, 8, false); + return Amount.serialize(value, 8, num); }; /** * Safely convert satoshis to a BTC string. * This function explicitly avoids any * floating point arithmetic. - * @param {Amount} value - Satoshis. - * @returns {String} BTC string. + * @param {Amount} value + * @param {Number} dec - Number of decimals. + * @param {Boolean} num - Return a number. + * @returns {String} */ Amount.serialize = function serialize(value, dec, num) { @@ -167,11 +185,9 @@ Amount.serialize = function serialize(value, dec, num) { negative = true; } - assert(value <= utils.MAX_SAFE_INTEGER, 'Number exceeds 2^53-1.'); - value = value.toString(10); - assert(value.length <= 8 + dec, 'Number exceeds 2^53-1.'); + assert(value.length <= 16, 'Number exceeds 2^53-1.'); while (value.length < dec + 1) value = '0' + value; @@ -195,22 +211,44 @@ Amount.serialize = function serialize(value, dec, num) { return result; }; +/** + * Unsafely convert satoshis to a BTC string. + * @param {Amount} value + * @param {Number} dec - Number of decimals. + * @param {Boolean} num - Return a number. + * @returns {String} + */ + +Amount.serializeUnsafe = function serializeUnsafe(value, dec, num) { + assert(utils.isInt(value), 'Non-satoshi value for conversion.'); + + value /= pow10(dec); + value = value.toFixed(dec); + + if (num) + return +value; + + if (dec !== 0) { + value = value.replace(/0+$/, ''); + if (value[value.length - 1] === '.') + value += '0'; + } + + return value; +}; + /** * Safely convert a BTC string to satoshis. - * This function explicitly avoids any - * floating point arithmetic. It also does - * extra validation to ensure the resulting - * Number will be 53 bits or less. * @param {String} value - BTC * @returns {Amount} Satoshis. * @throws on parse error */ -Amount.satoshi = function satoshi(value) { +Amount.value = function value(value, num) { if (utils.isInt(value)) return value; - return Amount.parse(value, 8, false); + return Amount.parse(value, 8, num); }; /** @@ -220,15 +258,17 @@ Amount.satoshi = function satoshi(value) { * extra validation to ensure the resulting * Number will be 53 bits or less. * @param {String} value - BTC + * @param {Number} dec - Number of decimals. + * @param {Boolean} num - Allow numbers. * @returns {Amount} Satoshis. * @throws on parse error */ Amount.parse = function parse(value, dec, num) { var negative = false; - var mult = Math.pow(10, dec); - var maxLo = utils.MAX_SAFE_INTEGER % mult; - var maxHi = (utils.MAX_SAFE_INTEGER - maxLo) / mult; + var mult = pow10(dec); + var maxLo = modSafe(mult); + var maxHi = divSafe(mult); var parts, hi, lo, result; if (num && typeof value === 'number') { @@ -276,6 +316,110 @@ Amount.parse = function parse(value, dec, num) { return result; }; +/** + * Unsafely convert a BTC string to satoshis. + * @param {String} value - BTC + * @param {Number} dec - Number of decimals. + * @param {Boolean} num - Allow numbers. + * @returns {Amount} Satoshis. + * @throws on parse error + */ + +Amount.parseUnsafe = function parseUnsafe(value, dec, num) { + if (typeof value === 'string') { + assert(utils.isFloat(value), 'Non-BTC value for conversion.'); + value = parseFloat(value, 10); + } else { + assert(utils.isNumber(value), 'Non-BTC value for conversion.'); + assert(num, 'Cannot parse number.'); + } + + value *= pow10(dec); + + assert(value % 1 === 0, 'Too many decimal places.'); + + return value; +}; + +/* + * Helpers + */ + +function pow10(exp) { + switch (exp) { + case 0: + return 1; + case 1: + return 10; + case 2: + return 100; + case 3: + return 1000; + case 4: + return 10000; + case 5: + return 100000; + case 6: + return 1000000; + case 7: + return 10000000; + case 8: + return 100000000; + default: + assert(false); + } +} + +function modSafe(mod) { + switch (mod) { + case 1: + return 0; + case 10: + return 1; + case 100: + return 91; + case 1000: + return 991; + case 10000: + return 991; + case 100000: + return 40991; + case 1000000: + return 740991; + case 10000000: + return 4740991; + case 100000000: + return 54740991; + default: + assert(false); + } +} + +function divSafe(div) { + switch (div) { + case 1: + return 9007199254740991; + case 10: + return 900719925474099; + case 100: + return 90071992547409; + case 1000: + return 9007199254740; + case 10000: + return 900719925474; + case 100000: + return 90071992547; + case 1000000: + return 9007199254; + case 10000000: + return 900719925; + case 100000000: + return 90071992; + default: + assert(false); + } +} + /* * Expose */ diff --git a/lib/utils/btcutils.js b/lib/utils/btcutils.js index dab5711e..85745aa2 100644 --- a/lib/utils/btcutils.js +++ b/lib/utils/btcutils.js @@ -11,6 +11,7 @@ var assert = require('assert'); var BN = require('bn.js'); var constants = require('../protocol/constants'); var utils = require('./utils'); +var Amount = require('./amount'); var btcutils = exports; /** @@ -194,42 +195,9 @@ btcutils.getRate = function getRate(size, fee) { */ btcutils.btc = function btc(value) { - var negative = false; - var hi, lo, result; - if (utils.isFloat(value)) return value; - - assert(utils.isInt(value), 'Non-satoshi value for conversion.'); - - if (value < 0) { - value = -value; - negative = true; - } - - assert(value <= utils.MAX_SAFE_INTEGER, 'Number exceeds 2^53-1.'); - - value = value.toString(10); - - assert(value.length <= 16, 'Number exceeds 2^53-1.'); - - while (value.length < 9) - value = '0' + value; - - hi = value.slice(0, -8); - lo = value.slice(-8); - - lo = lo.replace(/0+$/, ''); - - if (lo.length === 0) - lo += '0'; - - result = hi + '.' + lo; - - if (negative) - result = '-' + result; - - return result; + return Amount.fromValue(value).toBTC(); }; /** @@ -244,50 +212,9 @@ btcutils.btc = function btc(value) { */ btcutils.satoshi = function satoshi(value) { - var negative = false; - var parts, hi, lo, result; - if (utils.isInt(value)) return value; - - assert(utils.isFloat(value), 'Non-BTC value for conversion.'); - - if (value[0] === '-') { - negative = true; - value = value.substring(1); - } - - parts = value.split('.'); - - assert(parts.length <= 2, 'Bad decimal point.'); - - hi = parts[0] || '0'; - lo = parts[1] || '0'; - - hi = hi.replace(/^0+/, ''); - lo = lo.replace(/0+$/, ''); - - assert(hi.length <= 8, 'Number exceeds 2^53-1.'); - assert(lo.length <= 8, 'Too many decimal places.'); - - if (hi.length === 0) - hi = '0'; - - while (lo.length < 8) - lo += '0'; - - hi = parseInt(hi, 10); - lo = parseInt(lo, 10); - - assert(hi < 90071992 || (hi === 90071992 && lo <= 54740991), - 'Number exceeds 2^53-1.'); - - result = hi * 100000000 + lo; - - if (negative) - result = -result; - - return result; + return Amount.fromBTC(value).toValue(); }; /** @@ -297,11 +224,8 @@ btcutils.satoshi = function satoshi(value) { */ btcutils.isSatoshi = function isSatoshi(value) { - if (typeof value !== 'number') - return false; - try { - utils.satoshi(value); + Amount.fromValue(value); return true; } catch (e) { return false; @@ -315,11 +239,8 @@ btcutils.isSatoshi = function isSatoshi(value) { */ btcutils.isBTC = function isBTC(value) { - if (typeof value !== 'string') - return false; - try { - utils.btc(value); + Amount.fromBTC(value); return true; } catch (e) { return false; diff --git a/lib/utils/errors.js b/lib/utils/errors.js index 1e5c01cb..ad4575bd 100644 --- a/lib/utils/errors.js +++ b/lib/utils/errors.js @@ -9,6 +9,7 @@ var utils = require('../utils/utils'); var constants = require('../protocol/constants'); +var Amount = require('./amount'); /** * An error thrown during verification. Can be either @@ -140,8 +141,8 @@ function FundingError(msg, available, required) { if (Error.captureStackTrace) Error.captureStackTrace(this, FundingError); - msg += ' (available=' + utils.btc(available) + ','; - msg += ' required=' + utils.btc(required) + ')'; + msg += ' (available=' + Amount.btc(available) + ','; + msg += ' required=' + Amount.btc(required) + ')'; this.type = 'FundingError'; this.message = msg; diff --git a/lib/utils/uri.js b/lib/utils/uri.js index 0459c79a..32737d57 100644 --- a/lib/utils/uri.js +++ b/lib/utils/uri.js @@ -6,8 +6,9 @@ 'use strict'; -var utils = require('../utils/utils'); +var utils = require('./utils'); var Address = require('../primitives/address'); +var Amount = require('./amount'); var assert = require('assert'); function URI(options) { @@ -88,7 +89,7 @@ URI.prototype.fromString = function fromString(str) { query = parsePairs(query); if (query.amount) - this.amount = utils.satoshi(query.amount); + this.amount = Amount.value(query.amount); if (query.label) this.label = query.label; @@ -113,7 +114,7 @@ URI.prototype.toString = function toString() { str += this.address.toBase58(); if (this.amount !== -1) - query.push('amount=' + utils.btc(this.amount)); + query.push('amount=' + Amount.btc(this.amount)); if (this.label) query.push('label=' + escape(this.label)); diff --git a/lib/utils/utils.js b/lib/utils/utils.js index e21a5e7f..9650d119 100644 --- a/lib/utils/utils.js +++ b/lib/utils/utils.js @@ -9,20 +9,13 @@ /* global gc */ -/** - * @exports utils - */ - -var utils = exports; - var assert = require('assert'); var util = require('util'); var fs = require('fs'); var os = require('os'); var BN = require('bn.js'); -var base58 = require('./base58'); +var utils = exports; var Number, Math, Date; -var lazy; /** * Reference to the global object. @@ -47,6 +40,10 @@ utils.global = (function() { assert(false, 'No global defined.'); })(); +/* + * Globals + */ + Number = utils.global.Number; Math = utils.global.Math; Date = utils.global.Date; @@ -264,111 +261,6 @@ utils.merge = function merge(target) { if (Object.assign) utils.merge = Object.assign; -/** - * Safely convert satoshis to a BTC string. - * This function explicitly avoids any - * floating point arithmetic. - * @param {Amount} value - Satoshis. - * @returns {String} BTC string. - */ - -utils.btc = function btc(value) { - var negative = false; - var hi, lo, result; - - if (utils.isFloat(value)) - return value; - - assert(utils.isInt(value), 'Non-satoshi value for conversion.'); - - if (value < 0) { - value = -value; - negative = true; - } - - assert(value <= utils.MAX_SAFE_INTEGER, 'Number exceeds 2^53-1.'); - - value = value.toString(10); - - assert(value.length <= 16, 'Number exceeds 2^53-1.'); - - while (value.length < 9) - value = '0' + value; - - hi = value.slice(0, -8); - lo = value.slice(-8); - - lo = lo.replace(/0+$/, ''); - - if (lo.length === 0) - lo += '0'; - - result = hi + '.' + lo; - - if (negative) - result = '-' + result; - - return result; -}; - -/** - * Safely convert a BTC string to satoshis. - * This function explicitly avoids any - * floating point arithmetic. It also does - * extra validation to ensure the resulting - * Number will be 53 bits or less. - * @param {String} value - BTC - * @returns {Amount} Satoshis. - * @throws on parse error - */ - -utils.satoshi = function satoshi(value) { - var negative = false; - var parts, hi, lo, result; - - if (utils.isInt(value)) - return value; - - assert(utils.isFloat(value), 'Non-BTC value for conversion.'); - - if (value[0] === '-') { - negative = true; - value = value.substring(1); - } - - parts = value.split('.'); - - assert(parts.length <= 2, 'Bad decimal point.'); - - hi = parts[0] || '0'; - lo = parts[1] || '0'; - - hi = hi.replace(/^0+/, ''); - lo = lo.replace(/0+$/, ''); - - assert(hi.length <= 8, 'Number exceeds 2^53-1.'); - assert(lo.length <= 8, 'Too many decimal places.'); - - if (hi.length === 0) - hi = '0'; - - while (lo.length < 8) - lo += '0'; - - hi = parseInt(hi, 10); - lo = parseInt(lo, 10); - - assert(hi < 90071992 || (hi === 90071992 && lo <= 54740991), - 'Number exceeds 2^53-1.'); - - result = hi * 100000000 + lo; - - if (negative) - result = -result; - - return result; -}; - /** * Max safe integer (53 bits). * @const {Number} diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 93a229d8..d7512336 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -15,6 +15,7 @@ var constants = require('../protocol/constants'); var BufferReader = require('../utils/reader'); var BufferWriter = require('../utils/writer'); var btcutils = require('../utils/btcutils'); +var Amount = require('../utils/amount'); var TX = require('../primitives/tx'); var Coin = require('../primitives/coin'); var Outpoint = require('../primitives/outpoint'); @@ -81,8 +82,8 @@ TXDB.prototype.open = co(function* open() { this.logger.info( 'Balance: unconfirmed=%s confirmed=%s.', - utils.btc(this.state.unconfirmed), - utils.btc(this.state.confirmed)); + Amount.btc(this.state.unconfirmed), + Amount.btc(this.state.confirmed)); }); /** @@ -2808,8 +2809,8 @@ Balance.prototype.toJSON = function toJSON(minimal) { wid: !minimal ? this.wid : undefined, id: !minimal ? this.id : undefined, account: !minimal ? this.account : undefined, - unconfirmed: utils.btc(this.unconfirmed), - confirmed: utils.btc(this.confirmed) + unconfirmed: Amount.btc(this.unconfirmed), + confirmed: Amount.btc(this.confirmed) }; }; @@ -2820,8 +2821,8 @@ Balance.prototype.toJSON = function toJSON(minimal) { Balance.prototype.toString = function toString() { return ''; }; @@ -2944,8 +2945,8 @@ TXDBState.prototype.toJSON = function toJSON(minimal) { id: !minimal ? this.id : undefined, tx: this.tx, coin: this.coin, - unconfirmed: utils.btc(this.unconfirmed), - confirmed: utils.btc(this.confirmed) + unconfirmed: Amount.btc(this.unconfirmed), + confirmed: Amount.btc(this.confirmed) }; }; @@ -3221,8 +3222,8 @@ Details.prototype.toJSON = function toJSON() { index: this.index, size: this.size, virtualSize: this.vsize, - fee: utils.btc(fee), - rate: utils.btc(rate), + fee: Amount.btc(fee), + rate: Amount.btc(rate), confirmations: this.getConfirmations(), inputs: this.inputs.map(function(input) { return input.toJSON(self.network); @@ -3259,7 +3260,7 @@ function DetailsMember() { DetailsMember.prototype.toJSON = function toJSON(network) { return { - value: utils.btc(this.value), + value: Amount.btc(this.value), address: this.address ? this.address.toBase58(network) : null, diff --git a/test/http-test.js b/test/http-test.js index d9c618fb..820701a7 100644 --- a/test/http-test.js +++ b/test/http-test.js @@ -9,6 +9,7 @@ var crypto = require('../lib/crypto/crypto'); var assert = require('assert'); var scriptTypes = constants.scriptTypes; var co = require('../lib/utils/co'); +var Amount = require('../lib/utils/amount'); var cob = co.cob; var dummyInput = { @@ -108,16 +109,16 @@ describe('HTTP', function() { assert.equal(receive.type, 'pubkeyhash'); assert.equal(receive.branch, 0); assert(balance); - assert.equal(utils.satoshi(balance.confirmed), 0); - assert.equal(utils.satoshi(balance.unconfirmed), 201840); + assert.equal(Amount.value(balance.confirmed), 0); + assert.equal(Amount.value(balance.unconfirmed), 201840); assert(details); assert.equal(details.hash, t1.rhash); })); it('should get balance', cob(function* () { var balance = yield wallet.getBalance(); - assert.equal(utils.satoshi(balance.confirmed), 0); - assert.equal(utils.satoshi(balance.unconfirmed), 201840); + assert.equal(Amount.value(balance.confirmed), 0); + assert.equal(Amount.value(balance.unconfirmed), 201840); })); it('should send a tx', cob(function* () { @@ -138,8 +139,8 @@ describe('HTTP', function() { assert.equal(tx.inputs.length, 1); assert.equal(tx.outputs.length, 2); - value += utils.satoshi(tx.outputs[0].value); - value += utils.satoshi(tx.outputs[1].value); + value += Amount.value(tx.outputs[0].value); + value += Amount.value(tx.outputs[1].value); assert.equal(value, 48190); hash = tx.hash; @@ -160,7 +161,7 @@ describe('HTTP', function() { it('should get balance', cob(function* () { var balance = yield wallet.getBalance(); - assert.equal(utils.satoshi(balance.unconfirmed), 199570); + assert.equal(Amount.value(balance.unconfirmed), 199570); })); it('should execute an rpc call', cob(function* () { diff --git a/test/utils-test.js b/test/utils-test.js index 26772fd3..72308421 100644 --- a/test/utils-test.js +++ b/test/utils-test.js @@ -9,6 +9,7 @@ var base58 = require('../lib/utils/base58'); var encoding = require('../lib/utils/encoding'); var crypto = require('../lib/crypto/crypto'); var schnorr = require('../lib/crypto/schnorr'); +var Amount = require('../lib/utils/amount'); describe('Utils', function() { var vectors = [ @@ -49,42 +50,42 @@ describe('Utils', function() { }); it('should convert satoshi to btc', function() { - var btc = btcutils.btc(5460); + var btc = Amount.btc(5460); assert.equal(btc, '0.0000546'); - btc = btcutils.btc(54678 * 1000000); + btc = Amount.btc(54678 * 1000000); assert.equal(btc, '546.78'); - btc = btcutils.btc(5460 * 10000000); + btc = Amount.btc(5460 * 10000000); assert.equal(btc, '546.0'); }); it('should convert btc to satoshi', function() { - var btc = btcutils.satoshi('0.0000546'); + var btc = Amount.value('0.0000546'); assert(btc === 5460); - btc = btcutils.satoshi('546.78'); + btc = Amount.value('546.78'); assert(btc === 54678 * 1000000); - btc = btcutils.satoshi('546'); + btc = Amount.value('546'); assert(btc === 5460 * 10000000); - btc = btcutils.satoshi('546.0'); + btc = Amount.value('546.0'); assert(btc === 5460 * 10000000); - btc = btcutils.satoshi('546.0000'); + btc = Amount.value('546.0000'); assert(btc === 5460 * 10000000); assert.doesNotThrow(function() { - btcutils.satoshi('546.00000000000000000'); + Amount.value('546.00000000000000000'); }); assert.throws(function() { - btcutils.satoshi('546.00000000000000001'); + Amount.value('546.00000000000000001'); }); assert.doesNotThrow(function() { - btcutils.satoshi('90071992.54740991'); + Amount.value('90071992.54740991'); }); assert.doesNotThrow(function() { - btcutils.satoshi('090071992.547409910'); + Amount.value('090071992.547409910'); }); assert.throws(function() { - btcutils.satoshi('90071992.54740992'); + Amount.value('90071992.54740992'); }); assert.throws(function() { - btcutils.satoshi('190071992.54740991'); + Amount.value('190071992.54740991'); }); });