oceanprotocol/ocean.js

View on GitHub
src/contracts/Datatoken.ts

Summary

Maintainability
F
1 wk
Test Coverage
A
93%
import { ethers, Signer } from 'ethers'
import Decimal from 'decimal.js'
import ERC20Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json'
import ERC20TemplateEnterprise from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20TemplateEnterprise.sol/ERC20TemplateEnterprise.json'
import { amountToUnits, sendTx, ZERO_ADDRESS } from '../utils'
import {
  AbiItem,
  ConsumeMarketFee,
  FreOrderParams,
  FreCreationParams,
  ProviderFees,
  PublishingMarketFee,
  DispenserParams,
  OrderParams,
  DatatokenRoles,
  ReceiptOrEstimate
} from '../@types'
import { Nft } from './NFT'
import { Config } from '../config'
import { SmartContract } from './SmartContract'

export class Datatoken extends SmartContract {
  public abiEnterprise: AbiItem[]
  public nft: Nft

  getDefaultAbi() {
    return ERC20Template.abi as AbiItem[]
  }

  /**
   * Instantiate Datatoken class
   * @param {Signer} signer The signer object.
   * @param {string | number} [network] Network id or name
   * @param {Config} [config] The configuration object.
   * @param {AbiItem[]} [abi] ABI array of the smart contract
   * @param {AbiItem[]} abiEnterprise Enterprise ABI array of the smart contract
   */
  constructor(
    signer: Signer,
    network?: string | number,
    config?: Config,
    abi?: AbiItem[],
    abiEnterprise?: AbiItem[]
  ) {
    super(signer, network, config, abi)
    this.abiEnterprise = abiEnterprise || (ERC20TemplateEnterprise.abi as AbiItem[])
    this.nft = new Nft(this.signer)
  }

  /**
   * Approves a spender to spend a certain amount of datatokens.
   * @param {String} dtAddress Datatoken address
   * @param {String} spender Spender address
   * @param {string} amount Number of datatokens, as number. Will be converted to wei
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>}
   */
  public async approve<G extends boolean = false>(
    dtAddress: string,
    spender: string,
    amount: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)

