kodadot/nft-gallery

View on GitHub
composables/collectionActivity/helpers.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import { Interaction } from '@kodadot1/minimark/v1'
import type { Prefix } from '@kodadot1/static'
import type {
  Flippers,
  InteractionWithNFT,
  NFTHistoryState,
  NFTMap,
  Owners,
} from './types'
import { sum } from '@/utils/math'

export const chainsWithMintInteraction: Prefix[] = ['ksm', 'ahk', 'ahp', 'base'] // 'ahr'

export const mintInteraction = () => {
  const { urlPrefix } = usePrefix()
  // https://github.com/kodadot/snek/issues/183
  return chainsWithMintInteraction.includes(urlPrefix.value)
    ? Interaction.MINT
    : Interaction.MINTNFT
}

const flipperInitialState = () => ({
  flips: [],
  bestFlip: 0,
  latestflipTimestamp: 0,
  owned: 0,
  totalBought: 0,
  totalsold: 0,
})

const newOwnerEntry = () => ({
  nftCount: 0,
  totalBought: 0,
  totalSold: 0,
  lastActivityTimestamp: -Infinity,
  longestHold: 0,
  nfts: [],
})

const preProccessForFindingFlippers = (interactions: InteractionWithNFT[]) => {
  const changeHandsInteractions: InteractionWithNFT[] = []
  const NFTS: NFTMap = {}

  interactions.forEach((event) => {
    if (event.interaction === mintInteraction()) {
      NFTS[event.nft.id] = {
        owner: event.caller,
        nft: event.nft,
        latestInteraction: mintInteraction(),
        latestPrice: 0,
      }
    }

    if (
      event.interaction === Interaction.SEND
      || event.interaction === Interaction.BUY
    ) {
      changeHandsInteractions.push(event)
    }
  })

  return { NFTS, changeHandsInteractions }
}

const summerizeFlips = (flips) => {
  return {
    owned: flips.length,
    totalBought: sum(flips.map(flip => flip.boughtPrice)),
    totalsold: sum(flips.map(flip => flip.soldPrice)),
    bestFlip: Math.max(...flips.map(flip => flip.profit)),
    latestflipTimestamp: Math.max(...flips.map(flip => flip.sellTimeStamp)),
    flips,
  }
}

const getLatestPrice = (previousNFTState: NFTHistoryState) =>
  previousNFTState.latestInteraction === Interaction.BUY
    ? previousNFTState.latestPrice
    : 0

const updateOwnerWithNewNft = ({
  owner,
  latestEvent,
  lastestTimeStamp,
  nft,
}) => {
  owner.nftCount++
  if (latestEvent.interaction === Interaction.BUY) {
    owner.totalBought += parseInt(latestEvent.meta)
  }

  owner.lastActivityTimestamp
    = lastestTimeStamp > owner.lastActivityTimestamp
      ? lastestTimeStamp
      : owner.lastActivityTimestamp

  owner.nfts = [...owner.nfts, nft]

  let longestHoldTimestamp = 0

  owner.nfts.forEach((nft) => {
    longestHoldTimestamp = Math.max(
      longestHoldTimestamp,
      new Date().getTime() - new Date(nft.updatedAt).getTime(),
    )
  })
  owner.longestHold = longestHoldTimestamp

  return owner
}

export const getOwners = (nfts) => {
  const owners: Owners = {}

  nfts.forEach((nft) => {
    const interactions = nft.events.map(e => e.interaction)
    const { events } = nft
    const owner = owners[nft.currentOwner] || newOwnerEntry()

    if (interactions.includes(Interaction.CONSUME)) {
      // no owner
      return
    }
    if (
      interactions.includes(Interaction.BUY)
      || interactions.includes(Interaction.SEND)
    ) {
      // NFT changed hands
      const latestchangeHandsEvent = events.findLast(
        event =>
          event.interaction === Interaction.BUY
          || event.interaction === Interaction.SEND,
      )
      const lastestTimeStamp = new Date(
        latestchangeHandsEvent.timestamp,
      ).getTime()

      owners[nft.currentOwner] = updateOwnerWithNewNft({
        owner,
        latestEvent: latestchangeHandsEvent,
        lastestTimeStamp,
        nft,
      })
      return
    }

    // nft isn't consumed and it hasn't change hands => it is owned by it's creator
    const mintInteraction = events[0]
    const mintTimeStamp = new Date(mintInteraction.timestamp).getTime()

    owners[nft.currentOwner] = updateOwnerWithNewNft({
      owner,
      latestEvent: mintInteraction,
      lastestTimeStamp: mintTimeStamp,
      nft,
    })
  })
  return owners
}

export const getFlippers = (interactions: InteractionWithNFT[]): Flippers => {
  const { NFTS, changeHandsInteractions }
    = preProccessForFindingFlippers(interactions)

  // Create an object that will hold all the flipper data
  const flippers: Flippers = {}

  // Loop through all the change hands interactions
  changeHandsInteractions.forEach((interaction) => {
    const nftId = interaction.nft.id
    const PreviousNFTState = NFTS[nftId]
    if (interaction.interaction === Interaction.SEND) {
      // NFT has been sent from one address to another
      PreviousNFTState.owner = interaction.meta
      PreviousNFTState.latestInteraction = Interaction.SEND
    }

    if (interaction.interaction === Interaction.BUY) {
      // Update the NFT state
      NFTS[nftId] = {
        ...PreviousNFTState,
        owner: interaction.caller,
        latestInteraction: Interaction.BUY,
        latestPrice: parseInt(interaction.meta),
      }
      if (PreviousNFTState.latestInteraction === Interaction.BUY) {
        // NFT has been bought, and previous interaction is also a buy => it's a Flip!
        const boughtPrice = getLatestPrice(PreviousNFTState)
        // Calculate profit
        const profit
          = boughtPrice > 0 ? (parseInt(interaction.meta) / boughtPrice) * 100 : 0

        // Create the new FlipEvent object
        const thisFlip = {
          nft: PreviousNFTState.nft,
          soldPrice: parseInt(interaction.meta),
          soldTo: interaction.caller,
          sellTimeStamp: new Date(interaction.timestamp).getTime(),
          boughtPrice,
          profit,
        }

        // Check if the previous owner is already a flipper, if not initialize them
        if (flippers[PreviousNFTState.owner] === undefined) {
          flippers[PreviousNFTState.owner] = flipperInitialState()
        }

        // Add the new FlipEvent to the previous owner's flips array
        flippers[PreviousNFTState.owner].flips.push(thisFlip)
      }
    }
  })

  Object.entries(flippers).forEach(([flipperId, { flips }]) => {
    flippers[flipperId] = summerizeFlips(flips)
  })
  return flippers
}