kodadot/nft-gallery

View on GitHub
composables/useTransaction.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { Interaction } from '@kodadot1/minimark/v1'
import type { ApiPromise } from '@polkadot/api'
import type { Address } from 'viem'

import { execBuyTx } from './transaction/transactionBuy'
import { execListTx } from './transaction/transactionList'
import { execSendTx } from './transaction/transactionSend'
import {
  execBurnCollection,
  execBurn,
} from './transaction/transactionBurn'
import { execCancelSwap } from './transaction/transactionSwapCancel'
import { execAcceptSwap } from './transaction/transactionSwapAccept'
import { execCancelOffer } from './transaction/transactionOfferCancel'
import { execAcceptOfferTx } from './transaction/transactionOfferAccept'
import { execMakingOfferTx } from './transaction/transactionOffer'
import { execCreateSwap } from './transaction/transactionCreateSwap'
import { execMintToken } from './transaction/transactionMintToken'
import { execMintCollection } from './transaction/transactionMintCollection'
import { execUpdateCollection } from './transaction/transactionUpdateCollection'
import { execSetNftMetadata } from './transaction/transactionSetNftMetadata'
import type {
  ActionAcceptOffer,
  ActionBuy,
  ActionConsume,
  ActionOffer,
  ActionSwap,
  ActionDeleteCollection,
  ActionList,
  ActionMintCollection,
  ActionMintDrop,
  ActionMintToken,
  ActionSend,
  ActionUpdateCollection,
  ActionSetNftMetadata,
  ActionCancelOffer,
  Actions,
  ExecuteEvmTransactionParams,
  ExecuteSubstrateTransactionParams,
  ExecuteTransactionParams,
  ObjectMessage,
  ActionCancelSwap,
  ActionAcceptSwap,
} from './transaction/types'
import { Collections, NFTs } from './transaction/types'
import { isActionValid } from './transaction/utils'
import { execMintDrop } from './transaction/transactionMintDrop'
import type {
  HowAboutToExecuteOnResultParam,
  HowAboutToExecute as SubstrateHowAboutToExecute,
} from './useMetaTransaction'
import useEvmMetaTransaction, {
  type EvmHowAboutToExecute,
  type EvmHowAboutToExecuteParam,
} from '@/composables/transaction/evm/useMetaTransaction'
import { doAfterCheckCurrentChainVM } from '@/components/common/ConnectWallet/openReconnectWalletModal'
import { hasOperationsDisabled } from '@/utils/prefix'
import { ShoppingActions } from '@/utils/shoppingActions'
import {
  showLargeNotification,
  successMessage as successNotification,
  warningMessage,
} from '@/utils/notification'

export type TransactionOptions = {
  disableSuccessNotification?: boolean
}

const resolveLargeSuccessNotification = (
  block: string,
  objectMessage: ObjectMessage,
) => {
  const { $i18n } = useNuxtApp()
  const title = $i18n.t('mint.success')
  const message = resolveSuccessMessage(block, objectMessage.message)
  showLargeNotification({ message, title, shareLink: objectMessage.shareLink })
}

const resolveMessage = (message?: string | (() => string)) => {
  if (!message) {
    return
  }
  if (typeof message === 'function') {
    return message()
  }
  return message
}

export const resolveSuccessMessage = (
  block: string,
  successMessage?: string | ((blockNumber) => string),
): string => {
  if (typeof successMessage === 'function') {
    return successMessage(block)
  }
  return successMessage || 'Success!'
}

const useExecuteTransaction = (options: TransactionOptions) => {
  const { accountId } = useAuth()
  const error = ref(false)

  const {
    howAboutToExecute,
    isLoading,
    status,
    initTransactionLoader,
    isError,
  } = execByVm({
    SUB: () => useMetaTransaction(),
    EVM: () => useEvmMetaTransaction() as unknown,
  }) as
  | ReturnType<typeof useMetaTransaction>
  | ReturnType<typeof useEvmMetaTransaction>

  const blockNumber = ref<string>()
  const txHash = ref<string>()

  const executeTransaction = ({
    arg,
    successMessage,
    errorMessage,
    ...params
  }: ExecuteTransactionParams) => {
    const successCb = ({
      blockNumber: block,
      txHash: hash,
    }: HowAboutToExecuteOnSuccessParam) => {
      blockNumber.value = block
      txHash.value = hash
      if (options.disableSuccessNotification) {
        return
      }

      const isObject = typeof successMessage === 'object'
      if (isObject && successMessage.large) {
        return resolveLargeSuccessNotification(block, successMessage)
      }

      const message = resolveSuccessMessage(
        block,
        isObject ? successMessage.message : successMessage,
      )
      successNotification(message)
    }

    const errorCb = () => {
      if (!errorMessage) {
        return
      }

      const message = resolveMessage(errorMessage)
      warningMessage(message)
    }

    const resultCb = (param: HowAboutToExecuteOnResultParam) => {
      txHash.value = param.txHash || undefined
    }

    doAfterCheckCurrentChainVM(() => {
      initTransactionLoader()
      execByVm({
        SUB: () => {
          ;(howAboutToExecute as SubstrateHowAboutToExecute)(
            accountId.value,
            (params as ExecuteSubstrateTransactionParams).cb,
            arg,
            {
              onSuccess: successCb,
              onError: errorCb,
              onResult: resultCb,
            },
          )
        },
        EVM: () => {
          const evmParams = params as ExecuteEvmTransactionParams
          ;(howAboutToExecute as EvmHowAboutToExecute)({
            account: accountId.value as Address,
            address: evmParams.address,
            abi: evmParams.abi,
            args: arg,
            functionName: evmParams.functionName,
            value: evmParams.value,
            onSuccess: successCb,
            onError: errorCb,
          } as EvmHowAboutToExecuteParam)
        },
      })
    })
  }

  const clear = () => {
    status.value = TransactionStatus.Unknown
    isError.value = false
    blockNumber.value = undefined
    txHash.value = undefined
    isLoading.value = false
  }

  return {
    isLoading,
    status,
    error,
    executeTransaction,
    blockNumber,
    txHash,
    isError,
    clear,
  }
}

