From d670f92a917a785574f343c7d8e7f29a78661865 Mon Sep 17 00:00:00 2001
From: Jarsen <31397967+Jarsen136@users.noreply.github.com>
Date: Sun, 16 Feb 2025 18:58:39 +0100
Subject: [PATCH 1/5] feat: Create Collection Atomic Swap
---
components/collection/CollectionTrades.vue | 3 +-
.../GalleryItemTrade.vue | 21 +-------
.../swap/CreateCollectionSwapButton.vue | 49 +++++++++++++++++
components/swap/Preview.vue | 44 ++++++++-------
components/swap/PreviewItem.vue | 2 +
components/swap/banner/CollectionPreview.vue | 53 +++++++++++++++++++
components/swap/banner/accounts.vue | 12 ++++-
.../swap/collection/DestinationProfile.vue | 35 ++++++++++++
components/swap/layout/index.vue | 1 +
components/swap/review.vue | 40 +++++++++++++-
components/trade/TradeActivityTable.vue | 4 +-
.../transaction/transactionCreateSwap.ts | 2 +-
composables/transaction/types.ts | 4 +-
composables/useTradeActionClick.ts | 28 ++++++++++
i18n/locales/en.json | 4 ++
middleware/redirects.global.ts | 5 ++
middleware/swap.ts | 24 +++++----
pages/[prefix]/swap/collection/[id]/index.vue | 10 ++++
stores/atomicSwaps.ts | 6 ++-
utils/swap.ts | 21 ++++++--
20 files changed, 304 insertions(+), 64 deletions(-)
create mode 100644 components/swap/CreateCollectionSwapButton.vue
create mode 100644 components/swap/banner/CollectionPreview.vue
create mode 100644 components/swap/collection/DestinationProfile.vue
create mode 100644 composables/useTradeActionClick.ts
create mode 100644 pages/[prefix]/swap/collection/[id]/index.vue
diff --git a/components/collection/CollectionTrades.vue b/components/collection/CollectionTrades.vue
index be9de7d98c..05c2c65588 100644
--- a/components/collection/CollectionTrades.vue
+++ b/components/collection/CollectionTrades.vue
@@ -12,6 +12,7 @@
:key="key"
:query="tradeQuery"
:type="tradeType"
+ :enable-collection-swap="tradeType === TradeType.SWAP"
/>
@@ -19,7 +20,7 @@
diff --git a/components/swap/Preview.vue b/components/swap/Preview.vue
index 1b913bdddd..7782c5de64 100644
--- a/components/swap/Preview.vue
+++ b/components/swap/Preview.vue
@@ -24,7 +24,7 @@
{{ $t('shoppingCart.clearAll') }}
@@ -46,6 +46,7 @@
:name="nft.name"
:image="sanitizeIpfsUrl(nft.meta.image)"
image-class="border border-k-shade"
+ :hide-remove="isCollectionSwap && nft.id === null"
@remove="swapStore.removeStepItem(nft.id)"
/>
@@ -107,7 +108,7 @@
size="large"
label="Next"
variant="primary"
- :disabled
+ :disabled="disabled"
no-shadow
expanded
@click="onNext"
@@ -133,23 +134,6 @@ type StepDetails = {
surchargeDirection: SwapSurchargeDirection
}
-const stepDetailsMap: Partial> = {
- [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
}>()
@@ -161,6 +145,24 @@ const { accountId } = useAuth()
const { decimals } = useChain()
const { urlPrefix } = usePrefix()
const { getChainIcon } = useIcon()
+const isCollectionSwap = computed(() => swap.value.isCollectionSwap)
+
+const stepDetailsMap: ComputedRef>> = 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()
@@ -168,13 +170,15 @@ 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
diff --git a/components/swap/PreviewItem.vue b/components/swap/PreviewItem.vue
index 64a29f9e85..f49a6dfb98 100644
--- a/components/swap/PreviewItem.vue
+++ b/components/swap/PreviewItem.vue
@@ -22,6 +22,7 @@
()
diff --git a/components/swap/banner/CollectionPreview.vue b/components/swap/banner/CollectionPreview.vue
new file mode 100644
index 0000000000..8001b397aa
--- /dev/null
+++ b/components/swap/banner/CollectionPreview.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+ {{ collection.name }}
+
+
+
+
+
+
+
diff --git a/components/swap/banner/accounts.vue b/components/swap/banner/accounts.vue
index 74bd74fdde..888508f9ec 100644
--- a/components/swap/banner/accounts.vue
+++ b/components/swap/banner/accounts.vue
@@ -7,6 +7,7 @@
@@ -21,7 +22,15 @@
{{ $t('swap.counterparty') }}
-
+
+
@@ -32,5 +41,6 @@ import { NeoIcon } from '@kodadot1/brick'
defineProps<{
creator?: string
counterparty: string
+ isCollectionSwap?: boolean
}>()
diff --git a/components/swap/collection/DestinationProfile.vue b/components/swap/collection/DestinationProfile.vue
new file mode 100644
index 0000000000..fa344e98ce
--- /dev/null
+++ b/components/swap/collection/DestinationProfile.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/swap/layout/index.vue b/components/swap/layout/index.vue
index 7c7a3e8037..b473919865 100644
--- a/components/swap/layout/index.vue
+++ b/components/swap/layout/index.vue
@@ -6,6 +6,7 @@
diff --git a/components/swap/review.vue b/components/swap/review.vue
index 28b831bfaf..9a8089c04b 100644
--- a/components/swap/review.vue
+++ b/components/swap/review.vue
@@ -55,7 +55,36 @@
{{ $t('swap.reviewCounterpartyAccept') }}
+
+
+
+
+
+
+
import { NeoIcon, NeoButton } from '@kodadot1/brick'
import { successMessage } from '@/utils/notification'
+import ItemsGridImage from '@/components/items/ItemsGrid/ItemsGridImage.vue'
+import type { NFTWithMetadata } from '@/composables/useNft'
+import { SwapStep } from '@/components/swap/types'
const router = useRouter()
const { $i18n } = useNuxtApp()
@@ -122,6 +154,8 @@ const { swap } = storeToRefs(swapStore)
const offeredQuery = computed(() => ({ id_in: swap.value?.offered.map(item => item.id) }))
const desiredQuery = computed(() => ({ id_in: swap.value?.desired.map(item => item.id) }))
+const desiredNfts = computed(() => swap.value?.desired as NFTWithMetadata[])
+
const toTokenToSwap = (item: SwapItem) => ({
id: item.id,
collectionId: item.collectionId,
@@ -130,6 +164,10 @@ const toTokenToSwap = (item: SwapItem) => ({
const surcharge = computed(() => swap.value?.surcharge)
+const onModifyOfferClick = () => {
+ router.push({ name: getSwapStepRouteName(SwapStep.DESIRED, swap.value?.isCollectionSwap), params: { id: swap.value?.counterparty }, query: { swapId: swap.value?.id } })
+}
+
const submit = () => {
if (!swap.value) {
return
diff --git a/components/trade/TradeActivityTable.vue b/components/trade/TradeActivityTable.vue
index 90aa9ad09b..0b13eb2bd4 100644
--- a/components/trade/TradeActivityTable.vue
+++ b/components/trade/TradeActivityTable.vue
@@ -22,6 +22,7 @@
+
@@ -100,6 +101,7 @@
diff --git a/stores/atomicSwaps.ts b/stores/atomicSwaps.ts
index 92f6ca7a94..a4beb5b9ea 100644
--- a/stores/atomicSwaps.ts
+++ b/stores/atomicSwaps.ts
@@ -18,13 +18,14 @@ export type AtomicSwap = {
surcharge?: SwapSurcharge
duration: number
blockNumber?: string
+ isCollectionSwap?: boolean
} & CartItem
export type SwapItem = {
- id: string
+ id: string | null
name: string
collectionId: string
- sn: string
+ sn: string | null
meta: any
}
@@ -62,6 +63,7 @@ export const useAtomicSwapStore = defineStore('atomicSwap', () => {
const newAtomicSwap: AtomicSwap = {
id: window.crypto.randomUUID().split('-')[0],
counterparty,
+ isCollectionSwap: false,
offered: [],
desired: [],
createdAt: Date.now(),
diff --git a/utils/swap.ts b/utils/swap.ts
index f94c3d53ac..083fe702de 100644
--- a/utils/swap.ts
+++ b/utils/swap.ts
@@ -2,22 +2,33 @@ import { SwapStep } from '@/components/swap/types'
import type { TradeToken, TradeNftItem } from '@/components/trade/types'
import type { NFT } from '@/types'
-export const SWAP_ROUTE_NAME_STEP_MAP = {
+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,
}
+const COLLECTION_SWAP_ROUTE_NAME_STEP_MAP = {
+ 'prefix-collection-id-swaps': SwapStep.COUNTERPARTY,
+ 'prefix-swap-collection-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-collection-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 getRouteNameStepMap = (isCollectionSwap?: boolean) => isCollectionSwap ? COLLECTION_SWAP_ROUTE_NAME_STEP_MAP : SWAP_ROUTE_NAME_STEP_MAP
+
+export const getSwapStepRouteName = (step: SwapStep, isCollectionSwap?: boolean) => {
+ const routeNameStepMap = getRouteNameStepMap(isCollectionSwap)
+ const index = Object.values(routeNameStepMap).findIndex(name => name === step)
+ return Object.keys(routeNameStepMap)[index]
}
export const getSwapStep = (swap: AtomicSwap): SwapStep => {
@@ -48,7 +59,7 @@ export const getStepItemsKey = (step: SwapStep) => {
export const navigateToSwap = (swap: AtomicSwap) => {
navigateTo({
- name: 'prefix-swap-id',
+ name: getSwapStepRouteName(SwapStep.DESIRED, swap.isCollectionSwap),
params: { id: swap.counterparty },
query: { swapId: swap.id },
})
From 8366465a4e7dda2f5638ddf441ccf6b04f15fe13 Mon Sep 17 00:00:00 2001
From: Jarsen <31397967+Jarsen136@users.noreply.github.com>
Date: Mon, 17 Feb 2025 10:38:21 +0100
Subject: [PATCH 2/5] refactor: props
---
components/collection/CollectionTrades.vue | 11 ++++--
components/collection/drop/ItemsGrid.vue | 2 +-
components/items/ItemsGrid/ItemsGrid.vue | 6 +--
components/items/ItemsGrid/ItemsGridImage.vue | 4 +-
components/shared/nftCard/NftCard.vue | 4 +-
components/shared/nftCard/NftMediaInfo.vue | 6 +--
components/swap/Preview.vue | 2 +-
components/swap/PreviewItem.vue | 17 +++++---
components/swap/collection/SwapGridList.vue | 39 +++++++++++++++++++
components/swap/review.vue | 33 +---------------
components/trade/TradeActivityTable.vue | 4 +-
11 files changed, 73 insertions(+), 55 deletions(-)
create mode 100644 components/swap/collection/SwapGridList.vue
diff --git a/components/collection/CollectionTrades.vue b/components/collection/CollectionTrades.vue
index 05c2c65588..1c4b0b9605 100644
--- a/components/collection/CollectionTrades.vue
+++ b/components/collection/CollectionTrades.vue
@@ -12,15 +12,20 @@
:key="key"
:query="tradeQuery"
:type="tradeType"
- :enable-collection-swap="tradeType === TradeType.SWAP"
- />
+ >
+
+
+
+
diff --git a/components/swap/collection/SwapGridList.vue b/components/swap/collection/SwapGridList.vue
new file mode 100644
index 0000000000..750ba60a78
--- /dev/null
+++ b/components/swap/collection/SwapGridList.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/swap/review.vue b/components/swap/review.vue
index 9a8089c04b..39331091bc 100644
--- a/components/swap/review.vue
+++ b/components/swap/review.vue
@@ -55,34 +55,9 @@
{{ $t('swap.reviewCounterpartyAccept') }}
-
-
-
-
-
-
-
+ />
import { NeoIcon, NeoButton } from '@kodadot1/brick'
import { successMessage } from '@/utils/notification'
-import ItemsGridImage from '@/components/items/ItemsGrid/ItemsGridImage.vue'
-import type { NFTWithMetadata } from '@/composables/useNft'
import { SwapStep } from '@/components/swap/types'
const router = useRouter()
@@ -154,8 +127,6 @@ const { swap } = storeToRefs(swapStore)
const offeredQuery = computed(() => ({ id_in: swap.value?.offered.map(item => item.id) }))
const desiredQuery = computed(() => ({ id_in: swap.value?.desired.map(item => item.id) }))
-const desiredNfts = computed(() => swap.value?.desired as NFTWithMetadata[])
-
const toTokenToSwap = (item: SwapItem) => ({
id: item.id,
collectionId: item.collectionId,
diff --git a/components/trade/TradeActivityTable.vue b/components/trade/TradeActivityTable.vue
index 0b13eb2bd4..9a4ab4a4b5 100644
--- a/components/trade/TradeActivityTable.vue
+++ b/components/trade/TradeActivityTable.vue
@@ -22,7 +22,7 @@
-
+
@@ -101,7 +101,6 @@
diff --git a/components/trade/makeOffer/MakeOfferModal.vue b/components/trade/makeOffer/MakeOfferModal.vue
index c89d2c5a6a..0a27abecbd 100644
--- a/components/trade/makeOffer/MakeOfferModal.vue
+++ b/components/trade/makeOffer/MakeOfferModal.vue
@@ -26,7 +26,10 @@
-
+
diff --git a/components/trade/makeOffer/MakeOfferSingleItem.vue b/components/trade/makeOffer/MakeOfferSingleItem.vue
index 09c642c301..d7f6bf1151 100644
--- a/components/trade/makeOffer/MakeOfferSingleItem.vue
+++ b/components/trade/makeOffer/MakeOfferSingleItem.vue
@@ -2,7 +2,10 @@
-
+
{{ itemPrice }}
@@ -61,6 +64,7 @@ const emit = defineEmits([
const props = defineProps<{
offerPrice?: number
+ showPrice?: boolean
}>()
const offerPrice = useVModel(props, 'offerPrice')
diff --git a/components/trade/types.ts b/components/trade/types.ts
index 1dc3658c69..467cfe3aea 100644
--- a/components/trade/types.ts
+++ b/components/trade/types.ts
@@ -11,13 +11,13 @@ export type MakingOfferItem = {
highestOffer?: string
offerPrice?: string
offerExpiration?: number
- id: string
+ id: string | null
name: string
currentOwner: string
collection: EntityWithId & CollectionFloorPrice
meta?: NFTMetadata
metadata: string
- sn: string
+ sn: string | null
}
export enum TradeStatus {
diff --git a/composables/useTradeActionClick.ts b/composables/useTradeActionClick.ts
index b84bc9be63..4242acad34 100644
--- a/composables/useTradeActionClick.ts
+++ b/composables/useTradeActionClick.ts
@@ -1,7 +1,7 @@
import { useIdentityStore } from '@/stores/identity'
import { doAfterCheckCurrentChainVM } from '@/components/common/ConnectWallet/openReconnectWalletModal'
-export default function (isOwner: ComputedRef
) {
+export default function (disabled?: ComputedRef) {
const identityStore = useIdentityStore()
const { doAfterLogin } = useDoAfterlogin()
@@ -9,7 +9,7 @@ export default function (isOwner: ComputedRef) {
const onTradeActionClick = (cb: () => void) => {
const fn = () => {
- if (!isOwner.value) {
+ if (!disabled || !disabled.value) {
cb()
}
}
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 4fc99a2d2c..c79cedabe0 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -1522,9 +1522,11 @@
"notifications": "Notifications"
},
"offer": {
+ "anyNftFromCollection": "Any NFT",
"bestOffer": "Best Offer",
"cancelOffer": "Cancel Offer",
"collectionFloorPrice": "Collection Floor",
+ "createCollectionOffer": "Create Collection Offer",
"emptyInput": "Please enter your offer",
"expiration": "Offer Expiration",
"floorDifference": "Floor Difference",
diff --git a/queries/subsquid/general/collectionByIdMinimalWithRoyalty.graphql b/queries/subsquid/general/collectionByIdMinimalWithRoyalty.graphql
index 20cb384607..d0aed4649b 100644
--- a/queries/subsquid/general/collectionByIdMinimalWithRoyalty.graphql
+++ b/queries/subsquid/general/collectionByIdMinimalWithRoyalty.graphql
@@ -19,5 +19,12 @@ query collectionByIdMinimalWithRoyalty($id: String!) {
name
currentOwner
createdAt
+ floorPrice: nfts(
+ where: { burned_eq: false, price_not_eq: "0" }
+ orderBy: price_ASC
+ limit: 1
+ ) {
+ price
+ }
}
}
diff --git a/queries/subsquid/general/highestOfferByCollectionId.graphql b/queries/subsquid/general/highestOfferByCollectionId.graphql
new file mode 100644
index 0000000000..5e66a57662
--- /dev/null
+++ b/queries/subsquid/general/highestOfferByCollectionId.graphql
@@ -0,0 +1,8 @@
+query highestOfferByCollectionId($id: String!) {
+ offers(where: {status_eq: ACTIVE, desired: {collectionId_eq: $id}}, orderBy: price_DESC, limit: 1) {
+ expiration
+ status
+ price
+ id
+ }
+}
\ No newline at end of file
From aa621ec31af122dd2d6adce86316aaae29a28110 Mon Sep 17 00:00:00 2001
From: Jarsen <31397967+Jarsen136@users.noreply.github.com>
Date: Tue, 18 Feb 2025 15:25:35 +0100
Subject: [PATCH 4/5] chore: types
---
components/collection/CollectionTrades.vue | 9 ++++++---
.../trade/makeOffer/CreateCollectionOfferButton.vue | 4 ++--
composables/useTradeActionClick.ts | 2 +-
3 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/components/collection/CollectionTrades.vue b/components/collection/CollectionTrades.vue
index 107efcba06..821a7af9c7 100644
--- a/components/collection/CollectionTrades.vue
+++ b/components/collection/CollectionTrades.vue
@@ -13,13 +13,16 @@
:query="tradeQuery"
:type="tradeType"
>
-
+
diff --git a/components/trade/makeOffer/CreateCollectionOfferButton.vue b/components/trade/makeOffer/CreateCollectionOfferButton.vue
index 98ddff2e55..3e72c570e0 100644
--- a/components/trade/makeOffer/CreateCollectionOfferButton.vue
+++ b/components/trade/makeOffer/CreateCollectionOfferButton.vue
@@ -25,7 +25,7 @@ const preferencesStore = usePreferencesStore()
const collectionId = computed(() => props.collectionId)
const makeOfferStore = useMakingOfferStore()
-const { data: collectionOfferData } = useGraphql({
+const { data: collectionOfferData } = useGraphql<{ offers: NFTOffer[] }>({
queryName: 'highestOfferByCollectionId',
variables: {
id: collectionId.value,
@@ -38,7 +38,7 @@ const { collection } = useCollectionMinimal({
const { onTradeActionClick } = useTradeActionClick()
const { urlPrefix } = usePrefix()
-const highestOfferPrice = computed(() => (collectionOfferData.value as unknown as { offers: NFTOffer[] })?.offers[0]?.price)
+const highestOfferPrice = computed(() => (collectionOfferData.value)?.offers[0]?.price)
const openOfferModal = () => {
makeOfferStore.clear()
diff --git a/composables/useTradeActionClick.ts b/composables/useTradeActionClick.ts
index 4242acad34..f2d0a4ef2b 100644
--- a/composables/useTradeActionClick.ts
+++ b/composables/useTradeActionClick.ts
@@ -9,7 +9,7 @@ export default function (disabled?: ComputedRef) {
const onTradeActionClick = (cb: () => void) => {
const fn = () => {
- if (!disabled || !disabled.value) {
+ if (!disabled?.value) {
cb()
}
}
From 563986477dc4c15ee3cdde2a28bb5fd48a3f5827 Mon Sep 17 00:00:00 2001
From: Jarsen <31397967+Jarsen136@users.noreply.github.com>
Date: Wed, 19 Feb 2025 09:43:28 +0100
Subject: [PATCH 5/5] fix: removable
---
components/swap/PreviewItem.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/components/swap/PreviewItem.vue b/components/swap/PreviewItem.vue
index ccb0ce5814..a0a690c53b 100644
--- a/components/swap/PreviewItem.vue
+++ b/components/swap/PreviewItem.vue
@@ -22,7 +22,7 @@