Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Create Collection Atomic Swap #11410

Merged
merged 10 commits into from
Mar 4, 2025
19 changes: 18 additions & 1 deletion components/collection/CollectionTrades.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,31 @@
:key="key"
:query="tradeQuery"
:type="tradeType"
/>
>
<template
v-if="collectionId"
#action
>
<CreateCollectionSwapButton
v-if="isTradeSwap(tradeType)"
:collection-id="collectionId"
/>
<CreateCollectionOfferButton
v-else-if="isTradeOffer(tradeType)"
:collection-id="collectionId"
/>
</template>
</TradeActivityTable>
</div>
</div>
</template>

<script setup lang="ts">
import { type TradeTableQuery } from '@/components/trade/TradeActivityTable.vue'
import type { TradeType } from '@/components/trade/types'
import { isTradeSwap } from '@/composables/useTradeType'
import CreateCollectionSwapButton from '@/components/swap/CreateCollectionSwapButton.vue'
import CreateCollectionOfferButton from '@/components/trade/makeOffer/CreateCollectionOfferButton.vue'

defineProps<{
tradeType: TradeType
Expand Down
2 changes: 1 addition & 1 deletion components/collection/drop/ItemsGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<ItemsGrid
:search="itemsGridSearch"
:grid-size="'medium'"
collection-popover-hide
hide-collection-popover
hide-listing
show-timestamp
:reset-search-query-params="['sort']"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import { usePreferencesStore } from '@/stores/preferences'
import { useMakingOfferStore } from '@/stores/makeOffer'
import GalleryItemPriceSection from '@/components/gallery/GalleryItemAction/GalleryItemActionSection.vue'
import type { NFTOffer } from '@/composables/useNft'
import { doAfterCheckCurrentChainVM } from '@/components/common/ConnectWallet/openReconnectWalletModal'

const props = defineProps<{
nft: NFT
Expand All @@ -51,10 +50,9 @@ const props = defineProps<{
const preferencesStore = usePreferencesStore()
const makeOfferStore = useMakingOfferStore()
const swapStore = useAtomicSwapStore()
const { doAfterLogin } = useDoAfterlogin()
const { isCurrentAccount, isLogIn } = useAuth()
const { isCurrentAccount } = useAuth()
const isOwner = computed(() => isCurrentAccount(props.nft?.currentOwner))

const { onTradeActionClick } = useTradeActionClick(isOwner)
const highestOfferPrice = computed(() => props.highestOffer?.price || '')

const openOfferModal = () => {
Expand All @@ -65,21 +63,6 @@ const openOfferModal = () => {
preferencesStore.setMakeOfferModalOpen(true)
}

const onTradeActionClick = (cb: () => void) => {
const fn = () => {
if (!isOwner.value) {
cb()
}
}

if (isLogIn.value) {
doAfterCheckCurrentChainVM(fn)
}
else {
doAfterLogin({ onLoginSuccess: fn })
}
}

const onMakeOfferClick = () => {
onTradeActionClick(openOfferModal)
}
Expand Down
6 changes: 3 additions & 3 deletions components/items/ItemsGrid/ItemsGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
:hide-media-info="hideMediaInfo"
:hide-action="hideNFTHoverAction"
:show-timestamp="showTimestamp"
:collection-popover-hide="collectionPopoverHide"
:hide-collection-popover="hideCollectionPopover"
:hide-listing="hideListing"
:lazy-loading="
shouldLazyLoad({
Expand Down Expand Up @@ -148,7 +148,7 @@ const props = defineProps<{
loadingOtherNetwork?: boolean
showTimestamp?: boolean
hideHoverAction?: boolean
collectionPopoverHide?: boolean
hideCollectionPopover?: boolean
hideListing?: boolean
linkTarget?: string
fetchOnchainData?: boolean
Expand Down Expand Up @@ -284,7 +284,7 @@ const getSkeletonVariant = (slotProps) => {
if (slotProps.isMobileVariant || slotProps.grid === 'small') {
return 'minimal'
}
if (props.collectionPopoverHide) {
if (props.hideCollectionPopover) {
return 'slim'
}
return 'primary'
Expand Down
4 changes: 2 additions & 2 deletions components/items/ItemsGrid/ItemsGridImage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
:variant="variant"
:hide-media-info="hideMediaInfo"
:show-timestamp="showTimestamp"
:collection-popover-hide="collectionPopoverHide"
:hide-collection-popover="hideCollectionPopover"
:lazy-loading="lazyLoading"
:class="{ 'in-cart-border': shoppingCartStore.isItemInCart(nft.id) || isSelectActionItemInCart }"
:show-action-on-hover="!showActionSection"
Expand Down Expand Up @@ -104,7 +104,7 @@ const props = defineProps<{
hideVideoControls?: boolean
hideListing?: boolean
showTimestamp?: boolean
collectionPopoverHide?: boolean
hideCollectionPopover?: boolean
lazyLoading?: boolean
skeletonVariant: string
linkTarget?: string
Expand Down
4 changes: 2 additions & 2 deletions components/shared/nftCard/NftCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
:prefix="prefix"
:show-price="showPrice"
:show-timestamp="showTimestamp"
:collection-popover-hide="collectionPopoverHide"
:hide-collection-popover="hideCollectionPopover"
:collection-popover-show-delay="collectionPopoverShowDelay"
/>
</component>
Expand Down Expand Up @@ -125,7 +125,7 @@ const props = withDefaults(
prefix: string
showPrice?: boolean
showTimestamp?: boolean
collectionPopoverHide?: boolean
hideCollectionPopover?: boolean
collectionPopoverShowDelay?: number
variant?: NftCardVariant
placeholder?: string
Expand Down
6 changes: 3 additions & 3 deletions components/shared/nftCard/NftMediaInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
:class="[
`nft-media-info__${variant}`,
{
'nft-media-info__slim': collectionPopoverHide,
'nft-media-info__slim': hideCollectionPopover,
},
]"
>
Expand All @@ -17,7 +17,7 @@
<CollectionDetailsPopover
v-if="
!isMinimal
&& !collectionPopoverHide
&& !hideCollectionPopover
&& (nft.collection.name || nft.collection.id)
"
:show-delay="collectionPopoverShowDelay"
Expand Down Expand Up @@ -74,7 +74,7 @@ const props = withDefaults(
prefix: string
showPrice?: boolean
showTimestamp?: boolean
collectionPopoverHide?: boolean
hideCollectionPopover?: boolean
collectionPopoverShowDelay?: number
variant?: NftCardVariant
}>(),
Expand Down
49 changes: 49 additions & 0 deletions components/swap/CreateCollectionSwapButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<template>
<NeoButton
variant="secondary"
class=""
@click="onCreateCollectionSwapClick"
>
{{ $t('swap.createCollectionSwap') }}
</NeoButton>
</template>

<script lang="ts" setup>
import { NeoButton } from '@kodadot1/brick'
import { useCollectionMinimal } from '@/components/collection/utils/useCollectionDetails'

const props = defineProps<{
collectionId: string
}>()

const { $i18n } = useNuxtApp()

const collectionId = computed(() => props.collectionId)
const swapStore = useAtomicSwapStore()

const { collection } = useCollectionMinimal({
collectionId: collectionId,
})
const { onTradeActionClick } = useTradeActionClick()

const onCreateCollectionSwapClick = () => {
onTradeActionClick(() => {
const swap = swapStore.createSwap(collectionId.value!, {
isCollectionSwap: true,
desired: [
{
id: null,
collectionId: collectionId.value!,
name: $i18n.t('swap.anyNftFromCollection', [collection.value?.name]),
sn: null,
meta: {
image: collection.value?.meta?.image,
},
},
],

})
navigateToSwap(swap)
})
}
</script>
44 changes: 24 additions & 20 deletions components/swap/Preview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
</span>

<a
v-if="count"
v-if="count && !isCollectionSwapDesired"
@click="clearAll"
>
{{ $t('shoppingCart.clearAll') }}
Expand All @@ -46,6 +46,7 @@
:name="nft.name"
:image="sanitizeIpfsUrl(nft.meta.image)"
image-class="border border-k-shade"
:removable="!(isCollectionSwap && nft.id === null)"
@remove="swapStore.removeStepItem(nft.id)"
/>

Expand Down Expand Up @@ -107,7 +108,7 @@
size="large"
label="Next"
variant="primary"
:disabled
:disabled="disabled"
no-shadow
expanded
@click="onNext"
Expand All @@ -133,23 +134,6 @@ type StepDetails = {
surchargeDirection: SwapSurchargeDirection
}

const stepDetailsMap: Partial<Record<SwapStep, StepDetails>> = {
[SwapStep.DESIRED]: {
title: 'swap.yourSwapList',
surchargeTitle: 'swap.requestToken',
nextRouteName: getSwapStepRouteName(SwapStep.OFFERED),
backRouteName: getSwapStepRouteName(SwapStep.COUNTERPARTY),
surchargeDirection: 'Receive',
},
[SwapStep.OFFERED]: {
title: 'swap.yourOffer',
surchargeTitle: 'swap.addToken',
nextRouteName: getSwapStepRouteName(SwapStep.REVIEW),
backRouteName: getSwapStepRouteName(SwapStep.DESIRED),
surchargeDirection: 'Send',
},
}

const props = defineProps<{
step: SwapStep
}>()
Expand All @@ -161,20 +145,40 @@ const { accountId } = useAuth()
const { decimals } = useChain()
const { urlPrefix } = usePrefix()
const { getChainIcon } = useIcon()
const isCollectionSwap = computed(() => swap.value.isCollectionSwap)

const stepDetailsMap: ComputedRef<Partial<Record<SwapStep, StepDetails>>> = computed(() => ({
[SwapStep.DESIRED]: {
title: 'swap.yourSwapList',
surchargeTitle: 'swap.requestToken',
nextRouteName: getSwapStepRouteName(SwapStep.OFFERED, isCollectionSwap.value),
backRouteName: getSwapStepRouteName(SwapStep.COUNTERPARTY, isCollectionSwap.value),
surchargeDirection: 'Receive',
},
[SwapStep.OFFERED]: {
title: 'swap.yourOffer',
surchargeTitle: 'swap.addToken',
nextRouteName: getSwapStepRouteName(SwapStep.REVIEW, isCollectionSwap.value),
backRouteName: getSwapStepRouteName(SwapStep.DESIRED, isCollectionSwap.value),
surchargeDirection: 'Send',
},
}))

const target = ref()
const amount = ref()
const itemsContainer = ref()

const isTargetVisible = useElementVisibility(target)
const stepItems = computed(() => swapStore.getStepItems(props.step))
const stepDetails = computed(() => stepDetailsMap[props.step] as StepDetails)
const stepDetails = computed(() => stepDetailsMap.value[props.step] as StepDetails)
const title = computed(() => $i18n.t(stepDetails.value.title))
const surchargeTitle = computed(() => $i18n.t(stepDetails.value.surchargeTitle))
const surchargeDisabled = computed(() => Boolean(swap.value.surcharge))
const stepHasSurcharge = computed(() => swap.value.surcharge?.direction === stepDetails.value.surchargeDirection)
const count = computed(() => stepItems.value.length + (stepHasSurcharge.value ? 1 : 0))
const isOverOneToOneSwap = computed(() => swap.value.offered.length > swap.value.desired.length && props.step === SwapStep.OFFERED)
const isCollectionSwapDesired = computed(() => isCollectionSwap.value && props.step === SwapStep.DESIRED)

const disabled = computed(() => {
if ((!accountId.value && props.step === SwapStep.OFFERED) || isOverOneToOneSwap.value) {
return true
Expand Down
17 changes: 12 additions & 5 deletions components/swap/PreviewItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
</div>

<NeoButton
v-if="removable"
size="small"
variant="icon"
icon="xmark"
Expand All @@ -35,9 +36,15 @@ import { NeoButton } from '@kodadot1/brick'

const emit = defineEmits(['remove'])

defineProps<{
name?: string
image: string
imageClass?: string
}>()
withDefaults(
defineProps<{
name?: string
image: string
imageClass?: string
removable?: boolean
}>(),
{
removable: true,
},
)
</script>
53 changes: 53 additions & 0 deletions components/swap/banner/CollectionPreview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<template>
<div
class="rounded-full min-w-[236px] h-[62px] md:w-auto border border-k-shade inline-flex justify-start px-2.5"
data-testid="drop-created-by-container"
>
<div class="flex items-center">
<div
class="rounded-full overflow-hidden bg-background-color border"
:style="{
width: `${imageSize}px`,
height: `${imageSize}px`,
padding: `${Math.round(imageSize / 16)}px`,
}"
>
<BaseMediaItem
:src="sanitizeIpfsUrl(collection.meta?.image)"
:image-component="NuxtImg"
:sizes="`${imageSize}px`"
title="User Avatar"
class="object-cover overflow-hidden rounded-full h-full w-full !shadow-none"
inner-class="object-cover"
/>
</div>
<div class="ml-3.5">
<NuxtLink :to="`/${urlPrefix}/collection/${collectionId}`">
{{ collection.name }}
</NuxtLink>
</div>
</div>
</div>
</template>

<script lang="ts" setup>
import { useCollectionMinimal } from '@/components/collection/utils/useCollectionDetails'
import { sanitizeIpfsUrl } from '@/utils/ipfs'

const NuxtImg = resolveComponent('NuxtImg')

const props = withDefaults(
defineProps<{
collectionId: string
imageSize?: number
}>(),
{
imageSize: 48,
},
)

const { urlPrefix } = usePrefix()
const { collection } = useCollectionMinimal({
collectionId: computed(() => props.collectionId),
})
</script>
Loading