kodadot/nft-gallery

View on GitHub
composables/useMigrate.ts

Summary

Maintainability
D
1 day
Test Coverage
import type { Prefix } from '@kodadot1/static'
import { allPrefixWithIcon } from '@/utils/chain'
import format from '@/utils/format/balance'
import collectionMigrateReady from '@/queries/subsquid/general/collectionMigrateReady.graphql'
import collectionMigrateWaiting from '@/queries/subsquid/general/collectionMigrateWaiting.graphql'
import waifuApi from '@/services/waifu'

export type Steps =
  | 'init'
  | 'step1'
  | 'step1-check-id'
  | 'step2'
  | 'step2-migrate'
  | 'step3'
  | 'step3-burn'
  | 'step3-burn-collection'
  | 'step4'

export const iconIdle = {
  icon: 'circle',
  class: 'text-k-grey-light',
  pack: 'fass',
  textColor: 'text-k-grey',
}
export const iconLoading = {
  icon: 'spinner-third',
  class: 'fa-spin text-k-grey animate-spin',
  textColor: '',
}
export const iconSuccess = {
  icon: 'check',
  class: 'text-k-green',
  textColor: '',
}
export const iconError = {
  icon: 'xmark',
  class: 'text-k-red',
  textColor: '',
}

export const BATCH_SIZE = 200

export function calculateIterations(itemCount = '0', batchSize = BATCH_SIZE) {
  const items = parseInt(itemCount || '0')

  if (items <= 0 || batchSize <= 0) {
    return 0
  }

  return Math.ceil(items / batchSize)
}

type CollectionsReady = {
  collectionEntities?: {
    id: string
    name: string
    metadata: string
    meta?: {
      id: string
      image: string
      animationUrl: string
      name: string
      description: string
    }
    nftsOwned?: {
      id: string
    }[]
    nfts?: {
      id: string
      name?: string
      currentOwner?: string
      meta?: {
        id: string
        image: string
      }
    }[]
  }[]
}

export async function useCollectionReady(prefix = '', account = '') {
  const { accountId } = useAuth()
  const { client } = usePrefix()

  const { data } = await useAsyncQuery<CollectionsReady>({
    query: collectionMigrateReady,
    variables: {
      account: account || accountId.value,
    },
    clientId: prefix || client.value,
  })

  const collections = computed(() => {
    if (data.value?.collectionEntities?.length) {
      return data.value?.collectionEntities
    }

    return []
  })

  return {
    collections,
  }
}

const parseDeposit = (deposit, decimals) => {
  return parseFloat(format(deposit, decimals, false))
}

export function useMigrateDeposit(
  prefix: ComputedRef<Prefix>,
  itemCount: number,
  account = '',
) {
  const {
    balance,
    itemDeposit,
    existentialDeposit,
    metadataDeposit,
    totalCollectionDeposit,
    chainSymbol,
    chain,
  } = useDeposit(prefix)
  const fiatStore = useFiatStore()
  const preferencesStore = usePreferencesStore()

  const route = useRoute()
  const collectionOwner = route.query.collectionOwner?.toString()

  const chainDecimals = computed(() => {
    if (chain.value?.tokenDecimals) {
      return chain.value.tokenDecimals
    }

    return 12
  })

  const chainItemDeposit = computed(() => {
    // Calculate the sum of all deposits and then multiply by itemCount squared
    const total
      = (metadataDeposit.value + itemDeposit.value + existentialDeposit.value)
      * itemCount
      * itemCount

    return parseDeposit(total, chainDecimals.value)
  })

  const chainTokenPrice = computed(() =>
    Number(fiatStore.getCurrentTokenValue(chainSymbol.value) ?? 0),
  )

  const kodadotFee = computed(() =>
    parseDeposit(
      ((preferencesStore.hasSupport ? BASE_FEE : 0) / chainTokenPrice.value)
      * Math.pow(10, chainDecimals.value),
      chainDecimals.value,
    ),
  )

  const chainNetworkFee = computedAsync(async () => {
    if (account) {
      const fee = await estimateTransactionFee(account, chainDecimals.value)
      return parseDeposit(
        parseInt(fee) * itemCount * itemCount,
        chainDecimals.value,
      )
    }

    return 0
  })

  const totalChain = computed(() => {
    let total
      = chainNetworkFee.value + chainItemDeposit.value + kodadotFee.value

    if (!collectionOwner) {
      total += parseFloat(totalCollectionDeposit.value)
    }

    if (isNaN(total)) {
      return 0
    }

    return parseFloat(total.toString()).toFixed(4)
  })

  const totalChainUsd = computed(() => {
    const amount
      = parseFloat(totalChain.value.toString()) * chainTokenPrice.value
    return parseFloat(amount.toString()).toFixed(1)
  })

  return {
    balance,
    chainItemDeposit,
    chainNetworkFee,
    chainSymbol,
    kodadotFee,
    totalChain,
    totalChainUsd,
    totalCollectionDeposit,
  }
}

