/**
 * module contract/VSwapCtrt provides functionalities for V Swap contract.
 * @module contract/VSwapCtrt
 */

'use strict';

import * as ctrt from './ctrt.js';
import * as md from '../model.js';
import * as tx from '../tx_req.js';
import * as de from '../data_entry.js';
import * as tcf from './tok_ctrt_factory.js';
import * as msacnt from '../multisign_account.js';

/** FuncIdx is the class for function indexes */
export class FuncIdx extends ctrt.FuncIdx {
  static elems = {
    SUPERSEDE: 0,
    SET_SWAP: 1,
    ADD_LIQUIDITY: 2,
    REMOVE_LIQUIDITY: 3,
    SWAP_B_FOR_EXACT_A: 4,
    SWAP_EXACT_B_FOR_A: 5,
    SWAP_A_FOR_EXACT_B: 6,
    SWAP_EXACT_A_FOR_B: 7,
  };
  static _ = this.createElems();
}

/** StateVar is the class for state variables */
export class StateVar extends ctrt.StateVar {
  static elems = {
    MAKER: 0,
    TOKEN_A_ID: 1,
    TOKEN_B_ID: 2,
    LIQUIDITY_TOKEN_ID: 3,
    SWAP_STATUS: 4,
    MINIMUM_LIQUIDITY: 5,
    TOKEN_A_RESERVED: 6,
    TOKEN_B_RESERVED: 7,
    TOTAL_SUPPLY: 8,
    LIQUIDITY_TOKEN_LEFT: 9,
  };
  static _ = this.createElems();
}

/** StateMapIdx is the class for state map indexes */
class StateMapIdx extends ctrt.StateMapIdx {
  static elems = {
    TOKEN_A_BALANCE: 0,
    TOKEN_B_BALANCE: 1,
    LIQUIDITY_TOKEN_BALANCE: 2,
  };
  static _ = this.createElems();
}

/** DBKey is the class for DB key */
export class DBKey extends ctrt.DBKey {
  /**
   * forMaker returns the DBKey object for querying the maker.
   * @returns {DBKey} The DBKey object for querying the maker.
   */
  static forMaker() {
    return new this(StateVar.MAKER.serialize());
  }

  /**
   * forTokAId returns the DBKey object for querying the token A id.
   * @returns {DBKey} The DBKey object for querying the token A id.
   */
  static forTokAId() {
    return new this(StateVar.TOKEN_A_ID.serialize());
  }

  /**
   * forTokBId returns the DBKey object for querying the token B id.
   * @returns {DBKey} The DBKey object for querying the token B id.
   */
  static forTokBId() {
    return new this(StateVar.TOKEN_B_ID.serialize());
  }

  /**
   * forLiqTokId returns the DBKey object for querying the liquidity token id.
   * @returns {DBKey} The DBKey object for querying the liquidity token id.
   */
  static forLiqTokId() {
    return new this(StateVar.LIQUIDITY_TOKEN_ID.serialize());
  }

  /**
   * forSwapStatus returns the DBKey object for querying the swap status.
   * @returns {DBKey} The DBKey object for querying the swap status.
   */
  static forSwapStatus() {
    return new this(StateVar.SWAP_STATUS.serialize());
  }

  /**
   * forMinLiq returns the DBKey object for querying the minimum liquidity.
   * @returns {DBKey} The DBKey object for querying the minimum liquidity.
   */
  static forMinLiq() {
    return new this(StateVar.MINIMUM_LIQUIDITY.serialize());
  }

  /**
   * forTokARes returns the DBKey object for querying the amount of token A inside the pool.
   * @returns {DBKey} The DBKey object for querying the amount of token A inside the pool.
   */
  static forTokARes() {
    return new this(StateVar.TOKEN_A_RESERVED.serialize());
  }

  /**
   * forTokBRes returns the DBKey object for querying the amount of token B inside the pool.
   * @returns {DBKey} The DBKey object for querying the amount of token B inside the pool.
   */
  static forTokBRes() {
    return new this(StateVar.TOKEN_B_RESERVED.serialize());
  }

