oceanprotocol/ocean.js

View on GitHub
src/utils/OrderUtils.ts

Summary

Maintainability
D
2 days
Test Coverage
F
3%
import { Signer } from 'ethers'
import {
  ProviderInstance,
  Datatoken,
  Dispenser,
  Config,
  OrderParams,
  Asset,
  FreOrderParams,
  approve,
  FixedRateExchange,
  ConsumeMarketFee,
  ProviderFees,
  ZERO_ADDRESS,
  approveWei
} from '../index'
import Decimal from 'decimal.js'

/**
 * Orders an asset based on the specified pricing schema and configuration.
 * @param {Asset} asset - The asset to be ordered.
 * @param {Signer} consumerAccount - The signer account of the consumer.
 * @param {Config} config - The configuration settings.
 * @param {Datatoken} datatoken - The Datatoken instance.
 * @param {string} [providerUrl] - Optional the consumer address
 * @param {string} [consumerAccount] - Optional the consumer address
 * @param {ConsumeMarketFee} [consumeMarketOrderFee] - Optional consume market fee.
 *  @param {ProviderFees} [providerFees] - Optional provider fees
 * @param {string} [consumeMarketFixedSwapFee='0'] - Fixed swap fee for consuming the market.
 * @param {number} [datatokenIndex=0] - Index of the datatoken within the asset.
 * @param {number} [serviceIndex=0] - Index of the service within the asset.
 * @param {number} [fixedRateIndex=0] - Index of the fixed rate within the pricing schema.
 * @returns {Promise<void>} - A promise that resolves when the asset order process is completed.
 * @throws {Error} If the pricing schema is not supported or if required indexes are invalid.
 */