const source = allPrefixWithIcon().filter(
  item => item.value === 'ksm' || item.value === 'rmrk',
)
const destination = allPrefixWithIcon().filter(
  item => item.value === 'ahp' || item.value === 'ahk',
)

// set shared state here
const useSourceSelected = () =>
  useState('sourceSelected', () =>
    allPrefixWithIcon().find(item => item.value === 'ksm'),
  )
const useDestinationSelected = () =>
  useState('destinationSelected', () =>
    allPrefixWithIcon().find(item => item.value === 'ahp'),
  )

export const toReview = ({
  collectionId,
  itemCount,
  collectionOwner = '',
  setDestination = '',
}) => {
  const sourceSelected = useSourceSelected()
  const destinationSelected = useDestinationSelected()
  const { accountId } = useAuth()

  navigateTo({
    path: '/migrate/review',
    query: {
      accountId: accountId.value,
      collectionId: collectionId,
      source: sourceSelected.value?.value,
      destination: setDestination || destinationSelected.value?.value,
      itemCount,
      collectionOwner,
    },
  })
}

// default composables
export default function useMigrate() {
  const sourceSelected = useSourceSelected()
  const destinationSelected = useDestinationSelected()
  const { urlPrefix, setUrlPrefix } = usePrefix()

  watchEffect(() => {
    const chain = sourceSelected.value?.value

    if (chain !== urlPrefix.value) {
      setUrlPrefix(chain as Prefix)
    }

    if (chain === 'ahk') {
      destinationSelected.value = destination.find(
        item => item.value === 'ahp',
      )
    }

    if (chain === 'ahp') {
      destinationSelected.value = destination.find(
        item => item.value === 'ahk',
      )
    }
  })

  return {
    source,
    sourceSelected,
    destination,
    destinationSelected,
  }
}

// fetch items for waiting section
// -------------------------------
type Collections = {
  collectionEntities?: {
    id: string
    name: string
    currentOwner: string
    nfts?: {
      id: string
    }[]
    metadata: string
    meta?: {
      id: string
      image: string
    }
  }[]
}

export const useWaitingItems = () => {
  const { urlPrefix } = usePrefix()
  const { accountId } = useAuth()
  const { client } = usePrefix()

  const collections = ref<Collections['collectionEntities']>([])
  const entities = reactive({})
  const loading = ref(true)

  const fetchWaitingItems = async () => {
    const { data } = await useAsyncQuery<Collections>({
      query: collectionMigrateWaiting,
      variables: {
        account: accountId.value,
      },
      clientId: client.value,
    })

    if (data.value?.collectionEntities?.length) {
      collections.value = data.value.collectionEntities

      for (const collection of collections.value) {
        const metadata = await getNftMetadata(
          collection as unknown as NFTWithMetadata,
          urlPrefix.value,
        )
        const migrated = (
          await waifuApi(`/relocations/owners/${accountId.value}`)
        ).filter(item => item.collection === collection.id)

        if (migrated.length && collection.nfts?.length) {
          entities[collection.id] = {
            ...metadata,
            migrated,
          }
        }
      }
    }

    loading.value = false
  }

  watchEffect(async () => {
    if (!collections.value?.length) {
      await fetchWaitingItems()
    }
  })

  return {
    collections,
    entities,
    loading,
  }
}

// fetch items for ready section
// -------------------------------
export const useReadyItems = () => {
  const { urlPrefix } = usePrefix()

  const collections = ref<CollectionsReady['collectionEntities']>([])
  const entities = reactive({})
  const loading = ref(true)

  const fetchCollections = async () => {
    const { collections } = await useCollectionReady()
    return collections.value
  }

  watchEffect(async () => {
    const cols = await fetchCollections()

    if (cols.length) {
      collections.value = cols

      for (const collection of cols) {
        const metadata = await getNftMetadata(
          collection as unknown as NFTWithMetadata,
          urlPrefix.value,
        )
        const migrated = await waifuApi(
          `/relocations/${urlPrefix.value}-${collection.id}`,
        )

        if (!migrated?.id && collection.nfts?.length) {
          entities[collection.id] = {
            ...metadata,
          }
        }
      }
    }

    loading.value = false
  })

  return {
    collections,
    entities,
    loading,
  }
}