  /**
   * forTotalSupply returns the DBKey object for querying the total amount of liquidity tokens that can be minted.
   * @returns {DBKey} The DBKey object for querying the total amount of liquidity tokens that can be minted.
   */
  static forTotalSupply() {
    return new this(StateVar.TOTAL_SUPPLY.serialize());
  }

  /**
   * forLiqTokLeft returns the DBKey object for querying the amount of liquidity tokens left to be minted.
   * @returns {DBKey} The DBKey object for querying the amount of liquidity tokens left to be minted.
   */
  static forLiqTokLeft() {
    return new this(StateVar.LIQUIDITY_TOKEN_LEFT.serialize());
  }

  /**
   * forTokABal returns the DBKey object for querying the total amount of token A balance.
   * @returns {DBKey} The DBKey object for querying the total amount of token A balance.
   */
  static forTokABal(addr) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.TOKEN_A_BALANCE,
      de.Addr.fromStr(addr)
    );
    return new this(stmp.serialize());
  }

  /**
   * forTokBBal returns the DBKey object for querying the total amount of token B balance.
   * @returns {DBKey} The DBKey object for querying the total amount of token B balance.
   */
  static forTokBBal(addr) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.TOKEN_B_BALANCE,
      de.Addr.fromStr(addr)
    );
    return new this(stmp.serialize());
  }

  /**
   * forLiqTokBal returns the DBKey object for querying the total amount of liquidity token balance.
   * @returns {DBKey} The DBKey object for querying the total amount of liquidity token balance.
   */
  static forLiqTokBal(addr) {
    const stmp = new ctrt.StateMap(
      StateMapIdx.LIQUIDITY_TOKEN_BALANCE,
      de.Addr.fromStr(addr)
    );
    return new this(stmp.serialize());
  }
}

/** VSwapCtrt is the class for V Swap Contract */
export class VSwapCtrt extends ctrt.BaseTokCtrt {
  static CTRT_META = md.CtrtMeta.fromB58Str(
    ' '
  );

  constructor(ctrtId, chain) {
    super(ctrtId, chain);
    this._tokAId = undefined;
    this._tokBId = undefined;
    this._liqTokId = undefined;
    this._tokACtrt = undefined;
    this._tokBCtrt = undefined;
    this._liqTokCtrt = undefined;
    this._minLiq = 0;
  }

  /**
   * getMaker queries & returns the maker of the contract.
   * @returns {md.Addr} The maker of the contract.
   */
  async getMaker() {
    const rawVal = await this.queryDbKey(DBKey.forMaker());
    return new md.Addr(rawVal);
  }

  /**
   * getTokAId queries & returns the token A id of the contract.
   * @returns {md.TokenID} The token A id of the contract.
   */
  async getTokAId() {
    if (!this._tokAId) {
      const rawVal = await this.queryDbKey(DBKey.forTokAId());
      this._tokAId = new md.TokenID(rawVal);
    }
    return this._tokAId;
  }

  /**
   * getTokBId queries & returns the token B id of the contract.
   * @returns {md.TokenID} The token B id of the contract.
   */
  async getTokBId() {
    if (!this._tokBId) {
      const rawVal = await this.queryDbKey(DBKey.forTokBId());
      this._tokBId = new md.TokenID(rawVal);
    }
    return this._tokBId;
  }

  /**
   * getLiqTokId queries & returns the liquidity token id of the contract.
   * @returns {md.TokenID} The liquidity token id of the contract.
   */
  async getLiqTokId() {
    if (!this._liqTokId) {
      const rawVal = await this.queryDbKey(DBKey.forLiqTokId());
      this._liqTokId = new md.TokenID(rawVal);
    }
    return this._liqTokId;
  }

  /**
   * getTokACtrt queries & returns the instance of token A contract.
   * @returns {ctrt.BaseTokCtrt} The instance of token A contract.
   */
  async getTokACtrt() {
    if (!this._tokACtrt) {
      const tokAId = await this.getTokAId();
      this._tokACtrt = await tcf.fromTokId(tokAId, this.chain);
    }
    return this._tokACtrt;
  }

