From 9226fa6d20eb3aab5314473a67bc5156cc5d373a Mon Sep 17 00:00:00 2001 From: Nicole O'Brien Date: Tue, 20 Feb 2024 15:14:50 +0000 Subject: [PATCH] feat: mint nft collection (#1510) * initial mint nft collection form Co-authored-by: MarkNerdi996 * initial mint nft collection confirmation form * initial mint nft collection function * initial mint nft for collection * improve mint nft collection including id substitute Co-authored-by: Tuditi * fix: mint collection activity * fix: remove unnecessary on mount call backs --------- Co-authored-by: MarkNerdi996 Co-authored-by: Tuditi Co-authored-by: Tuditi <45079109+Tuditi@users.noreply.github.com> --- .../desktop/components/popup/Popup.svelte | 4 + .../MintNftCollectionConfirmationPopup.svelte | 149 +++++++++++++ .../popups/MintNftCollectionFormPopup.svelte | 205 ++++++++++++++++++ .../popups/MintNftConfirmationPopup.svelte | 19 +- .../popup/popups/MintNftFormPopup.svelte | 38 ++-- .../features/developer-tools.features.ts | 3 + .../auxiliary/popup/enums/popup-id.enum.ts | 2 + .../dashboard/developer/Developer.svelte | 4 + .../components/MintNftCollectionButton.svelte | 22 ++ .../dashboard/developer/components/index.ts | 1 + .../src/lib/core/wallet/actions/index.ts | 1 + .../src/lib/core/wallet/actions/mintNft.ts | 25 ++- .../core/wallet/actions/mintNftCollection.ts | 35 +++ .../src/lib/core/wallet/interfaces/index.ts | 1 + .../mint-nft-collection-details.interface.ts | 3 + .../interfaces/mint-nft-details.interface.ts | 2 + .../src/lib/core/wallet/stores/index.ts | 1 + .../mint-nft-collection-details.store.ts | 12 + .../developer-features.interface.ts | 1 + packages/shared/src/locales/en.json | 12 +- 20 files changed, 512 insertions(+), 28 deletions(-) create mode 100644 packages/desktop/components/popup/popups/MintNftCollectionConfirmationPopup.svelte create mode 100644 packages/desktop/components/popup/popups/MintNftCollectionFormPopup.svelte create mode 100644 packages/desktop/views/dashboard/developer/components/MintNftCollectionButton.svelte create mode 100644 packages/shared/src/lib/core/wallet/actions/mintNftCollection.ts create mode 100644 packages/shared/src/lib/core/wallet/interfaces/mint-nft-collection-details.interface.ts create mode 100644 packages/shared/src/lib/core/wallet/stores/mint-nft-collection-details.store.ts diff --git a/packages/desktop/components/popup/Popup.svelte b/packages/desktop/components/popup/Popup.svelte index ce1f4e0a3e..a2eaece25e 100644 --- a/packages/desktop/components/popup/Popup.svelte +++ b/packages/desktop/components/popup/Popup.svelte @@ -42,6 +42,8 @@ import ManageVotingPowerPopup from './popups/ManageVotingPowerPopup.svelte' import MintNativeTokenConfirmationPopup from './popups/MintNativeTokenConfirmationPopup.svelte' import MintNativeTokenFormPopup from './popups/MintNativeTokenFormPopup.svelte' + import MintNftCollectionConfirmationPopup from './popups/MintNftCollectionConfirmationPopup.svelte' + import MintNftCollectionFormPopup from './popups/MintNftCollectionFormPopup.svelte' import MintNftConfirmationPopup from './popups/MintNftConfirmationPopup.svelte' import MintNftFormPopup from './popups/MintNftFormPopup.svelte' import NodeAuthRequiredPopup from './popups/NodeAuthRequiredPopup.svelte' @@ -122,6 +124,8 @@ [PopupId.MintNativeTokenForm]: MintNativeTokenFormPopup, [PopupId.MintNftConfirmation]: MintNftConfirmationPopup, [PopupId.MintNftForm]: MintNftFormPopup, + [PopupId.MintNftCollectionForm]: MintNftCollectionFormPopup, + [PopupId.MintNftCollectionConfirmation]: MintNftCollectionConfirmationPopup, [PopupId.NodeAuthRequired]: NodeAuthRequiredPopup, [PopupId.NodeInfo]: NodeInfoPopup, [PopupId.ReceiveAddress]: ReceiveAddressPopup, diff --git a/packages/desktop/components/popup/popups/MintNftCollectionConfirmationPopup.svelte b/packages/desktop/components/popup/popups/MintNftCollectionConfirmationPopup.svelte new file mode 100644 index 0000000000..85179cca6e --- /dev/null +++ b/packages/desktop/components/popup/popups/MintNftCollectionConfirmationPopup.svelte @@ -0,0 +1,149 @@ + + + +
+ + + + + + + {#if selectedTab.key === PopupTab.Transaction} + + {:else if selectedTab.key === PopupTab.Nft} +
+ {:else if selectedTab.key === PopupTab.NftMetadata} +
+ {/if} + + + + diff --git a/packages/desktop/components/popup/popups/MintNftCollectionFormPopup.svelte b/packages/desktop/components/popup/popups/MintNftCollectionFormPopup.svelte new file mode 100644 index 0000000000..7a073721ee --- /dev/null +++ b/packages/desktop/components/popup/popups/MintNftCollectionFormPopup.svelte @@ -0,0 +1,205 @@ + + + + + + + + {#each Object.keys(optionalInputs) as key} + + {/each} + + {#if error} + + {/if} + + diff --git a/packages/desktop/components/popup/popups/MintNftConfirmationPopup.svelte b/packages/desktop/components/popup/popups/MintNftConfirmationPopup.svelte index d9249aa596..8ae4113ceb 100644 --- a/packages/desktop/components/popup/popups/MintNftConfirmationPopup.svelte +++ b/packages/desktop/components/popup/popups/MintNftConfirmationPopup.svelte @@ -19,8 +19,20 @@ let storageDeposit: number = 0 let totalStorageDeposit: number = 0 - const { standard, type, uri, name, collectionName, royalties, issuerName, description, attributes, quantity } = - $mintNftDetails || {} + const { + standard, + type, + uri, + name, + collectionName, + royalties, + issuerName, + description, + attributes, + quantity, + collectionId, + startIndex, + } = $mintNftDetails || {} $: irc27Metadata = { standard, @@ -53,6 +65,7 @@ openPopup({ id: PopupId.MintNftForm, overflow: true, + confirmClickOutside: true, }) } @@ -64,7 +77,7 @@ } try { - await mintNft(irc27Metadata, Number(quantity)) + await mintNft(irc27Metadata, startIndex, quantity, collectionId) closePopup() } catch (err) { handleError(err) diff --git a/packages/desktop/components/popup/popups/MintNftFormPopup.svelte b/packages/desktop/components/popup/popups/MintNftFormPopup.svelte index f7e9cad232..618973f5af 100644 --- a/packages/desktop/components/popup/popups/MintNftFormPopup.svelte +++ b/packages/desktop/components/popup/popups/MintNftFormPopup.svelte @@ -1,6 +1,5 @@ + diff --git a/packages/desktop/features/developer-tools.features.ts b/packages/desktop/features/developer-tools.features.ts index 50f71bea27..c066228d04 100644 --- a/packages/desktop/features/developer-tools.features.ts +++ b/packages/desktop/features/developer-tools.features.ts @@ -8,6 +8,9 @@ const developerToolsFeatures: IDeveloperFeatures = { mintNft: { enabled: true, }, + mintNftCollection: { + enabled: true, + }, mintNativeTokens: { enabled: true, }, diff --git a/packages/desktop/lib/auxiliary/popup/enums/popup-id.enum.ts b/packages/desktop/lib/auxiliary/popup/enums/popup-id.enum.ts index 4ca6056575..38e48f658a 100644 --- a/packages/desktop/lib/auxiliary/popup/enums/popup-id.enum.ts +++ b/packages/desktop/lib/auxiliary/popup/enums/popup-id.enum.ts @@ -28,6 +28,8 @@ export enum PopupId { MintNativeTokenForm = 'mintNativeTokenForm', MintNftConfirmation = 'mintNftConfirmation', MintNftForm = 'mintNftForm', + MintNftCollectionForm = 'mintNftCollectionForm', + MintNftCollectionConfirmation = 'mintNftCollectionConfirmation', NodeAuthRequired = 'nodeAuthRequired', NodeInfo = 'nodeInfo', ReceiveAddress = 'receiveAddress', diff --git a/packages/desktop/views/dashboard/developer/Developer.svelte b/packages/desktop/views/dashboard/developer/Developer.svelte index 30af9695bf..dd885e7568 100644 --- a/packages/desktop/views/dashboard/developer/Developer.svelte +++ b/packages/desktop/views/dashboard/developer/Developer.svelte @@ -9,6 +9,7 @@ FaucetRequestButton, MintNativeTokenButton, MintNftButton, + MintNftCollectionButton, TestDeepLinkButton, } from './components' import { DashboardRoute, dashboardRouter } from '@core/router' @@ -30,6 +31,9 @@ {#if features.developerTools.mintNativeTokens.enabled} {/if} + {#if features.developerTools.mintNftCollection.enabled} + + {/if} {#if features.developerTools.mintNft.enabled} {/if} diff --git a/packages/desktop/views/dashboard/developer/components/MintNftCollectionButton.svelte b/packages/desktop/views/dashboard/developer/components/MintNftCollectionButton.svelte new file mode 100644 index 0000000000..c44e0cbbba --- /dev/null +++ b/packages/desktop/views/dashboard/developer/components/MintNftCollectionButton.svelte @@ -0,0 +1,22 @@ + + + diff --git a/packages/desktop/views/dashboard/developer/components/index.ts b/packages/desktop/views/dashboard/developer/components/index.ts index 70d196eb86..1cd1180fcb 100644 --- a/packages/desktop/views/dashboard/developer/components/index.ts +++ b/packages/desktop/views/dashboard/developer/components/index.ts @@ -2,4 +2,5 @@ export { default as CreateAliasButton } from './CreateAliasButton.svelte' export { default as FaucetRequestButton } from './FaucetRequestButton.svelte' export { default as MintNativeTokenButton } from './MintNativeTokenButton.svelte' export { default as MintNftButton } from './MintNftButton.svelte' +export { default as MintNftCollectionButton } from './MintNftCollectionButton.svelte' export { default as TestDeepLinkButton } from './TestDeepLinkButton.svelte' diff --git a/packages/shared/src/lib/core/wallet/actions/index.ts b/packages/shared/src/lib/core/wallet/actions/index.ts index ec51b33b01..b276c4d6ee 100644 --- a/packages/shared/src/lib/core/wallet/actions/index.ts +++ b/packages/shared/src/lib/core/wallet/actions/index.ts @@ -8,6 +8,7 @@ export * from './isTrackedNftAddress' export * from './isTrackedTokenAddress' export * from './mintNativeToken' export * from './mintNft' +export * from './mintNftCollection' export * from './rejectActivity' export * from './sendOutput' export * from './updateL2BalanceWithoutActivity' diff --git a/packages/shared/src/lib/core/wallet/actions/mintNft.ts b/packages/shared/src/lib/core/wallet/actions/mintNft.ts index 598fa77661..df37f4f117 100644 --- a/packages/shared/src/lib/core/wallet/actions/mintNft.ts +++ b/packages/shared/src/lib/core/wallet/actions/mintNft.ts @@ -15,18 +15,33 @@ import { getTransactionOptions } from '../utils' import { resetMintNftDetails } from '../stores' import { getActiveNetworkId } from '@core/network' -export async function mintNft(metadata: IIrc27Metadata, quantity: number): Promise { +export async function mintNft( + metadata: IIrc27Metadata, + startIndex: number, + quantity: number, + collectionId?: string +): Promise { try { const account = getSelectedAccount() const networkId = getActiveNetworkId() updateSelectedAccount({ isTransferring: true }) - const mintNftParams: MintNftParams = { - issuer: account.depositAddress, - immutableMetadata: Converter.utf8ToHex(JSON.stringify(metadata)), + const allNftParams: MintNftParams[] = [] + for (let i = startIndex; i < startIndex + quantity; i++) { + const updatedMetadata = { + ...metadata, + name: metadata.name.replace('{id}', i.toString()), + description: metadata.description?.replace('{id}', i.toString()), + uri: metadata.uri.replace('{id}', i.toString()), + } + const mintNftParams: MintNftParams = { + address: account.depositAddress, + issuer: collectionId || account.depositAddress, + immutableMetadata: Converter.utf8ToHex(JSON.stringify(updatedMetadata)), + } + allNftParams.push(mintNftParams) } - const allNftParams: MintNftParams[] = Array(quantity).fill(mintNftParams) // Mint NFT const preparedTransaction = await account.prepareMintNfts( diff --git a/packages/shared/src/lib/core/wallet/actions/mintNftCollection.ts b/packages/shared/src/lib/core/wallet/actions/mintNftCollection.ts new file mode 100644 index 0000000000..088262ef45 --- /dev/null +++ b/packages/shared/src/lib/core/wallet/actions/mintNftCollection.ts @@ -0,0 +1,35 @@ +import { showNotification } from '@auxiliary/notification' +import { getSelectedAccount, updateSelectedAccount } from '@core/account/stores' +import { sendPreparedTransaction } from '@core/wallet/utils' +import { localize } from '@core/i18n' +import { IIrc27Metadata } from '@core/nfts' +import { Converter } from '@core/utils' +import { AliasOutputParams } from '@iota/sdk/out/types' +import { getActiveNetworkId } from '@core/network/actions' +import { processAndAddToActivities } from '@core/activity' + +export async function mintNftCollection(metadata: IIrc27Metadata): Promise { + try { + const account = getSelectedAccount() + const networkId = getActiveNetworkId() + updateSelectedAccount({ isTransferring: true }) + + const mintNftCollectionParams: AliasOutputParams = { + // issuer: account.depositAddress, // TODO: uncomment when added to iota-sdk + immutableMetadata: Converter.utf8ToHex(JSON.stringify(metadata)), + } + + // Mint NFT + const preparedTransaction = await account.prepareCreateAliasOutput(mintNftCollectionParams) + const transaction = await sendPreparedTransaction(preparedTransaction) + showNotification({ + variant: 'success', + text: localize('notifications.mintNft.success'), + }) + await processAndAddToActivities(transaction, account, networkId) + } catch (err) { + return Promise.reject(err) + } finally { + updateSelectedAccount({ isTransferring: false }) + } +} diff --git a/packages/shared/src/lib/core/wallet/interfaces/index.ts b/packages/shared/src/lib/core/wallet/interfaces/index.ts index 8569b56f37..516e9f5e3c 100644 --- a/packages/shared/src/lib/core/wallet/interfaces/index.ts +++ b/packages/shared/src/lib/core/wallet/interfaces/index.ts @@ -2,6 +2,7 @@ export * from './account-subject.interface' export * from './address-subject.interface' export * from './contact-subject.interface' export * from './mint-nft-details.interface' +export * from './mint-nft-collection-details.interface' export * from './mint-token-details.interface' export * from './network-subject.interface' export * from './participation.interface' diff --git a/packages/shared/src/lib/core/wallet/interfaces/mint-nft-collection-details.interface.ts b/packages/shared/src/lib/core/wallet/interfaces/mint-nft-collection-details.interface.ts new file mode 100644 index 0000000000..a6fce4641a --- /dev/null +++ b/packages/shared/src/lib/core/wallet/interfaces/mint-nft-collection-details.interface.ts @@ -0,0 +1,3 @@ +import { IIrc27Metadata } from '@core/nfts' + +export interface IMintNftCollectionDetails extends Omit {} diff --git a/packages/shared/src/lib/core/wallet/interfaces/mint-nft-details.interface.ts b/packages/shared/src/lib/core/wallet/interfaces/mint-nft-details.interface.ts index b05ec8a577..8d66b288e2 100644 --- a/packages/shared/src/lib/core/wallet/interfaces/mint-nft-details.interface.ts +++ b/packages/shared/src/lib/core/wallet/interfaces/mint-nft-details.interface.ts @@ -1,5 +1,7 @@ import { IIrc27Metadata } from '@core/nfts' export interface IMintNftDetails extends IIrc27Metadata { + collectionId?: string quantity: number + startIndex: number } diff --git a/packages/shared/src/lib/core/wallet/stores/index.ts b/packages/shared/src/lib/core/wallet/stores/index.ts index 1d872c4aba..eb3dfd58c0 100644 --- a/packages/shared/src/lib/core/wallet/stores/index.ts +++ b/packages/shared/src/lib/core/wallet/stores/index.ts @@ -1,3 +1,4 @@ +export * from './mint-nft-collection-details.store' export * from './mint-nft-details.store' export * from './mint-token-details.store' export * from './send-flow-parameter.store' diff --git a/packages/shared/src/lib/core/wallet/stores/mint-nft-collection-details.store.ts b/packages/shared/src/lib/core/wallet/stores/mint-nft-collection-details.store.ts new file mode 100644 index 0000000000..6432c52b54 --- /dev/null +++ b/packages/shared/src/lib/core/wallet/stores/mint-nft-collection-details.store.ts @@ -0,0 +1,12 @@ +import { writable } from 'svelte/store' +import { IMintNftCollectionDetails } from '../interfaces' + +export const mintNftCollectionDetails = writable(undefined) + +export function setMintNftCollectionDetails(payload: IMintNftCollectionDetails): void { + mintNftCollectionDetails.set(payload) +} + +export function resetMintNftCollectionDetails(): void { + mintNftCollectionDetails.set(undefined) +} diff --git a/packages/shared/src/lib/features/interfaces/developer-features.interface.ts b/packages/shared/src/lib/features/interfaces/developer-features.interface.ts index a7953609f0..e370f4aac2 100644 --- a/packages/shared/src/lib/features/interfaces/developer-features.interface.ts +++ b/packages/shared/src/lib/features/interfaces/developer-features.interface.ts @@ -3,6 +3,7 @@ import { IFeatureFlag } from './feature-flag.interface' export interface IDeveloperFeatures extends IFeatureFlag { faucet: IFeatureFlag mintNft: IFeatureFlag + mintNftCollection: IFeatureFlag mintNativeTokens: IFeatureFlag alias: IFeatureFlag refreshTokens: IFeatureFlag diff --git a/packages/shared/src/locales/en.json b/packages/shared/src/locales/en.json index 8807564d32..f561d50236 100644 --- a/packages/shared/src/locales/en.json +++ b/packages/shared/src/locales/en.json @@ -1159,7 +1159,7 @@ "errors": { "invalidMimetype": "Invalid MimeType, check if the file type is supported", "quantityTooSmall": "Quantity needs to be greater than 0", - "quantityTooLarge": "Quantity needs to be smaller than 64", + "quantityTooLarge": "Quantity needs to be smaller than 127", "emptyName": "Name is a required field", "invalidURI": "Invalid URI, please provide a valid URI", "notReachable": "URI not reachable, unable to check NFT type", @@ -1366,7 +1366,8 @@ "switchAccount": "Switch account", "syncAccounts": "Sync accounts", "importToken": "Import {type} token", - "syncTokens": "Force sync tokens" + "syncTokens": "Force sync tokens", + "mintNftCollection": "Mint NFT collection" }, "general": { "recipient": "Recipient", @@ -1587,7 +1588,9 @@ "raffle": "Raffle", "governance": "Governance", "baseCoin": "Base Coin", - "unverifiedContract": "Unverified Contract" + "unverifiedContract": "Unverified Contract", + "startIndex": "Start index", + "mintNftCollectionDescription": "Mint collection NFT in Alias Output" }, "filters":{ "title": "Filters", @@ -2071,7 +2074,8 @@ "description": "Optional parameter: A description of the NFT", "attributes": "Optional parameter: An array of traits and values that define attributes of the NFT", "uri": "To create a URI using custom media, first upload your file to IPFS via a storage service (e.g. https://nft.storage/)", - "quantity": "Optional parameter: The quantity of copies minted with this metadata." + "quantity": "Optional parameter: The quantity of copies minted with this metadata.", + "startIndex": "Optional parameter: The start index of the NFTs to mint." }, "governance": { "removeProposalWarning": "You must stop voting for this proposal before removing it.",