diff --git a/components/collection/drop/modal/PaidMint.vue b/components/collection/drop/modal/PaidMint.vue index 3ed3983495..f86785cc52 100644 --- a/components/collection/drop/modal/PaidMint.vue +++ b/components/collection/drop/modal/PaidMint.vue @@ -7,8 +7,6 @@ :title="title" :scrollable="false" :loading="loading" - :custom-skeleton-title="preStepTitle" - :estimated-time="estimedTime" @close="close" > amountToMint.value === 1 && !canMint.value, ) -const estimedTime = computed(() => - isSingleMintNotReady.value ? IPFS_ESTIMATED_TIME_SECONDS : undefined, -) - const mintButton = computed(() => { if (!canMint.value) { return { @@ -117,10 +109,6 @@ const loading = computed( || false, ) -const preStepTitle = computed(() => - isSingleMintNotReady.value ? $i18n.t('drops.mintDropError') : undefined, -) - const isMintOverviewStep = computed( () => modalStep.value === ModalStep.OVERVIEW, ) diff --git a/components/collection/drop/modal/shared/SuccessfulDrop.vue b/components/collection/drop/modal/shared/SuccessfulDrop.vue index a984ee0221..8dce884026 100644 --- a/components/collection/drop/modal/shared/SuccessfulDrop.vue +++ b/components/collection/drop/modal/shared/SuccessfulDrop.vue @@ -34,6 +34,7 @@ const { toast } = useToast() const { urlPrefix } = usePrefix() const { accountId } = useAuth() const { getCollectionFrameUrl } = useSocialShare() +const { toMintNFTs } = storeToRefs(useDropStore()) const cantList = computed(() => !props.canListNfts) const txHash = computed(() => props.mintingSession.txHash ?? '') @@ -42,30 +43,48 @@ const mintedNft = computed( () => props.mintingSession.items[0], ) -const items = computed(() => - props.mintingSession.items.map(item => ({ - id: item.id, - name: item.name, - image: item.image, - collection: item.collection.id, - collectionName: item.collection.name, - mimeType: item.mimeType, - })), -) +const itemMedias = props.mintingSession.items.map(item => ({ + id: item.id, + name: item.name, + image: item.image, + collection: item.collection.id, + collectionName: item.collection.name, + mimeType: item.mimeType, + metadata: item.metadata, +})) +const items = ref(itemMedias) + +// update serial number in nft.name asynchronously +onMounted(async () => { + const metadatas = await Promise.all( + items.value.map(item => $fetch<{ name?: string }>(item.metadata)), + ) + + items.value.forEach((_, index) => { + const metadata = metadatas[index] + if (metadata.name) { + items.value[index].name = metadata.name + toMintNFTs.value[index].name = metadata.name + } + }) +}) const nftPath = computed( - () => `/${mintedNft.value?.chain}/gallery/${mintedNft.value?.id}`, + () => + `/${mintedNft.value?.chain}/gallery/${mintedNft.value?.collection.id}-${mintedNft.value?.id}`, ) const nftFullUrl = computed(() => `${window.location.origin}${nftPath.value}`) const userProfilePath = computed( () => `/${urlPrefix.value}/u/${accountId.value}`, ) +const getItemSn = (name: string) => `#${name.split('#')[1]}` + const sharingTxt = computed(() => singleMint.value - ? $i18n.t('sharing.dropNft', [`#${mintedNft.value?.index}`]) + ? $i18n.t('sharing.dropNft', [getItemSn(items.value[0].name)]) : $i18n.t('sharing.dropNfts', [ - props.mintingSession.items.map(item => `#${item.index}`).join(', '), + items.value.map(item => getItemSn(item.name)).join(', '), ]), ) diff --git a/components/collection/drop/types.ts b/components/collection/drop/types.ts index 53818cb525..242bf3d8c9 100644 --- a/components/collection/drop/types.ts +++ b/components/collection/drop/types.ts @@ -33,8 +33,8 @@ export type MintedNFT = { chain: string name: string image: string - index: number - collection: { id: string, name: string, max: number } + collection: { id: string, name: string, max?: number } + metadata: string mimeType?: string } diff --git a/components/common/successfulModal/SuccessfulItemsMedia.vue b/components/common/successfulModal/SuccessfulItemsMedia.vue index 25e229f694..fd7deb92b1 100644 --- a/components/common/successfulModal/SuccessfulItemsMedia.vue +++ b/components/common/successfulModal/SuccessfulItemsMedia.vue @@ -27,6 +27,7 @@ export type ItemMedia = { collectionName: string price?: string mimeType?: string + metadata: string } const props = defineProps<{ diff --git a/composables/drop/massmint/useDropMassMint.ts b/composables/drop/massmint/useDropMassMint.ts index 1524c0c1e0..5e37e0bbb5 100644 --- a/composables/drop/massmint/useDropMassMint.ts +++ b/composables/drop/massmint/useDropMassMint.ts @@ -1,10 +1,10 @@ import { useCollectionEntity } from '../useGenerativeDropMint' import type { ToMintNft } from '@/components/collection/drop/types' -import type { DoResult } from '@/services/fxart' -import { updateMetadata } from '@/services/fxart' +import { generateId, setDyndataUrl } from '@/services/dyndata' export type MassMintNFT = Omit & { - metadata?: string + image: string + metadata: string nft: number // nft id sn?: number // serial numbers } @@ -12,25 +12,54 @@ export type MassMintNFT = Omit & { export default () => { const dropStore = useDropStore() const { collectionName } = useCollectionEntity() - const { drop, amountToMint, toMintNFTs, loading } = storeToRefs(dropStore) + const { isSub } = useIsChain(usePrefix().urlPrefix) + + // ensure tokenIds are unique on single user session + const tokenIds = ref([]) + const populateTokenIds = async () => { + for (const _ of Array.from({ length: amountToMint.value })) { + const tokenId = Number.parseInt(await generateId()) + if (!tokenIds.value.includes(tokenId)) { + tokenIds.value.push(tokenId) + } + } + + if (tokenIds.value.length < amountToMint.value) { + await populateTokenIds() + } + } - const clearMassmint = () => { + const clearMassMint = () => { dropStore.resetMassmint() + tokenIds.value = [] } const massGenerate = async () => { try { - clearMassmint() + clearMassMint() + if (isSub.value) { + await populateTokenIds() + } + + toMintNFTs.value = Array.from({ length: amountToMint.value }).map( + (_, index) => { + const { image, metadata } = setDyndataUrl({ + chain: drop.value.chain, + collection: drop.value.collection, + nft: tokenIds.value[index], + }) - toMintNFTs.value = Array.from({ length: amountToMint.value }).map(() => { - return { - name: drop.value.name, - collectionName: collectionName.value, - price: drop.value.price?.toString() || '', - nft: parseInt(uidMathDate()), - } - }) + return { + name: drop.value.name, + collectionName: collectionName.value, + price: drop.value.price?.toString() || '', + nft: tokenIds.value[index], + metadata, + image, + } + }, + ) console.log('[MASSMINT::GENERATE] Generated', toRaw(toMintNFTs.value)) } @@ -40,27 +69,10 @@ export default () => { } } - const submitMint = async (nft: MassMintNFT): Promise => { - return new Promise((resolve, reject) => { - try { - updateMetadata({ - chain: drop.value.chain, - collection: drop.value.collection, - nft: nft.nft, - sn: nft.sn, - }).then(result => resolve(result)) - } - catch (e) { - reject(e) - } - }) - } - - onBeforeUnmount(clearMassmint) + onBeforeUnmount(clearMassMint) return { massGenerate, - submitMint, - clearMassMint: clearMassmint, + clearMassMint, } } diff --git a/composables/drop/massmint/useDropMassMintListing.ts b/composables/drop/massmint/useDropMassMintListing.ts index b1c39614dd..a5f4359914 100644 --- a/composables/drop/massmint/useDropMassMintListing.ts +++ b/composables/drop/massmint/useDropMassMintListing.ts @@ -5,7 +5,7 @@ export default () => { const { client } = usePrefix() const { listNftByNftWithMetadata } = useListingCartModal() - const { mintedNFTs, mintingSession } = storeToRefs(useDropStore()) + const { mintedNFTs, toMintNFTs } = storeToRefs(useDropStore()) const subscribeForNftsWithMetadata = (nftIds: string[]) => { subscribeToNfts(nftIds, async (data) => { @@ -29,10 +29,11 @@ export default () => { } const listMintedNFTs = () => { - mintedNFTs.value.forEach((withMetadataNFT: NFTWithMetadata) => { - const mintingSessionNFT = mintingSession.value.items.find( - nft => nft.id === withMetadataNFT.id, + mintedNFTs.value.forEach(async (withMetadataNFT: NFTWithMetadata) => { + const mintingSessionNFT = toMintNFTs.value.find( + nft => nft.nft.toString() === withMetadataNFT.sn, ) + listNftByNftWithMetadata( { ...withMetadataNFT, diff --git a/composables/drop/useGenerativeDropMint.ts b/composables/drop/useGenerativeDropMint.ts index 8ed1aedf62..d3fdfce491 100644 --- a/composables/drop/useGenerativeDropMint.ts +++ b/composables/drop/useGenerativeDropMint.ts @@ -1,15 +1,10 @@ import { useQuery } from '@tanstack/vue-query' -import { type MintedNFT } from '@/components/collection/drop/types' -import type { DoResult } from '@/services/fxart' -import { setMetadataUrl } from '@/services/fxart' +import { type DoResult, updateMetadata } from '@/services/fxart' import { useDrop } from '@/components/drops/useDrops' import unlockableCollectionById from '@/queries/subsquid/general/unlockableCollectionById.graphql' import { FALLBACK_DROP_COLLECTION_MAX } from '@/utils/drop' -import type { - MassMintNFT, -} from '@/composables/drop/massmint/useDropMassMint' -import useDropMassMint from '@/composables/drop/massmint/useDropMassMint' import useDropMassMintListing from '@/composables/drop/massmint/useDropMassMintListing' +import type { MintedNFT } from '@/components/collection/drop/types' export type DropMintedNft = DoResult & { id: string @@ -81,9 +76,7 @@ export const useUpdateMetadata = async ({ blockNumber: Ref }) => { const { drop } = useDrop() - const { toMintNFTs, amountToMint, mintingSession } - = storeToRefs(useDropStore()) - const { submitMint } = useDropMassMint() + const { toMintNFTs, mintingSession } = storeToRefs(useDropStore()) const { subscribeForNftsWithMetadata } = useDropMassMintListing() const { collectionName, maxCount } = useCollectionEntity() const { $consola } = useNuxtApp() @@ -94,89 +87,32 @@ export const useUpdateMetadata = async ({ ?? FALLBACK_DROP_COLLECTION_MAX) const updateSubstrateMetdata = () => { - const status = ref<'index' | 'update'>('index') - - // 1. get nft index - const mintNFTs: Ref = ref([]) - useSubscriptionGraphql({ - query: ` - nfts: nftEntities( - where: {collection: {id_eq: "${drop.value.collection}"}}, - orderBy: [createdAt_ASC, sn_ASC] - ) { - id + mintingSession.value.items = toMintNFTs.value.map((item) => { + // trigger update metadata + updateMetadata({ + chain: drop.value.chain, + collection: drop.value.collection, + nft: item.nft, + }) + + return { + id: item.nft.toString(), + chain: drop.value.chain, + name: item.name, + image: item.image, + metadata: item.metadata, + collection: { + id: drop.value.collection, + name: item.collectionName, + max: drop.value.max, + }, } - `, - onChange: async ({ data: { nfts } }) => { - mintNFTs.value = [] - - if (status.value === 'update') { - return - } - - const checkIndex = new Set() // check duplicate index - for (const mintNFT of toMintNFTs.value) { - const index - = nfts.findIndex( - nft => nft.id === `${drop.value.collection}-${mintNFT.nft}`, - ) + 1 - - if (index > 0) { - checkIndex.add(index) - const metadata = setMetadataUrl({ - chain: drop.value.chain, - collection: drop.value.collection, - sn: index.toString(), - }) - - mintNFTs.value.push({ - ...mintNFT, - name: `${mintNFT.name} #${index}`, - metadata: metadata.toString(), - sn: index, - }) - } - } - - if (checkIndex.size === amountToMint.value) { - status.value = 'update' - await submitMetadata() - } - }, }) - - // 2. update metadata - const mintedNfts: Ref = ref([]) - const submitMetadata = async () => { - const response = await Promise.all(mintNFTs.value.map(submitMint)) - - for (const [index, res] of response.entries()) { - let metadata = { name: '', image: '' } - - try { - metadata = await $fetch(res.metadata || '') - } - catch (error) { - $consola.warn(error) - } - - mintedNfts.value.push({ - id: `${drop.value.collection}-${res.nft}`, - index: mintNFTs.value[index].sn as number, - chain: res.chain, - name: metadata.name, - image: metadata.image, - collection: { - id: res.collection, - name: collectionName.value, - max: collectionMax.value, - }, - }) - } - - mintingSession.value.items = mintedNfts.value - subscribeForNftsWithMetadata(mintedNfts.value.map(item => item.id)) - } + subscribeForNftsWithMetadata( + toMintNFTs.value.map( + item => `${drop.value.collection}-${item.nft.toString()}`, + ), + ) } const updateEvmMetdata = () => { diff --git a/composables/useNft.ts b/composables/useNft.ts index fe870383c0..6ec3a0d0d5 100644 --- a/composables/useNft.ts +++ b/composables/useNft.ts @@ -198,8 +198,9 @@ export async function getNftMetadata( prefix: string, unify = false, ) { - const checkMetadata = (nft.metadata || nft.meta?.id)?.includes( - DYNAMIC_METADATA, + const ignoreMetadata = [DYNAMIC_METADATA, 'dyndata'] + const checkMetadata = ignoreMetadata.some(item => + (nft.metadata || nft.meta?.id)?.includes(item), ) if (unify && !checkMetadata) { return await getMetadata(sanitizeIpfsUrl(nft.metadata || nft.meta.id)) diff --git a/services/dyndata.ts b/services/dyndata.ts new file mode 100644 index 0000000000..98c2f4b17a --- /dev/null +++ b/services/dyndata.ts @@ -0,0 +1,21 @@ +const BASE_URL = isProduction + ? 'https://dyndata.koda.art' + : 'https://dyndata-beta.koda.art' + +const api = $fetch.create({ + baseURL: BASE_URL, +}) + +export const generateId = async () => { + return (await api('/generate-id')) as string +} + +export const setDyndataUrl = ({ chain, collection, nft }) => { + const metadata = `https://dyndata.koda.art/v1/metadata/${chain}/${collection}/${nft}` + const image = `https://dyndata.koda.art/v1/image/${chain}/${collection}/${nft}` + + return { + metadata, + image, + } +} diff --git a/services/fxart.ts b/services/fxart.ts index 3af2862e61..8ee3e1d896 100644 --- a/services/fxart.ts +++ b/services/fxart.ts @@ -1,5 +1,4 @@ -import type { FetchError } from 'ofetch' -import { $fetch } from 'ofetch' +import { $fetch, type FetchError } from 'ofetch' import type { Prefix } from '@kodadot1/static' import type { DropItem } from '@/params/types' import { isProduction } from '@/utils/env' @@ -62,26 +61,14 @@ export const getDropMintedStatus = async (alias: string, accountId: string) => { }) } -export const setMetadataUrl = ({ chain, collection, sn }) => { - const metadataUrl = new URL( - 'https://fxart-beta.kodadot.workers.dev/metadata/v2/json', - ) - metadataUrl.searchParams.set('chain', chain) - metadataUrl.searchParams.set('collection', collection) - metadataUrl.searchParams.set('sn', sn.toString()) - - return metadataUrl.toString() -} - -export const updateMetadata = async ({ chain, collection, nft, sn }) => { +export const updateMetadata = async ({ chain, collection, nft }) => { try { - const response = await api('/metadata/v2/update', { + const response = await api('/metadata/v1/dyndata/update', { method: 'post', body: { chain, collection, nft, - sn, }, })