DeFiCh/jellyfish

View on GitHub
packages/jellyfish-transaction-builder/src/txn/txn_builder_account.ts

Summary

Maintainability
A
1 hr
Test Coverage
import BigNumber from 'bignumber.js'
import {
  AccountToAccount, AccountToUtxos, UtxosToAccount, SetFutureSwap,
  DeFiTransactionConstants, OP_CODES, Script, Transaction, TransactionSegWit, Vout, TransferDomain
} from '@defichain/jellyfish-transaction'
import { P2WPKHTxnBuilder } from './txn_builder'
import { TxnBuilderError, TxnBuilderErrorType } from './txn_builder_error'
import { Prevout } from '../provider'

export class TxnBuilderAccount extends P2WPKHTxnBuilder {
  /**
   * Requires UTXO in the same amount + fees to create a transaction.
   *
   * @param {UtxosToAccount} utxosToAccount txn to create
   * @param {Script} changeScript to send unspent to after deducting the (converted + fees)
   * @throws {TxnBuilderError} if 'utxosToAccount.to' length is less than `1`
   * @throws {TxnBuilderError} if 'utxosToAccount.to[any].balances' length is not `1`
   * @throws {TxnBuilderError} if 'utxosToAccount.to[any].balances[0].token' is not `0`\
   * @returns {Promise<TransactionSegWit>}
   */
  async utxosToAccount (utxosToAccount: UtxosToAccount, changeScript: Script): Promise<TransactionSegWit> {
    if (utxosToAccount.to.length < 1) {
      throw new TxnBuilderError(TxnBuilderErrorType.INVALID_UTXOS_TO_ACCOUNT_OUTPUT,
        'Conversion output `utxosToAccount.to` array length must be greater than or equal to one'
      )
    }

    for (let i = 0; i < utxosToAccount.to.length; i++) {
      const sb = utxosToAccount.to[i]
      if (sb.balances.length !== 1) {
        throw new TxnBuilderError(TxnBuilderErrorType.INVALID_UTXOS_TO_ACCOUNT_OUTPUT,
          'Each `utxosToAccount.to` array `balances` array length must be one'
        )
      }

      if (sb.balances[0].token !== 0x00) {
        throw new TxnBuilderError(TxnBuilderErrorType.INVALID_UTXOS_TO_ACCOUNT_OUTPUT,
          'Each `utxosToAccount.to` array `balances[0].token` must be 0x00, only DFI supported'
        )
      }
    }

    const amountToConvert = utxosToAccount.to.reduce((total, dest) => (
      total.plus(dest.balances[0].amount)
    ), new BigNumber(0))

    return await super.createDeFiTx(
      OP_CODES.OP_DEFI_TX_UTXOS_TO_ACCOUNT(utxosToAccount),
      changeScript,
      amountToConvert
    )
  }

  /**
   *
   * @param {AccountToUtxos} accountToUtxos txn to create
   * @param {Script} destinationScript vout destination, for both utxos minted and change after deducted fee
   * @throws {TxnBuilderError} if 'accountToUtxos.balances' length is not `1`
   * @throws {TxnBuilderError} if 'accountToUtxos.balances[0].token' is not `0`
   * @throws {TxnBuilderError} if 'accountToUtxos.mintingOutputsStart' is not `2`, vout[0] = DfTx, vout[1] = change, vout[2] = new minted utxos
   * @returns {Promise<TransactionSegWit>}
   */
  async accountToUtxos (accountToUtxos: AccountToUtxos, destinationScript: Script): Promise<TransactionSegWit> {
    if (accountToUtxos.balances.length !== 1) {
      throw new TxnBuilderError(TxnBuilderErrorType.INVALID_ACCOUNT_TO_UTXOS_INPUT,
        'Conversion output `accountToUtxos.balances` array length must be one'
      )
    }

    if (accountToUtxos.balances[0].token !== 0x00) {
      throw new TxnBuilderError(TxnBuilderErrorType.INVALID_ACCOUNT_TO_UTXOS_INPUT,
        '`accountToUtxos.balances[0].token` must be 0x00, only DFI support'
      )
    }

    if (accountToUtxos.mintingOutputsStart !== 2) {
      throw new TxnBuilderError(TxnBuilderErrorType.INVALID_ACCOUNT_TO_UTXOS_INPUT,
        '`accountToUtxos.mintingOutputsStart` must be `2` for simplicity'
      )
    }

    const minFee = new BigNumber(0.001)
    const { prevouts, vin, total } = await this.collectPrevouts(minFee)

    const deFiOut: Vout = {
      value: new BigNumber(0),
      script: {
        stack: [
          OP_CODES.OP_RETURN,
          OP_CODES.OP_DEFI_TX_ACCOUNT_TO_UTXOS(accountToUtxos)
        ]
      },
      tokenId: 0x00
    }

    const out: Vout = {
      value: accountToUtxos.balances[0].amount,
      script: destinationScript,
      tokenId: 0x00
    }

    const change: Vout = {
      value: total,
      script: destinationScript,
      tokenId: 0x00
    }

    const txn: Transaction = {
      version: DeFiTransactionConstants.Version,
      vin: vin,
      vout: [deFiOut, change, out],
      lockTime: 0x00000000
    }

    const fee = await this.calculateFee(txn)
    change.value = total.minus(fee)

    return await this.sign(txn, prevouts)
  }

  async accountToAccount (accountToAccount: AccountToAccount, changeScript: Script): Promise<TransactionSegWit> {
    return await super.createDeFiTx(
      OP_CODES.OP_DEFI_TX_ACCOUNT_TO_ACCOUNT(accountToAccount),
      changeScript
    )
  }

  /**
   * FutureSwap transaction.
   *
   * @param {SetFutureSwap} futureSwap txn to create
   * @param {Script} changeScript to send unspent to after deducting the (converted + fees)
   * @returns {Promise<TransactionSegWit>}
   */

  async futureSwap (futureSwap: SetFutureSwap, changeScript: Script): Promise<TransactionSegWit> {
    return await super.createDeFiTx(
      OP_CODES.OP_DEFI_TX_FUTURE_SWAP(futureSwap),
      changeScript
    )
  }

  /**
  * TransferDomain transaction.
  *
  * @param {TransferDomain} transferDomain txn to create
  * @param {Script} changeScript to send unspent to after deducting the (converted + fees)
  * @param {Prevout[]} utxos to be speficically provided if needed
  * @returns {Promise<TransactionSegWit>}
  */

  async transferDomain (transferDomain: TransferDomain, changeScript: Script, utxos: Prevout[] = []): Promise<TransactionSegWit> {
    return await super.createDeFiTx(
      OP_CODES.OP_DEFI_TX_TRANSFER_DOMAIN(transferDomain),
      changeScript,
      new BigNumber(0),
      utxos
    )
  }
}