text: /******************************************************************************
   * Copyright © 2013-2016 The Nxt Core Developers.                             *
   * Copyright © 2016-2019 Jelurida IP B.V.                                     *
   *                                                                            *
   * See the LICENSE.txt file at the top-level directory of this distribution   *
   * for licensing information.                                                 *
   *                                                                            *
   * Unless otherwise agreed in a custom licensing agreement with Jelurida B.V.,*
   * no part of the Nxt software, including this file, may be copied, modified, *
   * propagated, or distributed except according to the terms contained in the  *
   * LICENSE.txt file.                                                          *
   *                                                                            *
   * Removal or modification of this copyright notice is prohibited.            *
   *                                                                            *
   ******************************************************************************/
  
  /**
   * @depends {nrs.js}
   */
  
  var NRS = (function (NRS, $, undefined) {
      NRS.formatVolume = function (volume) {
  		var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
  		if (volume == 0) return '0 B';
  		var i = parseInt(Math.floor(Math.log(volume) / Math.log(1024)));
  
          volume = Math.round(volume / Math.pow(1024, i));
  		var size = sizes[i];
  
          var digits = [], formattedVolume = "";
  		do {
  			digits[digits.length] = volume % 10;
  			volume = Math.floor(volume / 10);
  		} while (volume > 0);
  		for (i = 0; i < digits.length; i++) {
  			if (i > 0 && i % 3 == 0) {
  				formattedVolume = "'" + formattedVolume;
  			}
  			formattedVolume = digits[i] + formattedVolume;
  		}
  		return formattedVolume + " " + size;
  	};
  
      NRS.formatWeight = function (weight) {
  		var digits = [],
  			formattedWeight = "",
  			i;
  		do {
  			digits[digits.length] = weight % 10;
  			weight = Math.floor(weight / 10);
  		} while (weight > 0);
  		for (i = 0; i < digits.length; i++) {
  			if (i > 0 && i % 3 == 0) {
  				formattedWeight = "'" + formattedWeight;
  			}
  			formattedWeight = digits[i] + formattedWeight;
  		}
  		return formattedWeight.escapeHTML();
  	};
  
      NRS.formatOrderPricePerWholeQNT = function (price, decimals, zeroPad) {
  		price = NRS.calculateOrderPricePerWholeQNT(price, decimals, true);
  		return NRS.format(price, false, zeroPad);
  	};
  
      NRS.calculateOrderPricePerWholeQNT = function (price, decimals, returnAsObject) {
  		if (typeof price != "object") {
  			price = new BigInteger(String(price));
  		}
  
  		return NRS.convertToNXT(price.multiply(new BigInteger("" + Math.pow(10, decimals))), returnAsObject);
  	};
  
      NRS.calculatePricePerWholeQNT = function (price, decimals) {
  		price = String(price);
  
  		if (decimals) {
  			var toRemove = price.slice(-decimals);
  
  			if (!/^[0]+$/.test(toRemove)) {
  				//return new Big(price).div(new Big(Math.pow(10, decimals))).round(8, 0);
  				throw $.t("error_invalid_input");
  			} else {
  				return price.slice(0, -decimals);
  			}
  		} else {
  			return price;
  		}
  	};
  
      function calculateOrderTotalImpl(quantityQNT, priceNQT) {
          if (typeof quantityQNT != "object") {
              quantityQNT = new BigInteger(String(quantityQNT));
          }
          if (typeof priceNQT != "object") {
              priceNQT = new BigInteger(String(priceNQT));
          }
          return quantityQNT.multiply(priceNQT);
      }
  
      NRS.calculateOrderTotalNQT = function (quantityQNT, priceNQT) {
          return calculateOrderTotalImpl(quantityQNT, priceNQT).toString();
      };
  
      NRS.calculateOrderTotal = function (quantityQNT, priceNQT) {
          return NRS.convertToNXT(calculateOrderTotalImpl(quantityQNT, priceNQT));
      };
  
      NRS.calculatePercentage = function (a, b, rounding_mode) {
  		if (String(b) == "0") {
              return "0";
          }
          if (rounding_mode != undefined) { // Rounding mode from Big.js
  			Big.RM = rounding_mode;
  		}
  		a = new Big(String(a));
  		b = new Big(String(b));
  
  		var result = a.div(b).times(new Big("100")).toFixed(2);
  		Big.RM = 1;
  		return NRS.format(result.toString());
  	};
  
      NRS.convertToNXT = function (amount, returnAsObject) {
          var negative = "";
          var mantissa = "";
  
  		if (typeof amount != "object") {
  			amount = new BigInteger(String(amount));
  		}
  
          if (amount.compareTo(BigInteger.ZERO) < 0) {
              amount = amount.abs();
              negative = "-";
          }
  
          var fractionalPart = amount.mod(new BigInteger("100000000")).toString();
          amount = amount.divide(new BigInteger("100000000"));
  
          if (fractionalPart && fractionalPart != "0") {
              mantissa = ".";
  
              for (var i = fractionalPart.length; i < 8; i++) {
                  mantissa += "0";
              }
  
              mantissa += fractionalPart.replace(/0+$/, "");
          }
  
  		amount = amount.toString();
  
          if (returnAsObject) {
              return {
                  "negative": negative,
                  "amount": amount,
                  "mantissa": mantissa
              };
          } else {
              return negative + amount + mantissa;
          }
      };
  
      NRS.amountToPrecision = function (amount, decimals) {
  		amount = String(amount);
  
  		var parts = amount.split(".");
  
  		//no fractional part
  		if (parts.length == 1) {
  			return parts[0];
  		} else if (parts.length == 2) {
  			var fraction = parts[1];
  			fraction = fraction.replace(/0+$/, "");
  
  			if (fraction.length > decimals) {
  				fraction = fraction.substring(0, decimals);
  			}
  
  			return parts[0] + "." + fraction;
  		} else {
  			throw $.t("error_invalid_input");
  		}
  	};
  
      NRS.convertToNQT = function (currency) {
  		currency = String(currency);
  
  		var parts = currency.split(".");
  
  		var amount = parts[0];
  
  		//no fractional part
          var fraction;
  		if (parts.length == 1) {
              fraction = "00000000";
  		} else if (parts.length == 2) {
  			if (parts[1].length <= 8) {
                  fraction = parts[1];
  			} else {
                  fraction = parts[1].substring(0, 8);
  			}
  		} else {
  			throw $.t("error_invalid_input");
  		}
  
  		for (var i = fraction.length; i < 8; i++) {
  			fraction += "0";
  		}
  
  		var result = amount + "" + fraction;
  
  		//in case there's a comma or something else in there.. at this point there should only be numbers
  		if (!/^\d+$/.test(result)) {
  			throw $.t("error_invalid_input");
  		}
  
  		//remove leading zeroes
  		result = result.replace(/^0+/, "");
  
  		if (result === "") {
  			result = "0";
  		}
  
  		return result;
  	};
  
      NRS.convertToQNTf = function (quantity, decimals, returnAsObject) {
  		quantity = String(quantity);
  
  		if (quantity.length < decimals) {
  			for (var i = quantity.length; i < decimals; i++) {
  				quantity = "0" + quantity;
  			}
  		}
  
          var mantissa = "";
  
          if (decimals) {
              mantissa = "." + quantity.substring(quantity.length - decimals);
              quantity = quantity.substring(0, quantity.length - decimals);
  
  			if (!quantity) {
  				quantity = "0";
  			}
  
              mantissa = mantissa.replace(/0+$/, "");
  
              if (mantissa == ".") {
                  mantissa = "";
              }
          }
  
          if (returnAsObject) {
              return {
                  "amount": quantity,
                  "mantissa": mantissa
              };
          } else {
              return quantity + mantissa;
          }
      };
  
      NRS.convertToQNT = function (quantity, decimals) {
  		quantity = String(quantity);
  
  		var parts = quantity.split(".");
  
  		var qnt = parts[0];
  
  		//no fractional part
          var i;
  		if (parts.length == 1) {
  			if (decimals) {
                  for (i = 0; i < decimals; i++) {
  					qnt += "0";
  				}
  			}
  		} else if (parts.length == 2) {
  			var fraction = parts[1];
  			if (fraction.length > decimals) {
  				throw $.t("error_fraction_decimals", {
  					"decimals": decimals
  				});
  			} else if (fraction.length < decimals) {
                  for (i = fraction.length; i < decimals; i++) {
  					fraction += "0";
  				}
  			}
  			qnt += fraction;
  		} else {
  			throw $.t("error_invalid_input");
  		}
  
  		//in case there's a comma or something else in there.. at this point there should only be numbers
  		if (!/^\d+$/.test(qnt)) {
  			throw $.t("error_invalid_input_numbers");
  		}
          try {
              if (parseInt(qnt) === 0) {
                  return "0";
              }
          } catch (e) {
          }
  
  		//remove leading zeroes
  		return qnt.replace(/^0+/, "");
  	};
  
      var zeros = "00000000";
      NRS.format = function (params, no_escaping, zeroPad) {
          var amount;
          var mantissa;
  		if (typeof params != "object") {
              amount = String(params);
              if (amount.indexOf(".") !== -1) {
                  mantissa = amount.substr(amount.indexOf("."));
                  amount = amount.replace(mantissa, "");
              } else {
                  mantissa = "";
              }
              var negative = amount.charAt(0) == "-" ? "-" : "";
              if (negative) {
                  amount = amount.substring(1);
              }
              params = {
                  "amount": amount,
                  "negative": negative,
                  "mantissa": mantissa
              };
          }
  
          amount = String(params.amount);
  		var digits = amount.split("").reverse();
  		var formattedAmount = "";
          var locale = NRS.getLocale();
          var formattedMantissa = params.mantissa.replace(".", locale.decimal);
          if (zeroPad) {
              var mantissaLen = formattedMantissa.length;
              if (mantissaLen > 0) {
                  formattedMantissa += zeros.substr(0, zeroPad - mantissaLen + 1);
              } else {
                  formattedMantissa += zeros.substr(0, zeroPad);
                  if (zeroPad != 0) {
                      formattedMantissa = locale.decimal + formattedMantissa;
                  }
              }
          }
  		for (var i = 0; i < digits.length; i++) {
  		    if (i > 0 && i % 3 == 0) {
                  formattedAmount = locale.section + formattedAmount;
              }
  			formattedAmount = digits[i] + formattedAmount;
          }
  
          var output = (params.negative ? params.negative : "") + formattedAmount + formattedMantissa;
  
  		if (!no_escaping) {
  			output = output.escapeHTML();
  		}
          return output;
  	};
  
      NRS.formatQuantity = function (quantity, decimals, no_escaping, zeroPad) {
  		return NRS.format(NRS.convertToQNTf(quantity, decimals, true), no_escaping, zeroPad);
  	};
  
      NRS.formatAmount = function (amount, round, no_escaping, zeroPad) {
          if (typeof amount == "undefined") {
              return "0";
          } else if (typeof amount == "string") {
              amount = new BigInteger(amount);
          }
  
          var negative = "";
          var mantissa = "";
  
          if (typeof amount == "object") {
              var params = NRS.convertToNXT(amount, true);
  
              negative = params.negative;
              amount = params.amount;
              mantissa = params.mantissa;
          } else {
              //rounding only applies to non-nqt
              if (round) {
                  amount = (Math.round(amount * 100) / 100);
              }
  
              if (amount < 0) {
                  amount = Math.abs(amount);
                  negative = "-";
              }
  
              amount = "" + amount;
              if (amount.indexOf(".") !== -1) {
                  mantissa = amount.substr(amount.indexOf("."));
                  amount = amount.replace(mantissa, "");
              } else {
                  mantissa = "";
              }
          }
          if (NRS.settings) {
              var offset = 0;
              if (mantissa != "" && mantissa.substring(0, 1) == ".") {
                  offset ++;
              }
              var maxLength = parseInt(NRS.settings.max_nxt_decimals) + offset;
              if (mantissa.length > maxLength) {
                  mantissa = mantissa.substring(0, maxLength);
                  if (mantissa.length == 1 && mantissa.substring(0, 1) == ".") {
                      mantissa = "";
                  }
              }
          }
  
          return NRS.format({
              "negative": negative,
              "amount": amount,
              "mantissa": mantissa
          }, no_escaping, zeroPad);
      };
  
      NRS.getTransactionsAmountDecimals = function(transactions) {
          var decimals = {};
     		decimals.amount = NRS.getNumberOfDecimals(transactions, "amountNQT", function (transaction) {
     			return NRS.formatAmount(transaction.amountNQT);
     		});
     		decimals.fee = NRS.getNumberOfDecimals(transactions, "feeNQT", function (transaction) {
     			return NRS.formatAmount(transaction.feeNQT);
     		});
          return decimals;
     	};
  
      NRS.getNumberOfDecimals = function(rows, key, callback) {
          var locale = NRS.getLocale();
          var decimals = 0;
          for (var i=0; i<rows.length; i++) {
              var val = rows[i][key];
              if (callback) {
                  val = callback(rows[i]);
              }
              var tokens = val.split(locale.decimal);
              if (tokens.length != 2) {
                  continue;
              }
              decimals = Math.max(decimals, tokens[1].length);
          }
          return decimals;
      };
  
      NRS.fromEpochTime = function (epochTime) {
          if (!NRS.constants || NRS.constants.EPOCH_BEGINNING == 0) {
              throw "undefined epoch beginning";
          }
          return epochTime * 1000 + NRS.constants.EPOCH_BEGINNING - 500;
      };
  
      NRS.toEpochTime = function (currentTime) {
          if (currentTime == undefined) {
              currentTime = new Date();
          }
          if (NRS.constants.EPOCH_BEGINNING == 0) {
              throw "undefined epoch beginning";
          }
          return Math.floor((currentTime - NRS.constants.EPOCH_BEGINNING) / 1000);
      };
  
      NRS.formatTimestamp = function (timestamp, date_only, isAbsoluteTime) {
          var locale = NRS.getLocale();
          var date;
  		if (typeof timestamp == "object") {
              date = timestamp;
          } else if (isAbsoluteTime) {
              date = new Date(timestamp);
          } else {
              date = new Date(NRS.fromEpochTime(timestamp));
  		}
  
  		if (!isNaN(date) && typeof(date.getFullYear) == 'function') {
  			var d = date.getDate();
  			var dd = d < 10 ? '0' + d : d;
  			var M = date.getMonth() + 1;
  			var MM = M < 10 ? '0' + M : M;
  			var yyyy = date.getFullYear();
              var yy = String(yyyy).substring(2);
  
              var res = locale.dateFormat
                  .replace(/dd/g, dd)
                  .replace(/d/g, d)
                  .replace(/MM/g, MM)
                  .replace(/M/g, M)
                  .replace(/yyyy/g, yyyy)
                  .replace(/yy/g, yy);
  
              if (!date_only) {
                  var hours = date.getHours();
                  var originalHours = hours;
                  var minutes = date.getMinutes();
                  var seconds = date.getSeconds();
  
                  if (!NRS.settings || NRS.settings["24_hour_format"] == "0") {
                      if (originalHours == 0) {
                          hours += 12;
                      } else if (originalHours >= 13 && originalHours <= 23) {
                          hours -= 12;
                      }
                  }
                  if (minutes < 10) {
                      minutes = "0" + minutes;
                  }
                  if (seconds < 10) {
                      seconds = "0" + seconds;
                  }
                  res += " " + hours + ":" + minutes + ":" + seconds;
  
                  if (!NRS.settings || NRS.settings["24_hour_format"] == "0") {
                      res += " " + (originalHours >= 12 ? "PM" : "AM");
  				}
  			}
  			return res;
  		} else {
  			return date.toLocaleString();
  		}
  	};
  
      NRS.getBlockHeightMoment = function(height) {
          if (!height || !NRS.lastBlockHeight || !NRS.averageBlockGenerationTime) {
              return "-";
          }
          var heightDiff = height - NRS.lastBlockHeight;
          return moment().add(heightDiff * NRS.averageBlockGenerationTime, 'seconds');
      };
  
      NRS.getBlockHeightTimeEstimate = function(height) {
          var heightMoment = NRS.getBlockHeightMoment(height);
          if (heightMoment == "-") {
              return "-";
          }
          return heightMoment.format("YYYY/MM/DD hh:mm a");
     	};
  
      NRS.baseTargetPercent = function(block) {
          if (block) {
              return Math.round(block.baseTarget / NRS.constants.INITIAL_BASE_TARGET * 100)
          } else {
              return 0;
          }
      };
  
      NRS.convertToHex16 = function (str) {
  		var hex, i;
  		var result = "";
  		for (i = 0; i < str.length; i++) {
  			hex = str.charCodeAt(i).toString(16);
  			result += ("000" + hex).slice(-4);
  		}
  
  		return result;
  	};
  
      NRS.convertFromHex16 = function (hex) {
  		var j;
  		var hexes = hex.match(/.{1,4}/g) || [];
  		var back = "";
  		for (j = 0; j < hexes.length; j++) {
  			back += String.fromCharCode(parseInt(hexes[j], 16));
  		}
  
  		return back;
  	};
  
      NRS.convertFromHex8 = function (hex) {
          var hexStr = hex.toString(); //force conversion
  		var str = '';
          for (var i = 0; i < hexStr.length; i += 2) {
              str += String.fromCharCode(parseInt(hexStr.substr(i, 2), 16));
          }
  		return str;
  	};
  
      NRS.convertToHex8 = function (str) {
  		var hex = '';
  		for (var i = 0; i < str.length; i++) {
  			hex += '' + str.charCodeAt(i).toString(16);
  		}
  		return hex;
  	};
  
      NRS.getFormData = function ($form, unmodified) {
  		var serialized = $form.serializeArray(); // Warning: converts \n to \r\n
  		var data = {};
          var multiValuedFields = ["phasingWhitelisted", "controlWhitelisted"];
  		for (var s in serialized) {
              if (!serialized.hasOwnProperty(s)) {
                  continue;
  		}
  			if (multiValuedFields.indexOf(serialized[s]["name"]) > -1) {
  				if (serialized[s]['value'] != "") {
  					if (serialized[s]['name'] in data) {
  						var index = data[serialized[s]['name']].length;
  						data[serialized[s]['name']][index] = serialized[s]['value'];
  					} else {
  						data[serialized[s]['name']] = [serialized[s]['value']]; //all data as list (traditional, to allow multiple values)
  					}
  				}
  			} else {
  				data[serialized[s]['name']] = serialized[s]['value'];
  			}
  		}
  		if (!unmodified) {
  			delete data.request_type;
  			delete data.converted_account_id;
  			delete data.merchant_info;
  		}
  		return data;
  	};
  
      NRS.mergeMaps = function (mergedMap, toMap, skipAttributes) {
          for (var attr in mergedMap) {
              if (!mergedMap.hasOwnProperty(attr)) {
                  continue;
              }
              if (skipAttributes[attr]) {
                  continue;
              }
              toMap[attr] = mergedMap[attr];
          }
      };
  
      NRS.convertNumericToRSAccountFormat = function (account) {
          if (NRS.isRsAccount(account)) {
              return String(account).escapeHTML();
          } else {
              var address = new NxtAddress();
              if (address.set(account)) {
                  return address.toString().escapeHTML();
              } else {
                  return "";
              }
          }
      };
  
      NRS.getAccountLink = function (object, accountKey, accountRef, title, showAccountRS, clazz) {
          var accountRS;
          if (typeof object[accountKey + "RS"] != "undefined") {
              accountRS = object[accountKey + "RS"];
          } else if (typeof object[accountKey] != "undefined") {
              accountRS = NRS.convertNumericToRSAccountFormat(object[accountKey]);
          } else {
              return '/';
          }
          var accountTitle;
          if (accountRef && accountRS == accountRef) {
              accountTitle = $.t(title);
          } else if(showAccountRS) {
              accountTitle = String(accountRS).escapeHTML();
          } else {
              accountTitle = NRS.getAccountTitle(object, accountKey);
          }
          if (!clazz) {
              clazz = "";
          } else {
              if (clazz.length > 0) {
                  if (String(clazz).indexOf(" ") != 0) {
                      clazz = " " + clazz;
                  }
              }
          }
          return "<a href='#' data-user='" + String(accountRS).escapeHTML() +
              "' class='show_account_modal_action user-info" + clazz + "'>" + accountTitle + "</a>";
      };
  
      NRS.getTransactionLink = function(id, text, isEscapedText) {
          if (!text) {
              text = id;
          }
          return "<a href='#' class='show_transaction_modal_action' data-transaction='" + String(id).escapeHTML() + "'>"
              + (isEscapedText ? text : String(text).escapeHTML()) + "</a>";
      };
  
      NRS.getBlockLink = function(height, text, isEscapedText) {
          if (!text) {
              text = height;
          }
          return "<a href='#' class='show_block_modal_action' data-block='" + String(height).escapeHTML() + "'>"
              + (isEscapedText ? text : String(text).escapeHTML()) + "</a>";
      };
  
      NRS.getPeerLink = function(address) {
          if (!address ) {
              return "(" + $.t("temporarily_disconnected") + ")";
          }
          return "<a href='#' class='show_peer_modal_action' data-address='" + String(address).escapeHTML() + "'>"
              + String(address).escapeHTML() + "</a>";
      };
  
      NRS.setBackLink = function() {
          var backLink = $(".back-link");
          if (NRS.modalStack.length > 0) {
              var backModalInfo = NRS.modalStack[NRS.modalStack.length - 1];
              backLink.removeClass("show_transaction_modal_action show_account_modal_action show_block_modal_action show_ledger_modal_action dgs_show_modal_action_purchase dgs_show_modal_action_product");
              backLink.addClass(backModalInfo.class);
              backLink.data(backModalInfo.key, backModalInfo.value);
              backLink.data("back", "true");
              backLink.show();
          } else {
              backLink.hide();
          }
      };
  
      NRS.getAccountTitle = function (object, acc) {
          var type = typeof object;
  
          var formattedAcc = "";
  
          if (type == "string" || type == "number") {
              formattedAcc = object;
              object = null;
          } else {
              if (object == null || typeof object[acc + "RS"] == "undefined") {
                  return "/";
              } else {
                  formattedAcc = String(object[acc + "RS"]).escapeHTML();
              }
          }
  
          if (formattedAcc == NRS.account || formattedAcc == NRS.accountRS) {
              return $.t("you");
          } else if (formattedAcc == NRS.constants.GENESIS || formattedAcc == NRS.constants.GENESIS_RS) {
              return $.t("genesis");
          } else if (formattedAcc in NRS.contacts) {
              return NRS.contacts[formattedAcc].name.escapeHTML();
          } else {
              return String(formattedAcc).escapeHTML();
          }
      };
  
      NRS.getAccountFormatted = function (object, acc) {
  		var type = typeof object;
  
  		if (type == "string" || type == "number") {
  			return String(object).escapeHTML();
  		} else {
  			if (typeof object[acc + "RS"] == "undefined") {
  				return "";
  			} else {
  				return String(object[acc + "RS"]).escapeHTML();
  			}
  		}
  	};
  
      NRS.dataLoaded = function (data, noPageLoad) {
  		var $el = $("#" + NRS.currentPage + "_contents");
  
  		if ($el.length) {
  			$el.empty().append(data);
  		} else {
              try {
                  $el = $("#" + NRS.currentPage + "_table");
                  $el.find("tbody").empty().append(data);
                  $el.find('[data-toggle="tooltip"]').tooltip();
              } catch (e) {
                  NRS.logException(e);
                  NRS.logConsole("Raw data: " + data);
                  $el.find("tbody").empty().append("<tr><td>Error processing table data: " + e.message + "</td></tr>");
              }
          }
  
  		NRS.dataLoadFinished($el);
  
  		if (!noPageLoad) {
  			NRS.pageLoaded();
  		}
  	};
  
      NRS.dataLoadFinished = function ($el, fadeIn) {
  		var $parent = $el.parent();
  
  		if (fadeIn) {
  			$parent.hide();
  		}
  
  		$parent.removeClass("data-loading");
  
  		var extra = $parent.data("extra");
  
  		var empty = false;
  
  		if ($el.is("table")) {
  			if ($el.find("tbody tr").length > 0) {
  				$parent.removeClass("data-empty");
  				if ($parent.data("no-padding")) {
  					$parent.parent().addClass("no-padding");
  				}
  
  				if (extra) {
  					$(extra).show();
  				}
  			} else {
  				empty = true;
  			}
  		} else {
  			if ($.trim($el.html()).length == 0) {
  				empty = true;
  			}
  		}
  
  		if (empty) {
  			$parent.addClass("data-empty");
  			if ($parent.data("no-padding")) {
  				$parent.parent().removeClass("no-padding");
  			}
  			if (extra) {
  				$(extra).hide();
  			}
  		} else {
  			$parent.removeClass("data-empty");
  		}
  
  		if (fadeIn) {
              $parent.stop(true, true).fadeIn(400, function () {
  				$parent.show();
  			});
  		}
  	};
  
      NRS.createInfoTable = function(data, fixed) {
          var orderedData = {};
          Object.keys(data).sort().forEach(function(key) {
              orderedData[key] = data[key];
          });
          data = orderedData;
  		var rows = "";
  		for (var key in data) {
              if (!data.hasOwnProperty(key)) {
                  continue;
              }
  			var value = data[key];
  
  			var match = key.match(/(.*)(NQT|QNT|RS)$/);
  			var type = "";
  
  			if (match && match[1]) {
  				key = match[1];
  				type = match[2];
  			}
  
              key = key.replace(/\s+/g, "").replace(/([A-Z])/g, function ($1) {
                  return "_" + $1.toLowerCase();
              });
  
              //no need to mess with input, already done if Formatted is at end of key
              if (/_formatted_html$/i.test(key)) {
                  key = key.replace("_formatted_html", "");
                  value = String(value);
              } else if (/_formatted$/i.test(key)) {
                  key = key.replace("_formatted", "");
                  value = NRS.escapeRespStr(value);
              } else if ((key == "quantity" || key == "units" || key == "initial_buy_supply" || key == "initial_sell_supply" ||
                  key == "total_buy_limit" || key == "total_sell_limit" || key == "units_exchanged" || key == "total_exchanged" ||
                  key == "initial_units" || key == "reserve_units" || key == "max_units" || key == "quantity_traded" || key == "initial_quantity") && $.isArray(value)) {
                  if ($.isArray(value)) {
                      value = NRS.formatQuantity(value[0], value[1]);
                  } else {
                      value = NRS.formatQuantity(value, 0);
                  }
              } else if (key == "price" || key == "total" || key == "amount" || key == "fee" || key == "refund" || key == "discount") {
                  value = NRS.formatAmount(new BigInteger(String(value))) + " " + NRS.constants.COIN_SYMBOL;
              } else if (key == "sender" || key == "recipient" || key == "account" || key == "seller" || key == "buyer" || key == "lessee") {
                  value = "<a href='#' data-user='" + NRS.escapeRespStr(value) + "' class='show_account_modal_action'>" + NRS.getAccountTitle(value) + "</a>";
              } else if (key == "request_processing_time") { /* Skip from displaying request processing time */
                  continue;
              } else {
                  value = NRS.escapeRespStr(value).nl2br();
              }
  
              rows += "<tr><td style='font-weight:bold" + (fixed ? ";width:150px" : "") + "'>" + $.t(key).escapeHTML() + (type ? " " + type.escapeHTML() : "") + ":</td><td style='width:90%;word-break:break-all'>" + value + "</td></tr>";
          }
  
          return rows;
      };
  
      NRS.getSelectedText = function () {
  		var t = "";
  		if (window.getSelection) {
  			t = window.getSelection().toString();
  		} else if (document.getSelection) {
  			t = document.getSelection().toString();
  		} else if (document.selection) {
  			t = document.selection.createRange().text;
  		}
  		return t;
  	};
  
      NRS.formatStyledAmount = function (strAmount, round) {
          var locale = NRS.getLocale();
          var amount = NRS.formatAmount(strAmount, round).split(locale.decimal);
  		if (amount.length == 2) {
              return amount[0] + "<span style='font-size:12px'>" + locale.decimal + amount[1] + "</span>";
  		} else {
              return amount[0];
  		}
  	};
  
      NRS.getUnconfirmedTransactionsFromCache = function (type, subtype, fields, single) {
  		if (!NRS.unconfirmedTransactions.length) {
  			return false;
  		}
  
  		if (typeof type == "number") {
  			type = [type];
  		}
  
  		if (typeof subtype == "number") {
  			subtype = [subtype];
  		}
  
  		var unconfirmedTransactions = [];
  
  		for (var i = 0; i < NRS.unconfirmedTransactions.length; i++) {
  			var unconfirmedTransaction = NRS.unconfirmedTransactions[i];
  
  			if (type.indexOf(unconfirmedTransaction.type) == -1 || (subtype.length > 0 && subtype.indexOf(unconfirmedTransaction.subtype) == -1)) {
  				continue;
  			}
  
  			if (fields) {
  				for (var key in fields) {
                      if (!fields.hasOwnProperty(key)) {
                          continue;
                      }
  					if (unconfirmedTransaction[key] == fields[key]) {
  						if (single) {
  							return NRS.completeUnconfirmedTransactionDetails(unconfirmedTransaction);
  						} else {
  							unconfirmedTransactions.push(unconfirmedTransaction);
  						}
  					}
  				}
  			} else {
  				if (single) {
  					return NRS.completeUnconfirmedTransactionDetails(unconfirmedTransaction);
  				} else {
  					unconfirmedTransactions.push(unconfirmedTransaction);
  				}
  			}
  		}
  
  		if (single || unconfirmedTransactions.length == 0) {
  			return false;
  		} else {
              $.each(unconfirmedTransactions, function (key, val) {
  				unconfirmedTransactions[key] = NRS.completeUnconfirmedTransactionDetails(val);
  			});
  
  			return unconfirmedTransactions;
  		}
  	};
  
      NRS.completeUnconfirmedTransactionDetails = function (unconfirmedTransaction) {
  		if (unconfirmedTransaction.type == 3 && unconfirmedTransaction.subtype == 4 && !unconfirmedTransaction.name) {
  			NRS.sendRequest("getDGSGood", {
  				"goods": unconfirmedTransaction.attachment.goods
              }, function (response) {
  				unconfirmedTransaction.name = response.name;
  				unconfirmedTransaction.buyer = unconfirmedTransaction.sender;
  				unconfirmedTransaction.buyerRS = unconfirmedTransaction.senderRS;
  				unconfirmedTransaction.seller = response.seller;
  				unconfirmedTransaction.sellerRS = response.sellerRS;
  			}, { isAsync: false });
  		} else if (unconfirmedTransaction.type == 3 && unconfirmedTransaction.subtype == 0) {
  			unconfirmedTransaction.goods = unconfirmedTransaction.transaction;
  		}
  
  		return unconfirmedTransaction;
  	};
  
      NRS.hasTransactionUpdates = function (transactions) {
  		return ((transactions && transactions.length) || NRS.unconfirmedTransactionsChange);
  	};
  
      NRS.showMore = function ($el) {
  		if (!$el) {
  			$el = $("#" + NRS.currentPage + "_contents");
  			if (!$el.length) {
  				$el = $("#" + NRS.currentPage + "_table");
  			}
  		}
  		var adjustheight = 40;
  		var moreText = "Show more...";
  		var lessText = "Show less...";
  
          $el.find(".showmore > .moreblock").each(function () {
  			if ($(this).height() > adjustheight) {
  				$(this).css("height", adjustheight).css("overflow", "hidden");
  				$(this).parent(".showmore").append(' <a href="#" class="adjust"></a>');
                  $(this).parent(".showmore").find("a.adjust").text(moreText).click(function (e) {
  					e.preventDefault();
  
  					if ($(this).text() == moreText) {
  						$(this).parents("div:first").find(".moreblock").css('height', 'auto').css('overflow', 'visible');
  						$(this).parents("div:first").find("p.continued").css('display', 'none');
  						$(this).text(lessText);
  					} else {
  						$(this).parents("div:first").find(".moreblock").css('height', adjustheight).css('overflow', 'hidden');
  						$(this).parents("div:first").find("p.continued").css('display', 'block');
  						$(this).text(moreText);
  					}
  				});
  			}
  		});
  	};
  
      NRS.showFullDescription = function ($el) {
  		$el.addClass("open").removeClass("closed");
  		$el.find(".description_toggle").text("Less...");
  	};
  
      NRS.showPartialDescription = function ($el) {
  		if ($el.hasClass("open") || $el.height() > 40) {
  			$el.addClass("closed").removeClass("open");
  			$el.find(".description_toggle").text("More...");
  		} else {
  			$el.find(".description_toggle").text("");
  		}
  	};
  
      $("body").on(".description_toggle", "click", function (e) {
  		e.preventDefault();
  
  		if ($(this).closest(".description").hasClass("open")) {
  			NRS.showPartialDescription();
  		} else {
  			NRS.showFullDescription();
  		}
  	});
  
      $("#offcanvas_toggle").on("click", function (e) {
  		e.preventDefault();
  
  		//If window is small enough, enable sidebar push menu
          var leftSide = $(".left-side");
          var rightSide = $(".right-side");
  		if ($(window).width() <= 992) {
              var rowOffCanvas = $('.row-offcanvas');
              rowOffCanvas.toggleClass('active');
              leftSide.removeClass("collapse-left");
              rightSide.removeClass("strech");
              rowOffCanvas.toggleClass("relative");
  		} else {
  			//Else, enable content streching
              leftSide.toggleClass("collapse-left");
              rightSide.toggleClass("strech");
  		}
  
          leftSide.one($.support.transition.end,
              function () {
  			$(".content.content-stretch:visible").width($(".page:visible").width());
  		});
  	});
  
      $.fn.tree = function () {
          return this.each(function () {
  			var btn = $(this).children("a").first();
  			var menu = $(this).children(".treeview-menu").first();
  			var isActive = $(this).hasClass('active');
  
  			//initialize already active menus
  			if (isActive) {
  				menu.show();
  				btn.children(".fa-angle-right").first().removeClass("fa-angle-right").addClass("fa-angle-down");
  			}
  			//Slide open or close the menu on link click
              btn.click(function (e) {
  				e.preventDefault();
  				if (isActive) {
  					//Slide up to close menu
  					menu.slideUp();
  					isActive = false;
  					btn.children(".fa-angle-down").first().removeClass("fa-angle-down").addClass("fa-angle-right");
  					btn.parent("li").removeClass("active");
  				} else {
  					//Slide down to open menu
  					menu.slideDown();
  					isActive = true;
  					btn.children(".fa-angle-right").first().removeClass("fa-angle-right").addClass("fa-angle-down");
  					btn.parent("li").addClass("active");
  				}
  			});
  		});
  	};
  
      NRS.translateServerError = function (response) {
          var match;
  		if (!response.errorDescription) {
  			if (response.errorMessage) {
  				response.errorDescription = response.errorMessage;
  			} else if (response.error) {
  				if (typeof response.error == "string") {
  					response.errorDescription = response.error;
  					response.errorCode = -1;
  				} else {
  					return $.t("error_unknown");
  				}
  			} else {
  				return $.t("error_unknown");
  			}
  		}
  
  		switch (response.errorCode) {
  			case -1:
  				switch (response.errorDescription) {
  					case "Invalid ordinary payment":
  						return $.t("error_invalid_ordinary_payment");
  						break;
  					case "Missing alias name":
  						return $.t("error_missing_alias_name");
  						break;
  					case "Transferring aliases to Genesis account not allowed":
  						return $.t("error_alias_transfer_genesis");
  						break;
  					case "Ask order already filled":
  						return $.t("error_ask_order_filled");
  						break;
  					case "Bid order already filled":
  						return $.t("error_bid_order_filled");
  						break;
  					case "Only text encrypted messages allowed":
  						return $.t("error_encrypted_text_messages_only");
  						break;
  					case "Missing feedback message":
  						return $.t("error_missing_feedback_message");
  						break;
  					case "Only text public messages allowed":
  						return $.t("error_public_text_messages_only");
  						break;
  					case "Purchase does not exist yet or not yet delivered":
  						return $.t("error_purchase_delivery");
  						break;
  					case "Purchase does not exist or is not delivered or is already refunded":
  						return $.t("error_purchase_refund");
  						break;
  					case "Recipient account does not have a public key, must attach a public key announcement":
  						return $.t("error_recipient_no_public_key_announcement");
  						break;
  					case "Transaction is not signed yet":
  						return $.t("error_transaction_not_signed");
  						break;
  					case "Transaction already signed":
  						return $.t("error_transaction_already_signed");
  						break;
  					case "PublicKeyAnnouncement cannot be attached to transactions with no recipient":
  						return $.t("error_public_key_announcement_no_recipient");
  						break;
  					case "Announced public key does not match recipient accountId":
  						return $.t("error_public_key_different_account_id");
  						break;
  					case "Public key for this account has already been announced":
  						return $.t("error_public_key_already_announced");
  						break;
  					default:
  						if (response.errorDescription.indexOf("Alias already owned by another account") != -1) {
  							return $.t("error_alias_owned_by_other_account");
  						} else if (response.errorDescription.indexOf("Invalid alias sell price") != -1) {
  							return $.t("error_invalid_alias_sell_price");
  						} else if (response.errorDescription.indexOf("Alias hasn't been registered yet") != -1) {
  							return $.t("error_alias_not_yet_registered");
  						} else if (response.errorDescription.indexOf("Alias doesn't belong to sender") != -1) {
  							return $.t("error_alias_not_from_sender");
  						} else if (response.errorDescription.indexOf("Alias is owned by account other than recipient") != -1) {
  							return $.t("error_alias_not_from_recipient");
  						} else if (response.errorDescription.indexOf("Alias is not for sale") != -1) {
  							return $.t("error_alias_not_for_sale");
  						} else if (response.errorDescription.indexOf("Invalid alias name") != -1) {
  							return $.t("error_invalid_alias_name");
  						} else if (response.errorDescription.indexOf("Invalid URI length") != -1) {
  							return $.t("error_invalid_alias_uri_length");
  						} else if (response.errorDescription.indexOf("Invalid ask order") != -1) {
  							return $.t("error_invalid_ask_order");
  						} else if (response.errorDescription.indexOf("Invalid bid order") != -1) {
  							return $.t("error_invalid_bid_order");
  						} else if (response.errorDescription.indexOf("Goods price or quantity changed") != -1) {
  							return $.t("error_dgs_price_quantity_changed");
  						} else if (response.errorDescription.indexOf("Invalid digital goods price change") != -1) {
  							return $.t("error_invalid_dgs_price_change");
  						} else if (response.errorDescription.indexOf("Invalid digital goods refund") != -1) {
  							return $.t("error_invalid_dgs_refund");
  						} else if (response.errorDescription.indexOf("Purchase does not exist yet, or already delivered") != -1) {
  							return $.t("error_purchase_not_exist_or_delivered");
  						} else if (response.errorDescription.match(/Goods.*not yet listed or already delisted/)) {
  							return $.t("error_dgs_not_listed");
  						} else if (response.errorDescription.match(/Delivery deadline has already expired/)) {
  							return $.t("error_dgs_delivery_deadline_expired");
  						} else if (response.errorDescription.match(/Invalid effective balance leasing:.*recipient account.*not found or no public key published/)) {
  							return $.t("error_invalid_balance_leasing_no_public_key");
  						} else if (response.errorDescription.indexOf("Invalid effective balance leasing") != -1) {
  							return $.t("error_invalid_balance_leasing");
  						} else if (response.errorDescription.match(/Wrong buyer for.*expected:.*/)) {
  							return $.t("error_wrong_buyer_for_alias");
  						} else {
  							return response.errorDescription;
  						}
  
  						break;
  				}
  			case 1:
  				switch (response.errorDescription) {
  					case "This request is only accepted using POST!":
  						return $.t("error_post_only");
  						break;
  					case "Incorrect request":
  						return $.t("error_incorrect_request");
  						break;
  					default:
  						return response.errorDescription;
  						break;
  				}
  				break;
  			case 2:
  				return response.errorDescription;
  				break;
  			case 3:
                  match = response.errorDescription.match(/"([^"]+)" not specified/i);
  				if (match && match[1]) {
  					return $.t("error_not_specified", {
  						"name": NRS.getTranslatedFieldName(match[1]).toLowerCase()
  					}).capitalize();
  				}
  
                  match = response.errorDescription.match(/At least one of \[(.*)\] must be specified/i);
                  if (match && match[1]) {
                      var fieldNames = match[1].split(",");
                      var translatedFieldNames = [];
                      for (var i=0; i<fieldNames.length; i++) {
                          translatedFieldNames.push(NRS.getTranslatedFieldName(fieldNames[i].toLowerCase()));
                      }
  
                      var translatedFieldNamesJoined = translatedFieldNames.join(", ");
  
                      return $.t("error_not_specified", {
                          "names": translatedFieldNamesJoined,
                          "count": translatedFieldNames.length
                      }).capitalize();
                  } else {
                      return response.errorDescription;
                  }
                  break;
              case 4:
                  match = response.errorDescription.match(/Incorrect "(.*)"(.*)/i);
  				if (match && match[1] && match[2]) {
                      return $.t("error_incorrect_name", {
  						"name": NRS.getTranslatedFieldName(match[1]).toLowerCase(),
                          "reason": match[2]
  					}).capitalize();
  				} else {
  					return response.errorDescription;
  				}
  				break;
  			case 5:
                  match = response.errorDescription.match(/Unknown (.*)/i);
  				if (match && match[1]) {
  					return $.t("error_unknown_name", {
  						"name": NRS.getTranslatedFieldName(match[1]).toLowerCase()
  					}).capitalize();
  				}
  
  				if (response.errorDescription == "Account is not forging") {
  					return $.t("error_not_forging");
  				} else {
  					return response.errorDescription;
  				}
  				break;
  			case 6:
  				switch (response.errorDescription) {
  					case "Not enough assets":
  						return $.t("error_not_enough_assets");
  						break;
  					case "Not enough funds":
  						return $.t("error_not_enough_funds");
  						break;
  					default:
  						return response.errorDescription;
  						break;
  				}
  				break;
  			case 7:
  				if (response.errorDescription == "Not allowed") {
  					return $.t("error_not_allowed");
  				} else {
  					return response.errorDescription;
  				}
  				break;
  			case 8:
  				switch (response.errorDescription) {
  					case "Goods have not been delivered yet":
  						return $.t("error_goods_not_delivered_yet");
  						break;
  					case "Feedback already sent":
  						return $.t("error_feedback_already_sent");
  						break;
  					case "Refund already sent":
  						return $.t("error_refund_already_sent");
  						break;
  					case "Purchase already delivered":
  						return $.t("error_purchase_already_delivered");
  						break;
  					case "Decryption failed":
  						return $.t("error_decryption_failed");
  						break;
  					case "No attached message found":
  						return $.t("error_no_attached_message");
  					case "recipient account does not have public key":
  						return $.t("error_recipient_no_public_key", { "symbol": NRS.constants.COIN_SYMBOL });
  					default:
  						return response.errorDescription;
  						break;
  				}
  				break;
  			case 9:
  				if (response.errorDescription == "Feature not available") {
  					return $.t("error_feature_not_available");
  				} else {
  					return response.errorDescription;
  				}
  				break;
  			default:
  				return response.errorDescription;
  				break;
  		}
  	};
  
      NRS.getTranslatedFieldName = function (name) {
          var nameKey = String(name).replace(/NQT|QNT|RS$/, "").replace(/\s+/g, "").replace(/([A-Z])/g, function ($1) {
  			return "_" + $1.toLowerCase();
  		});
  
  		if (nameKey.charAt(0) == "_") {
  			nameKey = nameKey.substring(1);
  		}
  
  		if ($.i18n && $.i18n.exists(nameKey)) {
  			return $.t(nameKey).escapeHTML();
  		} else {
  			return nameKey.replace(/_/g, " ").escapeHTML();
  		}
  	};
  
      NRS.isControlKey = function (charCode) {
          return !(charCode >= 32 || charCode == 10 || charCode == 13);
      };
  
      NRS.validateDecimals = function (maxFractionLength, charCode, val, e) {
          if (maxFractionLength) {
              //allow 1 single period character
              if (charCode == 110 || charCode == 190) {
                  if (val.indexOf(".") != -1) {
                      e.preventDefault();
                      return false;
                  } else {
                      return true;
                  }
              }
          } else {
              //do not allow period
              if (charCode == 110 || charCode == 190 || charCode == 188) {
                  $.growl($.t("error_fractions"), {
                      "type": "danger"
                  });
                  e.preventDefault();
                  return false;
              }
          }
          if (charCode >= 96 && charCode <= 105) {
              // convert numeric keyboard code to normal ascii otherwise String.fromCharCode()
              // returns the wrong value
              charCode = charCode + 48 - 96;
          }
          var input = val + String.fromCharCode(charCode);
  
          var mantissa = input.match(/\.(\d*)$/);
  
          //only allow as many as there are decimals allowed..
          if (mantissa && mantissa[1].length > maxFractionLength) {
              var selectedText = NRS.getSelectedText();
  
              if (selectedText != val) {
                  var errorMessage = $.t("error_decimals", {
                      "count": maxFractionLength
                  });
                  $.growl(errorMessage, {
                      "type": "danger"
                  });
  
                  e.preventDefault();
                  return false;
              }
          }
  
          //numeric characters, left/right key, backspace, delete
          if (charCode == 8 || charCode == 37 || charCode == 39 || charCode == 46 || (charCode >= 48 && charCode <= 57 && !isNaN(String.fromCharCode(charCode)))) {
              return true;
          } else {
              //comma
              if (charCode == 188) {
                  $.growl($.t("error_comma_not_allowed"), {
                      "type": "danger"
                  });
              }
              e.preventDefault();
              return false;
          }
      };
  
  	// http://stackoverflow.com/questions/12518830/java-string-getbytesutf8-javascript-analog
      NRS.getUtf8Bytes = function (str) {
          //noinspection JSDeprecatedSymbols
          var utf8 = unescape(encodeURIComponent(str));
          var arr = [];
          for (var i = 0; i < utf8.length; i++) {
              arr[i] = utf8.charCodeAt(i);
          }
          return arr;
      };
  
      NRS.getTransactionStatusIcon = function (phasedEntity) {
          var statusIcon;
          if (phasedEntity.expectedCancellation == true) {
              statusIcon = "<i class='fa fa-ban' title='" + $.t("cancelled") + "'></i>";
          } else if (phasedEntity.phased == true) {
              statusIcon = "<i class='fa fa-gavel' title='" + $.t("phased") + "'></i>";
          } else if (phasedEntity.phased == false) {
              statusIcon = "<i class='fa fa-circle-o' title='" + $.t("unconfirmed") + "'></i>";
          } else {
              statusIcon = "<i class='fa fa-circle' title='" + $.t("confirmed") + "'></i>";
          }
          return statusIcon;
      };
  
      NRS.getAccountForDecryption = function(transaction, recipient, sender) {
          if (!recipient && transaction.recipient == NRS.account) {
              return transaction.sender;
          }
          if (transaction[recipient] == NRS.account) {
              return transaction.sender || transaction[sender];
          }
          if (!sender && transaction.sender == NRS.account) {
              return transaction.recipient;
          }
          if (transaction[sender] == NRS.account) {
              return transaction.recipient || transaction[recipient];
          }
          return null;
      };
  
      NRS.phasingControlObjectToPhasingParams = function(controlObj) {
          var phasingParams = {};
  
          phasingParams.phasingVotingModel = controlObj.votingModel;
          phasingParams.phasingQuorum = controlObj.quorum;
          phasingParams.phasingMinBalance = controlObj.minBalance;
          phasingParams.phasingMinBalanceModel = controlObj.minBalanceModel;
          if (controlObj.holding) {
              phasingParams.phasingHolding = controlObj.holding;
          }
          if (controlObj.whitelist) {
              phasingParams.phasingWhitelisted = [];
              $.each(controlObj.whitelist, function(index, accObject) {
                  phasingParams.phasingWhitelisted.push(accObject.whitelisted);
              });
          }
          return phasingParams;
      };
  
      // http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
      NRS.strToUTF8Arr = function(str) {
          var utf8 = [];
          for (var i = 0; i < str.length; i++) {
              var charcode = str.charCodeAt(i);
              if (charcode < 0x80) utf8.push(charcode);
              else if (charcode < 0x800) {
                  utf8.push(0xc0 | (charcode >> 6),
                            0x80 | (charcode & 0x3f));
              }
              else if (charcode < 0xd800 || charcode >= 0xe000) {
                  utf8.push(0xe0 | (charcode >> 12),
                            0x80 | ((charcode >> 6) & 0x3f),
                            0x80 | (charcode & 0x3f));
              }
              // surrogate pair
              else {
                  i++;
                  // UTF-16 encodes 0x10000-0x10FFFF by
                  // subtracting 0x10000 and splitting the
                  // 20 bits of 0x0-0xFFFFF into two halves
                  charcode = 0x10000 + (((charcode & 0x3ff) << 10)
                            | (str.charCodeAt(i) & 0x3ff));
                  utf8.push(0xf0 | (charcode >> 18),
                            0x80 | ((charcode >> 12) & 0x3f),
                            0x80 | ((charcode >> 6) & 0x3f),
                            0x80 | (charcode & 0x3f));
              }
          }
          return utf8;
      };
  
      function byteArrayToBigInteger(byteArray) {
          var value = new BigInteger("0", 10);
          for (var i = byteArray.length - 1; i >= 0; i--) {
              value = value.multiply(new BigInteger("256", 10)).add(new BigInteger(byteArray[i].toString(10), 10));
          }
          return value;
      }
  
      NRS.initialCaps = function(str) {
          if (!str || str == "") {
              return str;
          }
          var firstChar = str.charAt(0).toUpperCase();
          if (str.length == 1) {
              return firstChar;
          }
          return firstChar + str.slice(1);
      };
  
      NRS.addEllipsis = function(str, length) {
          if (!str || str == "" || str.length <= length) {
              return str;
          }
          return str.substring(0, length) + "...";
      };
  
      NRS.generateToken = function(message, secretPhrase) {
          var messageBytes = NRS.getUtf8Bytes(message);
          var pubKeyBytes = converters.hexStringToByteArray(NRS.getPublicKey(converters.stringToHexString(secretPhrase)));
          var token = pubKeyBytes;
  
          var tsb = [];
          var ts = NRS.toEpochTime();
          tsb[0] = ts & 0xFF;
          tsb[1] = (ts >> 8) & 0xFF;
          tsb[2] = (ts >> 16) & 0xFF;
          tsb[3] = (ts >> 24) & 0xFF;
  
          messageBytes = messageBytes.concat(pubKeyBytes, tsb);
          token = token.concat(tsb, converters.hexStringToByteArray(
              NRS.signBytes(converters.byteArrayToHexString(messageBytes),
                  secretPhrase !== undefined ? converters.stringToHexString(secretPhrase) : undefined)));
  
          var buf = "";
          for (var ptr = 0; ptr < 100; ptr += 5) {
              var nbr = [];
              nbr[0] = token[ptr] & 0xFF;
              nbr[1] = token[ptr+1] & 0xFF;
              nbr[2] = token[ptr+2] & 0xFF;
              nbr[3] = token[ptr+3] & 0xFF;
              nbr[4] = token[ptr+4] & 0xFF;
              var number = byteArrayToBigInteger(nbr);
              if (number < 32) {
                  buf += "0000000";
              } else if (number < 1024) {
                  buf += "000000";
              } else if (number < 32768) {
                  buf += "00000";
              } else if (number < 1048576) {
                  buf += "0000";
              } else if (number < 33554432) {
                  buf += "000";
              } else if (number < 1073741824) {
                  buf += "00";
              } else if (number < 34359738368) {
                  buf += "0";
              }
              buf +=number.toString(32);
          }
          return buf;
      };
  
      NRS.versionCompare = function (v1, v2) {
          if (v2 == undefined) {
              return -1;
          } else if (v1 == undefined) {
              return -1;
          }
  
          //https://gist.github.com/TheDistantSea/8021359 (based on)
          var v1last = v1.slice(-1);
          var v2last = v2.slice(-1);
  
          if (v1last == 'e') {
              v1 = v1.substring(0, v1.length - 1);
          } else {
              v1last = '';
          }
  
          if (v2last == 'e') {
              v2 = v2.substring(0, v2.length - 1);
          } else {
              v2last = '';
          }
  
          var v1parts = v1.split('.');
          var v2parts = v2.split('.');
  
          function isValidPart(x) {
              return /^\d+$/.test(x);
          }
  
          if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
              return NaN;
          }
  
          v1parts = v1parts.map(Number);
          v2parts = v2parts.map(Number);
  
          for (var i = 0; i < v1parts.length; ++i) {
              if (v2parts.length == i) {
                  return 1;
              }
              if (v1parts[i] != v2parts[i]) {
                  if (v1parts[i] > v2parts[i]) {
                      return 1;
                  } else {
                      return -1;
                  }
              }
          }
  
          if (v1parts.length != v2parts.length) {
              return -1;
          }
  
          if (v1last && v2last) {
              return 0;
          } else if (v1last) {
              return 1;
          } else if (v2last) {
              return -1;
          } else {
              return 0;
          }
      };
  
      /**
       * Escapes all strings in a response object
       * @param obj
       */
      NRS.escapeResponseObjStrings = function (obj) {
          for (var key in obj) {
              if (obj.hasOwnProperty(key)) {
                  var val = obj[key];
                  if (typeof val === 'string') {
                      obj[key] = String(val).escapeHTML();
                  } else if (typeof val === 'object') {
                      NRS.escapeResponseObjStrings(obj[key]);
                  }
              }
          }
      };
  
      /**
       * Escapes a string that was returned in response from the server.
       * This is used to avoid the double escaping of strings since the response strings started to be escaped in a global
       * level because of the proxy feature
       * @param val
       */
      NRS.escapeRespStr = function (val) {
          return String(val).unescapeHTML().escapeHTML();
      };
  
      NRS.unescapeRespStr = function (val) {
          return String(val).unescapeHTML();
      };
  
      NRS.getRandomPermutation = function(array) {
          var currentIndex = array.length, temporaryValue, randomIndex;
  
          // While there remain elements to shuffle...
          while (0 !== currentIndex) {
              // Pick a remaining element...
              randomIndex = Math.floor(Math.random() * currentIndex);
              currentIndex -= 1;
  
              // And swap it with the current element.
              temporaryValue = array[currentIndex];
              array[currentIndex] = array[randomIndex];
              array[randomIndex] = temporaryValue;
          }
          return array;
      };
  
      NRS.getErrorMessage = function(response) {
          return response.errorDescription || response.errorMessage || response.error;
      };
  
      NRS.getMandatoryParams = function() {
          return {
              feeNQT: "0",
              deadline: "1440"
          }
      };
  
      NRS.isRsAccount = function(account) {
          return NRS.isRsAccountImpl(account, NRS.constants.ACCOUNT_RS_MATCH ? NRS.constants.ACCOUNT_RS_MATCH : NRS.getRsAccountRegex("NXT"));
      };
  
      NRS.isRsAccountImpl = function(account, regex) {
          return regex.test(account);
      };
  
      NRS.isNumericAccount = function(account) {
          return NRS.isNumericAccountImpl(account, NRS.constants.ACCOUNT_NUMERIC_MATCH);
      };
  
      NRS.isNumericAccountImpl = function(account, regex) {
          return regex.test(account);
      };
  
      NRS.getAccountMask = function(c) {
          switch(c) {
              case "*":
                  return NRS.constants.ACCOUNT_MASK_ASTERIX;
              case "_":
                  return NRS.constants.ACCOUNT_MASK_UNDERSCORE;
              default:
                  return NRS.constants.ACCOUNT_MASK_PREFIX ? NRS.constants.ACCOUNT_MASK_PREFIX : "NXT-";
          }
      };
  
      NRS.printPaperWallet = function(passphrase) {
          var $pageHeader = $("<h2 data-i18n='nxt_ardor_paper_wallet'>NXT and Ardor Paper Wallet</h2>");
          var $passphraseHeader = $("<h3 data-i18n='passphrase'>Passphrase</h3>");
          var $passphraseText = $("<div></div>");
          $passphraseText.text(passphrase);
          var passphraseImg = NRS.generateQRCode(null, passphrase, 2, 4);
          $(passphraseImg).load(function() {
              var account = NRS.getAccountId(passphrase, true);
              var $accountHeader = $("<h3 data-i18n='account'>Account</h3>");
              var $accountText = $("<div></div>");
              $accountText.html(account);
              var accountImg = NRS.generateQRCode(null, account, 2, 4);
              $(accountImg).load(function() {
                  $("<iframe>", { name: "paperWalletFrame", class: "printFrame"}).appendTo("body").contents().find("body")
                      .append($pageHeader).append($passphraseHeader).append($passphraseText).append(passphraseImg).append($accountHeader).append($accountText).append(accountImg);
                  window.frames['paperWalletFrame'].focus();
                  window.frames['paperWalletFrame'].print();
                  setTimeout(function () {
                      $(".printFrame").remove();
                  }, 1000);
              });
          });
      };
  
      return NRS;
  }(isNode ? client : NRS || {}, jQuery));
  
  if (isNode) {
      module.exports = NRS;
  }
  