  /**
   * getTokBCtrt queries & returns the instance of token B contract.
   * @returns {ctrt.BaseTokCtrt} The instance of token B contract.
   */
  async getTokBCtrt() {
    if (!this._tokBCtrt) {
      const tokBId = await this.getTokBId();
      this._tokBCtrt = await tcf.fromTokId(tokBId, this.chain);
    }
    return this._tokBCtrt;
  }

  /**
   * getLiqTokCtrt queries & returns the instance of liquidity token contract.
   * @returns {ctrt.BaseTokCtrt} The instance of liquidity token contract.
   */
  async getLiqTokCtrt() {
    if (!this._liqTokCtrt) {
      const liqTokId = await this.getLiqTokId();
      this._liqTokCtrt = await tcf.fromTokId(liqTokId, this.chain);
    }
    return this._liqTokCtrt;
  }

  /**
   * getTokAUnit queries & returns the token A's unit.
   * @returns {number} The unit of token A.
   */
  async getTokAUnit() {
    const tc = await this.getTokACtrt();
    return await tc.getUnit();
  }

  /**
   * getTokBUnit queries & returns the token B's unit.
   * @returns {number} The unit of token B.
   */
  async getTokBUnit() {
    const tc = await this.getTokBCtrt();
    return await tc.getUnit();
  }

  /**
   * getLiqTokUnit queries & returns the liquidity token's unit.
   * @returns {number} The unit of liquidity token.
   */
  async getLiqTokUnit() {
    const tc = await this.getLiqTokCtrt();
    return await tc.getUnit();
  }

  /**
   * getSwapStatus queries & returns the status of the swap.
   * @returns {bool} The status of the swap. True represents active and false is inactive.
   */
  async getSwapStatus() {
    const rawVal = await this.queryDbKey(DBKey.forSwapStatus());
    return rawVal == 'true';
  }

  /**
   * getMinLiq queries & returns the minimum liquidity.
   * @returns {md.Token} The minimum liquidity of the contract.
   */
  async getMinLiq() {
    if (this._minLiq === 0) {
      const rawVal = await this.queryDbKey(DBKey.forMinLiq());
      const unit = await this.getLiqTokUnit();
      this._minLiq = md.Token.fromNumber(rawVal, unit);
    }
    return this._minLiq;
  }

