diff --git a/components/collection/CollectionTrades.vue b/components/collection/CollectionTrades.vue
index be9de7d98c..821a7af9c7 100644
--- a/components/collection/CollectionTrades.vue
+++ b/components/collection/CollectionTrades.vue
@@ -12,7 +12,21 @@
:key="key"
:query="tradeQuery"
:type="tradeType"
- />
+ >
+
+
+
+
+
@@ -20,6 +34,9 @@
diff --git a/components/swap/Preview.vue b/components/swap/Preview.vue
index 1b913bdddd..c3c1566055 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"
+ :removable="!(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..a0a690c53b 100644
--- a/components/swap/PreviewItem.vue
+++ b/components/swap/PreviewItem.vue
@@ -22,6 +22,7 @@
()
+withDefaults(
+ defineProps<{
+ name?: string
+ image: string
+ imageClass?: string
+ removable?: boolean
+ }>(),
+ {
+ removable: true,
+ },
+)
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/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/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 ff7ef6805a..d61a30440c 100644
--- a/components/swap/review.vue
+++ b/components/swap/review.vue
@@ -55,7 +55,11 @@
{{ $t('swap.reviewCounterpartyAccept') }}
+
import { NeoIcon, NeoButton } from '@kodadot1/brick'
import { successMessage } from '@/utils/notification'
+import { SwapStep } from '@/components/swap/types'
const router = useRouter()
const { $i18n } = useNuxtApp()
@@ -131,6 +136,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..9a4ab4a4b5 100644
--- a/components/trade/TradeActivityTable.vue
+++ b/components/trade/TradeActivityTable.vue
@@ -22,6 +22,7 @@
+
@@ -111,7 +112,6 @@ const props = defineProps<{
const route = useRoute()
const { replaceUrl } = useReplaceUrl()
-
const dataKey = TRADES_QUERY_MAP[props.type].dataKey
const selectedTrade = ref()
diff --git a/components/trade/makeOffer/CreateCollectionOfferButton.vue b/components/trade/makeOffer/CreateCollectionOfferButton.vue
new file mode 100644
index 0000000000..3e72c570e0
--- /dev/null
+++ b/components/trade/makeOffer/CreateCollectionOfferButton.vue
@@ -0,0 +1,66 @@
+
+
+
+ {{ $t('offer.createCollectionOffer') }}
+
+
+
+
+
+
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/transaction/transactionCreateSwap.ts b/composables/transaction/transactionCreateSwap.ts
index db0e08ea13..7a52181fb7 100644
--- a/composables/transaction/transactionCreateSwap.ts
+++ b/composables/transaction/transactionCreateSwap.ts
@@ -18,7 +18,7 @@ async function execCreateSwapStatmine({ item, api, executeTransaction, isLoading
const swap = api.tx.nfts.createSwap(
offeredCollectionId,
- offeredItem,
+ offeredItem as string,
desiredCollectionId,
desiredItem,
item.surcharge
diff --git a/composables/transaction/types.ts b/composables/transaction/types.ts
index 21813d1121..cd8a105887 100644
--- a/composables/transaction/types.ts
+++ b/composables/transaction/types.ts
@@ -190,9 +190,9 @@ export type SwapSurchargeDirection = 'Send' | 'Receive'
export type SwapSurcharge = { amount: string, direction: SwapSurchargeDirection }
export type TokenToSwap = {
- id: string
+ id: string | null
collectionId: string
- sn: string
+ sn: string | null
}
export type ActionSwap = {
diff --git a/composables/useTradeActionClick.ts b/composables/useTradeActionClick.ts
new file mode 100644
index 0000000000..f2d0a4ef2b
--- /dev/null
+++ b/composables/useTradeActionClick.ts
@@ -0,0 +1,28 @@
+import { useIdentityStore } from '@/stores/identity'
+import { doAfterCheckCurrentChainVM } from '@/components/common/ConnectWallet/openReconnectWalletModal'
+
+export default function (disabled?: ComputedRef
) {
+ const identityStore = useIdentityStore()
+ const { doAfterLogin } = useDoAfterlogin()
+
+ const isLogIn = computed(() => Boolean(identityStore.getAuthAddress))
+
+ const onTradeActionClick = (cb: () => void) => {
+ const fn = () => {
+ if (!disabled?.value) {
+ cb()
+ }
+ }
+
+ if (isLogIn.value) {
+ doAfterCheckCurrentChainVM(fn)
+ }
+ else {
+ doAfterLogin({ onLoginSuccess: fn })
+ }
+ }
+
+ return {
+ onTradeActionClick,
+ }
+}
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 398f82345d..9b485416a1 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -1521,9 +1521,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",
@@ -1838,13 +1840,17 @@
"swap": {
"acceptSwap": "Accepting Swap",
"addToken": "Add Token",
+ "anyNftFromCollection": "Any NFT from \"{0}\" Collection",
"beginSwap": "Begin Swap offer",
"cantSwapWithYourself": "You can't swap with yourself",
"clickOnNft": "Click on any NFT to add it to your swap list.",
+ "collectionSwapDescription": "Any NFT from this collection will be offered in the swap",
+ "collectionSwapPreview": "Collection Swap",
"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.",
"counterSwap": "Counter Swap",
"counterparty": "Counterparty",
+ "createCollectionSwap": "Create Collection Swap",
"createSwap": "Create Swap",
"created": "Swap Created",
"creatingSwap": "Creating Swap",
diff --git a/middleware/redirects.global.ts b/middleware/redirects.global.ts
index 63396564ce..9f1dd8ff63 100644
--- a/middleware/redirects.global.ts
+++ b/middleware/redirects.global.ts
@@ -1,5 +1,6 @@
import { type Prefix } from '@kodadot1/static'
import type { RouteLocationRaw, RouteLocationNormalizedLoadedGeneric } from 'vue-router'
+import { isAddress } from '@polkadot/util-crypto'
import { createVisible, transferVisible, teleportVisible, migrateVisible, swapVisible } from '@/utils/config/permission.config'
type ReplaceRouteItemCondition = (route: RouteLocationNormalizedLoadedGeneric) => boolean
@@ -14,6 +15,10 @@ const getFormatAddressRouteCondition = (cond: ReplaceRouteItemCondition, { addre
cond,
replaceRoute: ({ params, name, query }) => {
const address = params[addressKey].toString()
+
+ if (!isAddress(address)) {
+ return
+ }
const prefix = params.prefix.toString() as Prefix
return execByVm({
diff --git a/middleware/swap.ts b/middleware/swap.ts
index 810d7e2cce..537ecb790c 100644
--- a/middleware/swap.ts
+++ b/middleware/swap.ts
@@ -1,4 +1,5 @@
import { type Prefix } from '@kodadot1/static'
+import { isAddress } from '@polkadot/util-crypto'
import { SwapStep } from '@/components/swap/types'
export default defineNuxtRouteMiddleware((to) => {
@@ -9,13 +10,10 @@ export default defineNuxtRouteMiddleware((to) => {
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
@@ -24,24 +22,28 @@ export default defineNuxtRouteMiddleware((to) => {
)[0]
if (!foundSwap) {
- return navigateTo({
- name: getSwapStepRouteName(SwapStep.DESIRED),
- params: { id, prefix },
- query: { swapId: swapStore.createSwap(id).id },
- })
+ if (isAddress(id)) {
+ return navigateTo({
+ name: getSwapStepRouteName(SwapStep.DESIRED),
+ params: { id, prefix },
+ query: { swapId: swapStore.createSwap(id).id },
+ })
+ }
+ return navigateTo({ name: getSwapStepRouteName(SwapStep.COUNTERPARTY, true), params: { id, prefix } })
}
+ const isCollectionSwap = foundSwap.isCollectionSwap
swap.value = foundSwap
const swapStep = getSwapStep(swap.value)
if (swapStep === SwapStep.CREATED) {
- return navigateTo({ name: getSwapStepRouteName(SwapStep.COUNTERPARTY), params: { prefix } })
+ return navigateTo({ name: getSwapStepRouteName(SwapStep.COUNTERPARTY, isCollectionSwap), params: { prefix } })
}
-
+ const routeStep = getRouteNameStepMap(isCollectionSwap)[routeName]
step.value = routeStep
if (routeStep > swapStep) {
- return navigateTo({ name: getSwapStepRouteName(swapStep), params: { id, prefix }, query: { swapId: swap.value.id } })
+ return navigateTo({ name: getSwapStepRouteName(swapStep, isCollectionSwap), params: { id, prefix }, query: { swapId: swap.value.id } })
}
})
diff --git a/pages/[prefix]/swap/collection/[id]/index.vue b/pages/[prefix]/swap/collection/[id]/index.vue
new file mode 100644
index 0000000000..f6ba535df6
--- /dev/null
+++ b/pages/[prefix]/swap/collection/[id]/index.vue
@@ -0,0 +1,10 @@
+
+
+
+
+
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
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 },
})