kodadot/nft-gallery

View on GitHub
utils/teleport.ts

Summary

Maintainability
B
5 hrs
Test Coverage
// Copyright 2017-2021 @polkadot/app-config authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { Prefix } from '@kodadot1/static'
import type { ApiPromise } from '@polkadot/api'
import { SubmittableExtrinsicFunction } from '@polkadot/api/types'
import { XcmVersionedMultiLocation } from '@polkadot/types/lookup'
import { AnyTuple } from '@polkadot/types/types'
import { isFunction } from '@polkadot/util'
import * as paraspell from '@paraspell/sdk'
import { ApiFactory } from '@kodadot1/sub-api'
import { getChainEndpointByPrefix } from '@/utils/chain'
import { TeleportParams } from '@/composables/useTeleport'
import { getAddress } from '@/utils/extension'
import { toDefaultAddress } from '@/utils/account'

type Extrisic = SubmittableExtrinsicFunction<'promise', AnyTuple>

// import { KUSAMA_GENESIS } from '@polkadot/apps-config';

const KUSAMA_GENESIS = ''
// 4 * BaseXcmWeight on Kusama
const KUSAMA_WEIGHT = 4 * 1_000_000_000

const DEFAULT_WEIGHT = KUSAMA_WEIGHT

const KNOWN_WEIGHTS: Record<string, number> = {
  [KUSAMA_GENESIS]: KUSAMA_WEIGHT,
}

export enum Chain {
  KUSAMA = 'Kusama',
  ASSETHUBKUSAMA = 'AssetHubKusama',
  ASSETHUBPOLKADOT = 'AssetHubPolkadot',
  POLKADOT = 'Polkadot',
}

export type TeleportChain = {
  prefix: Prefix
  chain: Chain
  name: string
}

export type TeleportTransition = {
  source: TeleportChain | null
  destination: TeleportChain
  amount: number
  amountFormatted: string
  amountUsd: string
  token: string
  txFees: number
}

export const allowedTransitions = {
  [Chain.KUSAMA]: [Chain.ASSETHUBKUSAMA],
  [Chain.ASSETHUBKUSAMA]: [Chain.KUSAMA],
  [Chain.POLKADOT]: [Chain.ASSETHUBPOLKADOT],
  [Chain.ASSETHUBPOLKADOT]: [Chain.POLKADOT],
}

export const chainToPrefixMap: Record<Chain, Prefix> = {
  [Chain.KUSAMA]: 'rmrk',
  [Chain.ASSETHUBKUSAMA]: 'ahk',
  [Chain.ASSETHUBPOLKADOT]: 'ahp',
  [Chain.POLKADOT]: 'dot',
}

export const prefixToChainMap: Partial<Record<Prefix, Chain>> = {
  rmrk: Chain.KUSAMA,
  ksm: Chain.KUSAMA,
  ahk: Chain.ASSETHUBKUSAMA,
  ahp: Chain.ASSETHUBPOLKADOT,
  dot: Chain.POLKADOT,
}

export enum TeleprtType {
  RelayToPara = 'RelayToPara',
  ParaToRelay = 'ParaToRelay',
  ParaToPara = 'ParaToPara',
}

export const whichTeleportType = ({
  from,
  to,
}: {
  from: Chain
  to: Chain
}): TeleprtType => {
  switch (from) {
    case Chain.KUSAMA:
    case Chain.POLKADOT:
      return TeleprtType.RelayToPara

    case Chain.ASSETHUBKUSAMA:
    case Chain.ASSETHUBPOLKADOT:
      return [Chain.KUSAMA, Chain.POLKADOT].includes(to)
        ? TeleprtType.ParaToRelay
        : TeleprtType.ParaToPara

    default:
      throw new Error(`Unknown chain: ${from}`)
  }
}

export function getTeleportWeight(api: ApiPromise): number {
  return KNOWN_WEIGHTS[api.genesisHash.toHex()] || DEFAULT_WEIGHT
}

export function findCall(api: ApiPromise): Extrisic {
  const m = XCM_LOC.filter(
    (x) => api.tx[x] && XCM_FNS.some((f) => isFunction(api.tx[x][f])),
  )[0]
  const f = XCM_FNS.filter((f) => isFunction(api.tx[m][f]))[0]

  return api.tx[m][f]
}

