Source: CypherPokerAccount.js

/**
* @file Contains information and functionality associated with a single managed account.
*
* @version 0.4.1
* @author Patrick Bay
* @copyright MIT License
*/

/**
* @class Information and functionality associated with a single managed account.
* The account is independently managed by either a TTP service or a smart contract
* and represents a deposit by the player that can be used to provide a balance
* in games.
*/
class CypherPokerAccount extends EventDispatcher {

   /**
   * Creates a new player account instance.
   *
   * @param {CypherPoker} cypherpokerRef A reference to a {@link CypherPoker}
   * instance through which managed account requests can be made.
   * @param {Object} [initData=null] Data with which to initialize the new instance.
   */
   constructor(cypherpokerRef, initData=null) {
      super();
      this._cypherpoker = cypherpokerRef;
      if (initData != null) {
         for (var item in initData) {
            try {
               this[item] = initData[item];
            } catch (err) {
            }
         }
      }
   }

   /**
   * @property {CypherPoker} cypherpoker A reference to the active {@link CypherPoker}
   * instance through which API functions can be invoked.
   *
   * @readonly
   */
   get cypherpoker() {
      return (this._cypherpoker);
   }

   /**
   * @property {String} address The managed cryptocurrency deposit address
   * associated with the account. Typically the address is also the unique account
   * identifier ({@link CypherPokerAccount#id}), but may differ.
   */
   set address(addrSet) {
      this._address = addrSet;
   }

   get address() {
      if (this._address == undefined) {
         this._address = null;
      }
      return (this._address);
   }

   /**
   * @property {String} type The type of cryptocurrency associated with the
   * {@link CypherPokerAccount#address}. Valid types include: "bitcoin"
   */
   set type(typeSet) {
      this._type = typeSet;
   }

   get type() {
      if (this._type == undefined) {
         this._type = null;
      }
      return (this._type);
   }

   /**
   * @property {String} network The network sub-type, if applicable.
   * For example, if {@link CypherPokerAccount#type} is "bitcoin",
   * the <code>network</code> may be "main" or "test3".
   */
   set network(networkSet) {
      this._network = networkSet;
   }

   get network() {
      if (this._network == undefined) {
         this._network = null;
      }
      return (this._network);
   }

   /**
   * @property {Array} domains Each element in this indexed array is a domain,
   * URL, API, or other service identifier with which this account is associated.
   * Using this account with other domains / services will most likely result in
   * an "account not found" error.
   */
   set domains(domainsSet) {
      this._domains = domainsSet;
   }

   get domains() {
      if (this._domains == undefined) {
         this._domains = new Array();
      }
      return (this._domains);
   }

   /**
   * @property {BigInteger} balance="0" The current total account balance. Note that
   * this is different than the {@link CypherPokerPlayer#balance} property
   * which reflects the player balance for a single game (e.g. buy-in).
   */
   set balance(balanceSet) {
      this._balance = bigInt(balanceSet);
   }

   get balance() {
      if (this._balance == undefined) {
         this._balance = bigInt(0);
      }
      return (this._balance);
   }

   /**
   * @property {Object} fees Any fees (e.g. miner) associated with the account,
   * usually--but not always--returned with the most recent the API call.
   * Some fees may be mandatory while others are simply suggested/default.
   */
   get fees() {
      if (this._fees == undefined) {
         this._fees = new Object();
      }
      return (this._fees);
   }

   /**
   * @property {Boolean} cashoutPending=false True if a cashout operation is
   * currently pending (incomplete), false otherwise.
   */
   get cashoutPending() {
      if (this._cashoutPending == undefined) {
         this._cashoutPending = false;
      }
      return (this._cashoutPending);
   }

   /**
   * @property {String} password=null The password associated with the account.
   */
   set password(pwSet) {
      this._password = pwSet;
   }

   get password() {
      if (this._password == undefined) {
         this._password = null;
      }
      return (this._password);
   }

   /**
   * Returns a condensed data object with copies of most of the properties of this
   * instance. Use the returned data from this function rathen than using
   * <code>JSON.stringify</code> on the instance as this may cause
   * a circular reference error.
   *
   * @param {Boolean} [includePassword=false] If true, the account password
   * is included in the returned data.
   *
   * @return {Object} A condensed data object containing copies of the
   * most properties of this instance.
   */
   toObject(includePassword=false) {
      var returnObj = new Object();
      returnObj.address = this.address;
      returnObj.type = this.type;
      returnObj.network = this.network;
      returnObj.balance = this.balance.toString(10);
      returnObj.domains = this.domains;
      if (includePassword) {
         returnObj.password = this.password;
      }
      return (returnObj);
   }

   /**
   * Creates a new account by calling the <code>CP_Account/new</code> RPC service using
   * the properties of this instance.
   *
   * @return {Promise} The promise resolves with a <code>true</code> value
   * if the account was succcessfully created, <code>false</code> otherwise.
   * @async
   */
   async create() {
      if ((this.password == null) || (this.password == null)) {
         return (false);
      }
      var params = new Object();
      params.password = this.password;
      params.type = this.type;
      params.network = this.network;
      var JSONObj = await this.callAccountAPI("new", params);
      if (JSONObj.error != undefined) {
         console.error (JSONObj.error);
         return (false);
      }
      this._fees = JSONObj.result.fees;
      this._address = JSONObj.result.address;
      return (true);
   }

