text: // Copyright (c) 2014-2017, MyMonero.com
  // 
  // All rights reserved.
  // 
  // Redistribution and use in source and binary forms, with or without modification, are
  // permitted provided that the following conditions are met:
  // 
  // 1. Redistributions of source code must retain the above copyright notice, this list of
  //    conditions and the following disclaimer.
  // 
  // 2. Redistributions in binary form must reproduce the above copyright notice, this list
  //    of conditions and the following disclaimer in the documentation and/or other
  //    materials provided with the distribution.
  // 
  // 3. Neither the name of the copyright holder nor the names of its contributors may be
  //    used to endorse or promote products derived from this software without specific
  //    prior written permission.
  // 
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  thinwalletCtrls.controller('SendCoinsCtrl', function($scope, $http, $q,
                                                       AccountService, ModalService,
                                                       ApiCalls)
  {
      "use strict";
      $scope.status = "";
      $scope.error = "";
      $scope.submitting = false;
      $scope.targets = [{}];
      $scope.totalAmount = JSBigInt.ZERO;
      $scope.mixins = config.defaultMixin.toString();
      $scope.view_only = AccountService.isViewOnly();
  
      $scope.success_page = false;
      $scope.sent_tx = {};
  
  
      var view_only = AccountService.isViewOnly();
  
      var explorerUrl = "";
  
      if (config.nettype == 0)
          explorerUrl = config.mainnetExplorerUrl;
      else if (config.nettype == 1)
          explorerUrl = config.testnetExplorerUrl;
      else
          explorerUrl = config.stagenetExplorerUrl;
  
      // few multiplayers based on uint64_t wallet2::get_fee_multiplier
      var fee_multiplayers = [1, 4, 20, 166];
  
      var default_priority = 2;
  
      $scope.priority = default_priority.toString();
  
      $scope.openaliasDialog = undefined;
  
      function isInt(value) {
          return !isNaN(value) &&
              parseInt(Number(value)) == value &&
              !isNaN(parseInt(value, 10));
      }
  
  
      function confirmOpenAliasAddress(domain, address, name, description, dnssec_used) {
          var deferred = $q.defer();
          if ($scope.openaliasDialog !== undefined) {
              deferred.reject("OpenAlias confirm dialog is already being shown!");
              return;
          }
          $scope.openaliasDialog = {
              address: address,
              domain: domain,
              name: name,
              description: description,
              dnssec_used: dnssec_used,
              confirm: function() {
                  $scope.openaliasDialog = undefined;
                  console.log("User confirmed OpenAlias resolution for " + domain + " to " + address);
                  deferred.resolve();
              },
              cancel: function() {
                  $scope.openaliasDialog = undefined;
                  console.log("User rejected OpenAlias resolution for " + domain + " to " + address);
                  deferred.reject("OpenAlias resolution rejected by user");
              }
          };
          return deferred.promise;
      }
  
      $scope.transferConfirmDialog = undefined;
  
      function confirmTransfer(address, amount, tx_hash, fee, tx_prv_key,
                               payment_id, mixin, priority, txBlobKBytes, raw_tx,
                               no_inputs, no_outputs) {
  
          var deferred = $q.defer();
  
          if ($scope.transferConfirmDialog !== undefined) {
              deferred.reject("transferConfirmDialog is already being shown!");
              return;
          }
  
          var priority_names = ["Low", "Medium", "High", "Paranoid"];
  
          $scope.transferConfirmDialog = {
              address: address,
              fee: fee,
              tx_hash: tx_hash,
              amount: amount,
              tx_prv_key: tx_prv_key,
              payment_id: payment_id,
              mixin: mixin + 1,
              raw_tx: raw_tx,
              no_inputs: no_inputs,
              no_outputs: no_outputs,
              txBlobKBytes: Math.round(txBlobKBytes*1e3) / 1e3,
              priority: priority_names[priority - 1],
              confirm: function() {
                  $scope.transferConfirmDialog = undefined;
                  deferred.resolve();
              },
              cancel: function() {
                  $scope.transferConfirmDialog = undefined;
                  deferred.reject("Transfer canceled by user");
              }
          };
          return deferred.promise;
      }
  
      function getTxCharge(amount) {
          amount = new JSBigInt(amount);
          // amount * txChargeRatio
          return amount.divide(1 / config.txChargeRatio);
      }
  
      $scope.removeTarget = function(index) {
          $scope.targets.splice(index, 1);
      };
  
      $scope.$watch('targets', function() {
          var totalAmount = JSBigInt.ZERO;
          for (var i = 0; i < $scope.targets.length; ++i) {
              try {
                  var amount = cnUtil.parseMoney($scope.targets[i].amount);
                  totalAmount = totalAmount.add(amount);
              } catch (e) {
              }
          }
          $scope.totalAmount = totalAmount;
      }, true);
  
      $scope.resetSuccessPage = function() {
          $scope.success_page = false;
          $scope.sent_tx = {};
      };
  
      $scope.sendCoins = function(targets, mixin, payment_id) {
          if ($scope.submitting) return;
          $scope.status = "";
          $scope.error = "";
          $scope.submitting = true;
          mixin = parseInt(mixin);
          var rct = true; //maybe want to set this later based on inputs (?)
          var realDsts = [];
          var targetPromises = [];
          for (var i = 0; i < targets.length; ++i) {
              var target = targets[i];
              if (!target.address && !target.amount) {
                  continue;
              }
              var deferred = $q.defer();
              targetPromises.push(deferred.promise);
              (function(deferred, target) {
                  var amount;
                  try {
                      amount = cnUtil.parseMoney(target.amount);
                  } catch (e) {
                      deferred.reject("Failed to parse amount (#" + i + ")");
                      return;
                  }
                  if (target.address.indexOf('.') === -1) {
                      try {
                          // verify that the address is valid
                          cnUtil.decode_address(target.address);
                          deferred.resolve({
                              address: target.address,
                              amount: amount
                          });
                      } catch (e) {
                          deferred.reject("Failed to decode address (#" + i + "): " + e);
                          return;
                      }
                  } else {
                      var domain = target.address.replace(/@/g, ".");
                      ApiCalls.get_txt_records(domain)
                          .then(function(response) {
                              var data = response.data;
                              var records = data.records;
                              var oaRecords = [];
                              console.log(domain + ": ", data.records);
                              if (data.dnssec_used) {
                                  if (data.secured) {
                                      console.log("DNSSEC validation successful");
                                  } else {
                                      deferred.reject("DNSSEC validation failed for " + domain + ": " + data.dnssec_fail_reason);
                                      return;
                                  }
                              } else {
                                  console.log("DNSSEC Not used");
                              }
                              for (var i = 0; i < records.length; i++) {
                                  var record = records[i];
                                  if (record.slice(0, 4 + config.openAliasPrefix.length + 1) !== "oa1:" + config.openAliasPrefix + " ") {
                                      continue;
                                  }
                                  console.log("Found OpenAlias record: " + record);
                                  oaRecords.push(parseOpenAliasRecord(record));
                              }
                              if (oaRecords.length === 0) {
                                  deferred.reject("No OpenAlias records found for: " + domain);
                                  return;
                              }
                              if (oaRecords.length !== 1) {
                                  deferred.reject("Multiple addresses found for given domain: " + domain);
                                  return;
                              }
                              console.log("OpenAlias record: ", oaRecords[0]);
                              var oaAddress = oaRecords[0].address;
                              try {
                                  cnUtil.decode_address(oaAddress);
                                  confirmOpenAliasAddress(domain, oaAddress,
                                      oaRecords[0].name, oaRecords[0].description,
                                      data.dnssec_used && data.secured)
                                      .then(function() {
                                          deferred.resolve({
                                              address: oaAddress,
                                              amount: amount,
                                              domain: domain
                                          });
                                  }, function(err) {
                                      deferred.reject(err);
                                  });
                              } catch (e) {
                                  deferred.reject("Failed to decode OpenAlias address: " + oaRecords[0].address + ": " + e);
                                  return;
                              }
                          }, function(data) {
                              deferred.reject("Failed to resolve DNS records for '" + domain + "': " + "Unknown error");
                          });
                  }
              })(deferred, target);
          }
  
          var strpad = function(org_str, padString, length)
          {   // from http://stackoverflow.com/a/10073737/248823
              var str = org_str;
              while (str.length < length)
                  str = padString + str;
              return str;
          };
  
          // Transaction will need at least 1KB fee (13KB for RingCT)
  
          var feePerKB = new JSBigInt(config.feePerKB);
  
          var priority = $scope.priority || default_priority;
  
          if (!isInt(priority))
          {
              $scope.submitting = false;
              $scope.error = "Priority is not an integer number";
              return;
          }
  
          if (!(priority >= 1 && priority <= 4))
          {
              $scope.submitting = false;
              $scope.error = "Priority is not between 1 and 4";
              return;
          }
  
          var fee_multiplayer = fee_multiplayers[priority - 1]; // default is 4
  
          var neededFee = rct ? feePerKB.multiply(13) : feePerKB;
          var totalAmountWithoutFee;
          var unspentOuts;
          var pid_encrypt = false; //don't encrypt payment ID unless we find an integrated one
  
          $q.all(targetPromises).then(function(destinations) {
              totalAmountWithoutFee = new JSBigInt(0);
              for (var i = 0; i < destinations.length; i++) {
                  totalAmountWithoutFee = totalAmountWithoutFee.add(destinations[i].amount);
              }
              realDsts = destinations;
              console.log("Parsed destinations: " + JSON.stringify(realDsts));
              console.log("Total before fee: " + cnUtil.formatMoney(totalAmountWithoutFee));
              if (realDsts.length === 0) {
                  $scope.submitting = false;
                  $scope.error = "You need to enter a valid destination";
                  return;
              }
              if (payment_id)
              {
                  if (payment_id.length <= 64 && /^[0-9a-fA-F]+$/.test(payment_id))
                  {
                      // if payment id is shorter, but has correct number, just
                      // pad it to required length with zeros
                      payment_id = strpad(payment_id, "0", 64);
                  }
  
                  // now double check if ok, when we padded it
                  if (payment_id.length !== 64 || !(/^[0-9a-fA-F]{64}$/.test(payment_id)))
                  {
                      $scope.submitting = false;
                      $scope.error = "支払いIDが無効です (The payment ID you've entered is not valid)";
                      return;
                  }
  
              }
              if (realDsts.length === 1) {//multiple destinations aren't supported by MyMonero, but don't include integrated ID anyway (possibly should error in the future)
                  var decode_result = cnUtil.decode_address(realDsts[0].address);
                  if (decode_result.intPaymentId && payment_id) {
                      $scope.submitting = false;
                      $scope.error = "Payment ID field must be blank when using an Integrated Address";
                      return;
                  } else if (decode_result.intPaymentId) {
                      payment_id = decode_result.intPaymentId;
                      pid_encrypt = true; //encrypt if using an integrated address
                  }
              }
              if (totalAmountWithoutFee.compare(0) <= 0) {
                  $scope.submitting = false;
                  $scope.error = "The amount you've entered is too low";
                  return;
              }
              $scope.status = "Generating transaction...";
              console.log("Destinations: ");
              // Log destinations to console
              for (var j = 0; j < realDsts.length; j++) {
                  console.log(realDsts[j].address + ": " + cnUtil.formatMoneyFull(realDsts[j].amount));
              }
              var getUnspentOutsRequest = {
                  address: AccountService.getAddress(),
                  view_key: AccountService.getViewKey(),
                  amount: '0',
                  mixin: mixin,
                  // Use dust outputs only when we are using no mixins
                  use_dust: mixin === 0,
                  dust_threshold: config.dustThreshold.toString()
              };
  
              ApiCalls.get_unspent_outs(getUnspentOutsRequest)
                  .then(function(request) {
                      var data = request.data;
                      unspentOuts = checkUnspentOuts(data.outputs || []);
                      unused_outs = unspentOuts.slice(0);
                      using_outs = [];
                      using_outs_amount = new JSBigInt(0);
                      if (data.per_kb_fee)
                      {
                          feePerKB = new JSBigInt(data.per_kb_fee);
                          neededFee = feePerKB.multiply(13).multiply(fee_multiplayer);
                      }
                      transfer().then(transferSuccess, transferFailure);
                  }, function(data) {
                      $scope.status = "";
                      $scope.submitting = false;
                      if (data && data.Error) {
                          $scope.error = data.Error;
                          console.warn(data.Error);
                      } else {
                          $scope.error = "Something went wrong with getting your available balance for spending";
                      }
                  });
          }, function(err) {
              $scope.submitting = false;
              $scope.error = err;
              console.log("Error decoding targets: " + err);
          });
  
          function checkUnspentOuts(outputs) {
              for (var i = 0; i < outputs.length; i++) {
                  for (var j = 0; outputs[i] && j < outputs[i].spend_key_images.length; j++) {
                      var key_img = AccountService.cachedKeyImage(outputs[i].tx_pub_key, outputs[i].index);
                      if (key_img === outputs[i].spend_key_images[j]) {
                          console.log("Output was spent with key image: " + key_img + " amount: " + cnUtil.formatMoneyFull(outputs[i].amount));
                          // Remove output from list
                          outputs.splice(i, 1);
                          if (outputs[i]) {
                              j = outputs[i].spend_key_images.length;
                          }
                          i--;
                      } else {
                          console.log("Output used as mixin (" + key_img + "/" + outputs[i].spend_key_images[j] + ")");
                      }
                  }
              }
              console.log("Unspent outs: " + JSON.stringify(outputs));
              return outputs;
          }
  
          function transferSuccess(tx_h) {
              var prevFee = neededFee;
              var raw_tx = tx_h.raw;
              var tx_hash = tx_h.hash;
              var tx_prvkey = tx_h.prvkey;
              var no_inputs = tx_h.no_inputs;
              var no_outputs = tx_h.no_outputs;
              // work out per-kb fee for transaction
              var txBlobBytes = raw_tx.length / 2;
              var txBlobKBytes = txBlobBytes / 1024.0;
              var numKB = Math.floor(txBlobKBytes);
              if (txBlobBytes % 1024) {
                  numKB++;
              }
              console.log(txBlobBytes + " bytes <= " + numKB + " KB (current fee: " + cnUtil.formatMoneyFull(prevFee) + ")");
              neededFee = feePerKB.multiply(numKB).multiply(fee_multiplayer);
              // if we need a higher fee
              if (neededFee.compare(prevFee) > 0) {
                  console.log("Previous fee: " + cnUtil.formatMoneyFull(prevFee) + " New fee: " + cnUtil.formatMoneyFull(neededFee));
                  transfer().then(transferSuccess, transferFailure);
                  return;
              }
  
              // generated with correct per-kb fee
              console.log("Successful tx generation, submitting tx");
              console.log("Tx hash: " + tx_hash);
              $scope.status = "Submitting...";
              var request = {
                  address: AccountService.getAddress(),
                  view_key: AccountService.getViewKey(),
                  tx: raw_tx
              };
  
  
              confirmTransfer(realDsts[0].address, realDsts[0].amount,
                              tx_hash, neededFee, tx_prvkey, payment_id,
                              mixin, priority, txBlobKBytes, raw_tx,
                              no_inputs, no_outputs).then(function() {
  
                  //alert('Confirmed ');
  
                  ApiCalls.submit_raw_tx(request.address, request.view_key, request.tx)
                      .then(function(response) {
  
                          var data = response.data;
  
                          if (data.status === "error")
                          {
                              $scope.status = "";
                              $scope.submitting = false;
                              $scope.error = "Something unexpected occurred when submitting your transaction: " + data.error;
                              return;
                          }
  
                          //console.log("Successfully submitted tx");
                          $scope.targets = [{}];
                          $scope.sent_tx = {
                              address: realDsts[0].address,
                              domain: realDsts[0].domain,
                              amount: realDsts[0].amount,
                              payment_id: payment_id,
                              tx_id: tx_hash,
                              tx_prvkey: tx_prvkey,
                              tx_fee: neededFee/*.add(getTxCharge(neededFee))*/,
                              explorerLink: explorerUrl + "tx/" + tx_hash
                          };
  
                          $scope.success_page = true;
                          $scope.status = "";
                          $scope.submitting = false;
                      }, function(error) {
                          $scope.status = "";
                          $scope.submitting = false;
                          $scope.error = "Something unexpected occurred when submitting your transaction: ";
                      });
  
              }, function(reason) {
                  //alert('Failed: ' + reason);
                  transferFailure("Transfer canceled");
              });
  
  
          }
  
          function transferFailure(reason) {
              $scope.status = "";
              $scope.submitting = false;
              $scope.error = reason;
              console.log("Transfer failed: " + reason);
          }
  
          var unused_outs;
          var using_outs;
          var using_outs_amount;
  
          function random_index(list) {
              return Math.floor(Math.random() * list.length);
          }
  
          function pop_random_value(list) {
              var idx = random_index(list);
              var val = list[idx];
              list.splice(idx, 1);
              return val;
          }
  
          function select_outputs(target_amount) {
              console.log("Selecting outputs to use. Current total: " + cnUtil.formatMoney(using_outs_amount) + " target: " + cnUtil.formatMoney(target_amount));
              while (using_outs_amount.compare(target_amount) < 0 && unused_outs.length > 0) {
                  var out = pop_random_value(unused_outs);
                  if (!rct && out.rct) {continue;} //skip rct outs if not creating rct tx
                  using_outs.push(out);
                  using_outs_amount = using_outs_amount.add(out.amount);
                  console.log("Using output: " + cnUtil.formatMoney(out.amount) + " - " + JSON.stringify(out));
              }
          }
  
          function transfer() {
              var deferred = $q.defer();
              (function() {
                  var dsts = realDsts.slice(0);
                  // Calculate service charge and add to tx destinations
                  //var chargeAmount = getTxCharge(neededFee);
                  //dsts.push({
                  //    address: config.txChargeAddress,
                  //    amount: chargeAmount
                  //});
                  // Add fee to total amount
                  var totalAmount = totalAmountWithoutFee.add(neededFee)/*.add(chargeAmount)*/;
                  console.log("Balance required: " + cnUtil.formatMoneySymbol(totalAmount));
  
                  select_outputs(totalAmount);
  
                  //compute fee as closely as possible before hand
                  if (using_outs.length > 1 && rct)
                  {
                      var newNeededFee = JSBigInt(Math.ceil(cnUtil.estimateRctSize(using_outs.length, mixin, 2) / 1024)).multiply(feePerKB).multiply(fee_multiplayer);
                      totalAmount = totalAmountWithoutFee.add(newNeededFee);
                      //add outputs 1 at a time till we either have them all or can meet the fee
                      while (using_outs_amount.compare(totalAmount) < 0 && unused_outs.length > 0)
                      {
                          var out = pop_random_value(unused_outs);
                          using_outs.push(out);
                          using_outs_amount = using_outs_amount.add(out.amount);
                          console.log("Using output: " + cnUtil.formatMoney(out.amount) + " - " + JSON.stringify(out));
                          newNeededFee = JSBigInt(Math.ceil(cnUtil.estimateRctSize(using_outs.length, mixin, 2) / 1024)).multiply(feePerKB).multiply(fee_multiplayer);
                          totalAmount = totalAmountWithoutFee.add(newNeededFee);
                      }
                      console.log("New fee: " + cnUtil.formatMoneySymbol(newNeededFee) + " for " + using_outs.length + " inputs");
                      neededFee = newNeededFee;
                  }
  
                  if (using_outs_amount.compare(totalAmount) < 0)
                  {
                      deferred.reject("Not enough spendable outputs / balance too low (have "
                          + cnUtil.formatMoneyFull(using_outs_amount) + " but need "
                          + cnUtil.formatMoneyFull(totalAmount)
                          + " (estimated fee " + cnUtil.formatMoneyFull(neededFee) + " included)");
                      return;
                  }
                  else if (using_outs_amount.compare(totalAmount) > 0)
                  {
                      var changeAmount = using_outs_amount.subtract(totalAmount);
  
                      if (!rct)
                      {   //for rct we don't presently care about dustiness
                          //do not give ourselves change < dust threshold
                          var changeAmountDivRem = changeAmount.divRem(config.dustThreshold);
                          if (changeAmountDivRem[1].toString() !== "0") {
                              // add dusty change to fee
                              console.log("Adding change of " + cnUtil.formatMoneyFullSymbol(changeAmountDivRem[1]) + " to transaction fee (below dust threshold)");
                          }
                          if (changeAmountDivRem[0].toString() !== "0") {
                              // send non-dusty change to our address
                              var usableChange = changeAmountDivRem[0].multiply(config.dustThreshold);
                              console.log("Sending change of " + cnUtil.formatMoneySymbol(usableChange) + " to " + AccountService.getAddress());
                              dsts.push({
                                  address: AccountService.getAddress(),
                                  amount: usableChange
                              });
                          }
                      }
                      else
                      {
                          //add entire change for rct
                          console.log("Sending change of " + cnUtil.formatMoneySymbol(changeAmount)
                              + " to " + AccountService.getAddress());
                          dsts.push({
                              address: AccountService.getAddress(),
                              amount: changeAmount
                          });
                      }
                  }
                  else if (using_outs_amount.compare(totalAmount) === 0 && rct)
                  {
                      //create random destination to keep 2 outputs always in case of 0 change
                      var fakeAddress = cnUtil.create_address(cnUtil.random_scalar()).public_addr;
                      console.log("Sending 0 SIN to a fake address to keep tx uniform (no change exists): " + fakeAddress);
                      dsts.push({
                          address: fakeAddress,
                          amount: 0
                      });
                  }
  
                  if (mixin > 0)
                  {
                      var amounts = [];
                      for (var l = 0; l < using_outs.length; l++)
                      {
                          amounts.push(using_outs[l].rct ? "0" : using_outs[l].amount.toString());
                          //amounts.push("0");
                      }
                      var request = {
                          amounts: amounts,
                          count: mixin + 1 // Add one to mixin so we can skip real output key if necessary
                      };
  
                      ApiCalls.get_random_outs(request.amounts, request.count)
                          .then(function(response) {
                              var data = response.data;
  
                              if (data.status === "error")
                              {
                                  $scope.status = "";
                                  $scope.submitting = false;
                                  $scope.error = "Failed to create transaction: " + data.error;
                                  return;
                              }
  
                              createTx(data.amount_outs);
                          }, function(data) {
                              deferred.reject('Failed to get unspent outs');
                          });
                  } else if (mixin < 0 || isNaN(mixin)) {
                      deferred.reject("Invalid mixin");
                      return;
                  } else { // mixin === 0
                      createTx();
                  }
  
                  // Create & serialize transaction
                  function createTx(mix_outs)
                  {
                      var signed;
                      try {
                          console.log('Destinations: ');
                          cnUtil.printDsts(dsts);
                          //need to get viewkey for encrypting here, because of splitting and sorting
                          if (pid_encrypt)
                          {
                              var realDestViewKey = cnUtil.decode_address(dsts[0].address).view;
                          }
  
                          var splittedDsts = cnUtil.decompose_tx_destinations(dsts, rct);
  
                          console.log('Decomposed destinations:');
  
                          cnUtil.printDsts(splittedDsts);
  
                          signed = cnUtil.create_transaction(
                              AccountService.getPublicKeys(),
                              AccountService.getSecretKeys(),
                              splittedDsts, using_outs,
                              mix_outs, mixin, neededFee,
                              payment_id, pid_encrypt,
                              realDestViewKey, 0, rct);
  
                      } catch (e) {
                          deferred.reject("Failed to create transaction: " + e);
                          return;
                      }
                      console.log("signed tx: ", JSON.stringify(signed));
                      //move some stuff here to normalize rct vs non
                      var raw_tx_and_hash = {};
                      if (signed.version === 1) {
                          raw_tx_and_hash.raw = cnUtil.serialize_tx(signed);
                          raw_tx_and_hash.hash = cnUtil.cn_fast_hash(raw_tx);
                          raw_tx_and_hash.prvkey = signed.prvkey;
                          raw_tx_and_hash.no_outputs = signed.vout.length;
                          raw_tx_and_hash.no_inputs = signed.vin.length;
                      } else {
                          raw_tx_and_hash = cnUtil.serialize_rct_tx_with_hash(signed);
                      }
                      console.log("raw_tx and hash:");
                      console.log(raw_tx_and_hash);
                      deferred.resolve(raw_tx_and_hash);
                  }
              })();
              return deferred.promise;
          }
      };
  });
  
  function parseOpenAliasRecord(record) {
      var parsed = {};
      if (record.slice(0, 4 + config.openAliasPrefix.length + 1) !== "oa1:" + config.openAliasPrefix + " ") {
          throw "Invalid OpenAlias prefix";
      }
      function parse_param(name) {
          var pos = record.indexOf(name + "=");
          if (pos === -1) {
              // Record does not contain param
              return undefined;
          }
          pos += name.length + 1;
          var pos2 = record.indexOf(";", pos);
          return record.substr(pos, pos2 - pos);
      }
  
      parsed.address = parse_param('recipient_address');
      parsed.name = parse_param('recipient_name');
      parsed.description = parse_param('tx_description');
      return parsed;
  }
  