From 523fa29173d5bd2a34188ee175da8679a18d0eb7 Mon Sep 17 00:00:00 2001 From: Jarsen <31397967+Jarsen136@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:25:31 +0200 Subject: [PATCH 1/6] feat: create offer on AssetHub --- .../autoTeleport/AutoTeleportActionButton.vue | 6 +- components/common/shoppingCart/utils.ts | 17 ++ .../GalleryItemAction/GalleryItemAction.vue | 9 +- .../GalleryItemOffer.vue | 73 ++++++ components/offer/MakeOffer.vue | 246 ++++++++++++++++++ components/offer/MakingOfferItemDetails.vue | 72 +++++ components/offer/MakingOfferSingleItem.vue | 70 +++++ .../offer/SuccessfulMakingOfferBody.vue | 76 ++++++ components/offer/types.ts | 18 ++ composables/transaction/transactionOffer.ts | 63 +++++ composables/transaction/types.ts | 14 +- composables/transaction/utils.ts | 2 +- composables/useTransaction.ts | 4 + locales/en.json | 9 +- stores/makeOffer.ts | 75 ++++++ stores/preferences.ts | 5 + utils/config/permission.config.ts | 4 + utils/shoppingActions.ts | 8 +- 18 files changed, 757 insertions(+), 14 deletions(-) create mode 100644 components/gallery/GalleryItemAction/GalleryItemActionType/GalleryItemOffer.vue create mode 100644 components/offer/MakeOffer.vue create mode 100644 components/offer/MakingOfferItemDetails.vue create mode 100644 components/offer/MakingOfferSingleItem.vue create mode 100644 components/offer/SuccessfulMakingOfferBody.vue create mode 100644 components/offer/types.ts create mode 100644 composables/transaction/transactionOffer.ts create mode 100644 stores/makeOffer.ts diff --git a/components/common/autoTeleport/AutoTeleportActionButton.vue b/components/common/autoTeleport/AutoTeleportActionButton.vue index 066a78bf25..9f924593e5 100644 --- a/components/common/autoTeleport/AutoTeleportActionButton.vue +++ b/components/common/autoTeleport/AutoTeleportActionButton.vue @@ -56,7 +56,7 @@ + + diff --git a/components/offer/MakeOffer.vue b/components/offer/MakeOffer.vue new file mode 100644 index 0000000000..82dbadecd2 --- /dev/null +++ b/components/offer/MakeOffer.vue @@ -0,0 +1,246 @@ + + + + + diff --git a/components/offer/MakingOfferItemDetails.vue b/components/offer/MakingOfferItemDetails.vue new file mode 100644 index 0000000000..1aece79303 --- /dev/null +++ b/components/offer/MakingOfferItemDetails.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/components/offer/MakingOfferSingleItem.vue b/components/offer/MakingOfferSingleItem.vue new file mode 100644 index 0000000000..633dda4d18 --- /dev/null +++ b/components/offer/MakingOfferSingleItem.vue @@ -0,0 +1,70 @@ + + + diff --git a/components/offer/SuccessfulMakingOfferBody.vue b/components/offer/SuccessfulMakingOfferBody.vue new file mode 100644 index 0000000000..fac0c2ff7e --- /dev/null +++ b/components/offer/SuccessfulMakingOfferBody.vue @@ -0,0 +1,76 @@ + + + diff --git a/components/offer/types.ts b/components/offer/types.ts new file mode 100644 index 0000000000..3f07015b3c --- /dev/null +++ b/components/offer/types.ts @@ -0,0 +1,18 @@ +import type { + CollectionFloorPrice, + EntityWithId, + NFTMetadata, +} from '@/components/rmrk/service/scheme' + +export type MakingOfferItem = { + urlPrefix: string + price?: string + offerPrice?: string + id: string + name: string + currentOwner: string + collection: EntityWithId & CollectionFloorPrice + meta?: NFTMetadata + metadata: string + sn: string +} diff --git a/composables/transaction/transactionOffer.ts b/composables/transaction/transactionOffer.ts new file mode 100644 index 0000000000..8f817c1573 --- /dev/null +++ b/composables/transaction/transactionOffer.ts @@ -0,0 +1,63 @@ +import type { Prefix } from '@kodadot1/static' +import type { ActionOffer } from './types' +import { generateId } from '@/services/dyndata' + +const getOfferId = (prefix: Prefix) => { + switch (prefix) { + case 'ahk': + return 464 + case 'ahp': + return 174 + default: + return 0 + } +} + +const OFFER_MINT_PRICE = '500000000' + +async function execMakingOffer(item: ActionOffer, api, executeTransaction) { + const { accountId } = useAuth() + const nfts = Array.isArray(item.token) ? item.token : [item.token] + const transactions = await Promise.all( + nfts.map(async ({ price, nftSn, collectionId }) => { + const offerId = getOfferId(item.urlPrefix as Prefix) + const nextId = Number.parseInt(await generateId()) + const create = api.tx.nfts.mint( + offerId, + nextId, + accountId.value, + { + mintPrice: OFFER_MINT_PRICE, + }, + ) + + const duration = 300 // this tells about one hour (12sec /block --> 300blocks/hr) + const offer = api.tx.nfts.createSwap( + offerId, + nextId, + collectionId, + nftSn, + { + amount: Number(price) || 0, + direction: 'Send', + }, + duration, + ) + + return [create, offer] + }), + ) + + executeTransaction({ + cb: api.tx.utility.batchAll, + arg: [transactions.flat()], + successMessage: item.successMessage, + errorMessage: item.errorMessage, + }) +} + +export async function execMakingOfferTx(item: ActionOffer, api, executeTransaction) { + if (item.urlPrefix === 'ahk' || item.urlPrefix === 'ahp') { + await execMakingOffer(item, api, executeTransaction) + } +} diff --git a/composables/transaction/types.ts b/composables/transaction/types.ts index e5200be726..0a06bdfe27 100644 --- a/composables/transaction/types.ts +++ b/composables/transaction/types.ts @@ -150,6 +150,12 @@ export type TokenToList = { nftId: string } +export type TokenToOffer = { + price: string + collectionId: string + nftSn: string +} + export type ActionList = { interaction: Interaction.LIST urlPrefix: string @@ -175,11 +181,9 @@ export type ActionSend = { export type ActionOffer = { interaction: typeof ShoppingActions.MAKE_OFFER urlPrefix: string - tokenId: string - day: number - price: number - currentOwner: string - successMessage?: string + token: TokenToOffer | TokenToOffer[] + duration: number + successMessage?: string | ((blockNumber: string) => string) errorMessage?: string } diff --git a/composables/transaction/utils.ts b/composables/transaction/utils.ts index 7be6df87d3..3e6a2a0d03 100644 --- a/composables/transaction/utils.ts +++ b/composables/transaction/utils.ts @@ -66,7 +66,7 @@ export function isActionValid(action: Actions): boolean { [Interaction.SEND]: (action: ActionSend) => Boolean(action.nftId), [Interaction.CONSUME]: (action: ActionConsume) => Boolean(action.nftId), [ShoppingActions.MAKE_OFFER]: (action: ActionOffer) => - Boolean(action.tokenId), + hasContent(action.token), [ShoppingActions.WITHDRAW_OFFER]: (action: ActionWithdrawOffer) => Boolean(action.nftId), [Interaction.MINT]: (action: ActionMintCollection) => diff --git a/composables/useTransaction.ts b/composables/useTransaction.ts index 0778c411ea..c1e266ab1e 100644 --- a/composables/useTransaction.ts +++ b/composables/useTransaction.ts @@ -12,6 +12,7 @@ import { } from './transaction/transactionBurn' import { execWithdrawOfferTx } from './transaction/transactionOfferWithdraw' import { execAcceptOfferTx } from './transaction/transactionOfferAccept' +import { execMakingOfferTx } from './transaction/transactionOffer' import { execMintToken } from './transaction/transactionMintToken' import { execMintCollection } from './transaction/transactionMintCollection' import { execSetCollectionMaxSupply } from './transaction/transactionSetCollectionMaxSupply' @@ -20,6 +21,7 @@ import type { ActionBurnMultipleNFTs, ActionBuy, ActionConsume, + ActionOffer, ActionDeleteCollection, ActionList, ActionMintCollection, @@ -226,6 +228,8 @@ export const executeAction = ({ execWithdrawOfferTx(item as ActionWithdrawOffer, api, executeTransaction), [ShoppingActions.ACCEPT_OFFER]: () => execAcceptOfferTx(item as ActionAcceptOffer, api, executeTransaction), + [ShoppingActions.MAKE_OFFER]: () => + execMakingOfferTx(item as ActionOffer, api, executeTransaction), [ShoppingActions.MINTNFT]: () => execMintToken({ item: item as ActionMintToken, diff --git a/locales/en.json b/locales/en.json index 5b7b00e094..24a5f71a44 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1082,6 +1082,7 @@ "boughtNfts": "Check out these awesome NFTs I bought on KodaDot:\n {0}", "listedNft": "Check out this awesome NFT I just listed on KodaDot", "listedNfts": "Check out these awesome NFTs I just listed on KodaDot:\n {0}", + "makeOfferNft": "Check out this awesome NFT I just make an offer on KodaDot", "nftWithArtist": "Check out this awesome NFT on {'@'}KodaDot from {'@'}{0}", "dropNft": "I've minted {0} generative art piece on Koda!", "dropNfts": "I've minted {0} generative art pieces on Koda!", @@ -1350,6 +1351,7 @@ "transfer": "Transfer", "transferTo": "Transfer to", "transferNft": "Transfer NFT", + "offer": "Create Offer", "transferingNft": "Transfering NFT", "inputAddressFirst": "Input address first", "addressIncorrect": "Address Incorrect", @@ -1360,7 +1362,8 @@ "new": "Your New Price", "list": "Your Listing Price", "success": "Price updated", - "error": "Price update failed" + "error": "Price update failed", + "offer": "Offer creation confirmed" }, "buy": { "error": "Failed to buy this item" @@ -1625,6 +1628,10 @@ "newListingPrice": "New Listing Price", "bulkListingSuccessful": "Bulk Listing Successful" }, + "offer": { + "yourOfferAmount": "Your Offer Amount" , + "newOffer": "New Offer" + }, "migrate": { "cta": "Migrate Now", "source": "Source", diff --git a/stores/makeOffer.ts b/stores/makeOffer.ts new file mode 100644 index 0000000000..69aed63e5b --- /dev/null +++ b/stores/makeOffer.ts @@ -0,0 +1,75 @@ +import { defineStore } from 'pinia' +import type { Prefix } from '@kodadot1/static' +import type { MakingOfferItem } from '@/components/offer/types' + +type ID = string + +interface State { + items: MakingOfferItem[] + chain: ComputedRef +} + +export const useMakingOfferStore = defineStore('makingOffer', { + state: (): State => ({ + items: [], + chain: usePrefix().urlPrefix, + }), + getters: { + getItems: state => state.items, + getItem: state => (id: ID) => state.items.find(item => item.id === id), + allItemsInChain: (state): MakingOfferItem[] => + state.items, + itemsInChain(): MakingOfferItem[] { + return this.allItemsInChain + }, + count() { + return this.itemsInChain.length + }, + }, + + actions: { + setItem(payload: MakingOfferItem) { + const existingItemIndex = this.items.findIndex( + item => item.id === payload.id, + ) + if (existingItemIndex === -1) { + this.items = [...this.items, payload] + } + else { + const items = [...this.items] + items[existingItemIndex] = payload + this.items = items + } + }, + updateItem(payload: Partial) { + const existingItemIndex = this.items.findIndex( + item => item.id === payload.id, + ) + if (existingItemIndex === -1) { + return + } + else { + const items = [...this.items] + items[existingItemIndex] = { + ...this.items[existingItemIndex], + ...payload, + } + this.items = items + } + }, + removeItem(id: ID) { + const existingItemIndex = this.items.findIndex(item => item.id === id) + if (existingItemIndex === -1) { + return + } + else { + const items = [...this.items] + items.splice(existingItemIndex, 1) + this.items = items + } + }, + clear() { + this.items = [] + }, + }, +}) diff --git a/stores/preferences.ts b/stores/preferences.ts index eec56a9034..14fa19a72a 100644 --- a/stores/preferences.ts +++ b/stores/preferences.ts @@ -46,6 +46,7 @@ interface State { mobileFilterCollapseOpen: boolean shoppingCartCollapseOpen: boolean listingCartModalOpen: boolean + makeOfferModalOpen: boolean completePurchaseModal: CompletePurchaseModalState triggerBuySuccess: boolean // Layout @@ -79,6 +80,7 @@ export const usePreferencesStore = defineStore('preferences', { sidebarFilterCollapseOpen: true, mobileFilterCollapseOpen: false, listingCartModalOpen: false, + makeOfferModalOpen: false, shoppingCartCollapseOpen: false, completePurchaseModal: { isOpen: false, @@ -152,6 +154,9 @@ export const usePreferencesStore = defineStore('preferences', { setCompletePurchaseModalOpen(payload) { this.completePurchaseModal.isOpen = payload }, + setMakeOfferModalOpen(payload) { + this.makeOfferModalOpen = payload + }, setTriggerBuySuccess(payload) { this.triggerBuySuccess = payload }, diff --git a/utils/config/permission.config.ts b/utils/config/permission.config.ts index 9f2bf07ec9..f24600cc86 100644 --- a/utils/config/permission.config.ts +++ b/utils/config/permission.config.ts @@ -35,6 +35,10 @@ export const listVisible = (prefix: Prefix | string): boolean => { return !isEvm(prefix as Prefix) } +export const offerVisible = (prefix: Prefix | string): boolean => { + return ['ahp', 'ahk'].includes(prefix) +} + export const seriesInsightVisible = (prefix: Prefix | string) => { return hasInsight[prefix] ?? true } diff --git a/utils/shoppingActions.ts b/utils/shoppingActions.ts index fb6c61aa69..7023501a7d 100644 --- a/utils/shoppingActions.ts +++ b/utils/shoppingActions.ts @@ -3,18 +3,18 @@ import { Interaction } from '@kodadot1/minimark/v1' import { hasMarketplace } from './prefix' -enum BasiliskActions { +enum OfferActions { MAKE_OFFER = 'MAKE_OFFER', SET_ROYALTY = 'SET_ROYALTY', WITHDRAW_OFFER = 'WITHDRAW_OFFER', ACCEPT_OFFER = 'ACCEPT_OFFER', } -export type ShoppingActions = Interaction | BasiliskActions +export type ShoppingActions = Interaction | OfferActions export type ShoppingActionToolTips = Partial> export const ShoppingActions = { ...Interaction, - ...BasiliskActions, + ...OfferActions, DOWNLOAD: 'DOWNLOAD', } @@ -33,7 +33,7 @@ export const ownerActions = [ export const listActions: [Interaction] = [ShoppingActions.LIST] export const buyActions: [Interaction] = [ShoppingActions.BUY] -export const makeOfferActions: [BasiliskActions] = [ShoppingActions.MAKE_OFFER] +export const makeOfferActions: [OfferActions] = [ShoppingActions.MAKE_OFFER] export const getActions = (isOwner: boolean, isAvailableToBuy: boolean) => { return getActionList('rmrk', isOwner, isAvailableToBuy) From 3514eaf66d69359c0a00044db81bbff55b6a3edc Mon Sep 17 00:00:00 2001 From: Jarsen <31397967+Jarsen136@users.noreply.github.com> Date: Fri, 16 Aug 2024 23:13:59 +0200 Subject: [PATCH 2/6] fix: deepscan --- components/offer/MakeOffer.vue | 2 +- composables/transaction/transactionOffer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/offer/MakeOffer.vue b/components/offer/MakeOffer.vue index 82dbadecd2..4aad9c32ae 100644 --- a/components/offer/MakeOffer.vue +++ b/components/offer/MakeOffer.vue @@ -149,7 +149,7 @@ const getAction = (items: MakingOfferItem[]): Actions => { interaction: ShoppingActions.MAKE_OFFER, urlPrefix: urlPrefix.value, token: items.map(item => ({ - price: String(calculateBalance(Number(item?.offerPrice), decimals.value)), + price: String(calculateBalance(Number(item.offerPrice), decimals.value)), collectionId: item.collection.id, nftSn: item.sn, } as TokenToOffer)), diff --git a/composables/transaction/transactionOffer.ts b/composables/transaction/transactionOffer.ts index 8f817c1573..9ff8c1f215 100644 --- a/composables/transaction/transactionOffer.ts +++ b/composables/transaction/transactionOffer.ts @@ -31,7 +31,7 @@ async function execMakingOffer(item: ActionOffer, api, executeTransaction) { }, ) - const duration = 300 // this tells about one hour (12sec /block --> 300blocks/hr) + const duration = 300 * 24 * 7 // Temporarily set to one week (12sec /block --> 300blocks/hr) const offer = api.tx.nfts.createSwap( offerId, nextId, From b4cdef74e0e2fbf314fe36a1e7a63dbbfa06cc87 Mon Sep 17 00:00:00 2001 From: Jarsen <31397967+Jarsen136@users.noreply.github.com> Date: Sun, 18 Aug 2024 15:42:59 +0200 Subject: [PATCH 3/6] refactor: offer modal --- ...artItemDetails.vue => CartItemDetails.vue} | 17 +++- .../common/listingCart/ListingCartModal.vue | 86 ++++++++-------- .../multipleItemsCart/ListingCartItem.vue | 6 +- .../ListingCartSingleItemCart.vue | 6 +- .../GalleryItemOffer.vue | 4 +- components/offer/MakeOffer.vue | 97 +++++++++---------- components/offer/MakingOfferSingleItem.vue | 6 +- .../useAutoTeleportActionButton.ts | 29 ++++++ stores/makeOffer.ts | 5 +- 9 files changed, 149 insertions(+), 107 deletions(-) rename components/common/{listingCart/shared/ListingCartItemDetails.vue => CartItemDetails.vue} (83%) create mode 100644 composables/autoTeleport/useAutoTeleportActionButton.ts diff --git a/components/common/listingCart/shared/ListingCartItemDetails.vue b/components/common/CartItemDetails.vue similarity index 83% rename from components/common/listingCart/shared/ListingCartItemDetails.vue rename to components/common/CartItemDetails.vue index bd4d59a19d..bab6913892 100644 --- a/components/common/listingCart/shared/ListingCartItemDetails.vue +++ b/components/common/CartItemDetails.vue @@ -1,10 +1,10 @@ diff --git a/composables/autoTeleport/useAutoTeleportActionButton.ts b/composables/autoTeleport/useAutoTeleportActionButton.ts index f4b678042b..1b3a0603e1 100644 --- a/composables/autoTeleport/useAutoTeleportActionButton.ts +++ b/composables/autoTeleport/useAutoTeleportActionButton.ts @@ -1,11 +1,11 @@ import type { Actions } from '@/composables/transaction/types' export default ({ - autoTeleport, - autoTeleportButton, - autoTeleportLoaded, getActionFn, }) => { + const autoTeleport = ref(false) + const autoTeleportButton = ref() + const autoTeleportLoaded = ref(false) const action = ref(emptyObject()) watch( @@ -25,5 +25,8 @@ export default ({ return { action, + autoTeleport, + autoTeleportButton, + autoTeleportLoaded, } } From ab157896f4b81bc6df2bb0026d734f7a9c29cf26 Mon Sep 17 00:00:00 2001 From: Jarsen <31397967+Jarsen136@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:14:53 +0200 Subject: [PATCH 5/6] Update components/offer/MakeOffer.vue Co-authored-by: Hassnian <44554284+hassnian@users.noreply.github.com> --- components/offer/MakeOffer.vue | 8 -------- 1 file changed, 8 deletions(-) diff --git a/components/offer/MakeOffer.vue b/components/offer/MakeOffer.vue index 3fd83ed86f..f6cb8f378b 100644 --- a/components/offer/MakeOffer.vue +++ b/components/offer/MakeOffer.vue @@ -202,14 +202,6 @@ watch( }, ) -watch( - () => preferencesStore.makeOfferModalOpen, - (makeOfferModalOpen) => { - if (!makeOfferModalOpen) { - offerStore.clear() - } - }, -) useModalIsOpenTracker({ isOpen: computed(() => preferencesStore.makeOfferModalOpen), From dd212f93456110aba72b43b0dd1ac0c4f6864d20 Mon Sep 17 00:00:00 2001 From: Jarsen <31397967+Jarsen136@users.noreply.github.com> Date: Tue, 20 Aug 2024 13:20:35 +0200 Subject: [PATCH 6/6] fix: eslint --- components/offer/MakeOffer.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/components/offer/MakeOffer.vue b/components/offer/MakeOffer.vue index f6cb8f378b..f56bdd23d1 100644 --- a/components/offer/MakeOffer.vue +++ b/components/offer/MakeOffer.vue @@ -202,7 +202,6 @@ watch( }, ) - useModalIsOpenTracker({ isOpen: computed(() => preferencesStore.makeOfferModalOpen), onChange: () => {