diff options
| author | Adam Soltys <[email protected]> | 2017-01-15 18:24:09 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2017-01-15 18:24:09 -0800 |
| commit | 7762ceaa7e2bb34f8758e17a4c8f512fa6a81b9e (patch) | |
| tree | 91bb1d1b8fe6ad77a48f0e429d9bfd35834c78d9 /static/js/wallet.js | |
| parent | 0ae266bf88f9f79c7e99b272688126898303daa4 (diff) | |
| download | coinos-server-7762ceaa7e2bb34f8758e17a4c8f512fa6a81b9e.tar.gz coinos-server-7762ceaa7e2bb34f8758e17a4c8f512fa6a81b9e.zip | |
Initial Vueification
Diffstat (limited to 'static/js/wallet.js')
| -rw-r--r-- | static/js/wallet.js | 565 |
1 files changed, 565 insertions, 0 deletions
diff --git a/static/js/wallet.js b/static/js/wallet.js new file mode 100644 index 00000000..05e0c2bc --- /dev/null +++ b/static/js/wallet.js @@ -0,0 +1,565 @@ +(function() { + var check_address, convertedAmount, createWallet, displayErrors, errors, g, getBalance, getExchangeRate, getUser, isBip32, multiplier, precision, sendTransaction, updateUser, validators, + 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; }; + + g = typeof exports !== "undefined" && exports !== null ? exports : this; + + g.proceed = false; + + g.api = '/blockcypher/v1/btc/main'; + + validators = { + address: function(e) { + var error; + if (e.val() === '') { + return true; + } + try { + return bitcoin.address.fromBase58Check(e.val()); + } catch (error) { + return false; + } + }, + key: function(e) { + return $('#keytype').val() !== 'unknown'; + } + }; + + errors = { + address: 'Invalid address.', + key: 'Could not detect key type.' + }; + + $(function() { + getUser(); + $('#types').attr('data-content', "Bitcoin Address (starts with 1 or 3)<br />\nPrivate Key (starts with 5 or L)<br />\nHD Wallet pubkey (starts with xpub)<br />\nHD Wallet private key (starts with xprv)<br />\nAES Encrypted Private Key (starts with U)<br />\nBIP38 Encrypted Private Key (starts with 6)<br />\nBIP39 Mnemonic (series of 12-24 english words)").popover({ + html: true + }).on("show.bs.popover", function() { + return $(this).data("bs.popover").tip().css({ + minWidth: "400px" + }); + }); + $('form').validator({ + custom: validators, + errors: errors, + delay: 1200 + }); + $('[data-toggle=tooltip]').tooltip({ + trigger: 'hover' + }); + $('#key').keyup(function() { + var ref, val; + val = $(this).val(); + $('#keytype').val('unknown'); + switch (val[0]) { + case '1': + try { + bitcoin.address.fromBase58Check(val); + return $('#keytype').val('address'); + } catch (undefined) {} + break; + case 'm': + try { + bitcoin.address.fromBase58Check(val); + return $('#keytype').val('address'); + } catch (undefined) {} + break; + case '5': + case 'L': + case 'K': + try { + bitcoin.ECPair.fromWIF(val); + return $('#keytype').val('wif'); + } catch (undefined) {} + break; + case 'U': + try { + if (CryptoJS.AES.decrypt(val, g.password).toString(CryptoJS.enc.Utf8)) { + return $('#keytype').val('aes'); + } + } catch (undefined) {} + break; + case '6': + if (bip38().verify(val)) { + return $('#keytype').val('bip38'); + } + break; + case 'x': + if ($(this).val()[3] === 'b') { + try { + bitcoin.HDNode.fromBase58(val); + return $('#keytype').val('xpub'); + } catch (undefined) {} + } else { + try { + bitcoin.HDNode.fromBase58(val); + return $('#keytype').val('xprv'); + } catch (undefined) {} + } + break; + default: + if (((ref = val.split(' ').length) === 12 || ref === 15 || ref === 18 || ref === 21 || ref === 24) && bip39.validateMnemonic(val)) { + return $('#keytype').val('bip39'); + } + } + }); + $('#save').click(function() { + var error, error1, key, master, privkey, proceed, pubkey, wif; + $('.form-control').blur(); + if ($('#keys .has-error').length > 0) { + $('#keys .has-error').effect('shake', 500); + return; + } + key = $('#key').val(); + proceed = true; + switch ($('#keytype').val()) { + case 'address': + $('#pubkey').val(key); + $('#privkey').val(''); + break; + case 'wif': + pubkey = bitcoin.ECPair.fromWIF(key).getAddress(); + privkey = CryptoJS.AES.encrypt(key, g.password); + $('#pubkey').val(pubkey); + $('#privkey').val(privkey); + break; + case 'aes': + try { + pubkey = bitcoin.HDNode.fromBase58(CryptoJS.AES.decrypt(key, g.password).toString(CryptoJS.enc.Utf8)).neutered().toString(); + $('#pubkey').val(pubkey); + $('#privkey').val(key); + } catch (error) { + try { + pubkey = bitcoin.ECPair.fromWIF(CryptoJS.AES.decrypt(key, g.password).toString(CryptoJS.enc.Utf8)).getAddress(); + $('#pubkey').val(pubkey); + $('#privkey').val(key); + } catch (error1) { + proceed = false; + } + } + break; + case 'bip38': + wif = bip38().decrypt(key, g.password); + pubkey = bitcoin.ECPair.fromWIF(wif).getAddress(); + privkey = CryptoJS.AES.encrypt(wif, g.password); + $('#pubkey').val(pubkey); + $('#privkey, #key').val(privkey); + break; + case 'xpub': + $('#pubkey').val(key); + $('#privkey').val(''); + break; + case 'xprv': + pubkey = bitcoin.HDNode.fromBase58(key).neutered().toString(); + privkey = CryptoJS.AES.encrypt(key, g.password); + $('#pubkey').val(pubkey); + $('#privkey').val(privkey); + break; + case 'bip39': + master = bitcoin.HDNode.fromSeedBuffer(bip39.mnemonicToSeed(key)).deriveHardened(44).deriveHardened(0); + pubkey = master.neutered().toString(); + privkey = CryptoJS.AES.encrypt(master.toString(), g.password); + $('#pubkey').val(pubkey); + $('#privkey').val(privkey); + } + if (proceed) { + updateUser(); + $('#keys').hide(); + $('#keys_updated').fadeIn(); + $('#balances').hide(); + return $('#fetching').show(); + } + }); + $('#withdrawal form input[type=button]').click(sendTransaction); + $('#currency_toggle').click(function() { + var amount; + if ($(this).html() === g.user.unit) { + $(this).html(g.user.currency); + g.amount = parseFloat($('#amount').val()).toFixed(precision()); + amount = (g.amount * g.exchange / multiplier()).toFixed(2); + $('#amount').val(amount); + $('#amount').attr('step', 0.01); + return $('#amount').attr('max', (g.balance * g.exchange / multiplier()).toFixed(2)); + } else { + $(this).html(g.user.unit); + $('#amount').val(convertedAmount()); + $('#amount').attr('step', 0.00000001 * multiplier()); + return $('#amount').attr('max', g.balance); + } + }); + $('#max').click(function() { + var amount; + if ($('#currency_toggle').html() === g.user.unit) { + return $('#amount').val(g.balance).blur(); + } else { + g.amount = parseFloat(g.balance).toFixed(precision()); + amount = (g.balance / multiplier() * g.exchange).toFixed(2); + return $('#amount').val(amount).blur(); + } + }); + $('#amount').change(function() { + if ($('#currency_toggle').html() === g.user.unit) { + $(this).val(parseFloat($(this).val()).toFixed(precision())); + } else { + $(this).val(parseFloat($(this).val()).toFixed(2)); + } + if (parseFloat($(this).val()) > parseFloat($(this).attr('max'))) { + return $(this).val($(this).attr('max')); + } + }); + $('#password').keyup(function() { + try { + if (g.user.privkey) { + g.privkey = CryptoJS.AES.decrypt(g.user.privkey, $(this).val()).toString(CryptoJS.enc.Utf8); + if (g.privkey[0] === 'x') { + g.key = bitcoin.HDNode.fromBase58(g.privkey); + } else { + g.key = bitcoin.ECPair.fromWIF(g.privkey); + } + $('#key').val(g.privkey).keyup(); + } else { + $('#key').val(g.user.pubkey).keyup(); + } + $(this).closest('.form-group').hide(); + $('.wallet').fadeIn(); + return g.password = $(this).val(); + } catch (undefined) {} + }); + $('#manage').click(function() { + $('#withdrawal').hide(); + $('#keys').show(); + return $('#privkey').val(g.user.privkey); + }); + $('#withdraw').click(function() { + $('#withdrawal').show(); + $('#amount').focus(); + $('#keys').hide(); + $('#withdrawal form').validator('destroy'); + return $('#withdrawal form').validator({ + custom: validators, + errors: errors, + delay: 1200 + }); + }); + $('#cancel').click(function() { + $('#keys').hide(); + if (g.balance > 0 && g.user.privkey) { + $('#withdrawal form').validator('destroy'); + $('#withdrawal form').validator({ + custom: validators, + errors: errors, + delay: 1200 + }); + $('#withdrawal').show(); + return $('#amount').focus(); + } + }); + $('#backup').click(function() { + var a, url; + url = 'data:application/json;base64,' + btoa(JSON.stringify(g.user.privkey)); + a = document.createElement('a'); + a.setAttribute('href', url); + a.setAttribute('download', 'wallet.json.aes'); + return a.click(); + }); + $('#generate').click(function() { + return bootbox.confirm('<h3>Are you sure?</h3> <p>This will overwrite your existing wallet so make sure that you have the backup we sent to your email in case you want to restore it.</p>', function(result) { + var key, mnemonic; + if (result) { + mnemonic = bip39.generateMnemonic(); + key = bitcoin.HDNode.fromSeedBuffer(bip39.mnemonicToSeed(mnemonic)).deriveHardened(44).deriveHardened(0); + return $('#key').val(key.toString()).effect('highlight', {}, 2000).keyup(); + } + }); + }); + return $('.close').on('click', function() { + return $(this).closest('.alert').fadeOut(); + }); + }); + + getUser = function() { + return $.getJSON("/" + ($('#username').val()) + ".json", function(user) { + g.user = user; + $('#key').val(user.pubkey); + $('#pubkey').val(user.pubkey); + $('#privkey').val(user.privkey); + $('#address').val(user.address); + $('.address').html(user.address); + $('#unit').html(user.unit); + $('#currency_toggle').html(user.unit); + $('#amount').attr('step', 0.00000001 * multiplier()); + $('#password').closest('.form-group').show(); + $('#password').focus(); + getExchangeRate(); + return $('#password').keyup(); + }); + }; + + getExchangeRate = function() { + return $.get("/ticker?currency=" + g.user.currency + "&symbol=" + g.user.symbol + "&type=bid", function(exchange) { + g.exchange = exchange; + return createWallet(); + }); + }; + + createWallet = function() { + return $.get(g.api + "/wallets", function(data) { + var params, ref; + if (ref = g.user.username, indexOf.call(data.wallet_names, ref) >= 0) { + return getBalance(); + } else { + params = { + name: g.user.username + }; + if (isBip32(g.user.pubkey)) { + params.extended_public_key = g.user.pubkey; + params.subchain_indexes = [0, 1]; + return $.post(g.api + "/wallets/hd", JSON.stringify(params)).done(getBalance).fail(function() { + return $('.wallet').fadeIn(); + }); + } else { + params.addresses = [g.user.pubkey]; + return $.post(g.api + "/wallets", JSON.stringify(params)).done(getBalance).fail(function() { + return $('.wallet').fadeIn(); + }); + } + } + }); + }; + + getBalance = function() { + return $.get(g.api + "/addrs/" + g.user.username + "/balance?omitWalletAddresses=true", function(data) { + var balance, fiat; + balance = data.final_balance; + g.balance = parseFloat(balance.toBTC()); + fiat = balance.toFiat(); + $('#balance').html(g.balance); + $('#fiat').html(fiat + " " + g.user.currency); + $('#amount').attr('max', g.balance); + $('#balances').show(); + $('#fetching').hide(); + if (g.balance > 0 && g.user.privkey) { + $('#keys').hide(); + $('#withdrawal form').validator('destroy'); + $('#withdrawal form').validator({ + custom: validators, + errors: errors, + delay: 1200 + }); + $('#withdrawal').show(); + return $('#amount').focus(); + } + }); + }; + + updateUser = function() { + var data; + data = $('#keys form').serializeObject(); + delete data['key']; + return $.post("/" + g.user.username, data, function() { + return $.ajax({ + url: g.api + "/wallets/" + g.user.username, + type: 'DELETE' + }).always(function() { + return $.ajax({ + url: g.api + "/wallets/hd/" + g.user.username, + type: 'DELETE' + }).always(function() { + return getUser(); + }); + }); + }); + }; + + sendTransaction = function() { + var dialog, params; + if (g.key && !(typeof g.key.d === 'undefined' && typeof g.key.keyPair.d === 'undefined')) { + 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", JSON.stringify(params)).done(function(data) { + var amount, value; + if ($('#currency_toggle').html() === g.user.unit) { + amount = $('#amount').val(); + } else { + amount = convertedAmount(); + } + 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; + } + return $.post(g.api + "/txs/new", JSON.stringify(params)).done(function(data) { + var fee, total; + data.pubkeys = []; + if (g.key instanceof bitcoin.ECPair) { + data.signatures = data.tosign.map(function(tosign, i) { + data.pubkeys.push(g.key.getPublicKeyBuffer().toString('hex')); + return g.key.sign(new buffer.Buffer(tosign, "hex")).toDER().toString("hex"); + }); + } else { + data.signatures = data.tosign.map(function(tosign, i) { + var key, path; + path = data.tx.inputs[i].hd_path.split('/'); + key = g.key.derive(parseInt(path[1])).derive(parseInt(path[2])); + data.pubkeys.push(key.keyPair.getPublicKeyBuffer().toString('hex')); + return key.keyPair.sign(new buffer.Buffer(tosign, "hex")).toDER().toString("hex"); + }); + } + g.data = data; + amount = data.tx.outputs[0].value; + fee = data.tx.fees; + total = amount; + if (value > parseFloat(g.balance).toSatoshis() - data.tx.fees) { + total += fee; + } + $('.dialog .amount').html((amount.toBTC()) + " " + g.user.unit + " (" + (amount.toFiat()) + " " + g.user.currency + ")"); + $('.dialog .fee').html((fee.toBTC()) + " " + g.user.unit + " (" + (fee.toFiat()) + " " + g.user.currency + ")"); + $('.dialog .total').html((total.toBTC()) + " " + g.user.unit + " (" + (total.toFiat()) + " " + g.user.currency + ")"); + $('.dialog .address').html(data.tx.outputs[0].addresses[0]); + dialog.getModalBody().html($('.dialog').html()); + if (amount.toBTC() < 0.00000534) { + displayErrors({ + errors: [ + { + error: 'Amount left over after fee is too small to send' + } + ] + }, dialog); + } + return dialog.getModal().find('.btn-primary').click(function() { + return $.post(g.api + "/txs/send", JSON.stringify(g.data)).then(function(finaltx) { + var balance, fiat; + $('#transaction_sent').fadeIn(); + balance = g.balance.toSatoshis() - finaltx.tx.outputs[0].value - finaltx.tx.fees; + g.balance = balance.toBTC(); + fiat = balance.toFiat(); + $('#balance').html(g.balance); + $('#fiat').html(fiat + " " + g.user.currency); + $('#blockchain').off('click').on('click', function() { + return window.open('https://blockchain.info/tx/' + finaltx.tx.hash, '_blank'); + }); + return dialog.close(); + }); + }); + }).fail(function(data) { + return displayErrors(data.responseJSON, dialog); + }); + }).fail(function(data) { + return displayErrors(data.responseJSON, dialog); + }); + } + }; + + displayErrors = function(data, dialog) { + var e, j, len, ref, results; + if (data.errors) { + dialog.getModalBody().html(''); + dialog.getModal().find('.btn-primary').hide(); + ref = data.errors; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + e = ref[j]; + results.push(dialog.getModalBody().append("<div class='alert alert-danger'>" + e.error + "</div>")); + } + return results; + } + }; + + convertedAmount = function() { + var amount, difference, tolerance; + if (!(g.amount && !isNaN(parseFloat(g.amount)))) { + g.amount = amount; + } + amount = parseFloat($('#amount').val() * multiplier() / g.exchange).toFixed(precision()); + difference = parseFloat(Math.abs(g.amount - amount).toFixed(precision())); + tolerance = parseFloat((.00000002 * g.exchange * multiplier()).toFixed(precision())); + if (difference > tolerance) { + return amount; + } else { + return g.amount; + } + }; + + check_address = function(address) { + var error; + try { + bitcoin.address.fromBase58Check(address); + return true; + } catch (error) { + return isBip32(address); + } + }; + + isBip32 = function(address) { + var error; + try { + bitcoin.HDNode.fromBase58(address); + return true; + } catch (error) { + return false; + } + }; + + multiplier = function() { + switch (g.user.unit) { + case 'BTC': + return 1; + case 'mBTC': + return 1000; + case 'µBTC': + return 1000000; + case 'bits': + return 1000000; + case 'satoshis': + return 100000000; + } + }; + + precision = function() { + return 9 - multiplier().toString().length; + }; + + Number.prototype.toBTC = function() { + return parseFloat(this / 100000000 * multiplier()).toFixed(precision()); + }; + + Number.prototype.toFiat = function() { + return parseFloat(this * g.exchange / 100000000).toFixed(2); + }; + + Number.prototype.toSatoshis = function() { + return parseInt(this * 100000000 / multiplier()); + }; + +}).call(this); |