export const XCM_LOC = ['xcm', 'xcmPallet', 'polkadotXcm']
export const XCM_FNS = ['limitedTeleportAssets', 'teleportAssets']

export function getApiParams(
  api: ApiPromise,
  call: Extrisic,
  isParaTeleport: string | undefined,
  account: string,
  amount: string,
): any[] {
  const firstType = api.createType<XcmVersionedMultiLocation>(
    call.meta.args[0].type.toString(),
  )
  const isCurrent = firstType.defKeys.includes('V1')

  const dst = isParaTeleport ? { X1: 'Parent' } : { X1: { ParaChain: 1000 } }

  const acc = {
    X1: {
      AccountId32: {
        id: api.createType('AccountId32', account).toHex(),
        network: 'Any',
      },
    },
  }
  const ass = isParaTeleport
    ? [{ ConcreteFungible: { amount, id: { X1: 'Parent' } } }]
    : // forgo id - 'Here' for 9100, 'Null' for 9110 (both is the default enum value)
      [{ ConcreteFungible: { amount } }]

  const destWeight = getTeleportWeight(api)

  return isCurrent
    ? call.meta.args.length === 5
      ? // with weight
        call.method === 'limitedTeleportAssets'
        ? [{ V0: dst }, { V0: acc }, { V0: ass }, 0, { Unlimited: null }]
        : [{ V0: dst }, { V0: acc }, { V0: ass }, 0, destWeight]
      : // without weight
        [{ V0: dst }, { V0: acc }, { V0: ass }, 0]
    : [dst, acc, ass, destWeight]
}

const getApi = (chain: Chain) => {
  const endpoint = getChainEndpointByPrefix(chainToPrefixMap[chain]) as string
  return ApiFactory.useApiInstance(endpoint)
}

export const getTransaction = async ({
  amount,
  from,
  to,
  address,
  currency,
}: {
  amount: number
  from: Chain
  to: Chain
  address: string
  currency: string
}) => {
  const api = await getApi(from)

  const telportType = whichTeleportType({
    from: from,
    to: to,
  })

  if (telportType === TeleprtType.RelayToPara) {
    return paraspell
      .Builder(api)
      .to(Chain[to.toUpperCase()])
      .amount(amount)
      .address(address)
      .build()
  }

  if (telportType === TeleprtType.ParaToRelay) {
    return paraspell
      .Builder(api)
      .from(Chain[from.toUpperCase()])
      .amount(amount)
      .address(address)
      .build()
  }

  if (telportType === TeleprtType.ParaToPara) {
    return paraspell
      .Builder(api)
      .from(Chain[from.toUpperCase()])
      .to(Chain[to.toUpperCase()])
      .currency(currency)
      .amount(amount)
      .address(address)
      .build()
  }
}

export const getTransactionFee = async ({
  amount,
  from,
  to,
  toAddress,
  fromAddress,
  currency,
}: TeleportParams) => {
  const promise = await getTransaction({
    amount: amount,
    from: from,
    to: to,
    address: toAddress,
    currency: currency,
  })

  if (!promise) {
    return
  }

  const injector = await getAddress(toDefaultAddress(fromAddress))

  const info = await promise.paymentInfo(
    fromAddress,
    injector ? { signer: injector.signer } : {},
  )

  return info.partialFee.toString()
}

export type Currency = 'KSM' | 'DOT'

export const getChainCurrency = (chain: Chain): Currency => {
  switch (chain) {
    case Chain.KUSAMA:
    case Chain.ASSETHUBKUSAMA:
      return 'KSM'
    case Chain.POLKADOT:
    case Chain.ASSETHUBPOLKADOT:
      return 'DOT'
  }
}

export const chainToPrecisionMap: Record<Chain, number> = {
  [Chain.KUSAMA]: 4,
  [Chain.BASILISK]: 4,
  [Chain.ASSETHUBKUSAMA]: 6,
  [Chain.ASSETHUBPOLKADOT]: 5,
  [Chain.POLKADOT]: 4,
}