export async function orderAsset(
  asset: Asset,
  consumerAccount: Signer,
  config: Config,
  datatoken: Datatoken,
  providerUrl?: string,
  consumerAddress?: string,
  consumeMarketOrderFee?: ConsumeMarketFee,
  providerFees?: ProviderFees,
  consumeMarketFixedSwapFee: string = '0',
  datatokenIndex: number = 0,
  serviceIndex: number = 0,
  fixedRateIndex: number = 0
) {
  if (!consumeMarketOrderFee)
    consumeMarketOrderFee = {
      consumeMarketFeeAddress: '0x0000000000000000000000000000000000000000',
      consumeMarketFeeAmount: '0',
      consumeMarketFeeToken:
        asset.stats.price.tokenAddress || '0x0000000000000000000000000000000000000000'
    }

  if (!asset.datatokens[datatokenIndex].address)
    throw new Error(
      `The datatoken with index: ${datatokenIndex} does not exist for the asset with did: ${asset.id}`
    )

  if (!asset.services[serviceIndex].id)
    throw new Error(
      `There is no service with index: ${serviceIndex} defined for the asset with did: ${asset.id}`
    )

  const templateIndex = await datatoken.getId(asset.datatokens[datatokenIndex].address)

  const fixedRates = await datatoken.getFixedRates(
    asset.datatokens[datatokenIndex].address
  )
  const dispensers = await datatoken.getDispensers(
    asset.datatokens[datatokenIndex].address
  )
  const publishMarketFees = await datatoken.getPublishingMarketFee(
    asset.datatokens[datatokenIndex].address
  )
  const pricingType =
    fixedRates.length > 0 ? 'fixed' : dispensers.length > 0 ? 'free' : 'NOT_ALLOWED'

  const fees =
    providerFees ||
    (
      await ProviderInstance.initialize(
        asset.id,
        asset.services[serviceIndex].id,
        0,
        await consumerAccount.getAddress(),
        providerUrl || config.providerUri
      )
    ).providerFee

  if (
    fees &&
    fees.providerFeeAddress !== ZERO_ADDRESS &&
    fees.providerFeeAmount &&
    parseInt(fees.providerFeeAmount) > 0
  ) {
    try {
      await approveWei(
        consumerAccount,
        config,
        await consumerAccount.getAddress(),
        fees.providerFeeToken,
        asset.services[0].datatokenAddress,
        fees.providerFeeAmount
      )
    } catch (error) {
      throw new Error(`Failed to approve provider fee token ${fees.providerFeeToken}`)
    }
  }

  const orderParams = {
    consumer: consumerAddress || (await consumerAccount.getAddress()),
    serviceIndex,
    _providerFee: fees,
    _consumeMarketFee: consumeMarketOrderFee
  } as OrderParams
  switch (pricingType) {
    case 'free': {
      if (templateIndex === 1) {
        const dispenser = new Dispenser(config.dispenserAddress, consumerAccount)
        const dispenserTx = await dispenser.dispense(
          asset.datatokens[datatokenIndex].address,
          '1',
          await consumerAccount.getAddress()
        )
        if (!dispenserTx) {
          throw new Error(`Failed to dispense !`)
        }
        await dispenserTx.wait()
        return await datatoken.startOrder(
          asset.datatokens[datatokenIndex].address,
          orderParams.consumer,
          orderParams.serviceIndex,
          orderParams._providerFee,
          orderParams._consumeMarketFee
        )
      }
      if (templateIndex === 2 || templateIndex === 4) {
        return await datatoken.buyFromDispenserAndOrder(
          asset.services[serviceIndex].datatokenAddress,
          orderParams,
          config.dispenserAddress
        )
      }
      break
    }
    case 'fixed': {
      const fre = new FixedRateExchange(config.fixedRateExchangeAddress, consumerAccount)

      if (!fixedRates[fixedRateIndex].id)
        throw new Error(
          `There is no fixed rate exchange with index: ${serviceIndex} for the asset with did: ${asset.id}`
        )
      const fees = await fre.getFeesInfo(fixedRates[fixedRateIndex].id)
      const exchange = await fre.getExchange(fixedRates[fixedRateIndex].id)

      const { baseTokenAmount } = await fre.calcBaseInGivenDatatokensOut(
        fees.exchangeId,
        '1',
        consumeMarketOrderFee.consumeMarketFeeAmount
      )

      const price = new Decimal(+baseTokenAmount || 0)
        .add(new Decimal(consumeMarketOrderFee.consumeMarketFeeAmount || 0))
        .add(new Decimal(+publishMarketFees.publishMarketFeeAmount || 0))
        .toString()

      const freParams = {
        exchangeContract: config.fixedRateExchangeAddress,
        exchangeId: fees.exchangeId,
        maxBaseTokenAmount: price,
        baseTokenAddress: exchange.baseToken,
        baseTokenDecimals: parseInt(exchange.btDecimals) || 18,
        swapMarketFee: consumeMarketFixedSwapFee,
        marketFeeAddress: publishMarketFees.publishMarketFeeAddress
      } as FreOrderParams

      if (templateIndex === 1) {
        const tx: any = await approve(
          consumerAccount,
          config,
          await consumerAccount.getAddress(),
          exchange.baseToken,
          config.fixedRateExchangeAddress,
          price,
          false
        )
        const txApprove = typeof tx !== 'number' ? await tx.wait() : tx
        if (!txApprove) {
          throw new Error(`Failed to approve ${exchange.baseToken} !`)
        }
        const freTx = await fre.buyDatatokens(
          exchange.exchangeId,
          '1',
          price,
          publishMarketFees.publishMarketFeeAddress,
          consumeMarketFixedSwapFee
        )
        const buyDtTx = await freTx.wait()
        if (!buyDtTx) {
          throw new Error(`Failed to buy datatoken from fixed rate!`)
        }
        return await datatoken.startOrder(
          asset.datatokens[datatokenIndex].address,
          orderParams.consumer,
          orderParams.serviceIndex,
          orderParams._providerFee,
          orderParams._consumeMarketFee
        )
      }
      if (templateIndex === 2 || templateIndex === 4) {
        const tx: any = await approve(
          consumerAccount,
          config,
          await consumerAccount.getAddress(),
          exchange.baseToken,
          asset.datatokens[datatokenIndex].address,
          price,
          false
        )
        if (!tx) {
          throw new Error(`Failed to approve ${exchange.baseToken} !`)
        }
        const txApprove = typeof tx !== 'number' ? await tx.wait() : tx
        if (!txApprove) {
          throw new Error(`Failed to confirm/mine approval transaction!`)
        }
        const txBuy = await datatoken.buyFromFreAndOrder(
          asset.datatokens[datatokenIndex].address,
          orderParams,
          freParams
        )
        return txBuy
      }
      break
    }
    default:
      throw new Error('Pricing schema not supported !')
  }
}