  /**
   * getTokARes queries & returns the amount of token A inside the pool.
   * @returns {md.Token} The amount of token A inside the pool.
   */
  async getTokARes() {
    const rawVal = await this.queryDbKey(DBKey.forTokARes());
    const unit = await this.getTokAUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getTokBRes queries & returns the amount of token B inside the pool.
   * @returns {md.Token} The amount of token B inside the pool.
   */
  async getTokBRes() {
    const rawVal = await this.queryDbKey(DBKey.forTokBRes());
    const unit = await this.getTokBUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getTotalSupply queries & returns the total amount of liquidity tokens that can be minted.
   * @returns {md.Token} The total amount of liquidity tokens that can be minted.
   */
  async getTotalSupply() {
    const rawVal = await this.queryDbKey(DBKey.forTotalSupply());
    const unit = await this.getLiqTokUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getLiqTokLeft queries & returns the amount of liquidity tokens left to be minted.
   * @returns {md.Token} The amount of liquidity tokens left to be minted.
   */
  async getLiqTokLeft() {
    const rawVal = await this.queryDbKey(DBKey.forLiqTokLeft());
    const unit = await this.getLiqTokUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getTokABal queries & returns the balance of token A stored within the contract belonging to the given user address.
   * @param {string} addr - The address of the user.
   * @returns {md.Token} The balance of token A stored within the contract belonging to the given user address.
   */
  async getTokABal(addr) {
    const rawVal = await this.queryDbKey(DBKey.forTokABal(addr));
    const unit = await this.getTokAUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getTokBBal queries & returns the balance of token B stored within the contract belonging to the given user address.
   * @param {string} addr - The address of the user.
   * @returns {md.Token} The balance of token B stored within the contract belonging to the given user address.
   */
  async getTokBBal(addr) {
    const rawVal = await this.queryDbKey(DBKey.forTokBBal(addr));
    const unit = await this.getTokBUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * getLiqTokBal queries & returns the balance of liquidity token stored within the contract belonging to the given user address.
   * @param {string} addr - The address of the user.
   * @returns {md.Token} The balance of liquidity token stored within the contract belonging to the given user address.
   */
  async getLiqTokBal(addr) {
    const rawVal = await this.queryDbKey(DBKey.forLiqTokBal(addr));
    const unit = await this.getLiqTokUnit();
    return md.Token.fromNumber(rawVal, unit);
  }

  /**
   * register registers a V Swap Contract.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {string} tokAId - The token A's id.
   * @param {string} tokBId - The token B's id.
   * @param {string} liqTokId - The liquidity token's id.
   * @param {number} minLiq - The minimum liquidity of the contract.
   * @param {string} ctrtDescription - The description of the contract. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.RegCtrtFee.DEFAULT.
   * @returns {VSwapCtrt} - The VSwapCtrt object of the registered V Swap Contract.
   */
  static async register(
    by,
    tokAId,
    tokBId,
    liqTokId,
    minLiq,
    ctrtDescription = '',
    fee = md.RegCtrtFee.DEFAULT
  ) {
    const resp = await by.chain.api.ctrt.getTokInfo(liqTokId);
    const liqUnit = resp.unity;

    const data = await by.registerContractImpl(
      new tx.RegCtrtTxReq(
        new de.DataStack(
          de.TokenID.fromStr(tokAId),
          de.TokenID.fromStr(tokBId),
          de.TokenID.fromStr(liqTokId),
          de.Amount.forTokAmount(minLiq, liqUnit)
        ),
        this.CTRT_META,
        md.VSYSTimestamp.now(),
        new md.Str(ctrtDescription),
        md.RegCtrtFee.fromNumber(fee)
      )
    );
    return new this(data.contractId, by.chain);
  }

  /**
   * supersede transfers the issuer role of the contract to a new account.
   * @param {any} by - The action taker.
   * @param {any} newOwner - The account address of the new owner.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async supersede(by, newOwner, attachment = '', fee = md.ExecCtrtFee.DEFAULT) {
    const newOwnerMd = new md.Addr(newOwner);
    newOwnerMd.mustOn(by.chain);

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.SUPERSEDE,
        new de.DataStack(new de.Addr(newOwnerMd)),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * setSwap creates a swap and deposit initial amounts into the pool.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amntA - The initial amount for token A.
   * @param {number} amntB - The initial amount for token B.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async setSwap(
    by,
    amntA,
    amntB,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const tokAUnit = await this.getTokAUnit();
    const tokBUnit = await this.getTokBUnit();

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.SET_SWAP,
        new de.DataStack(
          de.Amount.forTokAmount(amntA, tokAUnit),
          de.Amount.forTokAmount(amntB, tokBUnit)
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * addLiquidity adds liquidity to the pool. The final added amount of token A & B will
     be in the same proportion as the pool at that moment as the liquidity provider shouldn't
     change the price of the token while the price is determined by the ratio between A & B.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amntA - The desired amount for token A.
   * @param {number} amntB - The desired amount for token B.
   * @param {number} amntAMin - The minimum acceptable amount for token A.
   * @param {number} amntBMin - The minimum acceptable amount for token B.
   * @param {number} deadline - The deadline for this operation to complete.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async addLiquidity(
    by,
    amntA,
    amntB,
    amntAMin,
    amntBMin,
    deadline,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const tokAUnit = await this.getTokAUnit();
    const tokBUnit = await this.getTokBUnit();

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.ADD_LIQUIDITY,
        new de.DataStack(
          de.Amount.forTokAmount(amntA, tokAUnit),
          de.Amount.forTokAmount(amntB, tokBUnit),
          de.Amount.forTokAmount(amntAMin, tokAUnit),
          de.Amount.forTokAmount(amntBMin, tokBUnit),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(deadline))
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * removeLiquidity removes liquidity from the pool by redeeming token A & B with liquidity tokens.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amntLiq - The amount of liquidity token to return.
   * @param {number} amntAMin - The minimum acceptable amount of token A to redeem.
   * @param {number} amntBMin - The minimum acceptable amount of token B to redeem.
   * @param {any} deadline - The deadline for this operation to complete.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async removeLiquidity(
    by,
    amntLiq,
    amntAMin,
    amntBMin,
    deadline,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const tokAUnit = await this.getTokAUnit();
    const tokBUnit = await this.getTokBUnit();
    const liqTokUnit = await this.getLiqTokUnit();

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.REMOVE_LIQUIDITY,
        new de.DataStack(
          de.Amount.forTokAmount(amntLiq, liqTokUnit),
          de.Amount.forTokAmount(amntAMin, tokAUnit),
          de.Amount.forTokAmount(amntBMin, tokBUnit),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(deadline))
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * swapBForExactA swaps token B for token A where the desired amount of token A is fixed.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amntA - The desired amount for token A.
   * @param {number} amntBMax - The maximum amount of token B the action taker is willing to pay.
   * @param {any} deadline - The deadline for this operation to complete.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async swapBForExactA(
    by,
    amntA,
    amntBMax,
    deadline,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const tokAUnit = await this.getTokAUnit();
    const tokBUnit = await this.getTokBUnit();

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.SWAP_B_FOR_EXACT_A,
        new de.DataStack(
          de.Amount.forTokAmount(amntA, tokAUnit),
          de.Amount.forTokAmount(amntBMax, tokBUnit),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(deadline))
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * swapExactBForA swaps token B for token A where the amount of token B to pay is fixed.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amntAMin - The minimum acceptable amount for token A.
   * @param {number} amntB - The amount of token B to pay.
   * @param {number} deadline - The deadline for this operation to complete.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async swapExactBForA(
    by,
    amntAMin,
    amntB,
    deadline,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const tokAUnit = await this.getTokAUnit();
    const tokBUnit = await this.getTokBUnit();

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.SWAP_EXACT_B_FOR_A,
        new de.DataStack(
          de.Amount.forTokAmount(amntAMin, tokAUnit),
          de.Amount.forTokAmount(amntB, tokBUnit),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(deadline))
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * swapAForExactB swaps token A for token B where the desired amount of token B is fixed.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amntB - The desired amount of token B.
   * @param {number} amntAMax - The maximum amount of token A the action taker is willing to pay.
   * @param {number} deadline - The deadline for this operation to complete.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async swapAForExactB(
    by,
    amntB,
    amntAMax,
    deadline,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const tokAUnit = await this.getTokAUnit();
    const tokBUnit = await this.getTokBUnit();

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.SWAP_A_FOR_EXACT_B,
        new de.DataStack(
          de.Amount.forTokAmount(amntAMax, tokAUnit),
          de.Amount.forTokAmount(amntB, tokBUnit),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(deadline))
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }

  /**
   * swapExactAForB swaps token A for token B where the amount of token A to pay is fixed.
   * @param {acnt.Account | msacnt.MultiSignAccount} by - The action taker.
   * @param {number} amntBMin - The minimum acceptable amount of token B.
   * @param {number} amntA - The amount of token A to pay.
   * @param {number} deadline - The deadline for this operation to complete.
   * @param {string} attachment - The attachment of the action. Defaults to ''.
   * @param {number} fee - The fee to pay for this action. Defaults to md.ExecCtrtFee.DEFAULT.
   * @returns {object} The response returned by the Node API.
   */
  async swapExactAForB(
    by,
    amntBMin,
    amntA,
    deadline,
    attachment = '',
    fee = md.ExecCtrtFee.DEFAULT
  ) {
    const tokAUnit = await this.getTokAUnit();
    const tokBUnit = await this.getTokBUnit();

    const data = await by.executeContractImpl(
      new tx.ExecCtrtFuncTxReq(
        this.ctrtId,
        FuncIdx.SWAP_EXACT_A_FOR_B,
        new de.DataStack(
          de.Amount.forTokAmount(amntA, tokAUnit),
          de.Amount.forTokAmount(amntBMin, tokBUnit),
          new de.Timestamp(md.VSYSTimestamp.fromUnixTs(deadline))
        ),
        md.VSYSTimestamp.now(),
        new md.Str(attachment),
        md.ExecCtrtFee.fromNumber(fee)
      )
    );
    return data;
  }
}