    const estGas = await dtContract.estimateGas.approve(
      spender,
      amountToUnits(null, null, amount, 18)
    )
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.approve,
      spender,
      amountToUnits(null, null, amount, 18)
    )
    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Creates a new FixedRateExchange setup.
   * @param {String} dtAddress Datatoken address
   * @param {String} address Caller address
   * @param {FixedRateParams} fixedRateParams The parameters required to create a fixed-rate exchange contract.
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>}
   */
  public async createFixedRate<G extends boolean = false>(
    dtAddress: string,
    address: string,
    fixedRateParams: FreCreationParams,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)
    if (!(await this.isDatatokenDeployer(dtAddress, address))) {
      throw new Error(`User is not Datatoken Deployer`)
    }
    if (!fixedRateParams.allowedConsumer) fixedRateParams.allowedConsumer = ZERO_ADDRESS

    const withMint = fixedRateParams.withMint === false ? 0 : 1

    // should check DatatokenDeployer role using NFT level ..

    const estGas = await dtContract.estimateGas.createFixedRate(
      fixedRateParams.fixedRateAddress,
      [
        fixedRateParams.baseTokenAddress,
        fixedRateParams.owner,
        fixedRateParams.marketFeeCollector,
        fixedRateParams.allowedConsumer
      ],
      [
        fixedRateParams.baseTokenDecimals,
        fixedRateParams.datatokenDecimals,
        fixedRateParams.fixedRate,
        fixedRateParams.marketFee,
        withMint
      ]
    )
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.createFixedRate,
      fixedRateParams.fixedRateAddress,
      [
        fixedRateParams.baseTokenAddress,
        fixedRateParams.owner,
        fixedRateParams.marketFeeCollector,
        fixedRateParams.allowedConsumer
      ],
      [
        fixedRateParams.baseTokenDecimals,
        fixedRateParams.datatokenDecimals,
        fixedRateParams.fixedRate,
        fixedRateParams.marketFee,
        withMint
      ]
    )
    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Creates a new Dispenser
   * @param {String} dtAddress Datatoken address
   * @param {String} address Caller address
   * @param {String} dispenserAddress Dispenser contract address
   * @param {DispenserParams} dispenserParams The parameters required to create a dispenser contract.
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} transactionId
   */
  public async createDispenser<G extends boolean = false>(
    dtAddress: string,
    address: string,
    dispenserAddress: string,
    dispenserParams: DispenserParams,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    if (!(await this.isDatatokenDeployer(dtAddress, address))) {
      throw new Error(`User is not Datatoken Deployer`)
    }

    const dtContract = this.getContract(dtAddress)

    if (!dispenserParams.allowedSwapper) dispenserParams.allowedSwapper = ZERO_ADDRESS

    dispenserParams.withMint = dispenserParams.withMint !== false

    // should check DatatokenDeployer role using NFT level ..

    const estGas = await dtContract.estimateGas.createDispenser(
      dispenserAddress,
      dispenserParams.maxTokens,
      dispenserParams.maxBalance,
      dispenserParams.withMint,
      dispenserParams.allowedSwapper
    )
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.createDispenser,
      dispenserAddress,
      dispenserParams.maxTokens,
      dispenserParams.maxBalance,
      dispenserParams.withMint,
      dispenserParams.allowedSwapper
    )
    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Mints datatokens
   * @param {String} dtAddress Datatoken address
   * @param {String} address Minter address
   * @param {String} amount Number of datatokens, as number. Will be converted to wei
   * @param {String} toAddress only if toAddress is different from the minter
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} transactionId
   */
  public async mint<G extends boolean = false>(
    dtAddress: string,
    address: string,
    amount: string,
    toAddress?: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)

    if ((await this.getPermissions(dtAddress, address)).minter !== true) {
      throw new Error(`Caller is not Minter`)
    }

    const capAvailble = await this.getCap(dtAddress)
    if (new Decimal(capAvailble).gte(amount)) {
      const estGas = await dtContract.estimateGas.mint(
        toAddress || address,
        amountToUnits(null, null, amount, 18)
      )
      if (estimateGas) return <ReceiptOrEstimate<G>>estGas

      const trxReceipt = await sendTx(
        estGas,
        this.signer,
        this.config?.gasFeeMultiplier,
        dtContract.mint,
        toAddress || address,
        amountToUnits(null, null, amount, 18)
      )
      return <ReceiptOrEstimate<G>>trxReceipt
    } else {
      throw new Error(`Mint amount exceeds cap available`)
    }
  }

  /**
   * Add Minter for an ERC20 Datatoken
   * only DatatokenDeployer can succeed
   * @param {String} dtAddress Datatoken address
   * @param {String} address caller address
   * @param {String} minter address which is going to be a Minter
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} transactionId
   */
  public async addMinter<G extends boolean = false>(
    dtAddress: string,
    address: string,
    minter: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)

    if ((await this.isDatatokenDeployer(dtAddress, address)) !== true) {
      throw new Error(`Caller is not DatatokenDeployer`)
    }
    // Estimate gas cost for addMinter method
    const estGas = await dtContract.estimateGas.addMinter(minter)
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.addMinter,
      minter
    )

    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Revoke Minter permission for an ERC20 Datatoken
   * only DatatokenDeployer can succeed
   * @param {String} dtAddress Datatoken address
   * @param {String} address caller address
   * @param {String} minter address which will have removed the Minter permission
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>}
   */
  public async removeMinter<G extends boolean = false>(
    dtAddress: string,
    address: string,
    minter: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)

    if ((await this.isDatatokenDeployer(dtAddress, address)) !== true) {
      throw new Error(`Caller is not DatatokenDeployer`)
    }

    const estGas = await dtContract.estimateGas.removeMinter(minter)
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.removeMinter,
      minter
    )

    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   *  Adds a payment manager on a datatoken to a desired address.(can set who's going to collect fee when consuming orders)
   * only DatatokenDeployer can succeed
   * @param {String} dtAddress Datatoken address
   * @param {String} address Caller address
   * @param {String} paymentManager  The address of the payment manager
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} transactionId
   */
  public async addPaymentManager<G extends boolean = false>(
    dtAddress: string,
    address: string,
    paymentManager: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)

    if ((await this.isDatatokenDeployer(dtAddress, address)) !== true) {
      throw new Error(`Caller is not DatatokenDeployer`)
    }

    const estGas = await dtContract.estimateGas.addPaymentManager(paymentManager)
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.addPaymentManager,
      paymentManager
    )

    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Revoke paymentManager permission for an ERC20 Datatoken
   * only DatatokenDeployer can succeed
   * @param {String} dtAddress Datatoken address
   * @param {String} address User address
   * @param {String} paymentManager User which will be removed from paymentManager permission
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} trxReceipt
   */
  public async removePaymentManager<G extends boolean = false>(
    dtAddress: string,
    address: string,
    paymentManager: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)

    if ((await this.isDatatokenDeployer(dtAddress, address)) !== true) {
      throw new Error(`Caller is not DatatokenDeployer`)
    }

    const estGas = await dtContract.estimateGas.removePaymentManager(paymentManager)
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.removePaymentManager,
      paymentManager
    )

    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * This function allows to set a new PaymentCollector (receives DT when consuming)
   * If not set the paymentCollector is the NFT Owner
   * only NFT owner can call
   * @param dtAddress Datatoken address
   * @param address Caller address
   * @param paymentCollector User to be set as new payment collector
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} trxReceipt
   */
  public async setPaymentCollector<G extends boolean = false>(
    dtAddress: string,
    address: string,
    paymentCollector: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)
    const isPaymentManager = (await this.getPermissions(dtAddress, address))
      .paymentManager
    const nftAddress = !isPaymentManager && (await this.getNFTAddress(dtAddress))
    const isNftOwner = nftAddress && (await this.nft.getNftOwner(nftAddress)) === address
    const nftPermissions =
      nftAddress && !isNftOwner && (await this.nft.getNftPermissions(nftAddress, address))
    const isDatatokenDeployer = nftPermissions?.deployERC20
    if (!isPaymentManager && !isNftOwner && !isDatatokenDeployer) {
      throw new Error(`Caller is not Fee Manager, owner or Datatoken Deployer`)
    }

    const estGas = await dtContract.estimateGas.setPaymentCollector(paymentCollector)
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.setPaymentCollector,
      paymentCollector
    )
    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * getPaymentCollector - It returns the current paymentCollector
   * @param dtAddress datatoken address
   * @return {Promise<string>}
   */
  public async getPaymentCollector(dtAddress: string): Promise<string> {
    const dtContract = this.getContract(dtAddress)
    const paymentCollector = await dtContract.getPaymentCollector()
    return paymentCollector
  }

  /**
   * Transfer tokens(as number) from address to toAddress
   * @param {String} dtAddress Datatoken address
   * @param {String} toAddress Receiver address
   * @param {String} amount Number of datatokens, as number. Will be converted to wei.
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} transactionId
   */
  public async transfer<G extends boolean = false>(
    dtAddress: string,
    toAddress: string,
    amount: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    return this.transferWei(
      dtAddress,
      toAddress,
      await amountToUnits(null, null, amount, 18),
      estimateGas
    )
  }

  /**
   * Transfer in wei from address to toAddress
   * @param {String} dtAddress Datatoken address
   * @param {String} toAddress Receiver address
   * @param {String} amount Number of datatokens (number) expressed as wei
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} transactionId
   */
  public async transferWei<G extends boolean = false>(
    dtAddress: string,
    toAddress: string,
    amount: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)

    const estGas = await dtContract.estimateGas.transfer(toAddress, amount)
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.transfer,
      toAddress,
      amount
    )
    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Start Order: called by payer or consumer prior ordering a service consume on a marketplace.
   * @param {String} dtAddress Datatoken address
   * @param {String} consumer Consumer Address
   * @param {Number} serviceIndex  Service index in the metadata
   * @param {providerFees} providerFees provider fees
   * @param {consumeMarketFee} ConsumeMarketFee consume market fees
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} string
   */
  public async startOrder<G extends boolean = false>(
    dtAddress: string,
    consumer: string,
    serviceIndex: number,
    providerFees: ProviderFees,
    consumeMarketFee?: ConsumeMarketFee,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)
    if (!consumeMarketFee) {
      consumeMarketFee = {
        consumeMarketFeeAddress: ZERO_ADDRESS,
        consumeMarketFeeToken: ZERO_ADDRESS,
        consumeMarketFeeAmount: '0'
      }
    }

    const estGas = await dtContract.estimateGas.startOrder(
      consumer,
      serviceIndex,
      providerFees,
      consumeMarketFee
    )
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.startOrder,
      consumer,
      serviceIndex,
      providerFees,
      consumeMarketFee
    )
    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Reuse Order: called by payer or consumer having a valid order, but with expired provider access.
   * Pays the provider fee again, but it will not require a new datatoken payment
   * Requires previous approval of provider fee.
   * @param {String} dtAddress Datatoken address
   * @param {String} orderTxId previous valid order
   * @param {providerFees} providerFees provider fees
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} string
   */
  public async reuseOrder<G extends boolean = false>(
    dtAddress: string,
    orderTxId: string,
    providerFees: ProviderFees,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress)

    const estGas = await dtContract.estimateGas.reuseOrder(orderTxId, providerFees)
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.reuseOrder,
      orderTxId,
      providerFees
    )
    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Buys 1 DT from the FRE and then startsOrder, while burning that DT
   * @param {String} dtAddress Datatoken address
   * @param {OrderParams} orderParams  The parameters required to place an order.
   * @param {FreParams} freParams The parameters required to buy from a fixed-rate exchange.
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>}
   */
  public async buyFromFreAndOrder<G extends boolean = false>(
    dtAddress: string,
    orderParams: OrderParams,
    freParams: FreOrderParams,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress, this.abiEnterprise)

    const freContractParams = await this.getFreOrderParams(freParams)

    const estGas = await dtContract.estimateGas.buyFromFreAndOrder(
      orderParams,
      freContractParams
    )
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.buyFromFreAndOrder,
      orderParams,
      freContractParams
    )
    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Gets 1 DT from dispenser and then startsOrder, while burning that DT
   * @param {String} dtAddress Datatoken address
   * @param {OrderParams} orderParams - The parameters required to place an order.
   * @param {String} dispenserContract  dispenser address
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>}
   */
  public async buyFromDispenserAndOrder<G extends boolean = false>(
    dtAddress: string,
    orderParams: OrderParams,
    dispenserContract: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(dtAddress, this.abiEnterprise)

    const estGas = await dtContract.estimateGas.buyFromDispenserAndOrder(
      orderParams,
      dispenserContract
    )
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.buyFromDispenserAndOrder,
      orderParams,
      dispenserContract
    )
    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /** setData
   * This function allows to store data with a preset key (keccak256(dtAddress)) into NFT 725 Store
   * only DatatokenDeployer can succeed
   * @param {String} dtAddress Datatoken address
   * @param {String} address User address
   * @param {String} value Data to be stored into 725Y standard
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} transactionId
   */
  public async setData<G extends boolean = false>(
    dtAddress: string,
    address: string,
    value: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    if (!(await this.isDatatokenDeployer(dtAddress, address))) {
      throw new Error(`User is not Datatoken Deployer`)
    }

    const dtContract = this.getContract(dtAddress)

    const valueHex = ethers.utils.hexlify(ethers.utils.toUtf8Bytes(value))

    const estGas = await dtContract.estimateGas.setData(valueHex)
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.setData,
      valueHex
    )

    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Clean Datatoken level Permissions (minters, paymentManager and reset the paymentCollector) for an ERC20 Datatoken
   * Only NFT Owner (at 721 level) can call it.
   * @param {string} dtAddress Datatoken address where we want to clean permissions
   * @param {string} address User adress
   * @param {Boolean} estimateGas if True, return gas estimate
   * @return {Promise<ReceiptOrEstimate>} transactionId
   */
  public async cleanPermissions<G extends boolean = false>(
    dtAddress: string,
    address: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    if ((await this.nft.getNftOwner(await this.getNFTAddress(dtAddress))) !== address) {
      throw new Error('Caller is NOT Nft Owner')
    }
    const dtContract = this.getContract(dtAddress)

    const estGas = await dtContract.estimateGas.cleanPermissions()
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.cleanPermissions
    )

    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Returns ERC20 Datatoken user's permissions for a datatoken
   * @param {String} dtAddress Datatoken adress
   * @param {String} address user adress
   * @return {Promise<DatatokenRoles>}
   */
  public async getPermissions(
    dtAddress: string,
    address: string
  ): Promise<DatatokenRoles> {
    const dtContract = this.getContract(dtAddress)
    const roles = await dtContract.permissions(address)
    return roles
  }

  /**
   * Returns the Datatoken cap
   * @param {String} dtAddress Datatoken adress
   * @return {Promise<string>}
   */
  public async getCap(dtAddress: string): Promise<string> {
    const dtContract = this.getContract(dtAddress)
    const cap = await dtContract.cap()
    return await this.unitsToAmount(null, cap, 18)
  }

  /**
   * It returns the token decimals, how many supported decimal points
   * @param {String} dtAddress Datatoken adress
   * @return {Promise<number>}
   */
  public async getDecimals(dtAddress: string): Promise<number> {
    const dtContract = this.getContract(dtAddress)
    const decimals = await dtContract.decimals()
    return decimals
  }

  /**
   * It returns the token template index.
   * @param {String} dtAddress Datatoken adress
   * @return {Promise<number>}
   */
  public async getId(dtAddress: string): Promise<number> {
    const dtContract = this.getContract(dtAddress)
    const id = await dtContract.getId()
    return id
  }

  /**
   * It returns the token symbol
   * @param {String} dtAddress Datatoken adress
   * @return {Promise<number>}
   */
  public async getSymbol(dtAddress: string): Promise<string> {
    const dtContract = this.getContract(dtAddress)
    const symbol = await dtContract.symbol()
    return symbol
  }

  /**
   *  It returns the name of the token
   * @param {String} dtAddress Datatoken adress
   * @return {Promise<number>}
   */
  public async getName(dtAddress: string): Promise<string> {
    const dtContract = this.getContract(dtAddress)
    const name = await dtContract.name()
    return name
  }

  /**
   * It returns the token decimals, how many supported decimal points
   * @param {String} dtAddress Datatoken adress
   * @return {Promise<number>}
   */
  public async getNFTAddress(dtAddress: string): Promise<string> {
    const dtContract = this.getContract(dtAddress)
    const nftAddress = await dtContract.getERC721Address()
    return nftAddress
  }

  /**
   * It returns the list of fixedRateExchanges created for this datatoken.
   * @param {String} dtAddress Datatoken adress
   * @return {Promise<number>}
   */
  public async getFixedRates(dtAddress: string): Promise<any[]> {
    const dtContract = this.getContract(dtAddress)
    const fixedRates = await dtContract.getFixedRates()
    return fixedRates
  }

  /**
   * It returns the list of dispensers created for this datatoken.
   * @param {String} dtAddress Datatoken adress
   * @return {Promise<number>}
   */
  public async getDispensers(dtAddress: string): Promise<any[]> {
    const dtContract = this.getContract(dtAddress)
    const dispensers = await dtContract.getDispensers()
    return dispensers
  }

  /**
   *  Returns true if address has deployERC20 role
   * @param {String} dtAddress Datatoken adress
   * @param {String} dtAddress Datatoken adress
   * @return {Promise<boolean>}
   */
  public async isDatatokenDeployer(dtAddress: string, address: string): Promise<boolean> {
    const dtContract = this.getContract(dtAddress)
    const isDatatokenDeployer = await dtContract.isERC20Deployer(address)
    return isDatatokenDeployer
  }

  /**
   * Get Address Balance for datatoken
   * @param {String} dtAddress Datatoken adress
   * @param {String} address user adress
   * @return {Promise<String>} balance  Number of datatokens. Will be converted from wei
   */
  public async balance(datatokenAddress: string, address: string): Promise<string> {
    const dtContract = this.getContract(datatokenAddress)
    const balance = await dtContract.balanceOf(address)
    return await this.unitsToAmount(null, balance, 18)
  }

  /**
   * Allows to set the fee required by the publisherMarket
   * only publishMarketFeeAddress can call it
   * @param {string} datatokenAddress Datatoken adress
   * @param {string} publishMarketFeeAddress  new publish Market Fee Address
   * @param {string} publishMarketFeeToken new publish Market Fee Token
   * @param {string} publishMarketFeeAmount new fee amount
   * @param {String} address user adress
   * @param {Boolean} estimateGas if True, return gas estimate
   */
  public async setPublishingMarketFee<G extends boolean = false>(
    datatokenAddress: string,
    publishMarketFeeAddress: string,
    publishMarketFeeToken: string,
    publishMarketFeeAmount: string,
    address: string,
    estimateGas?: G
  ): Promise<ReceiptOrEstimate<G>> {
    const dtContract = this.getContract(datatokenAddress)
    const mktFeeAddress = (await dtContract.getPublishingMarketFee())[0]
    if (mktFeeAddress !== address) {
      throw new Error(`Caller is not the Publishing Market Fee Address`)
    }
    const estGas = await dtContract.estimateGas.setPublishingMarketFee(
      publishMarketFeeAddress,
      publishMarketFeeToken,
      publishMarketFeeAmount
    )
    if (estimateGas) return <ReceiptOrEstimate<G>>estGas

    const trxReceipt = await sendTx(
      estGas,
      this.signer,
      this.config?.gasFeeMultiplier,
      dtContract.setPublishingMarketFee,
      publishMarketFeeAddress,
      publishMarketFeeToken,
      publishMarketFeeAmount
    )

    return <ReceiptOrEstimate<G>>trxReceipt
  }

  /**
   * Returns the current fee set by the publishing market
   * @param {String} datatokenAddress Datatoken adress
   * @return {Promise<PublishingMarketFee>} Current fee set by the publishing market
   */
  public async getPublishingMarketFee(
    datatokenAddress: string
  ): Promise<PublishingMarketFee> {
    const dtContract = this.getContract(datatokenAddress)

    const publishingMarketFee = await dtContract.getPublishingMarketFee()
    const returnValues = {
      publishMarketFeeAddress: publishingMarketFee[0],
      publishMarketFeeToken: publishingMarketFee[1],
      publishMarketFeeAmount: publishingMarketFee[2].toString()
    }
    return returnValues
  }

  private async getFreOrderParams(freParams: FreOrderParams): Promise<any> {
    return {
      exchangeContract: freParams.exchangeContract,
      exchangeId: freParams.exchangeId,
      maxBaseTokenAmount: await amountToUnits(
        this.signer,
        freParams.baseTokenAddress,
        freParams.maxBaseTokenAmount,
        freParams.baseTokenDecimals
      ),
      swapMarketFee: await amountToUnits(
        this.signer,
        freParams.baseTokenAddress,
        freParams.swapMarketFee,
        freParams.baseTokenDecimals
      ),

      marketFeeAddress: freParams.marketFeeAddress
    }
  }
}