oceanprotocol/ocean.js

View on GitHub
src/utils/TokenUtils.ts

Summary

Maintainability
A
1 hr
Test Coverage
B
89%
import Decimal from 'decimal.js'
import { ethers, Signer } from 'ethers'
import { amountToUnits, unitsToAmount, minAbi, sendTx, LoggerInstance } from '.'
import { Config } from '../config'
import { ReceiptOrEstimate, ReceiptOrDecimal } from '../@types'

/**
 * Approve spender to spent amount tokens
 * @param {Signer} signer - The signer object
 * @param {Config} config - The config object
 * @param {string} account - The address of the caller
 * @param {string} tokenAddress - The address of the token
 * @param {string} spender - The address of the spender
 * @param {String} amount amount of ERC20 Datatokens (always expressed as wei)
 * @param {boolean} force  if true, will overwrite any previous allowence. Else, will check if allowence is enough and will not send a transaction if it's not needed
 * @param {number} [tokenDecimals] optional number of decimals of the token
 * @param {boolean} [estimateGas]  if true, returns the estimate gas cost for calling the method
 */
export async function approve<G extends boolean = false>(
  signer: Signer,
  config: Config,
  account: string,
  tokenAddress: string,
  spender: string,
  amount: string,
  force = false,
  tokenDecimals?: number,
  estimateGas?: G
): Promise<ReceiptOrDecimal<G> | number> {
  const tokenContract = new ethers.Contract(tokenAddress, minAbi, signer)
  if (!force) {
    const currentAllowence = await allowance(signer, tokenAddress, account, spender)
    if (new Decimal(currentAllowence).greaterThanOrEqualTo(new Decimal(amount))) {
      return <ReceiptOrDecimal<G>>new Decimal(currentAllowence).toNumber()
    }
  }
  const amountFormatted = await amountToUnits(signer, tokenAddress, amount, tokenDecimals)
  const estGas = await tokenContract.estimateGas.approve(spender, amountFormatted)
  if (estimateGas) return <ReceiptOrDecimal<G>>(<unknown>new Decimal(estGas.toString()))

  const trxReceipt = await sendTx(
    estGas,
    signer,
    config?.gasFeeMultiplier,
    tokenContract.approve,
    spender,
    amountFormatted
  )
  return <ReceiptOrDecimal<G>>trxReceipt
}

/**
 * Approve spender to spent amount tokens
 * @param {Signer} signer - The signer object
 * @param {Config} config - The config object
 * @param {string} account - The address of the caller
 * @param {string} tokenAddress - The address of the token
 * @param {string} spender - The address of the spender
 * @param {string} amount amount of ERC20 tokens (always expressed as wei)
 * @param {boolean} force  if true, will overwrite any previous allowence. Else, will check if allowence is enough and will not send a transaction if it's not needed
 * @param {boolean} [estimateGas]  if true, returns the estimate gas cost for calling the method
 */
export async function approveWei<G extends boolean = false>(
  signer: Signer,
  config: Config,
  account: string,
  tokenAddress: string,
  spender: string,
  amount: string,
  force = false,
  estimateGas?: G
): Promise<ReceiptOrEstimate<G>> {
  const tokenContract = new ethers.Contract(tokenAddress, minAbi, signer)
  if (!force) {
    const currentAllowence = await allowanceWei(signer, tokenAddress, account, spender)
    if (ethers.BigNumber.from(currentAllowence).gt(ethers.BigNumber.from(amount))) {
      return <ReceiptOrEstimate<G>>ethers.BigNumber.from(currentAllowence)
    }
  }
  let result = null

  const estGas = await tokenContract.estimateGas.approve(spender, amount)
  if (estimateGas) return <ReceiptOrEstimate<G>>estGas

  try {
    result = await sendTx(
      estGas,
      signer,
      config?.gasFeeMultiplier,
      tokenContract.approve,
      spender,
      amount
    )
  } catch (e) {
    LoggerInstance.error(
      `ERROR: Failed to approve spender to spend tokens : ${e.message}`
    )
  }
  return result
}

/**
 * Moves amount tokens from the caller’s account to recipient.
 * @param {Signer} signer - The signer object
 * @param {Config} config - The config object
 * @param {string} tokenAddress - The address of the token
 * @param {string} recipient - The address of the tokens receiver
 * @param {String} amount amount of ERC20 Datatokens (not as wei)
 * @param {String} estimateGas  if true returns the gas estimate
 */
export async function transfer<G extends boolean = false>(
  signer: Signer,
  config: Config,
  tokenAddress: string,
  recipient: string,
  amount: string,
  estimateGas?: G
): Promise<ReceiptOrEstimate<G>> {
  const tokenContract = new ethers.Contract(tokenAddress, minAbi, signer)
  const amountFormatted = await amountToUnits(signer, tokenAddress, amount)
  const estGas = await tokenContract.estimateGas.transfer(recipient, amountFormatted)
  if (estimateGas) return <ReceiptOrEstimate<G>>estGas

  const trxReceipt = await sendTx(
    estGas,
    signer,
    config?.gasFeeMultiplier,
    tokenContract.transfer,
    recipient,
    amountFormatted
  )
  return <ReceiptOrEstimate<G>>trxReceipt
}

/**
 * Get Allowance for any Datatoken
 * @param {Signer} signer - The signer object
 * @param {string} tokenAddress - The address of the token
 * @param {string} account - The address of the caller
 * @param {string} spender - The address of the spender
 * @param {number} tokenDecimals optional number of decimals of the token
 */
export async function allowance(
  signer: Signer,
  tokenAddress: string,
  account: string,
  spender: string,
  tokenDecimals?: number
): Promise<string> {
  const tokenContract = new ethers.Contract(tokenAddress, minAbi, signer)
  const trxReceipt = await tokenContract.allowance(account, spender)

  return await unitsToAmount(signer, tokenAddress, trxReceipt, tokenDecimals)
}

/**
 * Get balance for any Datatoken
 * @param {Signer} signer - The signer object
 * @param {string} tokenAddress - The address of the token
 * @param {string} account - The address of the caller
 * @param {number} [tokenDecimals] optional number of decimals of the token
 */
export async function balance(
  signer: Signer,
  tokenAddress: string,
  account: string,
  tokenDecimals?: number
): Promise<string> {
  const tokenContract = new ethers.Contract(tokenAddress, minAbi, signer)
  const trxReceipt = await tokenContract.balanceOf(account)

  return await unitsToAmount(signer, tokenAddress, trxReceipt, tokenDecimals)
}

/**
 * Get Allowance in wei for any erc20
 * @param {Signer} signer - The signer object
 * @param {string} tokenAddress - The address of the token
 * @param {string} account - The address of the caller
 * @param {string} spender - The address of the spneder
 */
export async function allowanceWei(
  signer: Signer,
  tokenAddress: string,
  account: string,
  spender: string
): Promise<string> {
  const tokenContract = new ethers.Contract(tokenAddress, minAbi, signer)
  return await tokenContract.allowance(account, spender)
}

/**
 * Get decimals for any Datatoken
 * @param {Signer} signer - The signer object
 * @param {String} tokenAddress - The address of the token
 * @return {Promise<number>} Number of decimals of the token
 */
export async function decimals(signer: Signer, tokenAddress: string): Promise<number> {
  const tokenContract = new ethers.Contract(tokenAddress, minAbi, signer)
  return await tokenContract.decimals()
}