Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Solana cNFT based Data NFT Minting and other MISC updates #148

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
025e79f
feature: Intial test build on Solana cNFT minting option
newbreedofgeek Sep 12, 2024
fe60bf3
feature: logic and error handling improvements to CNftSolMinter
newbreedofgeek Sep 13, 2024
507e028
feature: logic and error handling improvements to CNftSolMinter
newbreedofgeek Sep 13, 2024
c506deb
feature: logic and error handling improvements to CNftSolMinter
newbreedofgeek Sep 13, 2024
2786601
feature: add proof as well to CNftSolPostMintMetaType
newbreedofgeek Sep 13, 2024
cc08a3a
feat: cache duration on access request
bucurdavid Sep 26, 2024
4612ff9
chore: alpha version bump
bucurdavid Sep 26, 2024
638fe44
Merge branch 'alpha' into d-david
bucurdavid Sep 26, 2024
75323ef
Merge pull request #150 from Itheum/d-david
bucurdavid Sep 26, 2024
066e848
feature: signatureNonce and solSignature added for CNftSolMinter
newbreedofgeek Oct 10, 2024
8db64c2
fix: increase all mvx timeouts to 20s and upgrade mvx network provide…
newbreedofgeek Nov 8, 2024
aca7fd2
fix: tests that broke based on last update to timeout
newbreedofgeek Nov 8, 2024
f449a5b
fix: remove sdk-network-providers and use sdk-core for everything, fi…
newbreedofgeek Nov 8, 2024
b214ba7
feature: use ITH_GLOBAL_* if available, introduce poc for Simple Cach…
newbreedofgeek Nov 11, 2024
63e731d
feature: allow setNetworkConfig to accept useSpecificApiEndpoint so h…
newbreedofgeek Nov 11, 2024
753990e
chore: add debug
newbreedofgeek Nov 11, 2024
c0ec35c
chore: add debug
newbreedofgeek Nov 11, 2024
614b79a
chore: add debug
newbreedofgeek Nov 11, 2024
5d409d6
chore: add debug
newbreedofgeek Nov 11, 2024
9679af2
feature: upgrade the mvx core sdk
newbreedofgeek Nov 11, 2024
943fb3d
fi: downgrade the mvx core sdk
newbreedofgeek Nov 11, 2024
901eee1
fix: upgrade the mvx core sdk again
newbreedofgeek Nov 11, 2024
1f3b02c
feature: make data nft createManyFromApi and ownedByAddress to be opt…
newbreedofgeek Nov 14, 2024
4141cc6
version bump
newbreedofgeek Nov 14, 2024
0521f53
feature: sol mint aligned to backend service updates
newbreedofgeek Nov 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
283 changes: 238 additions & 45 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@itheum/sdk-mx-data-nft",
"version": "3.6.1",
"version": "3.8.0-alpha.16",
"description": "SDK for Itheum's Data NFT Technology on MultiversX Blockchain",
"main": "out/index.js",
"types": "out/index.d.js",
Expand All @@ -17,8 +17,7 @@
"author": "Itheum Protocol",
"license": "GPL-3.0-only",
"dependencies": {
"@multiversx/sdk-core": "13.2.2",
"@multiversx/sdk-network-providers": "2.4.3",
"@multiversx/sdk-core": "13.14.1",
"bignumber.js": "9.1.2",
"nft.storage": "7.2.0"
},
Expand Down
4 changes: 2 additions & 2 deletions src/bond.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ export class BondContract extends Contract {
/**
* Creates a new instance of the DataNftMarket which can be used to interact with the marketplace smart contract
* @param env 'devnet' | 'mainnet' | 'testnet'
* @param timeout Timeout for the network provider (DEFAULT = 10000ms)
* @param timeout Timeout for the network provider (DEFAULT = 20000ms)
*/
constructor(env: string, timeout: number = 10000) {
constructor(env: string, timeout: number = 20000) {
super(
env,
new Address(bondContractAddress[env as EnvironmentsEnum]),
Expand Down
263 changes: 263 additions & 0 deletions src/cnft-sol-minter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import {
dataNFTDataStreamAdvertise,
storeToIpfsOnlyImg,
createIpfsMetadataSolCNft,
storeToIpfsFullSolCNftMetadata
} from './common/mint-utils';
import { checkTraitsUrl, checkUrlIsUp } from './common/utils';
import { ErrArgumentNotSet } from './errors';
import { MinterSol } from './minter-sol';
import { StringValidator, validateResults } from './common/validator';
import { CNftSolPostMintMetaType } from './interfaces';
import { SolEnvChainIDEnum } from './config';

export class CNftSolMinter extends MinterSol {
/**
* Creates a new instance of the `SftMinter` class, which can be used to interact with the Data NFT-FT minter smart contract
* @param env 'devnet' | 'mainnet' | 'testnet'
*/
constructor(env: string) {
super(env);
}

/**
* Creates a `mint` transaction
*
* NOTE: The `dataStreamUrl` is being encrypted and the `media` and `metadata` urls are build and uploaded to IPFS
*
* NOTE: The `options.nftStorageToken` is required when not using custom image and traits, when using custom image and traits the traits should be compliant with the `traits` structure
*
* @param creatorAddress the address of the creator who we mint a CNft for
* @param tokenName the name of the DataNFT-FT. Between 3 and 20 alphanumeric characters, no spaces.
* @param dataMarshalUrl the url of the data marshal. A live HTTPS URL that returns a 200 OK HTTP code.
* @param dataStreamUrl the url of the data stream to be encrypted. A live HTTPS URL that returns a 200 OK HTTP code.
* @param dataPreviewUrl the url of the data preview. A live HTTPS URL that returns a 200 OK HTTP code.
* @param datasetTitle the title of the dataset. Between 10 and 60 alphanumeric characters.
* @param datasetDescription the description of the dataset. Between 10 and 400 alphanumeric characters.
* @param options [optional] below parameters are optional or required based on use case
* - imageUrl: the URL of the image for the Data NFT
* - traitsUrl: the URL of the traits for the Data NFT
* - nftStorageToken: the nft storage token to be used to upload the image and metadata to IPFS
* - extraAssets: [optional] extra URIs to attached to the NFT. Can be media files, documents, etc. These URIs are public
* - imgGenBg: [optional] the custom series bg to influence the image generation service
* - imgGenSet: [optional] the custom series layer set to influence the image generation service
* - signatureNonce: [optional] a recent nonce from the marshal network that will be signed to produce solSignature
* - solSignature: [optional] a solana signature of signatureNonce to prove creatorAddress ownership
*
*/
async mint(
creatorAddress: string,
tokenName: string,
dataMarshalUrl: string,
dataStreamUrl: string,
dataPreviewUrl: string,
datasetTitle: string,
datasetDescription: string,
options?: {
imageUrl?: string;
traitsUrl?: string;
nftStorageToken?: string;
extraAssets?: string[];
imgGenBg?: string;
imgGenSet?: string;
signatureNonce?: string;
solSignature?: string;
}
): Promise<{
imageUrl: string;
metadataUrl: string;
mintMeta: CNftSolPostMintMetaType;
}> {
let imageOnIpfsUrl: string = '';
let metadataOnIpfsUrl: string = '';
let mintMeta: CNftSolPostMintMetaType = {};

try {
const {
imageUrl,
traitsUrl,
nftStorageToken,
extraAssets,
imgGenBg,
imgGenSet,
signatureNonce,
solSignature
} = 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;

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'
);
}

// create the img generative service API based on user options
let imgGenServiceApi = `${this.imageServiceUrl}/v1/generateNFTArt?hash=${dataNftHash}`;

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') || '';

const { imageOnIpfsUrl: imgOnIpfsUrl } = await storeToIpfsOnlyImg(
nftStorageToken,
_imageFile
);

if (!imgOnIpfsUrl || imgOnIpfsUrl === '') {
throw new Error('Saving Image to IPFS failed');
}

const cNftMetadataContent = createIpfsMetadataSolCNft(
tokenName,
datasetTitle,
datasetDescription,
imgOnIpfsUrl,
creatorAddress,
dataNftStreamUrlEncrypted,
dataPreviewUrl,
dataMarshalUrl,
traitsFromImgHeader,
extraAssets ?? []
);

const { metadataIpfsUrl } = await storeToIpfsFullSolCNftMetadata(
nftStorageToken,
cNftMetadataContent
);

if (!metadataIpfsUrl || metadataIpfsUrl === '') {
throw new Error('Saving cNFT Metadata to IPFS failed');
}

imageOnIpfsUrl = imgOnIpfsUrl;
metadataOnIpfsUrl = metadataIpfsUrl;
} else {
if (!traitsUrl) {
throw new ErrArgumentNotSet(
'traitsUrl',
'Traits URL is required when using custom image'
);
}

await checkTraitsUrl(traitsUrl);

imageOnIpfsUrl = imageUrl;
metadataOnIpfsUrl = traitsUrl;
}

allDataStreamAndIPFSLogicDone = true;
} catch (e: any) {
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 || '',
chainId:
this.env === 'devnet'
? SolEnvChainIDEnum.devnet
: SolEnvChainIDEnum.mainnet
});

const requestOptions = {
method: 'POST',
headers: postHeaders,
body: raw
};

let resMintCall: any = '';
let dataMintCall: any = '';

resMintCall = await fetch(
this.solCNftNfMeIdMinterServiceUrl,
requestOptions
);

dataMintCall = await resMintCall.text();
mintMeta = dataMintCall;
} catch (e: any) {
mintMeta = { error: true, errMsg: e.toString() };
throw e;
}
}
} catch (error) {
console.error(error);
throw error;
}

return {
imageUrl: imageOnIpfsUrl,
metadataUrl: metadataOnIpfsUrl,
mintMeta
};
}
}
Loading
Loading