export const executeAction = ({
  item,
  api,
  executeTransaction,
  isLoading,
  status,
}: {
  item: Actions
  api?: ApiPromise
  isLoading: Ref<boolean>
  status: Ref<string>
  executeTransaction
}) => {
  const map = {
    [Interaction.BUY]: () =>
      execBuyTx(item as ActionBuy, api, executeTransaction),
    [Interaction.LIST]: () =>
      execListTx(item as ActionList, api, executeTransaction),
    [Interaction.SEND]: () =>
      execSendTx(item as ActionSend, api, executeTransaction),
    [ShoppingActions.CONSUME]: () =>
      execBurn(item as ActionConsume, api, executeTransaction),
    [ShoppingActions.CANCEL_SWAP]: () =>
      execCancelSwap(item as ActionCancelSwap, api!, executeTransaction),
    [ShoppingActions.ACCEPT_SWAP]: () =>
      execAcceptSwap(item as ActionAcceptSwap, api!, executeTransaction),
    [ShoppingActions.CANCEL_OFFER]: () =>
      execCancelOffer(item as ActionCancelOffer, api, executeTransaction),
    [ShoppingActions.ACCEPT_OFFER]: () =>
      execAcceptOfferTx(item as ActionAcceptOffer, api, executeTransaction),
    [ShoppingActions.MAKE_OFFER]: () =>
      execMakingOfferTx(item as ActionOffer, api, executeTransaction),
    [ShoppingActions.CREATE_SWAP]: () =>
      execCreateSwap({
        item: item as ActionSwap,
        api: api as ApiPromise,
        executeTransaction,
        isLoading,
        status,
      }),
    [ShoppingActions.MINTNFT]: () =>
      execMintToken({
        item: item as ActionMintToken,
        api,
        executeTransaction,
        isLoading,
        status,
      }),
    [ShoppingActions.MINT]: () =>
      execMintCollection({
        item: item as ActionMintCollection,
        api,
        executeTransaction,
        isLoading,
        status,
      }),
    [Collections.DELETE]: () =>
      execBurnCollection(
        item as ActionDeleteCollection,
        api as ApiPromise,
        executeTransaction,
      ),
    [Collections.UPDATE_COLLECTION]: () =>
      execUpdateCollection({
        item: item as ActionUpdateCollection,
        api: api as ApiPromise,
        executeTransaction,
        isLoading,
        status,
      }),
    [NFTs.SET_METADATA]: () =>
      execSetNftMetadata({
        item: item as ActionSetNftMetadata,
        api: api as ApiPromise,
        executeTransaction,
        isLoading,
        status,
      }),
    [NFTs.MINT_DROP]: () =>
      execMintDrop({
        item: item as ActionMintDrop,
        api,
        executeTransaction,
        isLoading,
        status,
      }),
  }

  if (!isActionValid(item)) {
    const { $consola } = useNuxtApp()
    $consola.warn(`Invalid action: ${JSON.stringify(item)}`)
    throw createError({
      statusCode: 404,
      statusMessage: 'Interaction Not Found',
    })
  }

  return map[item.interaction]?.() ?? 'UNKNOWN'
}

export const useTransaction = (
  options: TransactionOptions = { disableSuccessNotification: false },
) => {
  const { apiInstance, apiInstanceByPrefix } = useApi()
  const { $i18n } = useNuxtApp()
  const { urlPrefix } = usePrefix()
  const {
    isLoading,
    status,
    executeTransaction,
    blockNumber,
    txHash,
    isError,
    clear,
  } = useExecuteTransaction(options)

  const transaction = async (item: Actions, prefix = '') => {
    if (hasOperationsDisabled(prefix || urlPrefix.value)) {
      warningMessage($i18n.t('toast.unsupportedOperation'))
      return
    }

    const api = await execByVm({
      SUB: async () => {
        let api = await apiInstance.value
        if (prefix) {
          api = await apiInstanceByPrefix(prefix)
        }
        return api
      },
    })

    return executeAction({ item, executeTransaction, api, isLoading, status })
  }

  return {
    isLoading,
    status,
    transaction,
    blockNumber,
    txHash,
    isError,
    clear,
  }
}