diff --git a/.github/diagram.svg b/.github/diagram.svg index cc15935cdb..6fb91700ce 100644 --- a/.github/diagram.svg +++ b/.github/diagram.svg @@ -1 +1 @@ -utilsutilsteststestsstoresstoresservicesservicesqueriesqueriespagespageslibslibscomposablescomposablescomponentscomponentspublicpublicconfigconfigapiapie2ee2esubsquidsubsquid[prefix][prefix]uiuistaticstatictransactiontransactionmassmintmassmintdropdroptransfertransfersharedsharedsearchsearchrmrkrmrkprofileprofileofferoffermigratemigratemassmintmassmintlandinglandingitemsitemsidentityidentitygallerygalleryexploreexploredropsdropscreatecreatecommoncommoncollectioncollectioncodeCheckercodeCheckercarouselcarouselbsxbsxdropdropblogbloggeneralgeneralsrcsrcfiltersfiltersGalleryGallerycreatecreatestepsstepslandinglandingItemsGridItemsGridlistingCartlistingCartdropdropactivityactivitycomponentscomponentsmodulesmoduleseventseventsMediaItemMediaItem.ahk.csv.filters.graphql.html.js.mjs.scss.tab.ts.txt.ui.vue.xml.yamleach dot sized by file size \ No newline at end of file +utilsutilsteststestsstoresstoresservicesservicesqueriesqueriespagespageslibslibscomposablescomposablescomponentscomponentspublicpublicconfigconfigapiapie2ee2esubsquidsubsquid[prefix][prefix]uiuistaticstatictransactiontransactionmassmintmassmintdropdroptransfertransfertradetradesharedsharedsearchsearchrmrkrmrkprofileprofilemigratemigratemassmintmassmintlandinglandingitemsitemsidentityidentitygallerygallerydropsdropscreatecreatecommoncommoncollectioncollectioncodeCheckercodeCheckercarouselcarouselbsxbsxblogbloggeneralgeneralsrcsrcfiltersfiltersGalleryGallerycreatecreatestepsstepslandinglandingItemsGridItemsGridlistingCartlistingCartdropdropactivityactivitycomponentscomponentsmodulesmoduleseventseventsMediaItemMediaItem.ahk.csv.filters.graphql.html.js.mjs.scss.tab.ts.txt.ui.vue.xml.yamleach dot sized by file size \ No newline at end of file diff --git a/assets/svg/swap/arrow-down.svg b/assets/svg/swap/arrow-down.svg new file mode 100644 index 0000000000..a9a68c0d31 --- /dev/null +++ b/assets/svg/swap/arrow-down.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/swap/arrow-up.svg b/assets/svg/swap/arrow-up.svg new file mode 100644 index 0000000000..b522fb0b02 --- /dev/null +++ b/assets/svg/swap/arrow-up.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/swap/arrows.svg b/assets/svg/swap/arrows.svg new file mode 100644 index 0000000000..10a3492e44 --- /dev/null +++ b/assets/svg/swap/arrows.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/components/collection/EditModal.vue b/components/collection/EditModal.vue index cf5bfb0679..da1591778b 100644 --- a/components/collection/EditModal.vue +++ b/components/collection/EditModal.vue @@ -12,12 +12,45 @@ class="flex flex-col gap-6" @submit.prevent > - - + + + + + + + + + + + + + @@ -131,6 +164,8 @@ const props = defineProps<{ const isModalActive = useVModel(props, 'modelValue') +const name = ref() +const description = ref() const image = ref() const banner = ref() const imageUrl = ref() @@ -140,20 +175,30 @@ const unlimited = ref(true) const min = computed(() => props.min || 1) const max = ref(null) +const nameChanged = computed(() => props.collection.name !== name.value) +const hasImageChanged = computed(() => (!imageUrl.value && Boolean(props.collection.image)) || Boolean(image.value)) +const originalLogoImageUrl = computed(() => sanitizeIpfsUrl(props.collection.image)) + const disabled = computed(() => { const hasImage = imageUrl.value + const isNameFilled = Boolean(name.value) - const hasImagechanged = (!imageUrl.value && Boolean(props.collection?.image)) || Boolean(image.value) - const hasBannerChanged = (!bannerUrl.value && Boolean(props.collection?.banner)) || Boolean(banner.value) - const hasMaxChanged = max.value !== props.collection?.max + const descriptionChanged = props.collection.description !== description.value + const hasBannerChanged = (!bannerUrl.value && Boolean(props.collection.banner)) || Boolean(banner.value) + const hasMaxChanged = max.value !== props.collection.max - return !hasImage || (!hasImagechanged && !hasBannerChanged && !hasMaxChanged) + return !hasImage || !isNameFilled || (!nameChanged.value && !descriptionChanged && !hasImageChanged.value && !hasBannerChanged && !hasMaxChanged) }) +const initLogoImage = () => { + imageUrl.value = originalLogoImageUrl.value + image.value = undefined +} + const editCollection = async () => { emit('submit', { - name: props.collection.name, - description: props.collection.description, + name: name.value, + description: description.value, image: image.value || props.collection.image, imageType: props.collection.imageType, banner: bannerUrl.value ? banner.value || props.collection.banner : undefined, @@ -163,10 +208,11 @@ const editCollection = async () => { watch(isModalActive, (value) => { if (value && props.collection) { - imageUrl.value = sanitizeIpfsUrl(props.collection.image) + name.value = props.collection.name + description.value = props.collection.description bannerUrl.value = props.collection.banner && sanitizeIpfsUrl(props.collection.banner) - image.value = undefined banner.value = undefined + initLogoImage() unlimited.value = !props.collection.max max.value = props.collection.max } diff --git a/components/collection/HeroButtonDeleteNfts.vue b/components/collection/HeroButtonDeleteNfts.vue index 6e891ac433..6f66c65c08 100644 --- a/components/collection/HeroButtonDeleteNfts.vue +++ b/components/collection/HeroButtonDeleteNfts.vue @@ -12,8 +12,8 @@ diff --git a/components/common/ItemTransferModal.vue b/components/common/ItemTransferModal.vue new file mode 100644 index 0000000000..883d1079a8 --- /dev/null +++ b/components/common/ItemTransferModal.vue @@ -0,0 +1,114 @@ + + + diff --git a/components/common/NonRecommendFieldNotification.vue b/components/common/NonRecommendFieldNotification.vue new file mode 100644 index 0000000000..27de4d33e2 --- /dev/null +++ b/components/common/NonRecommendFieldNotification.vue @@ -0,0 +1,48 @@ + + + diff --git a/components/common/itemTransfer/ItemTransferModal.vue b/components/common/itemTransfer/ItemTransferModal.vue deleted file mode 100644 index ff3fdbad16..0000000000 --- a/components/common/itemTransfer/ItemTransferModal.vue +++ /dev/null @@ -1,284 +0,0 @@ - - - diff --git a/components/common/listingCart/ListingCartMini.vue b/components/common/listingCart/ListingCartMini.vue index 4b72e249b1..893e80f5cf 100644 --- a/components/common/listingCart/ListingCartMini.vue +++ b/components/common/listingCart/ListingCartMini.vue @@ -35,6 +35,22 @@
+ + + {{ $t('burn') }} + + + {{ $t('transfer') }} @@ -76,12 +92,14 @@ import { NeoButton, NeoTooltip } from '@kodadot1/brick' import { useListingCartStore } from '@/stores/listingCart' import { usePreferencesStore } from '@/stores/preferences' -import { listVisible } from '@/utils/config/permission.config' +import { listVisible, burnVisible } from '@/utils/config/permission.config' const listingCartStore = useListingCartStore() const preferencesStore = usePreferencesStore() const { urlPrefix } = usePrefix() +const { isEvm } = useIsChain(urlPrefix) +const isItemBurnDisabled = computed(() => isEvm.value ? listingCartStore.count > 1 : !burnVisible(urlPrefix.value)) const isItemTransferDisabled = computed(() => isSub(urlPrefix.value) ? false : listingCartStore.count > 1) const isListingDisabled = computed(() => !listVisible(urlPrefix.value)) diff --git a/components/common/listingCart/shared/ListingCartPriceInput.vue b/components/common/listingCart/shared/ListingCartPriceInput.vue index 8b29e58597..0260ab2b0c 100644 --- a/components/common/listingCart/shared/ListingCartPriceInput.vue +++ b/components/common/listingCart/shared/ListingCartPriceInput.vue @@ -6,12 +6,13 @@ >
{{ chainSymbol }} @@ -35,6 +36,8 @@ const props = defineProps<{ modelValue?: number | string check?: boolean fullWidth?: boolean + disabled?: boolean + placeholder?: string }>() const emit = defineEmits(['confirm', 'update:modelValue']) diff --git a/components/common/userCart/UserCartModal.vue b/components/common/userCart/UserCartModal.vue new file mode 100644 index 0000000000..91bf85e6f1 --- /dev/null +++ b/components/common/userCart/UserCartModal.vue @@ -0,0 +1,221 @@ + + + diff --git a/components/common/userCart/UserCartModals.vue b/components/common/userCart/UserCartModals.vue new file mode 100644 index 0000000000..f3f072cba4 --- /dev/null +++ b/components/common/userCart/UserCartModals.vue @@ -0,0 +1,10 @@ + + + diff --git a/components/common/itemTransfer/ItemTransferMultipleItems.vue b/components/common/userCart/UserCartMultipleItems.vue similarity index 100% rename from components/common/itemTransfer/ItemTransferMultipleItems.vue rename to components/common/userCart/UserCartMultipleItems.vue diff --git a/components/common/itemTransfer/ItemTransferSingleItem.vue b/components/common/userCart/UserCartSingleItem.vue similarity index 100% rename from components/common/itemTransfer/ItemTransferSingleItem.vue rename to components/common/userCart/UserCartSingleItem.vue diff --git a/components/create/CreateNft.vue b/components/create/CreateNft.vue index af31ca4a84..c829c7653e 100644 --- a/components/create/CreateNft.vue +++ b/components/create/CreateNft.vue @@ -302,7 +302,7 @@ import { toNFTId } from '@kodadot1/minimark/v2' import type { CreatedNFT } from '@kodadot1/minimark/v1' import { Interaction } from '@kodadot1/minimark/v1' import CreateNftPreview from './CreateNftPreview.vue' -import type { Actions, TokenToList } from '@/composables/transaction/types' +import type { ActionMintToken, ActionList, TokenToList } from '@/composables/transaction/types' import ChooseCollectionDropdown from '@/components/common/ChooseCollectionDropdown.vue' import BasicSwitch from '@/components/shared/form/BasicSwitch.vue' import CustomAttributeInput from '@/components/rmrk/Create/CustomAttributeInput.vue' @@ -429,14 +429,14 @@ const transactionStatus = ref< const createdItems = ref() const mintedBlockNumber = ref() -const mintAction = computed(() => ({ +const mintAction = computed(() => ({ interaction: Interaction.MINTNFT, urlPrefix: currentChain.value, token: { file: form.file, name: form.name, description: form.description, - selectedCollection: selectedCollection.value, + selectedCollection: selectedCollection.value || null, copies: form.copies, nsfw: form.nsfw, postfix: form.postfix, @@ -448,7 +448,7 @@ const mintAction = computed(() => ({ }, })) -const listAction = computed(() => { +const listAction = computed(() => { const list: TokenToList[] = createdItems.value?.map(nft => ({ price: balanceFrom(form.salePrice, decimals.value), nftId: toNFTId(nft, String(blockNumber.value)), diff --git a/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue b/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue index d9b944c21e..6273b793df 100644 --- a/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue +++ b/components/gallery/GalleryItemButton/GalleryItemMoreActionBtn.vue @@ -4,10 +4,10 @@ :title="signingModalTitle" :is-loading="isLoading" :status="status" - @try-again="burn" + @try-again="tryAgain" /> - + Transfer NFT @@ -48,7 +48,10 @@ Delist - + Report @@ -70,7 +73,6 @@ import { sanitizeIpfsUrl, toOriginalContentUrl } from '@/utils/ipfs' import { isMobileDevice } from '@/utils/extension' import { hasOperationsDisabled } from '@/utils/prefix' import { refreshOdaTokenMetadata } from '@/services/oda' -import ItemTransferModal from '@/components/common/itemTransfer/ItemTransferModal.vue' import type { NFT } from '@/types' import type { Abi } from '@/composables/transaction/types' @@ -91,15 +93,17 @@ const props = defineProps<{ abi?: Abi | null }>() -const action = ref('') +const action = ref<'unlist' | ''>('') +const id = computed(() => route.params.id.toString()) const isOwner = computed(() => accountId.value === props.nft?.currentOwner) +const isCollectionOwner = computed(() => accountId.value === props.nft?.collection?.currentOwner) const nftId = computed(() => props.nft?.id || '') const { data } = useQuery({ queryKey: ['nft-with-metadata', nftId], queryFn: async () => - nftId.value && canTransfer.value + nftId.value && canDoActions.value ? (await useAsyncGraphql({ query: 'nftEntitiesByIDs', variables: { ids: [nftId.value] }, @@ -108,12 +112,11 @@ const { data } = useQuery({ }) const nftWithMetadata = computed(() => data.value?.nftEntities?.[0]) -const canTransfer = computed(() => props.nft && isOwner.value) +const canDoActions = computed(() => props.nft && isOwner.value) const signingModalTitle = computed(() => { return ( { - burn: $i18n.t('mint.nft.burning'), unlist: $i18n.t('mint.nft.delisting'), }[action.value] || '' ) @@ -161,17 +164,12 @@ const downloadMedia = async () => { } const burn = () => { - action.value = 'burn' - transaction({ - interaction: Interaction.CONSUME, - urlPrefix: urlPrefix.value, - nftId: route.params.id as string, - nftSn: props.nft?.sn as string, - collectionId: props.nft?.collection?.id as string, - abi: props.abi, - successMessage: $i18n.t('transaction.consume.success') as string, - errorMessage: $i18n.t('transaction.consume.error') as string, - }) + openUserCartModal('burn') +} + +const tryAgain = () => { + const map = { unlist } + map[action.value]?.() } const unlist = () => { @@ -180,7 +178,7 @@ const unlist = () => { interaction: Interaction.LIST, urlPrefix: urlPrefix.value, token: { - nftId: route.params.id as string, + nftId: id.value, price: '0', }, successMessage: $i18n.t('transaction.unlist.success') as string, @@ -195,10 +193,12 @@ const refreshMetadata = async () => { } } -const transfer = () => { +const openUserCartModal = (mode: UserCartMode) => { if (nftWithMetadata.value) { listNftByNftWithMetadata(nftWithMetadata.value) - preferencesStore.itemTransferCartModalOpen = true + preferencesStore.setOpenedUserCartModal(mode) } } + +const transfer = () => openUserCartModal('transfer') diff --git a/components/items/ItemsGrid/ItemsGridImage.vue b/components/items/ItemsGrid/ItemsGridImage.vue index 23c229c9f8..9a3a40202b 100644 --- a/components/items/ItemsGrid/ItemsGridImage.vue +++ b/components/items/ItemsGrid/ItemsGridImage.vue @@ -13,7 +13,9 @@ :class="{ 'in-cart-border': shoppingCartStore.isItemInCart(nft.id) - || listingCartStore.isItemInCart(nft.id), + || listingCartStore.isItemInCart(nft.id) + || isAtomicSwapItemSelected + , }" :show-action-on-hover="!showActionSection" :link="NuxtLink" @@ -26,7 +28,7 @@ #action >
@@ -80,6 +82,7 @@ // PLEASE FIX bind-key href => to import { resolveComponent } from 'vue' import { NeoButton, NeoIcon } from '@kodadot1/brick' +import useAtomicSwapAction from './useAtomicSwapAction' import type { NftCardVariant } from '@/components/shared/nftCard/types' import type { NFTWithMetadata } from '@/composables/useNft' import { useShoppingCartStore } from '@/stores/shoppingCart' @@ -111,6 +114,12 @@ const props = defineProps<{ skeletonVariant: string }>() +const { + onAtomicSwapSelect, + showAtomicSwapAction, + isItemSelected: isAtomicSwapItemSelected, +} = useAtomicSwapAction(props.nft) + const showActionSection = computed(() => { return !isLogIn.value && shoppingCartStore.getItemToBuy?.id === props.nft.id }) @@ -125,8 +134,6 @@ const buyLabel = computed(function () { ) }) -const listLabel = computed(() => listingCartStore.isItemInCart(props.nft.id) ? $i18n.t('remove') : $i18n.t('select')) - const isOwner = computed(() => isCurrentOwner(props.nft?.currentOwner)) const openCompletePurcahseModal = () => { @@ -155,8 +162,29 @@ const onClickShoppingCart = () => { shoppingCartStore.setItem(nftToShoppingCartItem(props.nft)) } } -const onClickListingCart = () => { - listNftByNftWithMetadata(props.nft, { toggle: true }) + +const onClickListingCart = () => listNftByNftWithMetadata(props.nft, { toggle: true }) + +const selectLabel = computed(() => { + const selected = showAtomicSwapAction.value ? isAtomicSwapItemSelected.value : listingCartStore.isItemInCart(props.nft.id) + return selected ? $i18n.t('remove') : $i18n.t('select') +}) + +const showSelect = computed(() => { + if (showAtomicSwapAction.value) { + return true + } + + return isOwner.value && !props.hideListing +}) + +const onSelect = () => { + if (showAtomicSwapAction.value) { + onAtomicSwapSelect() + } + else { + onClickListingCart() + } } diff --git a/components/items/ItemsGrid/useAtomicSwapAction.ts b/components/items/ItemsGrid/useAtomicSwapAction.ts new file mode 100644 index 0000000000..3354453767 --- /dev/null +++ b/components/items/ItemsGrid/useAtomicSwapAction.ts @@ -0,0 +1,42 @@ +import type { NFTWithMetadata } from '@/composables/useNft' +import { SwapStep } from '@/components/swap/types' + +export default (nft: NFTWithMetadata) => { + const route = useRoute() + const swapStore = useAtomicSwapStore() + const { swap, step, stepItems } = storeToRefs(swapStore) + + const routeName = computed(() => route.name?.toString() as string) + + const showAtomicSwapAction = computed(() => ATOMIC_SWAP_PAGES.includes(routeName.value)) + + const isItemSelected = computed(() => { + return step.value === SwapStep.REVIEW + ? false + : [...swap.value.desired, ...swap.value.offered].flat().some(item => item.id === nft.id) + }) + + const onAtomicSwapSelect = () => { + if (isItemSelected.value) { + swapStore.removeStepItem(nft.id) + } + else { + swapStore.updateStepItems([ + ...stepItems.value, + { + id: nft.id, + collectionId: nft.collection.id, + sn: nft.sn, + name: nft.name, + meta: nft.meta, + }, + ]) + } + } + + return { + onAtomicSwapSelect, + showAtomicSwapAction, + isItemSelected, + } +} diff --git a/components/items/ItemsGrid/useItemsGrid.ts b/components/items/ItemsGrid/useItemsGrid.ts index 3e500705ba..5546446ca6 100644 --- a/components/items/ItemsGrid/useItemsGrid.ts +++ b/components/items/ItemsGrid/useItemsGrid.ts @@ -25,6 +25,7 @@ const EXCLUDED_TOKEN_USE_PAGES = [ 'prefix-collection-id', 'prefix-drops-id', 'prefix-u-id', + ...ATOMIC_SWAP_PAGES, ] export function useFetchSearch({ diff --git a/components/items/ItemsGrid/useNftActions.ts b/components/items/ItemsGrid/useNftActions.ts index b665480167..54f668de13 100644 --- a/components/items/ItemsGrid/useNftActions.ts +++ b/components/items/ItemsGrid/useNftActions.ts @@ -1,5 +1,4 @@ import nftById from '@/queries/subsquid/general/nftById.graphql' -import listCount from '@/queries/subsquid/general/countOfTokenNftsToList.graphql' import nftListWithSearch from '@/queries/subsquid/ahk/nftListWithSearch.graphql' import type { TokenId } from '@/types' @@ -120,19 +119,15 @@ export const getTokensNfts = async ( } export const checkIfAnythingToList = async (entities: TokenEntity[]) => { - const { client, urlPrefix } = usePrefix() const { accountId } = useAuth() - const { data } = await useAsyncQuery<{ count: { totalCount: number } }>({ - query: listCount, - variables: { - token: entities.map(({ id }) => id), - owner: accountId.value, - denyList: getDenyList(urlPrefix.value), - }, - clientId: client.value, + + const count = await getNftCount({ + token: { id_in: entities.map(n => n.id) }, + currentOwner_eq: accountId.value, + price_eq: 0, }) - return data.value.count.totalCount > 0 + return count > 0 } export function useNftActions(entity: TokenEntity) { diff --git a/components/migrate/steps/SignLoader3.vue b/components/migrate/steps/SignLoader3.vue index e704859fee..ddc182f8eb 100644 --- a/components/migrate/steps/SignLoader3.vue +++ b/components/migrate/steps/SignLoader3.vue @@ -82,8 +82,10 @@ diff --git a/components/swap/GridList.vue b/components/swap/GridList.vue new file mode 100644 index 0000000000..62394878e8 --- /dev/null +++ b/components/swap/GridList.vue @@ -0,0 +1,51 @@ + + + diff --git a/components/swap/GridListFilters.vue b/components/swap/GridListFilters.vue new file mode 100644 index 0000000000..dd41101d3b --- /dev/null +++ b/components/swap/GridListFilters.vue @@ -0,0 +1,35 @@ + + + diff --git a/components/swap/Preview.vue b/components/swap/Preview.vue new file mode 100644 index 0000000000..487218c5d1 --- /dev/null +++ b/components/swap/Preview.vue @@ -0,0 +1,224 @@ + + + diff --git a/components/swap/PreviewItem.vue b/components/swap/PreviewItem.vue new file mode 100644 index 0000000000..72f71ef7b1 --- /dev/null +++ b/components/swap/PreviewItem.vue @@ -0,0 +1,43 @@ + + + diff --git a/components/swap/YourProfile.vue b/components/swap/YourProfile.vue new file mode 100644 index 0000000000..983109cf8d --- /dev/null +++ b/components/swap/YourProfile.vue @@ -0,0 +1,34 @@ + + + diff --git a/components/swap/banner/accounts.vue b/components/swap/banner/accounts.vue new file mode 100644 index 0000000000..74bd74fdde --- /dev/null +++ b/components/swap/banner/accounts.vue @@ -0,0 +1,36 @@ + + + diff --git a/components/swap/banner/title.vue b/components/swap/banner/title.vue new file mode 100644 index 0000000000..2031828f81 --- /dev/null +++ b/components/swap/banner/title.vue @@ -0,0 +1,23 @@ + + + diff --git a/components/swap/landing.vue b/components/swap/landing.vue new file mode 100644 index 0000000000..fea924305e --- /dev/null +++ b/components/swap/landing.vue @@ -0,0 +1,71 @@ + + + diff --git a/components/swap/layout/Selection.vue b/components/swap/layout/Selection.vue new file mode 100644 index 0000000000..076cc491ff --- /dev/null +++ b/components/swap/layout/Selection.vue @@ -0,0 +1,33 @@ + + + diff --git a/components/swap/layout/index.vue b/components/swap/layout/index.vue new file mode 100644 index 0000000000..7c7a3e8037 --- /dev/null +++ b/components/swap/layout/index.vue @@ -0,0 +1,18 @@ + + + diff --git a/components/swap/review.vue b/components/swap/review.vue new file mode 100644 index 0000000000..5a7dcfccdb --- /dev/null +++ b/components/swap/review.vue @@ -0,0 +1,147 @@ + + + diff --git a/components/swap/types.ts b/components/swap/types.ts new file mode 100644 index 0000000000..f2109a0ff9 --- /dev/null +++ b/components/swap/types.ts @@ -0,0 +1,7 @@ +export enum SwapStep { + COUNTERPARTY, + DESIRED, + OFFERED, + REVIEW, + CREATED, +} diff --git a/components/trade/TradeExpirationSelector.vue b/components/trade/TradeExpirationSelector.vue index b644913bd8..0904d3b89f 100644 --- a/components/trade/TradeExpirationSelector.vue +++ b/components/trade/TradeExpirationSelector.vue @@ -3,7 +3,7 @@ v-model="selected" aria-role="list" :triggers="['click']" - position="bottom-left" + :position="position" append-to-body close-menu-on-move class="w-full" @@ -54,9 +54,14 @@ const options = EXPIRATION_DAYS_LIST.map(value => ({ const selectedItem = computed(() => options.find(option => option.value === selected.value)) -const props = defineProps<{ - modelValue?: number -}>() +const props = withDefaults( + defineProps<{ + modelValue?: number + position?: string + }>(), { + position: 'bottom-left', + }, +) const emit = defineEmits(['update:modelValue']) diff --git a/components/trade/TradeOwnerButton.vue b/components/trade/TradeOwnerButton.vue index ab118bee91..c059caa5f9 100644 --- a/components/trade/TradeOwnerButton.vue +++ b/components/trade/TradeOwnerButton.vue @@ -21,12 +21,12 @@ const onClick = () => emit('click', props.trade) const details = { [TradeType.SWAP]: { - cancel: 'swap.cancelSwap', - accept: 'swap.acceptSwap', + cancel: 'transaction.withdrawSwap', + accept: 'transaction.acceptSwap', }, [TradeType.OFFER]: { - cancel: 'transaction.offerWithdraw', - accept: 'transaction.offerAccept', + cancel: 'transaction.withdrawOffer', + accept: 'transaction.acceptOffer', }, } diff --git a/components/trade/overviewModal/TradeOverviewModal.vue b/components/trade/overviewModal/TradeOverviewModal.vue index 5433251699..abb6871406 100644 --- a/components/trade/overviewModal/TradeOverviewModal.vue +++ b/components/trade/overviewModal/TradeOverviewModal.vue @@ -61,12 +61,17 @@ import nftById from '@/queries/subsquid/general/nftById.graphql' import { TradeType } from '@/composables/useTrades' import type { NFT } from '@/types' -type Details = { +type OverviewModeDetails = { title: string signingTitle: string notificationTitle: string } +type Details = { + transactionSuccessTitle: string + transactionSuccessTab: string +} + type TradeNFTs = { desired: NFT, offered: NFT } type ExecTxParams = { @@ -88,36 +93,46 @@ const { urlPrefix, client } = usePrefix() const { transaction, status, isError, isLoading } = useTransaction({ disableSuccessNotification: true }) const { $i18n } = useNuxtApp() const { notification, lastSessionId, updateSession } = useLoadingNotfication() -const { $i18n: { t } } = useNuxtApp() const { mode } = useIsTradeOverview(computed(() => props.trade)) -const TradeTypeDetails: Record> = { +const TradeTypeOverviewModeDetails: Record> = { [TradeType.SWAP]: { incoming: { title: $i18n.t('swap.incomingSwap'), - signingTitle: $i18n.t('swap.acceptSwap'), + signingTitle: $i18n.t('transaction.acceptSwap'), notificationTitle: $i18n.t('swap.acceptSwap'), }, owner: { title: $i18n.t('swap.yourSwap'), - signingTitle: $i18n.t('swap.cancelSwap'), - notificationTitle: $i18n.t('swap.cancelSwap'), + signingTitle: $i18n.t('transaction.withdrawSwap'), + notificationTitle: $i18n.t('swap.swapWithdrawl'), }, }, [TradeType.OFFER]: { incoming: { title: $i18n.t('offer.incomingOffer'), - signingTitle: $i18n.t('transaction.offerAccept'), - notificationTitle: $i18n.t('transaction.offerAccept'), + signingTitle: $i18n.t('transaction.acceptOffer'), + notificationTitle: $i18n.t('offer.offerAccept'), }, owner: { title: $i18n.t('offer.yourOffer'), - signingTitle: $i18n.t('transaction.offerWithdraw'), + signingTitle: $i18n.t('transaction.withdrawOffer'), notificationTitle: $i18n.t('offer.offerWithdrawl'), }, }, } +const TradeTypeDetails: Record = { + [TradeType.SWAP]: { + transactionSuccessTitle: $i18n.t('swap.manageSwaps'), + transactionSuccessTab: 'swaps', + }, + [TradeType.OFFER]: { + transactionSuccessTitle: $i18n.t('offer.manageOffers'), + transactionSuccessTab: 'offers', + }, +} + const TradeTypeTx: Record void>> = { [TradeType.SWAP]: { owner: ({ offered }) => { @@ -166,13 +181,18 @@ const nftId = computed(() => props.trade?.desired.id) const offeredItemId = computed(() => props.trade?.offered.id) const offeredItemSn = computed(() => props.trade?.offered.sn) -const details = computed
(() => +const details = computed
(() => props.trade - ? TradeTypeDetails[props.trade.type][mode.value] + ? { + ...TradeTypeDetails[props.trade.type], + ...TradeTypeOverviewModeDetails[props.trade.type][mode.value], + } : { title: '', signingTitle: '', notificationTitle: '', + transactionSuccessTitle: '', + transactionSuccessTab: '', }) const { data: nft, pending: nftLoading } = await useAsyncData(`tarde-nft-id-${nftId.value}`, async () => { @@ -238,9 +258,9 @@ useTransactionNotification({ action: computed(() => { if (isSessionState('succeeded')) { return { - label: t('offer.manageOffers'), + label: details.value.transactionSuccessTitle, icon: 'arrow-up-right', - url: `/${urlPrefix.value}/u/${accountId.value}?tab=offers&filter=outgoing`, + url: `/${urlPrefix.value}/u/${accountId.value}?tab=${details.value.transactionSuccessTab}&filter=outgoing`, } } return undefined diff --git a/components/unique/utils.ts b/components/unique/utils.ts index 50f8b1b134..7755456c00 100644 --- a/components/unique/utils.ts +++ b/components/unique/utils.ts @@ -1,15 +1,15 @@ import BN from 'bn.js' +import { destructTokenId } from '@/utils/gallery/abstractCalls' const LEGACY_PREFIX = /^u-/ export const tokenIdToRoute = ( tokenId: string, ): { id: string, item: string } => { - const sanitized = correctId(tokenId) - const [collection, item] = sanitized.split('-') + const { collectionId: id, tokenId: item } = destructTokenId(tokenId) return { - id: collection, + id, item, } } diff --git a/composables/massmint/useMassMint.ts b/composables/massmint/useMassMint.ts index 4e8cf84fec..3645d0fb0a 100644 --- a/composables/massmint/useMassMint.ts +++ b/composables/massmint/useMassMint.ts @@ -47,7 +47,7 @@ export const useCollectionForMint = () => { collections.value = [] - return (await useAsyncGraphql({ + return (await useAsyncGraphql<{ collectionEntities: any[] }>({ query: 'collectionForMint', variables: { account: accountId.value, @@ -62,7 +62,7 @@ export const useCollectionForMint = () => { if (collectionEntities?.length) { const newCollections = collectionEntities .map(collection => ({ ...collection, lastIndexUsed: Number(collection.lastNft[0]?.sn || 0) })) - .filter(collection => (collection.max || Infinity) - collection.minted > 0) + .filter((collection: MintedCollection) => (collection.max || Infinity) - collection.alreadyMinted > 0) collections.value = unwrapSafe(newCollections) } diff --git a/composables/transaction/mintToken/utils.ts b/composables/transaction/mintToken/utils.ts index 1ce5be1c66..e28c35fca6 100644 --- a/composables/transaction/mintToken/utils.ts +++ b/composables/transaction/mintToken/utils.ts @@ -1,6 +1,5 @@ import type { ActionMintToken, - Max, MintedCollection, SubstrateMintTokenParams, TokenToMint, @@ -11,7 +10,7 @@ import type { SupportTokens } from '@/utils/support' export const copiesToMint = (token: T): number => { const { copies, selectedCollection } = token - const { alreadyMinted, max } = selectedCollection as MintedCollection & Max + const { alreadyMinted, max } = selectedCollection as MintedCollection const maxAllowedNftsInCollection = (max || 0) === 0 ? Infinity : max const remaining = maxAllowedNftsInCollection - alreadyMinted diff --git a/composables/transaction/transactionBurn.ts b/composables/transaction/transactionBurn.ts index f24e572b59..5bfc9362d4 100644 --- a/composables/transaction/transactionBurn.ts +++ b/composables/transaction/transactionBurn.ts @@ -5,134 +5,74 @@ import { } from '@kodadot1/minimark/v2' import type { ApiPromise } from '@polkadot/api' import type { PalletNftsDestroyWitness } from '@polkadot/types/lookup' -import type { Prefix } from '@kodadot1/static' +import type { SubmittableExtrinsicFunction } from '@polkadot/api-base/types' import type { ActionDeleteCollection, ExecuteSubstrateTransactionParams, - ActionBurnMultipleNFTs, ActionConsume, + ActionConsume, ExecuteTransaction, Abi, } from '@/composables/transaction/types' - import { warningMessage } from '@/utils/notification' - import { isLegacy } from '@/components/unique/utils' import { assetHubParamResolver, + destructTokenId, getApiCall, } from '@/utils/gallery/abstractCalls' function execBurnEvm(item: ActionConsume, executeTransaction: ExecuteTransaction) { + const { collectionId, tokenId } = destructTokenId(item.nftIds[0]) + executeTransaction({ - address: item.collectionId, + address: collectionId, abi: item.abi as Abi, functionName: 'burn', - arg: [item.nftSn], + arg: [tokenId], }) } -export function execBurnTx(item: ActionConsume, api, executeTransaction) { - if (isEvm(item.urlPrefix as Prefix)) { - return execBurnEvm(item, executeTransaction) - } +function execBurnAssetHub(item: ActionConsume, api: ApiPromise, executeTransaction: ExecuteTransaction) { + const getApiCallParams = (nftId: string) => { + const legacy = isLegacy(nftId) + const paramResolver = assetHubParamResolver(legacy) - if (item.urlPrefix === 'rmrk') { - executeTransaction({ - cb: api.tx.system.remark, - arg: [createInteraction(Interaction.CONSUME, item.nftId, '')], - successMessage: item.successMessage, - errorMessage: item.errorMessage, - }) - } + const apiCall = getApiCall(api, item.urlPrefix, Interaction.CONSUME) + const params = paramResolver(nftId, Interaction.CONSUME, '') - if (item.urlPrefix === 'ksm') { - executeTransaction({ - cb: api.tx.system.remark, - arg: [ - createNewInteraction({ - action: NewInteraction.BURN, - payload: { id: item.nftId }, - }), - ], - successMessage: item.successMessage, - errorMessage: item.errorMessage, - }) + return { apiCall, params } } - // item.urlPrefix === 'ahr' - if (item.urlPrefix === 'ahk' || item.urlPrefix === 'ahp') { - const legacy = isLegacy(item.nftId) - const paramResolver = assetHubParamResolver(legacy) - executeTransaction({ - cb: getApiCall(api, item.urlPrefix, Interaction.CONSUME), - arg: paramResolver(item.nftId, Interaction.CONSUME, ''), - successMessage: item.successMessage, - errorMessage: item.errorMessage, - }) + let cb: SubmittableExtrinsicFunction<'promise'>, arg + + if (item.nftIds.length > 1) { + cb = api.tx.utility.batch + arg = [ + item.nftIds.map((nftId) => { + const { apiCall, params } = getApiCallParams(nftId) + return apiCall(...params) + }), + ] + } + else { + ({ apiCall: cb, params: arg } = getApiCallParams(item.nftIds[0])) } -} -export function execBurnMultiple( - item: ActionBurnMultipleNFTs, - api: ApiPromise, - executeTransaction: ({ + executeTransaction({ cb, - arg: [arg], - successMessage, - errorMessage, - }: ExecuteSubstrateTransactionParams) => void, -) { - const cb = api.tx.utility.batch - - if (item.urlPrefix === 'rmrk') { - const arg = item.nftIds.map((nftId) => { - return api.tx.system.remark( - createInteraction(Interaction.CONSUME, nftId, ''), - ) - }) - - executeTransaction({ - cb, - arg: [arg], - successMessage: item.successMessage, - errorMessage: item.errorMessage, - }) - } + arg, + successMessage: item.successMessage, + errorMessage: item.errorMessage, + }) +} - if (item.urlPrefix === 'ksm') { - const arg = item.nftIds.map((nftId) => { - return api.tx.system.remark( - createNewInteraction({ - action: NewInteraction.BURN, - payload: { id: nftId }, - }), - ) - }) - - executeTransaction({ - cb, - arg: [arg], - successMessage: item.successMessage, - errorMessage: item.errorMessage, - }) +export function execBurn(item: ActionConsume, api, executeTransaction) { + if (isEvm(item.urlPrefix)) { + return execBurnEvm(item, executeTransaction) } - // item.urlPrefix === 'ahr' - if (item.urlPrefix === 'ahk' || item.urlPrefix === 'ahp') { - const arg = item.nftIds.map((nftId) => { - const legacy = isLegacy(nftId) - const paramResolver = assetHubParamResolver(legacy) - const apiCall = getApiCall(api, item.urlPrefix, Interaction.CONSUME) - const params = paramResolver(nftId, Interaction.CONSUME, '') - - return apiCall(...params) - }) - - executeTransaction({ - cb, - arg: [arg], - successMessage: item.successMessage, - errorMessage: item.errorMessage, - }) + + if (isAssetHub(item.urlPrefix)) { + return execBurnAssetHub(item, api, executeTransaction) } } diff --git a/composables/transaction/transactionCreateSwap.ts b/composables/transaction/transactionCreateSwap.ts new file mode 100644 index 0000000000..db0e08ea13 --- /dev/null +++ b/composables/transaction/transactionCreateSwap.ts @@ -0,0 +1,51 @@ +import type { SubmittableExtrinsic } from '@polkadot/api-base/types' +import type { CreateSwapParams } from './types' +import { BLOCKS_PER_DAY } from './transactionOffer' + +async function execCreateSwapStatmine({ item, api, executeTransaction, isLoading }: CreateSwapParams) { + isLoading.value = true + + const amountOfSwaps = item.desired.length + + const amount = Number(item.surcharge?.amount || 0) / amountOfSwaps + + const transactions = await Promise.all( + Array.from({ length: amountOfSwaps }).map(async (_, i) => { + const { collectionId: desiredCollectionId, sn: desiredItem } = item.desired[i] + const { collectionId: offeredCollectionId, sn: offeredItem } = item.offered[i] + + const arg: SubmittableExtrinsic<'promise'>[] = [] + + const swap = api.tx.nfts.createSwap( + offeredCollectionId, + offeredItem, + desiredCollectionId, + desiredItem, + item.surcharge + ? { + amount, + direction: item.surcharge.direction, + } + : null, + BLOCKS_PER_DAY * item.duration, + ) + + arg.push(swap) + + return arg + }), + ) + + executeTransaction({ + cb: api.tx.utility.batchAll, + arg: [transactions.flat()], + successMessage: item.successMessage, + errorMessage: item.errorMessage, + }) +} + +export async function execCreateSwap(params: CreateSwapParams) { + if (params.item.urlPrefix === 'ahk' || params.item.urlPrefix === 'ahp') { + await execCreateSwapStatmine(params) + } +} diff --git a/composables/transaction/transactionOffer.ts b/composables/transaction/transactionOffer.ts index 3ddc5d986d..a3b769f39c 100644 --- a/composables/transaction/transactionOffer.ts +++ b/composables/transaction/transactionOffer.ts @@ -15,7 +15,7 @@ export const getOfferCollectionId = (prefix: Prefix) => { export const OFFER_MINT_PRICE = 5e8 -const BLOCKS_PER_DAY = 300 * 24 // 12sec /block --> 300blocks/hr +export const BLOCKS_PER_DAY = 300 * 24 // 12sec /block --> 300blocks/hr async function execMakingOffer(item: ActionOffer, api, executeTransaction) { const { accountId } = useAuth() diff --git a/composables/transaction/types.ts b/composables/transaction/types.ts index 94a2d8a71e..8b2a7b042f 100644 --- a/composables/transaction/types.ts +++ b/composables/transaction/types.ts @@ -71,12 +71,12 @@ export type MintDropParams = BaseMintParams export type SubstrateMintDropParams = BaseSubstrateMintParams export type EvmMintDropParams = BaseEvmMintParams +export type CreateSwapParams = BaseSubstrateMintParams + export type NftCountType = { nftCount: number } -export type Max = { max: number } - export type SymbolType = { symbol: string } @@ -106,13 +106,10 @@ export type MintedCollection = { name?: string lastIndexUsed: number totalCount: number + max: number } -export type MintedCollectionKusama = MintedCollection & Max & SymbolType - -export type TokenToMint = BaseTokenType< - MintedCollection | MintedCollectionKusama -> & { +export type TokenToMint = BaseTokenType & { tags: Attribute[] nsfw: boolean postfix: boolean @@ -123,10 +120,8 @@ export type TokenToMint = BaseTokenType< export type ActionConsume = { interaction: Interaction.CONSUME - urlPrefix: string - nftId: string - nftSn: string - collectionId: string + urlPrefix: Prefix + nftIds: string[] abi?: Abi | null successMessage?: string errorMessage?: string @@ -193,6 +188,27 @@ export type ActionOffer = { errorMessage?: string } +export type SwapSurchargeDirection = 'Send' | 'Receive' + +export type SwapSurcharge = { amount: string, direction: SwapSurchargeDirection } + +export type TokenToSwap = { + id: string + collectionId: string + sn: string +} + +export type ActionSwap = { + interaction: typeof ShoppingActions.CREATE_SWAP + urlPrefix: string + offered: TokenToSwap[] + desired: TokenToSwap[] + surcharge?: SwapSurcharge + duration: number + successMessage?: string + errorMessage?: string +} + export type ActionWithdrawOffer = { interaction: typeof ShoppingActions.WITHDRAW_OFFER urlPrefix: Prefix @@ -206,7 +222,7 @@ export type ActionAcceptOffer = { urlPrefix: Prefix nftId: string collectionId: string - offeredId: number + offeredId: string price: string successMessage?: string errorMessage?: string @@ -277,19 +293,10 @@ export interface ActionDeleteCollection { } export enum NFTs { - BURN_MULTIPLE = 'burnMultiple', MINT_DROP = 'mintDrop', SET_METADATA = 'setMetadata', } -export interface ActionBurnMultipleNFTs { - interaction: NFTs.BURN_MULTIPLE - urlPrefix: string - nftIds: string[] - successMessage?: string | ((blockNumber: string) => string) - errorMessage?: string -} - export type ActionMetadataSetMetadata = Metadata & { image: File | string } export interface ActionSetNftMetadata { @@ -338,7 +345,7 @@ export type Actions = | ActionMintToken | ActionMintCollection | ActionDeleteCollection - | ActionBurnMultipleNFTs | ActionUpdateCollection | ActionSetNftMetadata | ActionMintDrop + | ActionSwap diff --git a/composables/transaction/utils.ts b/composables/transaction/utils.ts index 549f928499..b9a412fe51 100644 --- a/composables/transaction/utils.ts +++ b/composables/transaction/utils.ts @@ -7,7 +7,6 @@ import { } from '../transaction/types' import type { ActionAcceptOffer, - ActionBurnMultipleNFTs, ActionBuy, ActionConsume, ActionDeleteCollection, @@ -16,6 +15,7 @@ import type { ActionMintDrop, ActionMintToken, ActionOffer, + ActionSwap, ActionSend, ActionUpdateCollection, ActionWithdrawOffer, @@ -50,7 +50,7 @@ export function isActionValid(action: Actions): boolean { [Interaction.MINTNFT]: (action: ActionMintToken) => hasContent(action.token), [Interaction.SEND]: (action: ActionSend) => Boolean(action.nfts.length), - [Interaction.CONSUME]: (action: ActionConsume) => Boolean(action.nftId), + [Interaction.CONSUME]: (action: ActionConsume) => hasContent(action.nftIds), [ShoppingActions.MAKE_OFFER]: (action: ActionOffer) => hasContent(action.token), [ShoppingActions.WITHDRAW_OFFER]: (action: ActionWithdrawOffer) => @@ -67,12 +67,12 @@ export function isActionValid(action: Actions): boolean { Boolean(action.collectionId), [Collections.UPDATE_COLLECTION]: (action: ActionUpdateCollection) => Boolean(action.collectionId) && (action.update.metadata || action.update.max), - [NFTs.BURN_MULTIPLE]: (action: ActionBurnMultipleNFTs) => - hasContent(action.nftIds), [NFTs.SET_METADATA]: (action: ActionSetNftMetadata) => hasContent(action.nftSn), [NFTs.MINT_DROP]: (action: ActionMintDrop) => hasContent(action.collectionId), + [ShoppingActions.CREATE_SWAP]: (action: ActionSwap) => + hasContent(action.offered) && hasContent(action.desired) && action.offered.length === action.desired.length, } const checker = validityMap[action.interaction] diff --git a/composables/useAddress.ts b/composables/useAddress.ts index 1103046ceb..44040cb73f 100644 --- a/composables/useAddress.ts +++ b/composables/useAddress.ts @@ -1,7 +1,7 @@ import { isEthereumAddress } from '@polkadot/util-crypto' +import { type Prefix } from '@kodadot1/static' -export default function () { - const { urlPrefix } = usePrefix() +export default function (urlPrefix: ComputedRef = usePrefix().urlPrefix) { const { isEvm, isSub } = useIsChain(urlPrefix) const getPrefixByAddress = (address: string) => { diff --git a/composables/useAsyncGraphql.ts b/composables/useAsyncGraphql.ts index e20e4c1f2a..376b8e1125 100644 --- a/composables/useAsyncGraphql.ts +++ b/composables/useAsyncGraphql.ts @@ -7,10 +7,10 @@ type AyncQueryParams = { prefix?: string } -export default async function ({ query, variables, clientId, prefix }: AyncQueryParams) { +export default async function asyncGraphql({ query, variables, clientId, prefix }: AyncQueryParams) { const { urlPrefix, client } = usePrefix() - return useAsyncQuery({ + return useAsyncQuery({ query: (await resolveQueryPath(prefix || urlPrefix.value, query)).default, variables: variables, clientId: clientId || client.value, diff --git a/composables/useChainRedirect.ts b/composables/useChainRedirect.ts index 91d1dd9d1b..6212241979 100644 --- a/composables/useChainRedirect.ts +++ b/composables/useChainRedirect.ts @@ -1,6 +1,7 @@ import type { Prefix } from '@kodadot1/static' -import type { RawLocation } from 'vue-router/types/router' +import type { RouteLocationRaw, RouteLocationNormalizedLoadedGeneric } from 'vue-router' import { createVisible } from '@/utils/config/permission.config' +import { arePrefixesOfSameVm } from '@/utils/config/chain.config' import { getss58AddressByPrefix } from '@/utils/account' const NO_REDIRECT_ROUTE_NAMES = [ @@ -39,10 +40,30 @@ function getRedirectPathForPrefix({ }: { routeName: string chain: Prefix - route -}): RawLocation { + route: RouteLocationNormalizedLoadedGeneric +}): RouteLocationRaw { + const vmChanged = !arePrefixesOfSameVm(route.params.prefix.toString() as Prefix, chain) + + if (routeName.includes('prefix-swap-id')) { + const accountId = getss58AddressByPrefix(route.params.id.toString(), chain) + + return { + params: { + prefix: chain, + id: accountId, + }, + query: { + swapId: route.query.swapId, + }, + } + } + if (routeName === 'prefix-u-id') { - const accountId = getss58AddressByPrefix(route.params.id, chain) + if (vmChanged) { + return { path: `/${chain}` } + } + + const accountId = getss58AddressByPrefix(route.params.id.toString(), chain) delete route.query.collections @@ -97,7 +118,7 @@ export default function () { const router = useRouter() const redirectAfterChainChange = (newChain: Prefix): void => { - const routeName = route.name as string + const routeName = route.name?.toString() || '' if (isNoRedirect(routeName)) { return @@ -105,7 +126,7 @@ export default function () { const isSimpleCreate = routeName.includes('-create') - let redirectLocation: RawLocation = { path: `/${newChain}` } + let redirectLocation: RouteLocationRaw = { path: `/${newChain}` } if (route.params.prefix) { redirectLocation = getRedirectPathForPrefix({ diff --git a/composables/useCollectionAbi.ts b/composables/useCollectionAbi.ts index 8142da1b62..1932adad54 100644 --- a/composables/useCollectionAbi.ts +++ b/composables/useCollectionAbi.ts @@ -1,12 +1,16 @@ import { useQuery } from '@tanstack/vue-query' import type { Prefix } from '@kodadot1/static' +import type { MaybeComputedRef } from '@vueuse/core' import { fetchOdaCollectionAbi } from '@/services/oda' -export default (collectionId: Ref, prefix: Ref = usePrefix().urlPrefix) => { +export default (collectionId: Ref, { + prefix = usePrefix().urlPrefix, + disabled = false, +}: { prefix?: Ref, disabled?: MaybeComputedRef } = {}) => { const { data: abi } = useQuery({ queryKey: ['collection-abi', collectionId], queryFn: () => isEvm(prefix.value) ? fetchOdaCollectionAbi(prefix.value, collectionId.value as string) : Promise.resolve(null), - enabled: computed(() => Boolean(collectionId.value)), + enabled: computed(() => Boolean(collectionId.value) && !unref(disabled)), }) return abi diff --git a/composables/useIsChain.ts b/composables/useIsChain.ts index 2d4a33d01e..9a048fc976 100644 --- a/composables/useIsChain.ts +++ b/composables/useIsChain.ts @@ -4,17 +4,14 @@ import { vmOf } from '@/utils/config/chain.config' export const isEvm = (prefix: Prefix) => vmOf(prefix) === 'EVM' export const isSub = (prefix: Prefix) => vmOf(prefix) === 'SUB' +export const isAssetHub = (prefix: Prefix) => prefix === 'ahk' || prefix === 'ahp'// || prefix.value === 'ahr' export default function (prefix: ComputedRef) { - const isAssetHub = computed( - () => prefix.value === 'ahk' || prefix.value === 'ahp', // || prefix.value === 'ahr' - ) - const isBase = computed(() => 'base' === prefix.value) return { - isAssetHub, isBase, + isAssetHub: computed(() => isAssetHub(prefix.value)), isEvm: computed(() => isEvm(prefix.value)), isSub: computed(() => isSub(prefix.value)), } diff --git a/composables/useTransaction.ts b/composables/useTransaction.ts index 33566fd146..757cfed85d 100644 --- a/composables/useTransaction.ts +++ b/composables/useTransaction.ts @@ -7,24 +7,24 @@ import { execListTx } from './transaction/transactionList' import { execSendTx } from './transaction/transactionSend' import { execBurnCollection, - execBurnMultiple, - execBurnTx, + execBurn, } from './transaction/transactionBurn' import { execWithdrawSwap } from './transaction/transactionSwapWithdraw' import { execAcceptSwap } from './transaction/transactionSwapAccept' import { execWithdrawOfferTx } from './transaction/transactionOfferWithdraw' 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, - ActionBurnMultipleNFTs, ActionBuy, ActionConsume, ActionOffer, + ActionSwap, ActionDeleteCollection, ActionList, ActionMintCollection, @@ -230,7 +230,7 @@ export const executeAction = ({ [Interaction.SEND]: () => execSendTx(item as ActionSend, api, executeTransaction), [ShoppingActions.CONSUME]: () => - execBurnTx(item as ActionConsume, api, executeTransaction), + execBurn(item as ActionConsume, api, executeTransaction), [ShoppingActions.WITHDRAW_SWAP]: () => execWithdrawSwap(item as ActionWithdrawSwap, api!, executeTransaction), [ShoppingActions.ACCEPT_SWAP]: () => @@ -241,6 +241,14 @@ export const executeAction = ({ 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, @@ -271,12 +279,6 @@ export const executeAction = ({ isLoading, status, }), - [NFTs.BURN_MULTIPLE]: () => - execBurnMultiple( - item as ActionBurnMultipleNFTs, - api as ApiPromise, - executeTransaction, - ), [NFTs.SET_METADATA]: () => execSetNftMetadata({ item: item as ActionSetNftMetadata, diff --git a/layouts/explore-layout.vue b/layouts/explore-layout.vue index 7c0bb8a664..bdd9838d24 100644 --- a/layouts/explore-layout.vue +++ b/layouts/explore-layout.vue @@ -32,7 +32,7 @@ {0} is required to create a {1}. Please note, this initial deposit is refundable.", "royalty": { @@ -1811,10 +1816,38 @@ "tooltip": "Cover costs mean that users help to support initial costs for Koda which stores your JPEG on your behalf." }, "swap": { - "acceptSwap": "Accept Swap", - "cancelSwap": "Cancel Swap", + "acceptSwap": "Accepting Swap", + "addToken": "Add Token", + "beginSwap": "Begin Swap offer", + "clickOnNft": "Click on any NFT to add it to your swap list.", + "connectTrader": "Connect with a trader", + "connectTraderInfo": "Enter the wallet address of the trader you want to engage with, and we’ll guide you through the secure process of making a swap offer.", + "counterparty": "Counterparty", + "created": "Swap Created", + "creatingSwap": "Creating Swap", "incomingSwap": "Incoming Swap", - "yourSwap": "Your Swap" + "landingSubtitle": "Koda Swap facilitates secure and straightforward exchanges of digital assets. Connect directly with traders and confidently navigate your NFT transactions with our reliable platform.", + "landingTitle": "Koda Swap", + "learnMoreAboutSwaps": "Learn more about swaps", + "manageSwaps": "Manage Swaps", + "modifyOffer": "Modify Offer", + "requestToken": "Request Token", + "reviewCheckAssets": "Check the assets on both sides of the swap", + "reviewCounterpartyAccept": "If counterparty accept, these are the NFTs you will gain", + "reviewOffer": "Review swap offer", + "reviewSelected": "The NFTs you’ve selected to offer are displayed below. Confirm your selections are correct.", + "selectNft": "Select their NFTs for the Swap", + "selectOffer": "Select your offer", + "selectOfferSubtitle": "Select your NFTs you want to offer in this swap.", + "submit": "Submit Swap Offer", + "swap": "Swap", + "swapWithdrawl": "Swap Cancelation", + "youOffer": "You offer", + "youWillReceive": "You will receive", + "yourAddress": "Your Address", + "yourOffer": "Your offer", + "yourSwap": "Your Swap", + "yourSwapList": "Your Swap List" }, "swaps": "Swaps", "tabs": { @@ -1945,16 +1978,16 @@ } }, "transaction": { + "acceptOffer": "Accept Offer", + "acceptSwap": "Accept Swap", "addressIncorrect": "Address Incorrect", + "burnNft": "Burn NFT | Burn NFTs", "buy": { "error": "Failed to buy this item" }, "consume": { "error": "Failed to burn item", "success": "Item burned" }, "inputAddressFirst": "Input address first", "item": { "error": "Failed to send item", "success": "Item sent" }, "list": "List", "offer": "Create Offer", - "offerAccept": "Accept Offer", - "offerError": "Offer creation failed", - "offerWithdraw": "Cancel Offer", "price": { "change": "Change Price", "error": "Price update failed", @@ -1972,6 +2005,8 @@ "error": "Item unlisting failed", "success": "Item successfully unlisted" }, + "withdrawOffer": "Cancel Offer", + "withdrawSwap": "Cancel Swap", "wrongAddressCannotRecoveredWarning": "Items sent to the wrong address cannot be recovered." }, "transactionLoader": { diff --git a/middleware/redirects.global.ts b/middleware/redirects.global.ts index fbf7f97c05..688fbff27a 100644 --- a/middleware/redirects.global.ts +++ b/middleware/redirects.global.ts @@ -1,6 +1,6 @@ import { type Prefix } from '@kodadot1/static' import type { RouteLocationRaw } from 'vue-router' -import { createVisible, transferVisible, teleportVisible, migrateVisible } from '@/utils/config/permission.config' +import { createVisible, transferVisible, teleportVisible, migrateVisible, swapVisible } from '@/utils/config/permission.config' export default defineNuxtRouteMiddleware((route) => { const { urlPrefix } = usePrefix() @@ -51,6 +51,7 @@ export default defineNuxtRouteMiddleware((route) => { }, getPermissionRouteCondition((val: string) => val === `/${urlPrefix.value}/teleport`, teleportVisible), getPermissionRouteCondition((val: string) => val === `/${urlPrefix.value}/transfer`, transferVisible), + getPermissionRouteCondition((val: string) => val.includes(`/${urlPrefix.value}/swap`), swapVisible), getPermissionRouteCondition((val: string) => val === '/migrate', migrateVisible), { cond: (val: string) => val.startsWith('/transfer'), diff --git a/middleware/swap.ts b/middleware/swap.ts new file mode 100644 index 0000000000..810d7e2cce --- /dev/null +++ b/middleware/swap.ts @@ -0,0 +1,47 @@ +import { type Prefix } from '@kodadot1/static' +import { SwapStep } from '@/components/swap/types' + +export default defineNuxtRouteMiddleware((to) => { + const swapStore = useAtomicSwapStore() + const { swap, items, step } = storeToRefs(swapStore) + + const prefix = to.params.prefix?.toString() as Prefix + const swapId = to.query.swapId?.toString() + const id = to.params.id?.toString() + const routeName = to.name?.toString() + + if (!id || !routeName) { + return navigateTo({ name: getSwapStepRouteName(SwapStep.COUNTERPARTY) }) + } + + const routeStep = SWAP_ROUTE_NAME_STEP_MAP[routeName] + + const foundSwap = items.value + .filter(item => + item.counterparty === id + && item.id === swapId + && item.urlPrefix === prefix, + )[0] + + if (!foundSwap) { + return navigateTo({ + name: getSwapStepRouteName(SwapStep.DESIRED), + params: { id, prefix }, + query: { swapId: swapStore.createSwap(id).id }, + }) + } + + swap.value = foundSwap + + const swapStep = getSwapStep(swap.value) + + if (swapStep === SwapStep.CREATED) { + return navigateTo({ name: getSwapStepRouteName(SwapStep.COUNTERPARTY), params: { prefix } }) + } + + step.value = routeStep + + if (routeStep > swapStep) { + return navigateTo({ name: getSwapStepRouteName(swapStep), params: { id, prefix }, query: { swapId: swap.value.id } }) + } +}) diff --git a/pages/[prefix]/swap/[id]/index.vue b/pages/[prefix]/swap/[id]/index.vue new file mode 100644 index 0000000000..36e190dc1e --- /dev/null +++ b/pages/[prefix]/swap/[id]/index.vue @@ -0,0 +1,10 @@ + + + diff --git a/pages/[prefix]/swap/[id]/offer.vue b/pages/[prefix]/swap/[id]/offer.vue new file mode 100644 index 0000000000..fec12ce6a8 --- /dev/null +++ b/pages/[prefix]/swap/[id]/offer.vue @@ -0,0 +1,10 @@ + + + diff --git a/pages/[prefix]/swap/[id]/review.vue b/pages/[prefix]/swap/[id]/review.vue new file mode 100644 index 0000000000..2802fd2a61 --- /dev/null +++ b/pages/[prefix]/swap/[id]/review.vue @@ -0,0 +1,10 @@ + + + diff --git a/pages/[prefix]/swap/index.vue b/pages/[prefix]/swap/index.vue new file mode 100644 index 0000000000..ef7a323847 --- /dev/null +++ b/pages/[prefix]/swap/index.vue @@ -0,0 +1,18 @@ + + + diff --git a/pages/[prefix]/u/[id].vue b/pages/[prefix]/u/[id].vue index 507574966b..25203c4c55 100644 --- a/pages/[prefix]/u/[id].vue +++ b/pages/[prefix]/u/[id].vue @@ -3,7 +3,7 @@ - +
diff --git a/queries/subsquid/ahk/nftById.graphql b/queries/subsquid/ahk/nftById.graphql index eac7db68b0..09057edc03 100644 --- a/queries/subsquid/ahk/nftById.graphql +++ b/queries/subsquid/ahk/nftById.graphql @@ -9,6 +9,7 @@ query nftById($id: String!) { collection { id name + currentOwner } attributes { key: trait diff --git a/queries/subsquid/general/collectionForMint.graphql b/queries/subsquid/general/collectionForMint.graphql index 95e49f913e..67f4cdf039 100644 --- a/queries/subsquid/general/collectionForMint.graphql +++ b/queries/subsquid/general/collectionForMint.graphql @@ -7,7 +7,7 @@ query collectionForMint($account: String!) { name metadata max - minted: nftCount + alreadyMinted: nftCount totalCount: supply lastNft: nfts(orderBy: sn_DESC, limit: 1) { sn diff --git a/queries/subsquid/general/collectionLastList.graphql b/queries/subsquid/general/collectionLastList.graphql deleted file mode 100644 index d57778eaaa..0000000000 --- a/queries/subsquid/general/collectionLastList.graphql +++ /dev/null @@ -1,20 +0,0 @@ -#import "../../fragments/collection.graphql" -#import "../../fragments/collectionDetails.graphql" - -query collectionLastList { - collectionEntities( - orderBy: blockNumber_DESC - limit: 6 - where: { - nfts_some: { burned_eq: false } - name_isNull: false - name_not_eq: "" - } - ) { - ...collection - ...collectionDetails - meta { - image - } - } -} diff --git a/queries/subsquid/general/collectionListWithSearchMinimal.graphql b/queries/subsquid/general/collectionListWithSearchMinimal.graphql new file mode 100644 index 0000000000..e87134dc0f --- /dev/null +++ b/queries/subsquid/general/collectionListWithSearchMinimal.graphql @@ -0,0 +1,35 @@ +#import "../../fragments/collection.graphql" +#import "../../fragments/collectionDetails.graphql" + +query collectionListWithSearchMinimal( + $first: Int! + $offset: Int + $search: [CollectionEntityWhereInput!] + $orderBy: [CollectionEntityOrderByInput!] = [blockNumber_DESC] + $denyList: [String!] +) { + collectionEntities( + orderBy: $orderBy + limit: $first + offset: $offset + where: { + nfts_some: { burned_eq: false, issuer_not_in: $denyList } + AND: $search + metadata_isNull: false + } + ) { + ...collection + ...collectionDetails + ownerCount + } + stats: collectionEntitiesConnection( + where: { + nfts_some: { burned_eq: false, issuer_not_in: $denyList } + AND: $search + metadata_isNull: false + } + orderBy: blockNumber_DESC + ) { + totalCount + } +} diff --git a/queries/subsquid/general/countOfNfts.graphql b/queries/subsquid/general/countOfNfts.graphql new file mode 100644 index 0000000000..310bd2d200 --- /dev/null +++ b/queries/subsquid/general/countOfNfts.graphql @@ -0,0 +1,19 @@ +query countOfNfts( + $where: NFTEntityWhereInput! + $denyList: [String!] +) { + count: nftEntitiesConnection( + where: { + AND: [ + { + burned_eq: false + issuer_not_in: $denyList + }, + $where + ] + } + orderBy: blockNumber_DESC + ) { + totalCount + } +} diff --git a/queries/subsquid/general/countOfTokenNftsToList.graphql b/queries/subsquid/general/countOfTokenNftsToList.graphql deleted file mode 100644 index a3ea265e02..0000000000 --- a/queries/subsquid/general/countOfTokenNftsToList.graphql +++ /dev/null @@ -1,18 +0,0 @@ -query countOfTokenNftsToList( - $owner: String! - $token: [String!] - $denyList: [String!] -) { - count: nftEntitiesConnection( - where: { - burned_eq: false - issuer_not_in: $denyList - currentOwner_eq: $owner - token: { id_in: $token } - price_eq: 0 - } - orderBy: blockNumber_DESC - ) { - totalCount - } -} diff --git a/queries/subsquid/general/nftById.graphql b/queries/subsquid/general/nftById.graphql index 22e4e90e55..54f9d3b2ef 100644 --- a/queries/subsquid/general/nftById.graphql +++ b/queries/subsquid/general/nftById.graphql @@ -8,6 +8,7 @@ query nftById($id: String!) { collection { id name + currentOwner floorPrice: nfts( where: { burned_eq: false, price_not_eq: "0" } orderBy: price_ASC diff --git a/queries/subsquid/ksm/nftById.graphql b/queries/subsquid/ksm/nftById.graphql index 9cd904eaa6..75435b9c57 100644 --- a/queries/subsquid/ksm/nftById.graphql +++ b/queries/subsquid/ksm/nftById.graphql @@ -20,6 +20,7 @@ query nftById($id: String!) { collection { id name + currentOwner } emotes { caller diff --git a/queries/subsquid/rmrk/nftById.graphql b/queries/subsquid/rmrk/nftById.graphql index 870cc69d17..a7c34d9631 100644 --- a/queries/subsquid/rmrk/nftById.graphql +++ b/queries/subsquid/rmrk/nftById.graphql @@ -8,6 +8,7 @@ query nftById($id: String!) { collection { id name + currentOwner } emotes { caller diff --git a/stores/atomicSwaps.ts b/stores/atomicSwaps.ts new file mode 100644 index 0000000000..311e87644e --- /dev/null +++ b/stores/atomicSwaps.ts @@ -0,0 +1,113 @@ +import { defineStore } from 'pinia' +import { computed } from 'vue' +import { type SwapSurcharge } from '@/composables/transaction/types' +import { SwapStep } from '@/components/swap/types' + +export type AtomicSwap = { + counterparty: string + creator?: string + offered: SwapItem[] + desired: SwapItem[] + createdAt: number + surcharge?: SwapSurcharge + duration: number + blockNumber?: string +} & CartItem + +export type SwapItem = { + id: string + name: string + collectionId: string + sn: string + meta: any +} + +const DEFAULT_SWAP: Omit = { + id: '', + counterparty: '', + offered: [], + desired: [], + createdAt: 0, + duration: 7, +} + +export const useAtomicSwapStore = defineStore('atomicSwap', () => { + const { + items, + chain, + count, + itemsInChain, + getItem, + setItem, + clear, + updateItem, + } = useCart() + + const { accountId } = useAuth() + const { urlPrefix } = usePrefix() + + const swap = ref({ ...DEFAULT_SWAP, urlPrefix: urlPrefix.value }) + const step = ref(SwapStep.COUNTERPARTY) + + const getItems = computed(() => items.value) + const stepItems = computed(() => getStepItems(step.value)) + + const createSwap = (counterparty: string) => { + const newAtomicSwap: AtomicSwap = { + id: window.crypto.randomUUID().split('-')[0], + counterparty, + offered: [], + desired: [], + createdAt: Date.now(), + urlPrefix: urlPrefix.value, + duration: 7, + creator: accountId.value ? accountId.value : undefined, + } + + setItem(newAtomicSwap) + + return newAtomicSwap + } + + const getStepItems = (step: SwapStep) => swap.value[getStepItemsKey(step) || ''] || [] + + const updateStepItems = (items: SwapItem[]) => { + const key = getStepItemsKey(step.value) + if (key) { + updateSwap({ [key]: items }) + } + } + + const removeStepItem = (id: string) => { + updateStepItems(getStepItems(step.value).filter(item => item.id !== id)) + } + + const updateSwap = (payload: Partial) => { + swap.value = { + ...swap.value, + ...payload, + } + updateItem(swap.value) + } + + return { + // state + items, + swap, + step, + // getters + chain, + count, + itemsInChain, + getItems, + stepItems, + // actions + getItem, + getStepItems, + clear, + createSwap, + updateSwap, + updateStepItems, + removeStepItem, + } +}, { persist: true }) diff --git a/stores/preferences.ts b/stores/preferences.ts index 364f66b4c9..9ece1816e2 100644 --- a/stores/preferences.ts +++ b/stores/preferences.ts @@ -41,12 +41,14 @@ type NewsletterSubscription = { email?: string } +export type UserCartMode = 'transfer' | 'burn' + interface State { sidebarFilterCollapseOpen: boolean mobileFilterCollapseOpen: boolean shoppingCartCollapseOpen: boolean listingCartModalOpen: boolean - itemTransferCartModalOpen: boolean + userCartModal: { open: boolean, mode: UserCartMode } | undefined makeOfferModalOpen: boolean completePurchaseModal: CompletePurchaseModalState triggerBuySuccess: boolean @@ -81,7 +83,7 @@ export const usePreferencesStore = defineStore('preferences', { sidebarFilterCollapseOpen: true, mobileFilterCollapseOpen: false, listingCartModalOpen: false, - itemTransferCartModalOpen: false, + userCartModal: undefined, makeOfferModalOpen: false, shoppingCartCollapseOpen: false, completePurchaseModal: { @@ -239,6 +241,9 @@ export const usePreferencesStore = defineStore('preferences', { setNewsletterSubscription(subscription: NewsletterSubscription) { this.newsletterSubscription = subscription }, + setOpenedUserCartModal(mode: UserCartMode) { + this.userCartModal = { mode, open: true } + }, }, persist: true, }) diff --git a/types/index.ts b/types/index.ts index a20328adbb..81f1b02db8 100644 --- a/types/index.ts +++ b/types/index.ts @@ -110,6 +110,7 @@ export interface TokenId { export type EntityWithId = { id: string name: string + currentOwner: string floor: string } diff --git a/utils/config/permission.config.ts b/utils/config/permission.config.ts index fd048f4660..f72346f4bd 100644 --- a/utils/config/permission.config.ts +++ b/utils/config/permission.config.ts @@ -3,7 +3,6 @@ import type { PartialConfig, Prefix } from './types' const hasCreate: PartialConfig = { dot: false, ksm: false, - rmrk: false, } const hasInsight: PartialConfig = { @@ -24,7 +23,6 @@ const hasMassmintCreate: PartialConfig = { const hasExplorer: PartialConfig = { dot: false, - rmrk: false, ksm: false, } @@ -49,7 +47,7 @@ export const migrateVisible = (prefix: Prefix | string): boolean => { } export const offerVisible = (prefix: Prefix | string): boolean => { - return ['ahp', 'ahk'].includes(prefix) + return isAssetHub(prefix as Prefix) } export const seriesInsightVisible = (prefix: Prefix | string) => { @@ -65,9 +63,17 @@ export const salesVisible = (prefix: Prefix | string) => { } export const dropsVisible = (prefix: Prefix | string) => { - return prefix === 'ahk' || prefix === 'ahp' || isEvm(prefix) + return isAssetHub(prefix as Prefix) || isEvm(prefix as Prefix) } export const explorerVisible = (prefix: Prefix | string): boolean => { return hasExplorer[prefix] ?? true } + +export const burnVisible = (prefix: Prefix): boolean => { + return isSub(prefix) || isEvm(prefix) +} + +export const swapVisible = (prefix: Prefix): boolean => { + return isAssetHub(prefix) +} diff --git a/utils/gallery/abstractCalls.ts b/utils/gallery/abstractCalls.ts index a5d2be97af..72f7ff63c1 100644 --- a/utils/gallery/abstractCalls.ts +++ b/utils/gallery/abstractCalls.ts @@ -25,13 +25,18 @@ export const nftActionResolver: CallDictionary = { // REVOKE: ['uniques', 'cancelApproval'], } +export const destructTokenId = (id: string) => { + const sanitized = correctId(id) + const [collectionId, tokenId] = sanitized.split('-') + return { collectionId, tokenId } +} + export const uniqueParamResolver = ( id: string, selectedAction: string, meta: string | number, ): any[] => { - const sanitized = correctId(id) - const [collectionId, tokenId] = sanitized.split('-') + const { collectionId, tokenId } = destructTokenId(id) const actions = { SEND: [collectionId, tokenId, meta], CONSUME: [collectionId, tokenId], @@ -47,8 +52,7 @@ export const nftParamResolver = ( selectedAction: string, meta: string | number, ): any[] => { - const sanitized = correctId(id) - const [collectionId, tokenId] = sanitized.split('-') + const { collectionId, tokenId } = destructTokenId(id) const actions = { SEND: [collectionId, tokenId, meta], CONSUME: [collectionId, tokenId], diff --git a/utils/nfts.ts b/utils/nfts.ts new file mode 100644 index 0000000000..28a0ce24ee --- /dev/null +++ b/utils/nfts.ts @@ -0,0 +1,13 @@ +export const getNftCount = async (where: Record) => { + const { urlPrefix } = usePrefix() + + const response = await useAsyncGraphql<{ count: { totalCount: number } }>({ + query: 'countOfNfts', + variables: { + where, + denyList: getDenyList(urlPrefix.value), + }, + }) + + return response.data.value.count.totalCount +} diff --git a/utils/shoppingActions.ts b/utils/shoppingActions.ts index 90b4f0287d..32db53a4de 100644 --- a/utils/shoppingActions.ts +++ b/utils/shoppingActions.ts @@ -7,6 +7,7 @@ enum OfferActions { SET_ROYALTY = 'SET_ROYALTY', WITHDRAW_OFFER = 'WITHDRAW_OFFER', ACCEPT_OFFER = 'ACCEPT_OFFER', + CREATE_SWAP = 'CREATE_SWAP', } enum SwapActions { diff --git a/utils/swap.ts b/utils/swap.ts new file mode 100644 index 0000000000..721656171d --- /dev/null +++ b/utils/swap.ts @@ -0,0 +1,45 @@ +import { SwapStep } from '@/components/swap/types' + +export const SWAP_ROUTE_NAME_STEP_MAP = { + 'prefix-swap': SwapStep.COUNTERPARTY, + 'prefix-swap-id': SwapStep.DESIRED, + 'prefix-swap-id-offer': SwapStep.OFFERED, + 'prefix-swap-id-review': SwapStep.REVIEW, +} + +export const ATOMIC_SWAP_PAGES = [ + 'prefix-swap-id', + 'prefix-swap-id-offer', + 'prefix-swap-id-review', +] + +export const getSwapStepRouteName = (step: SwapStep) => { + const index = Object.values(SWAP_ROUTE_NAME_STEP_MAP).findIndex(name => name === step) + return Object.keys(SWAP_ROUTE_NAME_STEP_MAP)[index] +} + +export const getSwapStep = (swap: AtomicSwap): SwapStep => { + if (swap.blockNumber) { + return SwapStep.CREATED + } + + if (!swap.desired.length) { + return SwapStep.DESIRED + } + + if (!swap.offered.length || swap.offered.length !== swap.desired.length) { + return SwapStep.OFFERED + } + + return SwapStep.REVIEW +} + +export const getStepItemsKey = (step: SwapStep) => { + if (step === SwapStep.DESIRED) { + return 'desired' + } + + if (step === SwapStep.OFFERED) { + return 'offered' + } +}