From 507e028d0ca63713970eb8c1bc10fd224eb8f0e0 Mon Sep 17 00:00:00 2001 From: Mark Paul Date: Fri, 13 Sep 2024 11:04:25 +1000 Subject: [PATCH] feature: logic and error handling improvements to CNftSolMinter --- package.json | 2 +- src/cnft-sol-minter.ts | 304 ++++++++++++++++++++------------------- src/common/mint-utils.ts | 42 ++++-- 3 files changed, 192 insertions(+), 156 deletions(-) diff --git a/package.json b/package.json index c0def82..9aad241 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@itheum/sdk-mx-data-nft", - "version": "3.7.0-alpha.2", + "version": "3.7.0-alpha.3", "description": "SDK for Itheum's Data NFT Technology on MultiversX and Solana", "main": "out/index.js", "types": "out/index.d.js", diff --git a/src/cnft-sol-minter.ts b/src/cnft-sol-minter.ts index 28b85a1..cbf2e3d 100644 --- a/src/cnft-sol-minter.ts +++ b/src/cnft-sol-minter.ts @@ -63,168 +63,182 @@ export class CNftSolMinter extends MinterSol { metadataUrl: string; mintMeta: CNftSolPostMintMetaType; }> { - const { - imageUrl, - traitsUrl, - nftStorageToken, - extraAssets, - imgGenBg, - imgGenSet - } = options ?? {}; - - const tokenNameValidator = new StringValidator() - .notEmpty() - .alphanumeric() - .minLength(3) - .maxLength(20) - .validate(tokenName); - - const datasetTitleValidator = new StringValidator() - .notEmpty() - .minLength(10) - .maxLength(60) - .validate(datasetTitle.trim()); - - const datasetDescriptionValidator = new StringValidator() - .notEmpty() - .minLength(10) - .maxLength(400) - .validate(datasetDescription); - - validateResults([ - tokenNameValidator, - datasetTitleValidator, - datasetDescriptionValidator - ]); - - // deep validate all mandatory URLs - try { - await checkUrlIsUp(dataPreviewUrl, [200]); - await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); - } catch (error) { - throw error; - } + let imageOnIpfsUrl: string = ''; + let metadataOnIpfsUrl: string = ''; + let mintMeta: CNftSolPostMintMetaType = {}; - let imageOnIpfsUrl: string; - let metadataOnIpfsUrl: string; + try { + const { + imageUrl, + traitsUrl, + nftStorageToken, + extraAssets, + imgGenBg, + imgGenSet + } = options ?? {}; + + const tokenNameValidator = new StringValidator() + .notEmpty() + .alphanumeric() + .minLength(3) + .maxLength(20) + .validate(tokenName); + + const datasetTitleValidator = new StringValidator() + .notEmpty() + .minLength(10) + .maxLength(60) + .validate(datasetTitle.trim()); + + const datasetDescriptionValidator = new StringValidator() + .notEmpty() + .minLength(10) + .maxLength(400) + .validate(datasetDescription); + + validateResults([ + tokenNameValidator, + datasetTitleValidator, + datasetDescriptionValidator + ]); + + // deep validate all mandatory URLs + try { + await checkUrlIsUp(dataPreviewUrl, [200]); + await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); + } catch (error) { + throw error; + } - // handle all logic related to data stream and ipfs gen of img,traits etc - let allDataStreamAndIPFSLogicDone = false; + // handle all logic related to data stream and ipfs gen of img,traits etc + let allDataStreamAndIPFSLogicDone = false; - try { - const { dataNftHash, dataNftStreamUrlEncrypted } = - await dataNFTDataStreamAdvertise( - dataStreamUrl, - dataMarshalUrl, - creatorAddress // the caller is the Creator - ); - - if (!imageUrl) { - if (!nftStorageToken) { - throw new ErrArgumentNotSet( - 'nftStorageToken', - 'NFT Storage token is required when not using custom image and traits' + try { + const { dataNftHash, dataNftStreamUrlEncrypted } = + await dataNFTDataStreamAdvertise( + dataStreamUrl, + dataMarshalUrl, + creatorAddress // the caller is the Creator ); - } - // create the img generative service API based on user options - let imgGenServiceApi = `${this.imageServiceUrl}/v1/generateNFTArt?hash=${dataNftHash}`; + if (!imageUrl) { + if (!nftStorageToken) { + throw new ErrArgumentNotSet( + 'nftStorageToken', + 'NFT Storage token is required when not using custom image and traits' + ); + } - if (imgGenBg && imgGenBg.trim() !== '') { - imgGenServiceApi += `&bg=${imgGenBg.trim()}`; - } + // create the img generative service API based on user options + let imgGenServiceApi = `${this.imageServiceUrl}/v1/generateNFTArt?hash=${dataNftHash}`; - if (imgGenSet && imgGenSet.trim() !== '') { - imgGenServiceApi += `&set=${imgGenSet.trim()}`; - } + if (imgGenBg && imgGenBg.trim() !== '') { + imgGenServiceApi += `&bg=${imgGenBg.trim()}`; + } + + if (imgGenSet && imgGenSet.trim() !== '') { + imgGenServiceApi += `&set=${imgGenSet.trim()}`; + } + + let resImgCall: any = ''; + let dataImgCall: any = ''; + let _imageFile: Blob = new Blob(); + + resImgCall = await fetch(imgGenServiceApi); + dataImgCall = await resImgCall.blob(); + _imageFile = dataImgCall; + + const traitsFromImgHeader = + resImgCall.headers.get('x-nft-traits') || ''; - let resImgCall: any = ''; - let dataImgCall: any = ''; - let _imageFile: Blob = new Blob(); - - resImgCall = await fetch(imgGenServiceApi); - dataImgCall = await resImgCall.blob(); - _imageFile = dataImgCall; - - const traitsFromImgHeader = - resImgCall.headers.get('x-nft-traits') || ''; - - const { imageOnIpfsUrl: imgOnIpfsUrl } = await storeToIpfsOnlyImg( - nftStorageToken, - _imageFile - ); - - const cNftMetadataContent = createIpfsMetadataSolCNft( - tokenName, - datasetTitle, - datasetDescription, - imgOnIpfsUrl, - creatorAddress, - dataNftStreamUrlEncrypted, - dataPreviewUrl, - dataMarshalUrl, - traitsFromImgHeader, - extraAssets ?? [] - ); - - const { metadataIpfsUrl } = await storeToIpfsFullSolCNftMetadata( - nftStorageToken, - cNftMetadataContent - ); - - imageOnIpfsUrl = imgOnIpfsUrl; - metadataOnIpfsUrl = metadataIpfsUrl; - } else { - if (!traitsUrl) { - throw new ErrArgumentNotSet( - 'traitsUrl', - 'Traits URL is required when using custom image' + const { imageOnIpfsUrl: imgOnIpfsUrl } = await storeToIpfsOnlyImg( + nftStorageToken, + _imageFile ); - } - await checkTraitsUrl(traitsUrl); + if (!imgOnIpfsUrl || imgOnIpfsUrl === '') { + throw new Error('Saving Image to IPFS failed'); + } + + const cNftMetadataContent = createIpfsMetadataSolCNft( + tokenName, + datasetTitle, + datasetDescription, + imgOnIpfsUrl, + creatorAddress, + dataNftStreamUrlEncrypted, + dataPreviewUrl, + dataMarshalUrl, + traitsFromImgHeader, + extraAssets ?? [] + ); - imageOnIpfsUrl = imageUrl; - metadataOnIpfsUrl = traitsUrl; - } + const { metadataIpfsUrl } = await storeToIpfsFullSolCNftMetadata( + nftStorageToken, + cNftMetadataContent + ); - allDataStreamAndIPFSLogicDone = true; - } catch (e: any) { - throw e; - } + if (!metadataIpfsUrl || metadataIpfsUrl === '') { + throw new Error('Saving cNFT Metadata to IPFS failed'); + } - // we not make a call to our private cNFt minter API - let mintMeta: CNftSolPostMintMetaType = {}; + imageOnIpfsUrl = imgOnIpfsUrl; + metadataOnIpfsUrl = metadataIpfsUrl; + } else { + if (!traitsUrl) { + throw new ErrArgumentNotSet( + 'traitsUrl', + 'Traits URL is required when using custom image' + ); + } - if (allDataStreamAndIPFSLogicDone) { - try { - const postHeaders = new Headers(); - postHeaders.append('Content-Type', 'application/json'); - - const raw = JSON.stringify({ - metadataOnIpfsUrl, - tokenName, - mintForSolAddr: creatorAddress, - solSignature: 'solSignature', - signatureNonce: 'signatureNonce' - }); - - const requestOptions = { - method: 'POST', - headers: postHeaders, - body: raw - }; - - let resMintCall: any = ''; - let dataMintCall: any = ''; - - resMintCall = await fetch(this.solCNftMinterServiceUrl, requestOptions); - dataMintCall = await resMintCall.text(); - mintMeta = dataMintCall; + await checkTraitsUrl(traitsUrl); + + imageOnIpfsUrl = imageUrl; + metadataOnIpfsUrl = traitsUrl; + } + + allDataStreamAndIPFSLogicDone = true; } catch (e: any) { - mintMeta = { error: true, errMsg: e.toString() }; throw e; } + + // we not make a call to our private cNFt minter API + if (allDataStreamAndIPFSLogicDone) { + try { + const postHeaders = new Headers(); + postHeaders.append('Content-Type', 'application/json'); + + const raw = JSON.stringify({ + metadataOnIpfsUrl, + tokenName, + mintForSolAddr: creatorAddress, + solSignature: 'solSignature', + signatureNonce: 'signatureNonce' + }); + + const requestOptions = { + method: 'POST', + headers: postHeaders, + body: raw + }; + + let resMintCall: any = ''; + let dataMintCall: any = ''; + + resMintCall = await fetch( + this.solCNftMinterServiceUrl, + requestOptions + ); + dataMintCall = await resMintCall.text(); + mintMeta = dataMintCall; + } catch (e: any) { + mintMeta = { error: true, errMsg: e.toString() }; + throw e; + } + } + } catch (e) { + console.error(e); } return { diff --git a/src/common/mint-utils.ts b/src/common/mint-utils.ts index 709723c..5310f7c 100644 --- a/src/common/mint-utils.ts +++ b/src/common/mint-utils.ts @@ -43,10 +43,18 @@ export async function storeToIpfs( try { const imageHash = await storeImageToIpfs(image, storageToken); const traitsHash = await storeTraitsToIpfs(traits, storageToken); - return { - imageOnIpfsUrl: `https://ipfs.io/ipfs/${imageHash}`, - metadataOnIpfsUrl: `https://ipfs.io/ipfs/${traitsHash}` - }; + + if (imageHash && traitsHash) { + return { + imageOnIpfsUrl: `https://ipfs.io/ipfs/${imageHash}`, + metadataOnIpfsUrl: `https://ipfs.io/ipfs/${traitsHash}` + }; + } else { + return { + imageOnIpfsUrl: '', + metadataOnIpfsUrl: '' + }; + } } catch (error) { throw error; } @@ -61,9 +69,16 @@ export async function storeToIpfsFullSolCNftMetadata( metadataStructureSolCNft, storageToken ); - return { - metadataIpfsUrl: `https://ipfs.io/ipfs/${metadataIpfsHash}` - }; + + if (metadataIpfsHash) { + return { + metadataIpfsUrl: `https://ipfs.io/ipfs/${metadataIpfsHash}` + }; + } else { + return { + metadataIpfsUrl: '' + }; + } } catch (error) { throw error; } @@ -75,9 +90,16 @@ export async function storeToIpfsOnlyImg( ): Promise<{ imageOnIpfsUrl: string }> { try { const imageHash = await storeImageToIpfs(image, storageToken); - return { - imageOnIpfsUrl: `https://ipfs.io/ipfs/${imageHash}` - }; + + if (imageHash) { + return { + imageOnIpfsUrl: `https://ipfs.io/ipfs/${imageHash}` + }; + } else { + return { + imageOnIpfsUrl: '' + }; + } } catch (error) { throw error; }