# Source: src/main/js/wallet/MoneroWallet.js

    const assert = require("assert");
    const MoneroBlock = require("../daemon/model/MoneroBlock");
    const BigInteger = require("../common/biginteger").BigInteger;
    const MoneroError = require("../common/MoneroError");
    const MoneroOutputQuery = require("./model/MoneroOutputQuery");
    const MoneroTransferQuery = require("./model/MoneroTransferQuery");
    const MoneroTxConfig = require("./model/MoneroTxConfig");
    const MoneroTxQuery = require("./model/MoneroTxQuery");
    const MoneroTxSet = require("./model/MoneroTxSet");

    /**
     * Copyright (c) woodser
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to deal
     * in the Software without restriction, including without limitation the rights
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     * copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice shall be included in all
     * copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     * SOFTWARE.
     */

    /**
     * Monero wallet interface and default implementations.
     *
     * @interface
     */
    class MoneroWallet {

      /**
       * Register a listener to receive wallet notifications.
       *
       * @param {MoneroWalletListener} listener - listener to receive wallet notifications
       */
      async addListener(listener) {
        throw new Error("Not supported");
      }

      /**
       * Unregister a listener to receive wallet notifications.
       *
       * @param {MoneroWalletListener} listener - listener to unregister
       */
      async removeListener(listener) {
        throw new Error("Not supported");
      }

      /**
       * Get the listeners registered with the wallet.
       *
       * @return {MoneroWalletListener[]} the registered listeners
       */
      getListeners() {
        throw new Error("Not supported");
      }

      /**
       * Indicates if the wallet is view-only, meaning it does not have the private
       * spend key and can therefore only observe incoming outputs.
       *
       * @return {bool} true if the wallet is view-only, false otherwise
       */
      async isViewOnly() {
        throw new MoneroError("Not supported");
      }

      /**
       * Set the wallet's daemon connection.
       *
       * @param {string|MoneroRpcConnection} uriOrConnection - daemon's URI or connection (defaults to offline)
       * @param {string} username - username to authenticate with the daemon (optional)
       * @param {string} password - password to authenticate with the daemon (optional)
       */
      async setDaemonConnection(uriOrConnection, username, password) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the wallet's daemon connection.
       *
       * @return {MoneroRpcConnection} the wallet's daemon connection
       */
      async getDaemonConnection() {
        throw new MoneroError("Not supported");
      }

      /**
       * Indicates if the wallet is connected to daemon.
       *
       * @return {boolean} true if the wallet is connected to a daemon, false otherwise
       */
      async isConnectedToDaemon() {
        throw new MoneroError("Not supported");
      }

      /**
       * Gets the version of the wallet.
       *
       * @return {MoneroVersion} the version of the wallet
       */
      async getVersion() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the wallet's path.
       *
       * @return {string} the path the wallet can be opened with
       */
      async getPath() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the wallet's mnemonic phrase derived from the seed.
       *
       * @return {string} the wallet's mnemonic phrase
       */
      async getMnemonic() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the language of the wallet's mnemonic phrase.
       *
       * @return {string} the language of the wallet's mnemonic phrase
       */
      async getMnemonicLanguage() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the wallet's private view key.
       *
       * @return {string} the wallet's private view key
       */
      async getPrivateViewKey() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the wallet's private spend key.
       *
       * @return {string} the wallet's private spend key
       */
      async getPrivateSpendKey() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the wallet's public view key.
       *
       * @return {string} the wallet's public view key
       */
      async getPublicViewKey() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the wallet's public spend key.
       *
       * @return {string} the wallet's public spend key
       */
      async getPublicSpendKey() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the wallet's primary address.
       *
       * @return {string} the wallet's primary address
       */
      async getPrimaryAddress() {
        return await this.getAddress(0, 0);
      }

      /**
       * Get the address of a specific subaddress.
       *
       * @param {int} accountIdx - the account index of the address's subaddress
       * @param {int} subaddressIdx - the subaddress index within the account
       * @return {string} the receive address of the specified subaddress
       */
      async getAddress(accountIdx, subaddressIdx) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the account and subaddress index of the given address.
       *
       * @param {string} address - address to get the account and subaddress index from
       * @return {MoneroSubaddress} the account and subaddress indices
       */
      async getAddressIndex(address) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get an integrated address based on the given standard address and payment
       * ID. Uses the wallet's primary address if an address is not given.
       * Generates a random payment ID if a payment ID is not given.
       *
       * @param {string} standardAddress is the standard address to generate the integrated address from (wallet's primary address if undefined)
       * @param {string} paymentId is the payment ID to generate an integrated address from (randomly generated if undefined)
       * @return {MoneroIntegratedAddress} the integrated address
       */
      async getIntegratedAddress(standardAddress, paymentId) {
        throw new MoneroError("Not supported");
      }

      /**
       * Decode an integrated address to get its standard address and payment id.
       *
       * @param {string} integratedAddress - integrated address to decode
       * @return {MoneroIntegratedAddress} the decoded integrated address including standard address and payment id
       */
      async decodeIntegratedAddress(integratedAddress) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the block height that the wallet is synced to.
       *
       * @return {int} the block height that the wallet is synced to
       */
      async getHeight() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the blockchain's height.
       *
       * @return {int} the blockchain's height
       */
      async getDaemonHeight() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the blockchain's height by date as a conservative estimate for scanning.
       *
       * @param {int} year - year of the height to get
       * @param {int} month - month of the height to get as a number between 1 and 12
       * @param {int} day - day of the height to get as a number between 1 and 31
       * @return the blockchain's approximate height at the given date
       */
      async getHeightByDate(year, month, day) {
        throw new MoneroError("Not supported");
      }

      /**
       * Synchronize the wallet with the daemon as a one-time synchronous process.
       *
       * @param {MoneroWalletListener|number} listenerOrStartHeight - listener xor start height (defaults to no sync listener, the last synced block)
       * @param {number} startHeight - startHeight if not given in first arg (defaults to last synced block)
       */
      async sync(listenerOrStartHeight, startHeight) {
        throw new MoneroError("Not supported");
      }

      /**
       * Start background synchronizing with a maximum period between syncs.
       *
       * @param {int} syncPeriodInMs - maximum period between syncs in milliseconds (default is wallet-specific)
       */
      async startSyncing(syncPeriodInMs) {
        throw new MoneroError("Not supported");
      }

      /**
       * Stop synchronizing the wallet with the daemon.
       */
      async stopSyncing() {
        throw new MoneroError("Not supported");
      }

      /**
       * Scan transactions by their hash/id.
       *
       * @param {string[]} txHashes - tx hashes to scan
       */
      async scanTxs(txHashes) {
        throw new MoneroError("Not supported");
      }

      /**
       * <p>Rescan the blockchain for spent outputs.</p>
       *
       * <p>Note: this can only be called with a trusted daemon.</p>
       *
       * <p>Example use case: peer multisig hex is import when connected to an untrusted daemon,
       * so the wallet will not rescan spent outputs.  Then the wallet connects to a trusted
       * daemon.  This method should be manually invoked to rescan outputs.</p>
       */
      async rescanSpent() {
        throw new MoneroError("Not supported");
      }

      /**
       * <p>Rescan the blockchain from scratch, losing any information which cannot be recovered from
       * the blockchain itself.</p>
       *
       * <p>WARNING: This method discards local wallet data like destination addresses, tx secret keys,
       * tx notes, etc.</p>
       */
      async rescanBlockchain() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the balance of the wallet, account, or subaddress.
       *
       * @param {int} accountIdx - index of the account to get the balance of (default all accounts)
       * @param {int} subaddressIdx - index of the subaddress to get the balance of (default all subaddresses)
       * @return {BigInteger} the balance of the wallet, account, or subaddress
       */
      async getBalance(accountIdx, subaddressIdx) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the unlocked balance of the wallet, account, or subaddress.
       *
       * @param {int} accountIdx - index of the account to get the unlocked balance of (optional)
       * @param {int} subaddressIdx - index of the subaddress to get the unlocked balance of (optional)
       * @return {BigInteger} the unlocked balance of the wallet, account, or subaddress
       */
      async getUnlockedBalance(accountIdx, subaddressIdx) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get the number of blocks until the next and last funds unlock.
       *
       * @return {int[]} the number of blocks until the next and last funds unlock in elements 0 and 1, respectively, or undefined if no balance
       */
      async getNumBlocksToUnlock() {

        // get balances
        let balance = await this.getBalance();
        if (balance.compare(new BigInteger(0)) === 0) return [undefined, undefined]; // skip if no balance
        let unlockedBalance = await this.getUnlockedBalance();

        // compute number of blocks until next funds available
        let txs;
        let height;
        let numBlocksToNextUnlock = undefined;
        if (unlockedBalance.compare(new BigInteger(0)) > 0) numBlocksToNextUnlock = 0;
        else {
          txs = await this.getTxs({isLocked: true}); // get locked txs
          height = await this.getHeight(); // get most recent height
          for (let tx of txs) {
            let numBlocksToUnlock = Math.max((tx.isConfirmed() ? tx.getHeight() : height) + 10, tx.getUnlockHeight()) - height;
            numBlocksToNextUnlock = numBlocksToNextUnlock === undefined ? numBlocksToUnlock : Math.min(numBlocksToNextUnlock, numBlocksToUnlock);
          }
        }

        // compute number of blocks until all funds available
        let numBlocksToLastUnlock = undefined;
        if (balance.compare(unlockedBalance) === 0) {
          if (unlockedBalance.compare(new BigInteger(0)) > 0) numBlocksToLastUnlock = 0;
        } else {
          if (!txs) {
            txs = await this.getTxs({isLocked: true}); // get locked txs
            height = await this.getHeight(); // get most recent height
          }
          for (let tx of txs) {
            let numBlocksToUnlock = Math.max((tx.isConfirmed() ? tx.getHeight() : height) + 10, tx.getUnlockHeight()) - height;
            numBlocksToLastUnlock = numBlocksToLastUnlock === undefined ? numBlocksToUnlock : Math.max(numBlocksToLastUnlock, numBlocksToUnlock);
          }
        }

        return [numBlocksToNextUnlock, numBlocksToLastUnlock];
      }

      /**
       * Get accounts with a given tag.
       *
       * @param {boolean} includeSubaddresses - include subaddresses if true
       * @param {string} tag - tag for filtering accounts, all accounts if undefined
       * @return {MoneroAccount[]} all accounts with the given tag
       */
      async getAccounts(includeSubaddresses, tag) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get an account.
       *
       * @param {int} accountIdx - index of the account to get
       * @param {boolean} includeSubaddresses - include subaddresses if true
       * @return {MoneroAccount} the retrieved account
       */
      async getAccount(accountIdx, includeSubaddresses) {
        throw new MoneroError("Not supported");
      }

      /**
       * Create a new account with a label for the first subaddress.
       *
       * @param {string} label - label for account's first subaddress (optional)
       * @return {MoneroAccount} the created account
       */
      async createAccount(label) {
        throw new MoneroError("Not supported");
      }

      /**
       * Set an account label.
       *
       * @param {number} accountIdx - index of the account to set the label for
       * @param {string} label - the label to set
       */
      async setAccountLabel(accountIdx, label) {
        await this.setSubaddressLabel(accountIdx, 0, label);
      }

      /**
       * Get subaddresses in an account.
       *
       * @param {int} accountIdx - account to get subaddresses within
       * @param {int[]} subaddressIndices - indices of subaddresses to get (optional)
       * @return {MoneroSubaddress[]} the retrieved subaddresses
       */
      async getSubaddresses(accountIdx, subaddressIndices) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get a subaddress.
       *
       * @param {int} accountIdx - index of the subaddress's account
       * @param {int} subaddressIdx - index of the subaddress within the account
       * @return {MoneroSubaddress} the retrieved subaddress
       */
      async getSubaddress(accountIdx, subaddressIdx) {
        assert(accountIdx >= 0);
        assert(subaddressIdx >= 0);
        return (await this.getSubaddresses(accountIdx, subaddressIdx))[0];
      }

      /**
       * Create a subaddress within an account.
       *
       * @param {int} accountIdx - index of the account to create the subaddress within
       * @param {string} label - the label for the subaddress (optional)
       * @return {MoneroSubaddress} the created subaddress
       */
      async createSubaddress(accountIdx, label) {
        throw new MoneroError("Not supported");
      }

      /**
       * Set a subaddress label.
       *
       * @param {number} accountIdx - index of the account to set the label for
       * @param {number} subaddressIdx - index of the subaddress to set the label for
       * @param {string} label - the label to set
       */
      async setSubaddressLabel(accountIdx, subaddressIdx, label) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get a wallet transaction by hash.
       *
       * @param {string} txHash - hash of a transaction to get
       * @return {MoneroTxWallet} the identified transactions
       * @throws {MoneroError} if the transaction is not found
       */
      async getTx(txHash) {
        let txs = await this.getTxs([txHash]);
        return txs.length === 0 ? undefined : txs[0];
      }

      /**
       * <p>Get wallet transactions.  Wallet transactions contain one or more
       * transfers that are either incoming or outgoing to the wallet.<p>
       *
       * <p>Results can be filtered by passing a query object.  Transactions must
       * meet every criteria defined in the query in order to be returned.  All
       * criteria are optional and no filtering is applied when not defined.</p>
       *
       * @param {(MoneroTxQuery|string[]|object)} query - configures the query (optional)
       * @param {boolean} query.isConfirmed - get txs that are confirmed or not (optional)
       * @param {boolean} query.inTxPool - get txs that are in the tx pool or not (optional)
       * @param {boolean} query.isRelayed - get txs that are relayed or not (optional)
       * @param {boolean} query.isFailed - get txs that are failed or not (optional)
       * @param {boolean} query.isMinerTx - get miner txs or not (optional)
       * @param {string} query.hash - get a tx with the hash (optional)
       * @param {string[]} query.hashes - get txs with the hashes (optional)
       * @param {string} query.paymentId - get transactions with the payment id (optional)
       * @param {string[]} query.paymentIds - get transactions with the payment ids (optional)
       * @param {boolean} query.hasPaymentId - get transactions with a payment id or not (optional)
       * @param {int} query.minHeight - get txs with height >= the given height (optional)
       * @param {int} query.maxHeight - get txs with height <= the given height (optional)
       * @param {boolean} query.isOutgoing - get txs with an outgoing transfer or not (optional)
       * @param {boolean} query.isIncoming - get txs with an incoming transfer or not (optional)
       * @param {MoneroTransferQuery} query.transferQuery - get txs that have a transfer that meets this query (optional)
       * @param {boolean} query.includeOutputs - specifies that tx outputs should be returned with tx results (optional)
       * @param {string[]} missingTxHashes - populated with hashes of unfound or unmet transactions that were queried by hash (throws error if undefined and queried transaction hashes are unfound or unmet)
       * @return {MoneroTxWallet[]} wallet transactions per the configuration
       */
      async getTxs(query, missingTxHashes) {
        throw new MoneroError("Not supported");
      }

      /**
       * <p>Get incoming and outgoing transfers to and from this wallet.  An outgoing
       * transfer represents a total amount sent from one or more subaddresses
       * within an account to individual destination addresses, each with their
       * own amount.  An incoming transfer represents a total amount received into
       * a subaddress within an account.  Transfers belong to transactions which
       * are stored on the blockchain.</p>
       *
       * <p>Results can be filtered by passing a query object.  Transfers must
       * meet every criteria defined in the query in order to be returned.  All
       * criteria are optional and no filtering is applied when not defined.</p>
       *
       * @param {(MoneroTransferQuery|object)} query - configures the query (optional)
       * @param {boolean} query.isOutgoing - get transfers that are outgoing or not (optional)
       * @param {boolean} query.isIncoming - get transfers that are incoming or not (optional)
       * @param {string} query.address - wallet's address that a transfer either originated from (if outgoing) or is destined for (if incoming) (optional)
       * @param {int} query.accountIndex - get transfers that either originated from (if outgoing) or are destined for (if incoming) a specific account index (optional)
       * @param {int} query.subaddressIndex - get transfers that either originated from (if outgoing) or are destined for (if incoming) a specific subaddress index (optional)
       * @param {int[]} query.subaddressIndices - get transfers that either originated from (if outgoing) or are destined for (if incoming) specific subaddress indices (optional)
       * @param {BigInteger} query.amount - amount being transferred (optional)
       * @param {MoneroDestination[]} query.destinations - individual destinations of an outgoing transfer, which is local wallet data and NOT recoverable from the blockchain (optional)
       * @param {boolean} query.hasDestinations - get transfers that have destinations or not (optional)
       * @param {MoneroTxQuery} query.txQuery - get transfers whose transaction meets this query (optional)
       * @return {MoneroTransfer[]} wallet transfers that meet the query
       */
      async getTransfers(query) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get incoming transfers.
       *
       * @param {(MoneroTransferQuery|object)} query - configures the query (optional)
       * @param {string} query.address - get incoming transfers to a specific address in the wallet (optional)
       * @param {int} query.accountIndex - get incoming transfers to a specific account index (optional)
       * @param {int} query.subaddressIndex - get incoming transfers to a specific subaddress index (optional)
       * @param {int[]} query.subaddressIndices - get transfers destined for specific subaddress indices (optional)
       * @param {BigInteger} query.amount - amount being transferred (optional)
       * @param {MoneroTxQuery} query.txQuery - get transfers whose transaction meets this query (optional)
       * @return {MoneroIncomingTransfer[]} incoming transfers that meet the query
       */
      async getIncomingTransfers(query) {
        query = MoneroWallet._normalizeTransferQuery(query);
        if (query.isIncoming() === false) throw new MoneroError("Transfer query contradicts getting incoming transfers");
        query.setIsIncoming(true);
        return this.getTransfers(query);
      }

      /**
       * Get outgoing transfers.
       *
       * @param {(MoneroTransferQuery|object)} query - configures the query (optional)
       * @param {string} query.address - get outgoing transfers from a specific address in the wallet (optional)
       * @param {int} query.accountIndex - get outgoing transfers from a specific account index (optional)
       * @param {int} query.subaddressIndex - get outgoing transfers from a specific subaddress index (optional)
       * @param {int[]} query.subaddressIndices - get outgoing transfers from specific subaddress indices (optional)
       * @param {BigInteger} query.amount - amount being transferred (optional)
       * @param {MoneroDestination[]} query.destinations - individual destinations of an outgoing transfer, which is local wallet data and NOT recoverable from the blockchain (optional)
       * @param {boolean} query.hasDestinations - get transfers that have destinations or not (optional)
       * @param {MoneroTxQuery} query.txQuery - get transfers whose transaction meets this query (optional)
       * @return {MoneroOutgoingTransfer[]} outgoing transfers that meet the query
       */
      async getOutgoingTransfers(query) {
        query = MoneroWallet._normalizeTransferQuery(query);
        if (query.isOutgoing() === false) throw new MoneroError("Transfer query contradicts getting outgoing transfers");
        query.setIsOutgoing(true);
        return this.getTransfers(query);
      }

      /**
       * <p>Get outputs created from previous transactions that belong to the wallet
       * (i.e. that the wallet can spend one time).  Outputs are part of
       * transactions which are stored in blocks on the blockchain.</p>
       *
       * <p>Results can be filtered by passing a query object.  Outputs must
       * meet every criteria defined in the query in order to be returned.  All
       * filtering is optional and no filtering is applied when not defined.</p>
       *
       * @param {(MoneroOutputQuery|object)} query - configures the query (optional)
       * @param {int} query.accountIndex - get outputs associated with a specific account index (optional)
       * @param {int} query.subaddressIndex - get outputs associated with a specific subaddress index (optional)
       * @param {int[]} query.subaddressIndices - get outputs associated with specific subaddress indices (optional)
       * @param {BigInteger} query.amount - get outputs with a specific amount (optional)
       * @param {BigInteger} query.minAmount - get outputs greater than or equal to a minimum amount (optional)
       * @param {BigInteger} query.maxAmount - get outputs less than or equal to a maximum amount (optional)
       * @param {boolean} query.isSpent - get outputs that are spent or not (optional)
       * @param {string|MoneroKeyImage} query.keyImage - get output with a key image or which matches fields defined in a MoneroKeyImage (optional)
       * @param {MoneroTxQuery} query.txQuery - get outputs whose transaction meets this filter (optional)
       * @return {MoneroOutputWallet[]} the queried outputs
       */
      async getOutputs(query) {
        throw new MoneroError("Not supported");
      }

      /**
       * Export outputs in hex format.
       *
       * @param {boolean} all - export all outputs if true, else export the outputs since the last export
       * @return {string} outputs in hex format
       */
      async exportOutputs(all) {
        throw new MoneroError("Not supported");
      }

      /**
       * Import outputs in hex format.
       *
       * @param {string} outputsHex - outputs in hex format
       * @return {int} the number of outputs imported
       */
      async importOutputs(outputsHex) {
        throw new MoneroError("Not supported");
      }

      /**
       * Export signed key images.
       *
       * @param {boolean} all - export all key images if true, else export the key images since the last export
       * @return {MoneroKeyImage[]} the wallet's signed key images
       */
      async exportKeyImages(all) {
        throw new MoneroError("Not supported");
      }

      /**
       * Import signed key images and verify their spent status.
       *
       * @param {MoneroKeyImage[]} keyImages - images to import and verify (requires hex and signature)
       * @return {MoneroKeyImageImportResult} results of the import
       */
      async importKeyImages(keyImages) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get new key images from the last imported outputs.
       *
       * @return {MoneroKeyImage[]} the key images from the last imported outputs
       */
      async getNewKeyImagesFromLastImport() {
        throw new MoneroError("Not supported");
      }

      /**
       * Freeze an output.
       *
       * @param {string} keyImage - key image of the output to freeze
       */
      async freezeOutput(keyImage) {
        throw new MoneroError("Not supported");
      }

      /**
       * Thaw a frozen output.
       *
       * @param {string} keyImage - key image of the output to thaw
       */
      async thawOutput(keyImage) {
        throw new MoneroError("Not supported");
      }

      /**
       * Check if an output is frozen.
       *
       * @param {string} keyImage - key image of the output to check if frozen
       * @return {boolean} true if the output is frozen, false otherwise
       */
      async isOutputFrozen(keyImage) {
        throw new MoneroError("Not supported");
      }

      /**
       * Create a transaction to transfer funds from this wallet.
       *
       * @param {MoneroTxConfig|object} config - configures the transaction to create (required)
       * @param {string} config.address - single destination address (required unless `destinations` provided)
       * @param {BigInteger|string} config.amount - single destination amount (required unless `destinations` provided)
       * @param {int} config.accountIndex - source account index to transfer funds from (required)
       * @param {int} config.subaddressIndex - source subaddress index to transfer funds from (optional)
       * @param {int[]} config.subaddressIndices - source subaddress indices to transfer funds from (optional)
       * @param {boolean} config.relay - relay the transaction to peers to commit to the blockchain (default false)
       * @param {MoneroTxPriority} config.priority - transaction priority (default MoneroTxPriority.NORMAL)
       * @param {MoneroDestination[]} config.destinations - addresses and amounts in a multi-destination tx (required unless `address` and `amount` provided)
       * @param {string} config.paymentId - transaction payment ID (optional)
       * @param {int} config.unlockHeight - minimum height for the transaction to unlock (default 0)
       * @return {MoneroTxWallet} the created transaction
       */
      async createTx(config) {
        config = MoneroWallet._normalizeCreateTxsConfig(config);
        if (config.getCanSplit() !== undefined) assert.equal(config.getCanSplit(), false, "Cannot split transactions using createTx(); use createTxs()");
        config.setCanSplit(false);
        return (await this.createTxs(config))[0];
      }

      /**
       * Create one or more transactions to transfer funds from this wallet.
       *
       * @param {MoneroTxConfig|object} config - configures the transactions to create (required)
       * @param {string} config.address - single destination address (required unless `destinations` provided)
       * @param {BigInteger|string} config.amount - single destination amount (required unless `destinations` provided)
       * @param {int} config.accountIndex - source account index to transfer funds from (required)
       * @param {int} config.subaddressIndex - source subaddress index to transfer funds from (optional)
       * @param {int[]} config.subaddressIndices - source subaddress indices to transfer funds from (optional)
       * @param {boolean} config.relay - relay the transactions to peers to commit to the blockchain (default false)
       * @param {MoneroTxPriority} config.priority - transaction priority (default MoneroTxPriority.NORMAL)
       * @param {MoneroDestination[]} config.destinations - addresses and amounts in a multi-destination tx (required unless `address` and `amount` provided)
       * @param {string} config.paymentId - transaction payment ID (optional)
       * @param {int} config.unlockHeight - minimum height for the transactions to unlock (default 0)
       * @param {boolean} config.canSplit - allow funds to be transferred using multiple transactions (default true)
       * @return {MoneroTxWallet[]} the created transactions
       */
      async createTxs(config) {
        throw new MoneroError("Not supported");
      }

      /**
       * Sweep an output by key image.
       *
       * @param {MoneroTxConfig} config - configures the transaction to create (required)
       * @param {string} config.address - single destination address (required)
       * @param {string} config.keyImage - key image to sweep (required)
       * @param {boolean} config.relay - relay the transaction to peers to commit to the blockchain (default false)
       * @param {int} config.unlockHeight - minimum height for the transaction to unlock (default 0)
       * @param {MoneroTxPriority} config.priority - transaction priority (default MoneroTxPriority.NORMAL)
       * @return {MoneroTxWallet} the created transaction
       */
      async sweepOutput(config) {
        throw new MoneroError("Not supported");
      }

      /**
       * Sweep all unlocked funds according to the given configuration.
       *
       * @param {MoneroTxConfig|object} config - configures the transactions to create (required)
       * @param {string} config.address - single destination address (required)
       * @param {int} config.accountIndex - source account index to sweep from (optional, defaults to all accounts)
       * @param {int} config.subaddressIndex - source subaddress index to sweep from (optional, defaults to all subaddresses)
       * @param {int[]} config.subaddressIndices - source subaddress indices to sweep from (optional)
       * @param {boolean} config.relay - relay the transactions to peers to commit to the blockchain (default false)
       * @param {MoneroTxPriority} config.priority - transaction priority (default MoneroTxPriority.NORMAL)
       * @param {int} config.unlockHeight - minimum height for the transactions to unlock (default 0)
       * @param {boolean} config.sweepEachSubaddress - sweep each subaddress individually if true (default false)
       * @return {MoneroTxWallet[]} the created transactions
       */
      async sweepUnlocked(config) {
        throw new MoneroError("Not supported");
      }

      /**
       * <p>Sweep all unmixable dust outputs back to the wallet to make them easier to spend and mix.</p>
       *
       * <p>NOTE: Dust only exists pre RCT, so this method will throw "no dust to sweep" on new wallets.</p>
       *
       * @param {boolean} relay - specifies if the resulting transaction should be relayed (default false)
       * @return {MoneroTxWallet[]} the created transactions
       */
      async sweepDust(relay) {
        throw new MoneroError("Not supported");
      }

      /**
       * Relay a previously created transaction.
       *
       * @param {(MoneroTxWallet|string)} txOrMetadata - transaction or its metadata to relay
       * @return {string} the hash of the relayed tx
       */
      async relayTx(txOrMetadata) {
        return (await this.relayTxs([txOrMetadata]))[0];
      }

      /**
       * Relay previously created transactions.
       *
       * @param {(MoneroTxWallet[]|string[])} txsOrMetadatas - transactions or their metadata to relay
       * @return {string[]} the hashes of the relayed txs
       */
      async relayTxs(txsOrMetadatas) {
        throw new MoneroError("Not supported");
      }

      /**
       * Describe a tx set from unsigned tx hex.
       *
       * @param {string} unsignedTxHex - unsigned tx hex
       * @return {MoneroTxSet} the tx set containing structured transactions
       */
      async describeUnsignedTxSet(unsignedTxHex) {
        return this.describeTxSet(new MoneroTxSet().setUnsignedTxHex(unsignedTxHex));
      }

      /**
       * Describe a tx set from multisig tx hex.
       *
       * @param {string} multisigTxHex - multisig tx hex
       * @return {MoneroTxSet} the tx set containing structured transactions
       */
      async describeMultisigTxSet(multisigTxHex) {
        return this.describeTxSet(new MoneroTxSet().setMultisigTxHex(multisigTxHex));
      }

      /**
       * Describe a tx set containing unsigned or multisig tx hex to a new tx set containing structured transactions.
       *
       * @param {MoneroTxSet} txSet - a tx set containing unsigned or multisig tx hex
       * @return {MoneroTxSet} the tx set containing structured transactions
       */
      async describeTxSet(txSet) {
        throw new MoneroError("Not supported");
      }

      /**
       * Sign unsigned transactions from a view-only wallet.
       *
       * @param {string} unsignedTxHex - unsigned transaction hex from when the transactions were created
       * @return {string} the signed transaction hex
       */
      async signTxs(unsignedTxHex) {
        throw new MoneroError("Not supported");
      }

      /**
       * Submit signed transactions from a view-only wallet.
       *
       * @param {string} signedTxHex - signed transaction hex from signTxs()
       * @return {string[]} the resulting transaction hashes
       */
      async submitTxs(signedTxHex) {
        throw new MoneroError("Not supported");
      }

      /**
       * Sign a message.
       *
       * @param {string} message - the message to sign
       * @param {MoneroMessageSignatureType} signatureType - sign with spend key or view key (default spend key)
       * @param {int} accountIdx - the account index of the message signature (default 0)
       * @param {int} subaddressIdx - the subaddress index of the message signature (default 0)
       * @return {string} the signature
       */
      async signMessage(message, signatureType, accountIdx, subaddressIdx) {
        throw new MoneroError("Not supported");
      }

      /**
       * Verify a signature on a message.
       *
       * @param {string} message - signed message
       * @param {string} address - signing address
       * @param {string} signature - signature
       * @return {MoneroMessageSignatureResult} true if the signature is good, false otherwise
       */
      async verifyMessage(message, address, signature) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get a transaction's secret key from its hash.
       *
       * @param {string} txHash - transaction's hash
       * @return {string} - transaction's secret key
       */
      async getTxKey(txHash) {
        throw new MoneroError("Not supported");
      }

      /**
       * Check a transaction in the blockchain with its secret key.
       *
       * @param {string} txHash - transaction to check
       * @param {string} txKey - transaction's secret key
       * @param {string} address - destination public address of the transaction
       * @return {MoneroCheckTx} the result of the check
       */
      async checkTxKey(txHash, txKey, address) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get a transaction signature to prove it.
       *
       * @param {string} txHash - transaction to prove
       * @param {string} address - destination public address of the transaction
       * @param {string} message - message to include with the signature to further authenticate the proof (optional)
       * @return {string} the transaction signature
       */
      async getTxProof(txHash, address, message) {
        throw new MoneroError("Not supported");
      }

      /**
       * Prove a transaction by checking its signature.
       *
       * @param {string} txHash - transaction to prove
       * @param {string} address - destination public address of the transaction
       * @param {string} message - message included with the signature to further authenticate the proof (optional)
       * @param {string} signature - transaction signature to confirm
       * @return {MoneroCheckTx} the result of the check
       */
      async checkTxProof(txHash, address, message, signature) {
        throw new MoneroError("Not supported");
      }

      /**
       * Generate a signature to prove a spend. Unlike proving a transaction, it does not require the destination public address.
       *
       * @param {string} txHash - transaction to prove
       * @param {string} message - message to include with the signature to further authenticate the proof (optional)
       * @return {string} the transaction signature
       */
      async getSpendProof(txHash, message) {
        throw new MoneroError("Not supported");
      }

      /**
       * Prove a spend using a signature. Unlike proving a transaction, it does not require the destination public address.
       *
       * @param {string} txHash - transaction to prove
       * @param {string} message - message included with the signature to further authenticate the proof (optional)
       * @param {string} signature - transaction signature to confirm
       * @return {boolean} true if the signature is good, false otherwise
       */
      async checkSpendProof(txHash, message, signature) {
        throw new MoneroError("Not supported");
      }

      /**
       * Generate a signature to prove the entire balance of the wallet.
       *
       * @param message - message included with the signature to further authenticate the proof (optional)
       * @return the reserve proof signature
       */
      async getReserveProofWallet(message) {
        throw new MoneroError("Not supported");
      }

      /**
       * Generate a signature to prove an available amount in an account.
       *
       * @param {int} accountIdx - account to prove ownership of the amount
       * @param {BigInteger} amount - minimum amount to prove as available in the account
       * @param {string} message - message to include with the signature to further authenticate the proof (optional)
       * @return {string} the reserve proof signature
       */
      async getReserveProofAccount(accountIdx, amount, message) {
        throw new MoneroError("Not supported");
      }

      /**
       * Proves a wallet has a disposable reserve using a signature.
       *
       * @param {string} address - public wallet address
       * @param {string} message - message included with the signature to further authenticate the proof (optional)
       * @param {string} signature - reserve proof signature to check
       * @return {MoneroCheckReserve} the result of checking the signature proof
       */
      async checkReserveProof(address, message, signature) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get a transaction note.
       *
       * @param {string} txHash - transaction to get the note of
       * @return {string} the tx note
       */
      async getTxNote(txHash) {
        return (await this.getTxNotes([txHash]))[0];
      }

      /**
       * Get notes for multiple transactions.
       *
       * @param {string[]} txHashes - hashes of the transactions to get notes for
       * @return {string[]} notes for the transactions
       */
      async getTxNotes(txHashes) {
        throw new MoneroError("Not supported");
      }

      /**
       * Set a note for a specific transaction.
       *
       * @param {string} txHash - hash of the transaction to set a note for
       * @param {string} note - the transaction note
       */
      async setTxNote(txHash, note) {
        await this.setTxNotes([txHash], [note]);
      }

      /**
       * Set notes for multiple transactions.
       *
       * @param {string[]} txHashes - transactions to set notes for
       * @param {string[]} notes - notes to set for the transactions
       */
      async setTxNotes(txHashes, notes) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get address book entries.
       *
       * @param {int[]} entryIndices - indices of the entries to get
       * @return {MoneroAddressBookEntry[]} the address book entries
       */
      async getAddressBookEntries(entryIndices) {
        throw new MoneroError("Not supported");
      }

      /**
       * Add an address book entry.
       *
       * @param {string} address - entry address
       * @param {string} description - entry description (optional)
       * @return {int} the index of the added entry
       */
      async addAddressBookEntry(address, description) {
        throw new MoneroError("Not supported");
      }

      /**
       * Edit an address book entry.
       *
       * @param {number} index - index of the address book entry to edit
       * @param {boolean} setAddress - specifies if the address should be updated
       * @param {string} address - updated address
       * @param {boolean} setDescription - specifies if the description should be updated
       * @param {string} description - updated description
       */
      async editAddressBookEntry(index, setAddress, address, setDescription, description) {
        throw new MoneroError("Not supported");
      }

      /**
       * Delete an address book entry.
       *
       * @param {int} entryIdx - index of the entry to delete
       */
      async deleteAddressBookEntry(entryIdx) {
        throw new MoneroError("Not supported");
      }

      /**
       * Tag accounts.
       *
       * @param {string} tag - tag to apply to the specified accounts
       * @param {int[]} accountIndices - indices of the accounts to tag
       */
      async tagAccounts(tag, accountIndices) {
        throw new MoneroError("Not supported");
      }

      /**
       * Untag accounts.
       *
       * @param {int[]} accountIndices - indices of the accounts to untag
       */
      async untagAccounts(accountIndices) {
        throw new MoneroError("Not supported");
      }

      /**
       * Return all account tags.
       *
       * @return {MoneroAccountTag[]} the wallet's account tags
       */
      async getAccountTags() {
        throw new MoneroError("Not supported");
      }

      /**
       * Sets a human-readable description for a tag.
       *
       * @param {string} tag - tag to set a description for
       * @param {string} label - label to set for the tag
       */
      async setAccountTagLabel(tag, label) {
        throw new MoneroError("Not supported");
      }

      /**
       * Creates a payment URI from a send configuration.
       *
       * @param {MoneroTxConfig} config - specifies configuration for a potential tx
       * @return {string} the payment uri
       */
      async getPaymentUri(config) {
        throw new MoneroError("Not supported");
      }

      /**
       * Parses a payment URI to a tx config.
       *
       * @param {string} uri - payment uri to parse
       * @return {MoneroTxConfig} the send configuration parsed from the uri
       */
      async parsePaymentUri(uri) {
        throw new MoneroError("Not supported");
      }

      /**
       * Get an attribute.
       *
       * @param {string} key - attribute to get the value of
       * @return {string} the attribute's value
       */
      async getAttribute(key) {
        throw new MoneroError("Not supported");
      }

      /**
       * Set an arbitrary attribute.
       *
       * @param {string} key - attribute key
       * @param {string} val - attribute value
       */
      async setAttribute(key, val) {
        throw new MoneroError("Not supported");
      }

      /**
       * Start mining.
       *
       * @param {int} numThreads - number of threads created for mining (optional)
       * @param {boolean} backgroundMining - specifies if mining should occur in the background (optional)
       * @param {boolean} ignoreBattery - specifies if the battery should be ignored for mining (optional)
       */
      async startMining(numThreads, backgroundMining, ignoreBattery) {
        throw new MoneroError("Not supported");
      }

      /**
       * Stop mining.
       */
      async stopMining() {
        throw new MoneroError("Not supported");
      }

      /**
       * Indicates if importing multisig data is needed for returning a correct balance.
       *
       * @return {boolean} true if importing multisig data is needed for returning a correct balance, false otherwise
       */
      async isMultisigImportNeeded() {
        throw new MoneroError("Not supported");
      }

      /**
       * Indicates if this wallet is a multisig wallet.
       *
       * @return {boolean} true if this is a multisig wallet, false otherwise
       */
      async isMultisig() {
        return (await this.getMultisigInfo()).isMultisig();
      }

      /**
       * Get multisig info about this wallet.
       *
       * @return {MoneroMultisigInfo} multisig info about this wallet
       */
      async getMultisigInfo() {
        throw new MoneroError("Not supported");
      }

      /**
       * Get multisig info as hex to share with participants to begin creating a
       * multisig wallet.
       *
       * @return {string} this wallet's multisig hex to share with participants
       */
      async prepareMultisig() {
        throw new MoneroError("Not supported");
      }

      /**
       * Make this wallet multisig by importing multisig hex from participants.
       *
       * @param {String[]} multisigHexes - multisig hex from each participant
       * @param {int} threshold - number of signatures needed to sign transfers
       * @param {string} password - wallet password
       * @return {string} this wallet's multisig hex to share with participants
       */
      async makeMultisig(multisigHexes, threshold, password) {
        throw new MoneroError("Not supported");
      }

      /**
       * Exchange multisig hex with participants in a M/N multisig wallet.
       *
       * This process must be repeated with participants exactly N-M times.
       *
       * @param {string[]} multisigHexes are multisig hex from each participant
       * @param {string} password - wallet's password // TODO monero-project: redundant? wallet is created with password
       * @return {MoneroMultisigInitResult} the result which has the multisig's address xor this wallet's multisig hex to share with participants iff not done
       */
      async exchangeMultisigKeys(multisigHexes, password) {
        throw new MoneroError("Not supported");
      }

      /**
       * Export this wallet's multisig info as hex for other participants.
       *
       * @return {string} this wallet's multisig info as hex for other participants
       */
      async exportMultisigHex() {
        throw new MoneroError("Not supported?");
      }

      /**
       * Import multisig info as hex from other participants.
       *
       * @param {string[]} multisigHexes - multisig hex from each participant
       * @return {int} the number of outputs signed with the given multisig hex
       */
      async importMultisigHex(multisigHexes) {
        throw new MoneroError("Not supported");
      }

      /**
       * Sign multisig transactions from a multisig wallet.
       *
       * @param {string} multisigTxHex - unsigned multisig transactions as hex
       * @return {MoneroMultisigSignResult} the result of signing the multisig transactions
       */
      async signMultisigTxHex(multisigTxHex) {
        throw new MoneroError("Not supported");
      }

      /**
       * Submit signed multisig transactions from a multisig wallet.
       *
       * @param {string} signedMultisigTxHex - signed multisig hex returned from signMultisigTxHex()
       * @return {string[]} the resulting transaction hashes
       */
      async submitMultisigTxHex(signedMultisigTxHex) {
        throw new MoneroError("Not supported");
      }

      /**
       * Change the wallet password.
       *
       * @param {string} oldPassword - the wallet's old password
       * @param {string} newPassword - the wallet's new password
       */
      async changePassword(oldPassword, newPassword) {
        throw new MoneroError("Not supported");
      }

      /**
       * Save the wallet at its current path.
       */
      save() {
        throw new MoneroError("Not supported");
      }

      /**
       * Optionally save then close the wallet.
       *
       * @param {boolean} save - specifies if the wallet should be saved before being closed (default false)
       */
      async close(save) {
        throw new MoneroError("Not supported");
      }

      /**
       * Indicates if this wallet is closed or not.
       *
       * @return {boolean} true if the wallet is closed, false otherwise
       */
      async isClosed() {
        throw new MoneroError("Not supported");
      }

      // -------------------------------- PRIVATE ---------------------------------

      static _normalizeTxQuery(query) {
        if (query instanceof MoneroTxQuery) query = query.copy();
        else if (Array.isArray(query)) query = new MoneroTxQuery().setHashes(query);
        else {
          query = Object.assign({}, query);
          query = new MoneroTxQuery(query);
        }
        if (query.getBlock() === undefined) query.setBlock(new MoneroBlock().setTxs([query]));
        if (query.getInputQuery()) query.getInputQuery().setTxQuery(query);
        if (query.getOutputQuery()) query.getOutputQuery().setTxQuery(query);
        return query;
      }

      static _normalizeTransferQuery(query) {
        if (query === undefined) query = new MoneroTransferQuery();
        else if (query instanceof MoneroTransferQuery) {
          if (query.getTxQuery() === undefined) query = query.copy();
          else {
            let txQuery = query.getTxQuery().copy();
            if (query.getTxQuery().getTransferQuery() === query) query = txQuery.getTransferQuery();
            else {
              assert.equal(query.getTxQuery().getTransferQuery(), undefined, "Transfer query's tx query must be circular reference or null");
              query = query.copy();
              query.setTxQuery(txQuery);
            }
          }
        } else {
          query = Object.assign({}, query);
          query = new MoneroTransferQuery(query);
        }
        if (query.getTxQuery() === undefined) query.setTxQuery(new MoneroTxQuery());
        query.getTxQuery().setTransferQuery(query);
        if (query.getTxQuery().getBlock() === undefined) query.getTxQuery().setBlock(new MoneroBlock().setTxs([query.getTxQuery()]));
        return query;
      }

      static _normalizeOutputQuery(query) {
        if (query === undefined) query = new MoneroOutputQuery();
        else if (query instanceof MoneroOutputQuery) {
          if (query.getTxQuery() === undefined) query = query.copy();
          else {
            let txQuery = query.getTxQuery().copy();
            if (query.getTxQuery().getOutputQuery() === query) query = txQuery.getOutputQuery();
            else {
              assert.equal(query.getTxQuery().getOutputQuery(), undefined, "Output query's tx query must be circular reference or null");
              query = query.copy();
              query.setTxQuery(txQuery);
            }
          }
        } else {
          query = Object.assign({}, query);
          query = new MoneroOutputQuery(query);
        }
        if (query.getTxQuery() === undefined) query.setTxQuery(new MoneroTxQuery());
        query.getTxQuery().setOutputQuery(query);
        if (query.getTxQuery().getBlock() === undefined) query.getTxQuery().setBlock(new MoneroBlock().setTxs([query.getTxQuery()]));
        return query;
      }

      static _normalizeCreateTxsConfig(config) {
        if (config === undefined || !(config instanceof Object)) throw new MoneroError("Must provide MoneroTxConfig or equivalent JS object");
        config = new MoneroTxConfig(config);
        assert(config.getDestinations() && config.getDestinations().length > 0, "Must provide destinations");
        assert.equal(config.getSweepEachSubaddress(), undefined);
        assert.equal(config.getBelowAmount(), undefined);
        return config;
      }

      static _normalizeSweepOutputConfig(config) {
        if (config === undefined || !(config instanceof Object)) throw new MoneroError("Must provide MoneroTxConfig or equivalent JS object");
        config = new MoneroTxConfig(config);
        assert.equal(config.getSweepEachSubaddress(), undefined);
        assert.equal(config.getBelowAmount(), undefined);
        assert.equal(config.getCanSplit(), undefined, "Cannot split transactions when sweeping an output");
        if (!config.getDestinations() || config.getDestinations().length !== 1 || !config.getDestinations()[0].getAddress()) throw new MoneroError("Must provide exactly one destination address to sweep output to");
        return config;
      }

      static _normalizeSweepUnlockedConfig(config) {
        if (config === undefined || !(config instanceof Object)) throw new MoneroError("Must provide MoneroTxConfig or equivalent JS object");
        config = new MoneroTxConfig(config);
        if (config.getDestinations() === undefined || config.getDestinations().length != 1) throw new MoneroError("Must provide exactly one destination to sweep to");
        if (config.getDestinations()[0].getAddress() === undefined) throw new MoneroError("Must provide destination address to sweep to");
        if (config.getDestinations()[0].getAmount() !== undefined) throw new MoneroError("Cannot provide amount in sweep config");
        if (config.getKeyImage() !== undefined) throw new MoneroError("Key image defined; use sweepOutput() to sweep an output by its key image");
        if (config.getSubaddressIndices() !== undefined && config.getSubaddressIndices().length === 0) config.setSubaddressIndices(undefined);
        if (config.getAccountIndex() === undefined && config.getSubaddressIndices() !== undefined) throw new MoneroError("Must provide account index if subaddress indices are provided");
        return config;
      }
    }

    MoneroWallet.DEFAULT_LANGUAGE = "English";

    module.exports = MoneroWallet;