   /**
   * Updates this account's properties by calling the <code>CP_Account/info</code> RPC service.
   *
   * @return {Promise} The promise resolves with a <code>true</code> value
   * if the account was succcessfully updated. An <code>Error</code> object
   * is included with a rejection.
   * @async
   */
   async update() {
      if ((this.password == null) || (this.password == null)) {
         return (false);
      }
      var params = new Object();
      params.address = this.address;
      params.password = this.password;
      params.type = this.type;
      params.network = this.network;
      var JSONObj = await this.callAccountAPI("info", params);
      if (JSONObj.error != undefined) {
         this.balance = 0;
         throw(new Error(JSONObj.error.message));
      }
      this._fees = JSONObj.result.fees;
      //balance confirmed = JSONObj.result.confirmed
      this.balance = JSONObj.result.balance;
      return (true);
   }

   /**
   * Partially or fully cashes out the account, sending the funds to a
   * specified address.
   *
   * @param {String|Number|BigInteger} amount The full amount to send to
   * <code>toAddress</code>, including miner <code>fees</code>. This value is in the
   * smallest denominition of the associated cryptocurrency (e.g. satoshis if
   * <code>type="bitcoin</code>").
   * @param {String} toAddress The target or receiving address. This address
   * must be of the same cryptocurrency <code>type</code> and on the same
   * <code>network</code> as this account.
   * @param {String|Number|BigInteger} [fees=null] The miner fee to include
   * in this transaction in the smallest denomination of the associated cryptocurrency.
   * If <code>null</code>, the default miner fee is used. The amount that
   * will be received by <code>toAddress</code> will be the sending <code>amount</code>
   * minus this value.
   *
   * @return {Promise} The promise resolves with a an object containing information
   * about the transaction or rejects with a standard <code>Error</code> object.
   * @async
   */
   async cashout(amount, toAddress, fees=null) {
      if ((this.password == "") || (this.password == null)) {
         throw(new Error("Account password not set."));
      }
      if (toAddress == this.address) {
         throw(new Error("Sending and receiving addresses can't be the same."));
      }
      if (this.cashoutPending) {
         throw(new Error("A cashout request is currently pending. Only one request can be active at a time."));
      }
      this._cashoutPending = true;
      var params = new Object();
      params.address = this.address;
      params.password = this.password;
      params.type = this.type;
      params.network = this.network;
      params.toAddress = toAddress;
      if (bigInt.isInstance(amount)) {
         params.amount = amount.toString(10);
      } else {
         params.amount = String(amount);
      }
      if (fees != null) {
         if (bigInt.isInstance(fees)) {
            params.feeAmount = fees.toString(10);
         } else {
            params.feeAmount = String(fees);
         }
      }
      var JSONObj = await this.callAccountAPI("cashout", params);
      this._cashoutPending = false;
      if (JSONObj.error != undefined) {
         throw (new Error(JSONObj.error.message));
      }
      this.balance = JSONObj.result.balance;
      return (JSONObj.result);
   }

   /**
   * Partially or fully transfers the account balance to another account.
   *
   * @param {String|Number|BigInteger} amount The full amount to transfer to
   * <code>toAccount</code>. This value is in the smallest denominition of the
   * associated cryptocurrency (e.g. satoshis if <code>type="bitcoin</code>").
   * @param {String} toAccount The target or receiving account. This account
   * must be of the same cryptocurrency <code>type</code> and on the same
   * <code>network</code> as this account.
   *
   * @return {Promise} The promise resolves with a <code>true</code> value
   * if the transfer was successfully completed. An <code>Error</code>
   * object is included with a rejection.
   * @async
   */
   async transfer(amount, toAccount) {
      if ((this.password == "") || (this.password == null)) {
         throw(new Error("Account password not set."));
      }
      var params = new Object();
      params.address = this.address;
      params.password = this.password;
      params.type = this.type;
      params.network = this.network;
      params.toAccount = toAccount;
      if (bigInt.isInstance(amount)) {
         params.amount = amount.toString(10);
      } else {
         params.amount = String(amount);
      }
      var JSONObj = await this.callAccountAPI("transfer", params);
      if (JSONObj.error != undefined) {
         throw (new Error(JSONObj.error.message));
      }
      this.balance = JSONObj.result.balance;
      return (true);
   }

   /**
   * Asynchronously calls the account API and returns the JSON-RPC 2.0 result / error
   * of the call.
   *
   * @param {String} action The account action to apply to the <code>APIFunc</code> call.
   * This value is automatically appended to the <code>params</code> object as an
   * <code>action</code> property and will override any existing <code>action</code> property.
   * @param {Object} [params=null] The parameters to include with the remote function call.
   * If <code>null</code>, an empty params object is created.
   * @param {String} [APIFunc="CP_Account"] The remote API function to invoke.
   *
   * @return {Promise} The promise resolves with the parsed JSON-RPC 2.0 result or
   * error (native object) of the call. Currently there is no rejection state.
   */
   async callAccountAPI(action, params=null, APIFunc="CP_Account") {
      if (params == null) {
         params = new Object();
      }
      var sendObj = new Object();
      for (var item in params) {
         sendObj[item] = params[item];
      }
      sendObj.action = action;
      sendObj.user_token = this.cypherpoker.api.userToken;
      sendObj.server_token = this.cypherpoker.api.serverToken;
      var requestID = "CP" + String(Math.random()).split(".")[1];
      var rpc_result = await RPC(APIFunc, sendObj, this.cypherpoker.api, false, requestID);
      var result = JSON.parse(rpc_result.data);
      //since messages over web sockets are asynchronous the next immediate message may not be ours so:
      while (requestID != result.id) {
         rpc_result = await this.cypherpoker.api.rawConnection.onEventPromise("message");
         result = JSON.parse(rpc_result.data);
         //we could include a max wait limit here
      }
      return (result);
   }

}