diff options
| author | Adam Soltys <[email protected]> | 2015-12-02 18:35:38 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2015-12-02 18:35:38 -0800 |
| commit | 5c61a374297a00dee56c614f88eb37ebb548b949 (patch) | |
| tree | fbb0a5323ca4ce3d50377454ab5cdba90420987d | |
| parent | 62ca80f726b5af28b9f6de7f2b3ff32de732b566 (diff) | |
| download | coinos-server-5c61a374297a00dee56c614f88eb37ebb548b949.tar.gz coinos-server-5c61a374297a00dee56c614f88eb37ebb548b949.zip | |
Calculate fees
| -rw-r--r-- | public/js/wallet.coffee | 145 | ||||
| -rw-r--r-- | public/js/wallet.js | 181 | ||||
| -rw-r--r-- | views/sessions/new.html | 2 | ||||
| -rw-r--r-- | views/users/wallet.html | 125 |
4 files changed, 321 insertions, 132 deletions
diff --git a/public/js/wallet.coffee b/public/js/wallet.coffee index 5f549ec9..c6b405e5 100644 --- a/public/js/wallet.coffee +++ b/public/js/wallet.coffee @@ -57,7 +57,7 @@ $(-> else $(this).val(parseFloat($(this).val()).toFixed(2)) - if $(this).val() > $(this).attr('max') + if parseFloat($(this).val()) > parseFloat($(this).attr('max')) $(this).val($(this).attr('max')) ) ) @@ -89,15 +89,23 @@ getExchangeRate = -> ) createWallet = -> - if isBip32(g.user.pubkey) - data = - name: g.user.username - extended_public_key: g.user.pubkey - subchain_indexes: [0,1] - $.post("#{g.api}/wallets/hd?token=#{g.token}", JSON.stringify(data)).always(getBalance) - else - $('#balance').html(99) - $('#amount').val(99) + $.get("#{g.api}/wallets?token=#{g.token}", (data) -> + if g.user.username in data.wallet_names + getBalance() + else + if isBip32(g.user.pubkey) + data = + name: g.user.username + extended_public_key: g.user.pubkey + subchain_indexes: [0,1] + + $.post("#{g.api}/wallets/hd?token=#{g.token}", JSON.stringify(data)).always(getBalance) + else + # TODO implement this properly + $('#balance').html(99) + $('#amount').val(99) + ) + getBalance = -> $.get("#{g.api}/addrs/#{g.user.username}/balance?token=#{g.token}&omitWalletAddresses=true", (data) -> @@ -105,42 +113,100 @@ getBalance = -> g.balance = (balance * multiplier() / 100000000).toFixed(precision()) fiat = (g.balance * g.exchange / multiplier()).toFixed(2) $('#balance').html(g.balance) - $('#balance').parent().fadeIn() + $('.wallet').fadeIn() $('#fiat').html("#{fiat} #{g.user.currency}") - $('#amount').val(g.balance) $('#amount').attr('max', g.balance) ) sendTransaction = -> - master = bitcoin.HDNode.fromBase58(g.user.privkey) - if typeof master.keyPair.d isnt 'undefined' - fee = 12500 - if $('#currency_toggle').html() is g.user.unit - amount = $('#amount').val() + dialog = new BootstrapDialog( + title: '<h3>Confirm Transaction</h3>' + message: '<i class="fa fa-spinner fa-spin"></i> Calculating fee...</i>' + buttons: [ + label: 'Send' + cssClass: 'btn-primary' + , + label: ' Cancel' + cssClass: 'btn-default' + action: (dialogItself) -> dialogItself.close() + icon: 'glyphicon glyphicon-ban-circle' + ] + ).open() + + params = + inputs: [{wallet_name: g.user.username, wallet_token: g.token}] + outputs: [{addresses: [$('#recipient').val()], value: 1}] + preference: $('#priority').val() + + $.post("#{g.api}/txs/new?token=#{g.token}", JSON.stringify(params)).done((data) -> + if data.errors + dialog.getModalBody().html('') + for e in data.errors + dialog.getModalBody().append("<div class='alert alert-danger'>#{e}</div>") + + master = bitcoin.HDNode.fromBase58(g.user.privkey) + + if typeof master.keyPair.d is 'undefined' + $('#withdraw .alert-danger').fadeIn().delay(500).fadeOut() else - amount = convertedAmount() + if $('#currency_toggle').html() is g.user.unit + amount = $('#amount').val() + else + amount = convertedAmount() + + value = parseInt(amount * 100000000 / multiplier()) - value = parseInt(amount * 100000000 / multiplier()) + params.fees = data.tx.fees + params.outputs[0].value = value + if value > parseFloat(g.balance).toSatoshis() - params.fees + params.outputs[0].value -= params.fees - req = - inputs: [{wallet_name: g.user.username, wallet_token: g.token}], - outputs: [{addresses: [$('#recipient').val()], value: value}] - $.post("#{g.api}/txs/new?token=#{g.token}", JSON.stringify(req)).then((tx) -> - tx.pubkeys = [] - tx.signatures = tx.tosign.map((tosign, i) -> - path = tx.tx.inputs[i].hd_path.split('/') - key = master.derive(path[1]).derive(path[2]) - tx.pubkeys.push(key.getPublicKeyBuffer().toString('hex')) - return key.sign(new buffer.Buffer(tosign, "hex")).toDER().toString("hex") - ) + $.post("#{g.api}/txs/new?token=#{g.token}", JSON.stringify(params)).done((data) -> + data.pubkeys = [] + data.signatures = data.tosign.map((tosign, i) -> + path = data.tx.inputs[i].hd_path.split('/') + key = master.derive(path[1]).derive(path[2]) + data.pubkeys.push(key.getPublicKeyBuffer().toString('hex')) + return key.sign(new buffer.Buffer(tosign, "hex")).toDER().toString("hex") + ) - $.post("#{g.api}/txs/send?token=#{g.token}", JSON.stringify(tx)).then((finaltx) -> - $('#withdraw .alert-success').fadeIn().delay(500).fadeOut() - ) + amount = data.tx.outputs[0].value + fee = data.tx.fees + total = amount + if value > parseFloat(g.balance).toSatoshis() - data.tx.fees + total += fee + + if data.tx.outputs.length is 2 + $('#change').show() + change = data.tx.outputs[1].value + $('#transaction .change').html("#{(change.toBTC())} #{g.user.unit} (#{change.toFiat()} #{g.user.currency})") + + $('#transaction .amount').html("#{(amount.toBTC())} #{g.user.unit} (#{amount.toFiat()} #{g.user.currency})") + $('#transaction .fee').html("#{(fee.toBTC())} #{g.user.unit} (#{fee.toFiat()} #{g.user.currency})") + $('#transaction .total').html("#{(total.toBTC())} #{g.user.unit} (#{total.toFiat()} #{g.user.currency})") + $('#transaction .address').html(data.tx.outputs[0].addresses[0]) + + dialog.getModalBody().html($('#transaction').html()) + dialog.getModal().find('.btn-primary').click(-> + $.post("#{g.api}/txs/send?token=#{g.token}", JSON.stringify(data)).then((finaltx) -> + $('#withdraw .alert-success').fadeIn().delay(500).fadeOut() + ) + ) + ).fail((data) -> + displayErrors(data.responseJSON, dialog) ) - else - $('#withdraw .alert-danger').fadeIn().delay(500).fadeOut() + ).fail((data) -> + displayErrors(data.responseJSON, dialog) + ) + +displayErrors = (data, dialog) -> + if data.errors + dialog.getModalBody().html('') + dialog.getModal().find('.btn-primary').hide() + for e in data.errors + dialog.getModalBody().append("<div class='alert alert-danger'>#{e.error}</div>") + convertedAmount = -> amount = parseFloat($('#amount').val() * multiplier() / g.exchange).toFixed(precision()) @@ -176,3 +242,12 @@ multiplier = -> precision = -> 9 - multiplier().toString().length + +Number.prototype.toBTC = -> + (this / 100000000 * multiplier()).toFixed(precision()) + +Number.prototype.toFiat = -> + (this * g.exchange / 100000000).toFixed(2) + +Number.prototype.toSatoshis = -> + parseInt(this * 100000000 / multiplier()) diff --git a/public/js/wallet.js b/public/js/wallet.js index b2261204..5af74c35 100644 --- a/public/js/wallet.js +++ b/public/js/wallet.js @@ -1,6 +1,7 @@ ;(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0](function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){ (function(){(function() { - var buffer, check_address, convertedAmount, createWallet, g, getBalance, getExchangeRate, getToken, getUser, isBip32, multiplier, precision, sendTransaction; + var buffer, check_address, convertedAmount, createWallet, displayErrors, g, getBalance, getExchangeRate, getToken, getUser, isBip32, multiplier, precision, sendTransaction, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; buffer = require('buffer'); @@ -61,7 +62,7 @@ } else { $(this).val(parseFloat($(this).val()).toFixed(2)); } - if ($(this).val() > $(this).attr('max')) { + if (parseFloat($(this).val()) > parseFloat($(this).attr('max'))) { return $(this).val($(this).attr('max')); } }); @@ -95,18 +96,24 @@ }; createWallet = function() { - var data; - if (isBip32(g.user.pubkey)) { - data = { - name: g.user.username, - extended_public_key: g.user.pubkey, - subchain_indexes: [0, 1] - }; - return $.post("" + g.api + "/wallets/hd?token=" + g.token, JSON.stringify(data)).always(getBalance); - } else { - $('#balance').html(99); - return $('#amount').val(99); - } + return $.get("" + g.api + "/wallets?token=" + g.token, function(data) { + var _ref; + if (_ref = g.user.username, __indexOf.call(data.wallet_names, _ref) >= 0) { + return getBalance(); + } else { + if (isBip32(g.user.pubkey)) { + data = { + name: g.user.username, + extended_public_key: g.user.pubkey, + subchain_indexes: [0, 1] + }; + return $.post("" + g.api + "/wallets/hd?token=" + g.token, JSON.stringify(data)).always(getBalance); + } else { + $('#balance').html(99); + return $('#amount').val(99); + } + } + }); }; getBalance = function() { @@ -116,53 +123,123 @@ g.balance = (balance * multiplier() / 100000000).toFixed(precision()); fiat = (g.balance * g.exchange / multiplier()).toFixed(2); $('#balance').html(g.balance); - $('#balance').parent().fadeIn(); + $('.wallet').fadeIn(); $('#fiat').html("" + fiat + " " + g.user.currency); - $('#amount').val(g.balance); return $('#amount').attr('max', g.balance); }); }; sendTransaction = function() { - var amount, fee, master, req, value; - master = bitcoin.HDNode.fromBase58(g.user.privkey); - if (typeof master.keyPair.d !== 'undefined') { - fee = 12500; - if ($('#currency_toggle').html() === g.user.unit) { - amount = $('#amount').val(); + var dialog, params; + dialog = new BootstrapDialog({ + title: '<h3>Confirm Transaction</h3>', + message: '<i class="fa fa-spinner fa-spin"></i> Calculating fee...</i>', + buttons: [ + { + label: 'Send', + cssClass: 'btn-primary' + }, { + label: ' Cancel', + cssClass: 'btn-default', + action: function(dialogItself) { + return dialogItself.close(); + }, + icon: 'glyphicon glyphicon-ban-circle' + } + ] + }).open(); + params = { + inputs: [ + { + wallet_name: g.user.username, + wallet_token: g.token + } + ], + outputs: [ + { + addresses: [$('#recipient').val()], + value: 1 + } + ], + preference: $('#priority').val() + }; + return $.post("" + g.api + "/txs/new?token=" + g.token, JSON.stringify(params)).done(function(data) { + var amount, e, master, value, _i, _len, _ref; + if (data.errors) { + dialog.getModalBody().html(''); + _ref = data.errors; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + e = _ref[_i]; + dialog.getModalBody().append("<div class='alert alert-danger'>" + e + "</div>"); + } + } + master = bitcoin.HDNode.fromBase58(g.user.privkey); + if (typeof master.keyPair.d === 'undefined') { + $('#withdraw .alert-danger').fadeIn().delay(500).fadeOut(); } else { - amount = convertedAmount(); + if ($('#currency_toggle').html() === g.user.unit) { + amount = $('#amount').val(); + } else { + amount = convertedAmount(); + } } value = parseInt(amount * 100000000 / multiplier()); - req = { - inputs: [ - { - wallet_name: g.user.username, - wallet_token: g.token - } - ], - outputs: [ - { - addresses: [$('#recipient').val()], - value: value - } - ] - }; - return $.post("" + g.api + "/txs/new?token=" + g.token, JSON.stringify(req)).then(function(tx) { - tx.pubkeys = []; - tx.signatures = tx.tosign.map(function(tosign, i) { + params.fees = data.tx.fees; + params.outputs[0].value = value; + if (value > parseFloat(g.balance).toSatoshis() - params.fees) { + params.outputs[0].value -= params.fees; + } + return $.post("" + g.api + "/txs/new?token=" + g.token, JSON.stringify(params)).done(function(data) { + var change, fee, total; + data.pubkeys = []; + data.signatures = data.tosign.map(function(tosign, i) { var key, path; - path = tx.tx.inputs[i].hd_path.split('/'); + path = data.tx.inputs[i].hd_path.split('/'); key = master.derive(path[1]).derive(path[2]); - tx.pubkeys.push(key.getPublicKeyBuffer().toString('hex')); + data.pubkeys.push(key.getPublicKeyBuffer().toString('hex')); return key.sign(new buffer.Buffer(tosign, "hex")).toDER().toString("hex"); }); - return $.post("" + g.api + "/txs/send?token=" + g.token, JSON.stringify(tx)).then(function(finaltx) { - return $('#withdraw .alert-success').fadeIn().delay(500).fadeOut(); + amount = data.tx.outputs[0].value; + fee = data.tx.fees; + total = amount; + if (value > parseFloat(g.balance).toSatoshis() - data.tx.fees) { + total += fee; + } + if (data.tx.outputs.length === 2) { + $('#change').show(); + change = data.tx.outputs[1].value; + $('#transaction .change').html("" + (change.toBTC()) + " " + g.user.unit + " (" + (change.toFiat()) + " " + g.user.currency + ")"); + } + $('#transaction .amount').html("" + (amount.toBTC()) + " " + g.user.unit + " (" + (amount.toFiat()) + " " + g.user.currency + ")"); + $('#transaction .fee').html("" + (fee.toBTC()) + " " + g.user.unit + " (" + (fee.toFiat()) + " " + g.user.currency + ")"); + $('#transaction .total').html("" + (total.toBTC()) + " " + g.user.unit + " (" + (total.toFiat()) + " " + g.user.currency + ")"); + $('#transaction .address').html(data.tx.outputs[0].addresses[0]); + dialog.getModalBody().html($('#transaction').html()); + return dialog.getModal().find('.btn-primary').click(function() { + return $.post("" + g.api + "/txs/send?token=" + g.token, JSON.stringify(data)).then(function(finaltx) { + return $('#withdraw .alert-success').fadeIn().delay(500).fadeOut(); + }); }); + }).fail(function(data) { + return displayErrors(data.responseJSON, dialog); }); - } else { - return $('#withdraw .alert-danger').fadeIn().delay(500).fadeOut(); + }).fail(function(data) { + return displayErrors(data.responseJSON, dialog); + }); + }; + + displayErrors = function(data, dialog) { + var e, _i, _len, _ref, _results; + if (data.errors) { + dialog.getModalBody().html(''); + dialog.getModal().find('.btn-primary').hide(); + _ref = data.errors; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + e = _ref[_i]; + _results.push(dialog.getModalBody().append("<div class='alert alert-danger'>" + e.error + "</div>")); + } + return _results; } }; @@ -215,6 +292,18 @@ return 9 - multiplier().toString().length; }; + Number.prototype.toBTC = function() { + return (this / 100000000 * multiplier()).toFixed(precision()); + }; + + Number.prototype.toFiat = function() { + return (this * g.exchange / 100000000).toFixed(2); + }; + + Number.prototype.toSatoshis = function() { + return parseInt(this * 100000000 / multiplier()); + }; + }).call(this); diff --git a/views/sessions/new.html b/views/sessions/new.html index c9923744..97bfce17 100644 --- a/views/sessions/new.html +++ b/views/sessions/new.html @@ -13,7 +13,7 @@ {{/notice}} {{#badpw}} -<p class="alert alert-danger">Cannot find username/password on file, please try again.</p> +<p class="alert alert-danger">Invalid username or password, please try again.</p> {{/badpw}} <form class="form-signin" action="login" method="post"> diff --git a/views/users/wallet.html b/views/users/wallet.html index 25564f5d..c8af3cd1 100644 --- a/views/users/wallet.html +++ b/views/users/wallet.html @@ -1,65 +1,90 @@ {{#yield-scripts}} + <script src="/bootstrap3-dialog/dist/js/bootstrap-dialog.min.js"></script> + <script src="/jquery-serialize-object/dist/jquery.serialize-object.min.js"></script> <script src="/js/wallet.js"></script> {{/yield-scripts}} -<h1>Wallet</h1> +<div class="wallet" style="display: none"> + <h1>Wallet</h1> -<input type="hidden" id="username" name="username" value="{{user}}" /> -<input type="hidden" id="email" name="email" /> + <input type="hidden" id="username" name="username" value="{{user}}" /> + <input type="hidden" id="email" name="email" /> -<div class="form-group" style="display: none"> - <label>Balance:</label> - <b id="balance" class="lead">0</b> <b id="unit" class="lead"></b> - <b class="lead">(<b id="fiat" class="lead">0 CAD</b>)</b> -</div> + <div class="form-group"> + <label>Balance:</label> + <b id="balance" class="lead">0</b> <b id="unit" class="lead"></b> + <b class="lead">(<b id="fiat" class="lead">0 CAD</b>)</b> + </div> -<div> - <ul class="nav nav-tabs" role="tablist"> - <li role="presentation" class="active"><a href="#withdraw" aria-controls="withdraw" role="tab" data-toggle="tab">Withdraw</a></li> - <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">Settings</a></li> - </ul> + <div> + <ul class="nav nav-tabs" role="tablist"> + <li role="presentation" class="active"><a href="#withdraw" aria-controls="withdraw" role="tab" data-toggle="tab">Withdraw</a></li> + <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">Settings</a></li> + </ul> - <div class="tab-content"> - <div role="tabpanel" class="tab-pane active" id="withdraw"> - <form> - <div class="form-group"> - <label for="amount">Amount:</label> - <div class="input-group"> - <input type="number" id="amount" name="amount" class="form-control" min="0" /> - <span class="input-group-btn"> - <button id="currency_toggle" type="button" class="btn"></button> - </span> - <span class="input-group-btn"> - <button id="max" type="button" class="btn">Max</button> - </span> + <div class="tab-content"> + <div role="tabpanel" class="tab-pane active" id="withdraw"> + <form> + <div class="form-group"> + <label for="amount">Amount:</label> + <div class="input-group"> + <input type="number" id="amount" name="amount" class="form-control" min="0" /> + <span class="input-group-btn"> + <button id="currency_toggle" type="button" class="btn"></button> + </span> + <span class="input-group-btn"> + <button id="max" type="button" class="btn">Max</button> + </span> + </div> </div> - </div> - <div class="form-group"> - <label for="amount">Receiving Address:</label> - <div class="input-group"> - <input type="text" id="recipient" name="recipient" class="form-control" /> - <span class="input-group-btn"> - <button type="button" class="btn"><span class="glyphicon glyphicon-camera"></span></button> - </span> + <div class="form-group"> + <label for="amount">Receiving Address:</label> + <div class="input-group" style="width: 100%"> + <input type="text" id="recipient" name="recipient" class="form-control" /> + </div> + </div> + + <div class="form-group"> + <label for="priority">Fee Priority for Confirmation Time:</label> + <div class="input-group" style="width: 100%"> + <select id="priority" name="priority" class="form-control"> + <option value="low">Low</option> + <option value="medium">Medium</option> + <option value="high">High</option> + </select> + </div> </div> - </div> - <input id="send" class="btn btn-primary" type="button" value="Send Payment" /> - </form> - <div class="alert alert-success" style="display: none">Transaction sent</div> - <div class="alert alert-danger" style="display: none">Need private key</div> - </div> - <div role="tabpanel" class="tab-pane" id="settings"> - <form> - <div class="form-group"> - <label for="pubkey">Bitcoin Receiving Address or HD Wallet Pubkey:</label> - <textarea id="pubkey" name="pubkey" class="form-control" placeholder="xpub..." rows="3"></textarea> - <textarea id="privkey" name="privkey" class="form-control" placeholder="xpriv..." rows="3"></textarea> - </div> - <input class="btn btn-primary" type="button" value="Save" /> - </form> - <div class="alert alert-success" style="display: none">Settings updated</div> + + <input id="gsend" class="btn btn-primary" type="button" value="Calculate Fee and Confirm Transaction" /> + </form> + <div class="alert alert-success" style="display: none">Transaction sent</div> + <div class="alert alert-danger" style="display: none">Need private key</div> + </div> + <div role="tabpanel" class="tab-pane" id="settings"> + <form> + <div class="form-group"> + <label for="pubkey">Bitcoin Receiving Address or HD Wallet Pubkey:</label> + <textarea id="pubkey" name="pubkey" class="form-control" placeholder="xpub..." rows="3"></textarea> + <textarea id="privkey" name="privkey" class="form-control" placeholder="xpriv..." rows="3"></textarea> + </div> + <input class="btn btn-primary" type="button" value="Save" /> + </form> + <div class="alert alert-success" style="display: none">Settings updated</div> + </div> </div> </div> </div> +<div id="transaction" style="display: none"> + <div style="margin-bottom: 10px"> + <label>Amount:</label> <span class="amount"></span><br /> + <label>To:</label> <span class="address"></span> + </div> + <div> + <span id="change" style="display: none"> + <label>Change:</label> <span class="change"></span><br /> + </span> + <label>Fee:</label> <span class="fee"></span><br /> + </div> +</div> |
