From 61bf2cc7a1c653573cd62c7b181b32c20b7692f6 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Mon, 11 Sep 2023 11:23:18 +0300 Subject: [PATCH 01/32] fix: custom errors extends error base class Refs: #25 --- src/errors.ts | 51 ++++++++++--------------------------------- tests/datanft.test.ts | 20 +++++++---------- 2 files changed, 19 insertions(+), 52 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index 0556be6..089cf17 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,33 +1,4 @@ -/** - * The base class for exceptions (errors). - */ -export class Err extends Error { - inner: Error | undefined = undefined; - - public constructor(message: string, inner?: Error) { - super(message); - this.inner = inner; - } - - /** - * Returns a pretty, friendly summary for the error or for the chain of errors (if appropriate). - */ - summary(): any[] { - let result = []; - - result.push({ name: this.name, message: this.message }); - - let inner: any = this.inner; - while (inner) { - result.push({ name: inner.name, message: inner.message }); - inner = inner.inner; - } - - return result; - } -} - -export class ErrNetworkConfig extends Err { +export class ErrNetworkConfig extends Error { public constructor() { super( 'Network configuration is not set. Call setNetworkConfig static method before calling any method that requires network configuration.' @@ -35,37 +6,37 @@ export class ErrNetworkConfig extends Err { } } -export class ErrDataNftCreation extends Err { - public constructor(inner?: Error) { - super(`Could not create DataNft:`, inner); +export class ErrDataNftCreation extends Error { + public constructor() { + super(`Could not create DataNft:`); } } -export class ErrDecodeAttributes extends Err { +export class ErrDecodeAttributes extends Error { public constructor() { super('Could not decode attributes'); } } -export class ErrAttributeNotSet extends Err { +export class ErrAttributeNotSet extends Error { public constructor(attributeName: string) { super(`Attribute ${attributeName} is not set`); } } -export class ErrArgumentNotSet extends Err { +export class ErrArgumentNotSet extends Error { public constructor(argumentName: string, message: string) { super(`Argument ${argumentName} is not set. ${message}`); } } -export class ErrFailedOperation extends Err { - public constructor(methodName: string, inner?: Error) { - super(`Function failed: ${methodName}:`, inner); +export class ErrFailedOperation extends Error { + public constructor(methodName: string) { + super(`Function failed: ${methodName}:`); } } -export class ErrContractQuery extends Err { +export class ErrContractQuery extends Error { public constructor(message: string) { super(`Contract query failed: ${message}`); } diff --git a/tests/datanft.test.ts b/tests/datanft.test.ts index 40d8732..da32dd0 100644 --- a/tests/datanft.test.ts +++ b/tests/datanft.test.ts @@ -5,12 +5,10 @@ describe('Data NFT test', () => { test('#test not setting network config', async () => { try { await DataNft.createFromApi({ nonce: 62 }); - } catch (error) { - if (error instanceof Error) { - expect(error.message).toBe( - 'Network configuration is not set. Call setNetworkConfig static method before calling any method that requires network configuration.' - ); - } + } catch (error: any) { + expect(error.message).toBe( + 'Network configuration is not set. Call setNetworkConfig static method before calling any method that requires network configuration.' + ); } }); @@ -26,12 +24,10 @@ describe('Data NFT test', () => { signableMessage: new SignableMessage({ message: Buffer.from('test') }), stream: true }); - } catch (error) { - if (error instanceof Error) { - expect(error.message).toBe( - 'Network configuration is not set. Call setNetworkConfig static method before calling any method that requires network configuration.' - ); - } + } catch (error: any) { + expect(error.message).toBe( + 'Network configuration is not set. Call setNetworkConfig static method before calling any method that requires network configuration.' + ); } }); From 85502362c460442e9d4fe9918a0705b954a28cf6 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Mon, 11 Sep 2023 12:13:21 +0300 Subject: [PATCH 02/32] refactor: implement error handling classes Refs: #25 --- src/datanft.ts | 142 ++++++++++++++++++------------------------ src/errors.ts | 41 ++++++------ src/utils.ts | 7 +++ tests/datanft.test.ts | 15 ++++- 4 files changed, 104 insertions(+), 101 deletions(-) diff --git a/src/datanft.ts b/src/datanft.ts index 15471f4..6dbbe1c 100644 --- a/src/datanft.ts +++ b/src/datanft.ts @@ -11,6 +11,7 @@ import { networkConfiguration } from './config'; import { + checkStatus, createNftIdentifier, numberToPaddedHex, parseDataNft, @@ -18,13 +19,13 @@ import { } from './utils'; import minterAbi from './abis/datanftmint.abi.json'; import { NftType, ViewDataReturnType } from './interfaces'; -// import { -// ErrDataNftCreation, -// ErrDecodeAttributes, -// ErrFailedOperation, -// ErrAttributeNotSet, -// ErrNetworkConfig -// } from './errors'; +import { + ErrAttributeNotSet, + ErrDataNftCreate, + ErrDecodeAttributes, + ErrFetch, + ErrNetworkConfig +} from './errors'; export class DataNft { readonly tokenIdentifier: string = ''; @@ -60,6 +61,11 @@ export class DataNft { * @param env 'devnet' | 'mainnet' | 'testnet' */ static setNetworkConfig(env: string) { + if (env !== 'devnet' && env !== 'mainnet' && env !== 'testnet') { + throw new ErrNetworkConfig( + `Invalid environment: ${env}, Expected: 'devnet' | 'mainnet' | 'testnet'` + ); + } this.env = env; this.networkConfiguration = networkConfiguration[env as EnvironmentsEnum]; this.apiConfiguration = apiConfiguration[env as EnvironmentsEnum]; @@ -82,20 +88,19 @@ export class DataNft { dataNftTokenIdentifier[this.env as EnvironmentsEnum], token.nonce ); + const response = await fetch(`${this.apiConfiguration}/nfts/${identifier}`); + + checkStatus(response); + const data: NftType = await response.json(); try { const dataNft = parseDataNft(data); return dataNft; - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrDataNftCreation(error); - // } else { - // throw ErrDataNftCreation; - // } + } catch (error: any) { + throw new ErrDataNftCreate('Response could not be parsed'); } } @@ -118,24 +123,22 @@ export class DataNft { nonce ) ); - try { - const response = await fetch( - `${this.apiConfiguration}/nfts?identifiers=${identifiers.join( - ',' - )}&withSupply=true` - ); - const data: NftType[] = await response.json(); + const response = await fetch( + `${this.apiConfiguration}/nfts?identifiers=${identifiers.join( + ',' + )}&withSupply=true` + ); + + checkStatus(response); + const data: NftType[] = await response.json(); + + try { const dataNfts = data.map((value) => parseDataNft(value)); return dataNfts; - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrDataNftCreation(error); - // } else { - // throw ErrDataNftCreation; - // } + } catch (error: any) { + throw new ErrDataNftCreate('Response could not be parsed'); } } @@ -160,13 +163,8 @@ export class DataNft { parseNft(payload as NftType); return dataNfts; } - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrDataNftCreation(error); - // } else { - // throw ErrDataNftCreation; - // } + } catch (error: any) { + throw new ErrDataNftCreate('Response could not be parsed'); } } @@ -195,9 +193,8 @@ export class DataNft { description: decodedAttributes['description'].toString(), title: decodedAttributes['title'].toString() }; - } catch (error) { - throw error; - // throw ErrDecodeAttributes; + } catch (error: any) { + throw new ErrDecodeAttributes(error.message); } } @@ -211,22 +208,17 @@ export class DataNft { identifier = dataNftTokenIdentifier[this.env as EnvironmentsEnum] ): Promise { this.ensureNetworkConfigSet(); - try { - const res = await fetch( - `${this.apiConfiguration}/accounts/${address}/nfts?size=10000&collections=${identifier}&withSupply=true` - ); - const data = await res.json(); - const dataNfts: DataNft[] = this.createFromApiResponseOrBulk(data); - return dataNfts; - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation(this.ownedByAddress.name, error); - // } else { - // throw ErrFailedOperation; - // } - } + const res = await fetch( + `${this.apiConfiguration}/accounts/${address}/nfts?size=10000&collections=${identifier}&withSupply=true` + ); + + checkStatus(res); + + const data = await res.json(); + const dataNfts: DataNft[] = this.createFromApiResponseOrBulk(data); + + return dataNfts; } /** @@ -235,28 +227,22 @@ export class DataNft { async getMessageToSign(): Promise { DataNft.ensureNetworkConfigSet(); if (!this.dataMarshal) { - throw new Error('No data marshal set for getMessageToSign'); - // throw new ErrAttributeNotSet('dataMarshal'); - } - try { - const res = await fetch( - `${this.dataMarshal}/preaccess?chainId=${ - DataNft.networkConfiguration.chainID == 'D' - ? 'ED' - : DataNft.networkConfiguration.chainID - }` - ); - const data = await res.json(); - - return data.nonce; - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation(this.getMessageToSign.name, error); - // } else { - // throw ErrFailedOperation; - // } + throw new ErrAttributeNotSet('dataMarshal'); } + + const res = await fetch( + `${this.dataMarshal}/preaccess?chainId=${ + DataNft.networkConfiguration.chainID == 'D' + ? 'ED' + : DataNft.networkConfiguration.chainID + }` + ); + + checkStatus(res); + + const data = await res.json(); + + return data.nonce; } /** @@ -280,8 +266,7 @@ export class DataNft { }): Promise { DataNft.ensureNetworkConfigSet(); if (!this.dataMarshal) { - throw new Error('No data marshal set for viewData'); - // throw new ErrAttributeNotSet('dataMarshal'); + throw new ErrAttributeNotSet('dataMarshal'); } const signResult = { signature: '', @@ -510,10 +495,7 @@ export class DataNft { private static ensureNetworkConfigSet() { if (!this.env || !this.apiConfiguration) { - throw new Error( - 'Network configuration is not set. Call setNetworkConfig static method before calling any method that requires network configuration.' - ); - // throw ErrNetworkConfig; + throw new ErrNetworkConfig(); } } } diff --git a/src/errors.ts b/src/errors.ts index 089cf17..9ca8ee5 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,43 +1,44 @@ export class ErrNetworkConfig extends Error { - public constructor() { + public constructor(message?: string) { super( - 'Network configuration is not set. Call setNetworkConfig static method before calling any method that requires network configuration.' + message || + 'Network configuration is not set. Call setNetworkConfig static method before calling any method that requires network configuration.' ); } } -export class ErrDataNftCreation extends Error { - public constructor() { - super(`Could not create DataNft:`); +export class ErrInvalidArgument extends Error { + public constructor(message: string) { + super(`Invalid argument: ${message}`); } } -export class ErrDecodeAttributes extends Error { - public constructor() { - super('Could not decode attributes'); +export class ErrBadType extends Error { + public constructor(name: string, type: any, value?: any) { + super(`Bad type of "${name}": ${value}. Expected type: ${type}`); } } -export class ErrAttributeNotSet extends Error { - public constructor(attributeName: string) { - super(`Attribute ${attributeName} is not set`); +export class ErrDataNftCreate extends Error { + public constructor(message?: string) { + super(`Could not create Data NFT: ${message}`); } } -export class ErrArgumentNotSet extends Error { - public constructor(argumentName: string, message: string) { - super(`Argument ${argumentName} is not set. ${message}`); +export class ErrFetch extends Error { + public constructor(status: number, message: string) { + super(`Fetch error with status code: ${status} and message: ${message}`); } } -export class ErrFailedOperation extends Error { - public constructor(methodName: string) { - super(`Function failed: ${methodName}:`); +export class ErrDecodeAttributes extends Error { + public constructor(message?: string) { + super(`Could not decode attributes: ${message}`); } } -export class ErrContractQuery extends Error { - public constructor(message: string) { - super(`Contract query failed: ${message}`); +export class ErrAttributeNotSet extends Error { + public constructor(attribute: string) { + super(`Attribute "${attribute}" is not set`); } } diff --git a/src/utils.ts b/src/utils.ts index 4b39857..3c65aa5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ import BigNumber from 'bignumber.js'; import { DataNft } from './datanft'; import { NftEnumType, NftType, Offer } from './interfaces'; +import { ErrFetch } from './errors'; export function numberToPaddedHex(value: BigNumber.Value) { let hex = new BigNumber(value).toString(16); @@ -532,3 +533,9 @@ export async function checkUrlIsUp(url: string, expectedHttpCodes: number[]) { ); } } + +export function checkStatus(response: Response) { + if (response.status != 200) { + throw new ErrFetch(response.status, response.statusText); + } +} diff --git a/tests/datanft.test.ts b/tests/datanft.test.ts index da32dd0..342b1a2 100644 --- a/tests/datanft.test.ts +++ b/tests/datanft.test.ts @@ -31,6 +31,20 @@ describe('Data NFT test', () => { } }); + test('#test bad input on createFromApi', async () => { + try { + DataNft.setNetworkConfig('devnet'); + await DataNft.createFromApi({ + nonce: 62, + tokenIdentifier: 'DATANFTFT3-d0978a' + }); + } catch (error: any) { + expect(error.message).toBe( + 'Fetch error with status code: 404 and message: Not Found' + ); + } + }); + test('#getMessageToSign', async () => { DataNft.setNetworkConfig('devnet'); const dataNft = new DataNft({ @@ -40,7 +54,6 @@ describe('Data NFT test', () => { const nonceToSign = await dataNft.getMessageToSign(); expect(typeof nonceToSign).toBe('string'); - const nft = await DataNft.createFromApi({ nonce: 62, tokenIdentifier: 'DATANFTFT3-d0978e' From 4a61fa4045db930d8d00b353629844e67fdca9de Mon Sep 17 00:00:00 2001 From: Bucur David Date: Mon, 11 Sep 2023 12:51:35 +0300 Subject: [PATCH 03/32] refactor: implement custom error class for marketplace Refs: #25 --- src/datanft.ts | 2 +- src/errors.ts | 10 +++++++-- src/marketplace.ts | 53 +++++++++++++++++++++++++++------------------- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/datanft.ts b/src/datanft.ts index 6dbbe1c..7df3728 100644 --- a/src/datanft.ts +++ b/src/datanft.ts @@ -61,7 +61,7 @@ export class DataNft { * @param env 'devnet' | 'mainnet' | 'testnet' */ static setNetworkConfig(env: string) { - if (env !== 'devnet' && env !== 'mainnet' && env !== 'testnet') { + if (!(env in EnvironmentsEnum)) { throw new ErrNetworkConfig( `Invalid environment: ${env}, Expected: 'devnet' | 'mainnet' | 'testnet'` ); diff --git a/src/errors.ts b/src/errors.ts index 9ca8ee5..1999248 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -21,7 +21,7 @@ export class ErrBadType extends Error { export class ErrDataNftCreate extends Error { public constructor(message?: string) { - super(`Could not create Data NFT: ${message}`); + super(`Failed to create Data NFT: ${message}`); } } @@ -33,7 +33,7 @@ export class ErrFetch extends Error { export class ErrDecodeAttributes extends Error { public constructor(message?: string) { - super(`Could not decode attributes: ${message}`); + super(`Failed to decode attributes: ${message}`); } } @@ -42,3 +42,9 @@ export class ErrAttributeNotSet extends Error { super(`Attribute "${attribute}" is not set`); } } + +export class ErrContractQuery extends Error { + public constructor(method: string, message?: string) { + super(`Failed to query contract: Method: ${method} : ${message}`); + } +} diff --git a/src/marketplace.ts b/src/marketplace.ts index a3c5b38..df29383 100644 --- a/src/marketplace.ts +++ b/src/marketplace.ts @@ -26,7 +26,7 @@ import { import dataMarketAbi from './abis/data_market.abi.json'; import { MarketplaceRequirements, Offer } from './interfaces'; import { parseOffer } from './utils'; -// import { ErrContractQuery } from './errors'; +import { ErrContractQuery, ErrNetworkConfig } from './errors'; export class DataNftMarket { readonly contract: SmartContract; @@ -40,6 +40,11 @@ export class DataNftMarket { * @param timeout Timeout for the network provider (DEFAULT = 10000ms) */ constructor(env: string, timeout: number = 10000) { + if (!(env in EnvironmentsEnum)) { + throw new ErrNetworkConfig( + `Invalid environment: ${env}, Expected: 'devnet' | 'mainnet' | 'testnet'` + ); + } this.env = env; const networkConfig = networkConfiguration[env as EnvironmentsEnum]; this.chainID = networkConfig.chainID; @@ -87,7 +92,10 @@ export class DataNftMarket { ); return offers; } else { - return []; + throw new ErrContractQuery( + 'viewAddressListedOffers', + returnCode.toString() + ); } } @@ -122,7 +130,10 @@ export class DataNftMarket { ); return offers; } else { - return []; + throw new ErrContractQuery( + 'viewAddressPagedOffers', + returnCode.toString() + ); } } @@ -145,7 +156,10 @@ export class DataNftMarket { const returnValue = firstValue?.valueOf(); return returnValue.toNumber(); } else { - return 0; + throw new ErrContractQuery( + 'viewAddressTotalOffers', + returnCode.toString() + ); } } @@ -171,7 +185,10 @@ export class DataNftMarket { ); return offers; } else { - return []; + throw new ErrContractQuery( + 'viewAddressCancelledOffers', + returnCode.toString() + ); } } @@ -199,7 +216,7 @@ export class DataNftMarket { ); return offers; } else { - return []; + throw new ErrContractQuery('viewPagedOffers', returnCode.toString()); } } @@ -248,7 +265,7 @@ export class DataNftMarket { ); return offers; } else { - return []; + throw new ErrContractQuery('viewOffers', returnCode.toString()); } } @@ -281,10 +298,7 @@ export class DataNftMarket { }; return requirements; } else { - throw new Error('Error while retrieving the marketplace requirements'); - // throw new ErrContractQuery( - // 'Error while retrieving the marketplace requirements' - // ); + throw new ErrContractQuery('viewRequirements', returnCode.toString()); } } @@ -304,9 +318,7 @@ export class DataNftMarket { const returnValue = firstValue?.valueOf(); return new U8Value(returnValue).valueOf().toNumber(); } - - throw new Error('Error while retrieving the number of offers'); - // throw new ErrContractQuery('Error while retrieving the number of offers'); + throw new ErrContractQuery('viewNumberOfOffers', returnCode.toString()); } /** @@ -326,10 +338,7 @@ export class DataNftMarket { return new U64Value(returnValue).valueOf().toNumber(); } - throw new Error('Error while retrieving the last valid offer id'); - // throw new ErrContractQuery( - // 'Error while retrieving the last valid offer id' - // ); + throw new ErrContractQuery('viewLastValidOfferId', returnCode.toString()); } /** @@ -348,10 +357,10 @@ export class DataNftMarket { const returnValue = firstValue?.valueOf(); return new BooleanValue(returnValue).valueOf(); } else { - throw new Error('Error while retrieving the contract pause state'); - // throw new ErrContractQuery( - // 'Error while retrieving the contract pause state' - // ); + throw new ErrContractQuery( + 'viewContractPauseState', + returnCode.toString() + ); } } From 5606d14d31482b7557db2a19daebfeee268bdd5f Mon Sep 17 00:00:00 2001 From: Bucur David Date: Mon, 11 Sep 2023 13:17:54 +0300 Subject: [PATCH 04/32] refactor: implement custom error class handle for minter Refs: #25 --- src/errors.ts | 18 +++++++- src/minter.ts | 115 +++++++++++++++++++++++--------------------------- src/utils.ts | 6 +-- 3 files changed, 70 insertions(+), 69 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index 1999248..7d644cc 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -14,8 +14,10 @@ export class ErrInvalidArgument extends Error { } export class ErrBadType extends Error { - public constructor(name: string, type: any, value?: any) { - super(`Bad type of "${name}": ${value}. Expected type: ${type}`); + public constructor(name: string, type: any, value?: any, context?: string) { + super( + `Bad type of "${name}": ${value}. Expected type: ${type}. Context: ${context}` + ); } } @@ -48,3 +50,15 @@ export class ErrContractQuery extends Error { super(`Failed to query contract: Method: ${method} : ${message}`); } } + +export class ErrParamValidation extends Error { + public constructor(message: string) { + super(`Params have validation issues : ${message}`); + } +} + +export class ErrFailedOperation extends Error { + public constructor(method: string, message?: string) { + super(`Failed to perform operation: ${method} : ${message}`); + } +} diff --git a/src/minter.ts b/src/minter.ts index 710b13c..4f979ce 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -28,15 +28,18 @@ import { MinterRequirements } from './interfaces'; import { NFTStorage } from 'nft.storage'; import { File } from '@web-std/file'; import { + checkStatus, checkTraitsUrl, checkUrlIsUp, validateSpecificParamsMint } from './utils'; -// import { -// ErrArgumentNotSet, -// ErrContractQuery, -// ErrFailedOperation -// } from './errors'; +import { + ErrBadType, + ErrContractQuery, + ErrFailedOperation, + ErrNetworkConfig, + ErrParamValidation +} from './errors'; export class DataNftMinter { readonly contract: SmartContract; @@ -51,6 +54,11 @@ export class DataNftMinter { * @param timeout Timeout for the network provider (DEFAULT = 10000ms) */ constructor(env: string, timeout: number = 10000) { + if (!(env in EnvironmentsEnum)) { + throw new ErrNetworkConfig( + `Invalid environment: ${env}, Expected: 'devnet' | 'mainnet' | 'testnet'` + ); + } this.env = env; const networkConfig = networkConfiguration[env as EnvironmentsEnum]; this.imageServiceUrl = imageService[env as EnvironmentsEnum]; @@ -114,8 +122,10 @@ export class DataNftMinter { }; return requirements; } else { - throw new Error('Could not retrieve minter contract requirements'); - // throw new ErrContractQuery('Could not retrieve requirements'); + throw new ErrContractQuery( + 'viewMinterRequirements', + returnCode.toString() + ); } } @@ -135,10 +145,10 @@ export class DataNftMinter { const returnValue = firstValue?.valueOf(); return new BooleanValue(returnValue).valueOf(); } else { - throw new Error('Error while retrieving the contract pause state'); - // throw new ErrContractQuery( - // 'Error while retrieving the contract pause state' - // ); + throw new ErrContractQuery( + 'viewContractPauseState', + returnCode.toString() + ); } } @@ -247,27 +257,15 @@ export class DataNftMinter { }); if (!allPassed) { - throw new Error(`Params have validation issues = ${validationMessages}`); - // throw new ErrFailedOperation( - // this.mint.name, - // new Error(`params have validation issues = ${validationMessages}`) - // ); + throw new ErrParamValidation(validationMessages); } // E: run any format specific validation... // deep validate all mandatory URLs - try { - await checkUrlIsUp(dataStreamUrl, [200, 403]); - await checkUrlIsUp(dataPreviewUrl, [200]); - await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation(this.mint.name, error); - // } else { - // throw new ErrFailedOperation(this.mint.name); - // } - } + + await checkUrlIsUp(dataStreamUrl, [200, 403]); + await checkUrlIsUp(dataPreviewUrl, [200]); + await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); let imageOnIpfsUrl: string; let metadataOnIpfsUrl: string; @@ -277,13 +275,12 @@ export class DataNftMinter { if (!imageUrl) { if (!nftStorageToken) { - throw new Error( + throw new ErrBadType( + 'nftStorageToken', + 'string', + nftStorageToken, 'NFT Storage token is required when not using custom image and traits' ); - // throw new ErrArgumentNotSet( - // 'nftStorageToken', - // 'NFT Storage token is required when not using custom image and traits' - // ); } const { image, traits } = await this.createFileFromUrl( `${this.imageServiceUrl}/v1/generateNFTArt?hash=${dataNftHash}`, @@ -302,11 +299,12 @@ export class DataNftMinter { metadataOnIpfsUrl = metadataIpfsUrl; } else { if (!traitsUrl) { - throw new Error('Traits URL is required when using custom image'); - // throw new ErrArgumentNotSet( - // 'traitsUrl', - // 'Traits URL is required when using custom image' - // ); + throw new ErrBadType( + 'traitsUrl', + 'string', + traitsUrl, + 'Traits URL is required when using custom image' + ); } await checkTraitsUrl(traitsUrl); @@ -374,29 +372,21 @@ export class DataNftMinter { body: JSON.stringify({ dataNFTStreamUrl }) }; - try { - const res = await fetch(`${dataMarshalUrl}/generate`, requestOptions); - const data = await res.json(); + const res = await fetch(`${dataMarshalUrl}/generate`, requestOptions); + const data = await res.json(); - if (data && data.encryptedMessage && data.messageHash) { - return { - dataNftHash: data.messageHash, - dataNftStreamUrlEncrypted: data.encryptedMessage - }; - } else { - throw new Error('Issue with data marshal generate payload'); - // throw new ErrFailedOperation(this.dataNFTDataStreamAdvertise.name); - } - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation( - // this.dataNFTDataStreamAdvertise.name, - // error - // ); - // } else { - // throw new ErrFailedOperation(this.dataNFTDataStreamAdvertise.name); - // } + checkStatus(res); + + if (data && data.encryptedMessage && data.messageHash) { + return { + dataNftHash: data.messageHash, + dataNftStreamUrlEncrypted: data.encryptedMessage + }; + } else { + throw new ErrFailedOperation( + 'dataNFTDataStreamAdvertise', + 'Invalid response from Data Marshal' + ); } } @@ -412,9 +402,8 @@ export class DataNftMinter { }); const dir = [image, traits]; res = await nftstorage.storeDirectory(dir); - } catch (error) { - throw error; - // throw new ErrFailedOperation(this.storeToIpfs.name); + } catch (error: any) { + throw new ErrFailedOperation('storeToIpfs', error.message); } return { imageOnIpfsUrl: `https://ipfs.io/ipfs/${res}/image.png`, diff --git a/src/utils.ts b/src/utils.ts index 3c65aa5..35fbb49 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -528,14 +528,12 @@ export async function checkUrlIsUp(url: string, expectedHttpCodes: number[]) { const response = await fetch(url); if (!expectedHttpCodes.includes(response.status)) { - throw new Error( - `URL needs to return a 200 OK response code (or a 403 Forbidden error code is also allowed for protected Data Streams). url : ${url}, actual HTTP status: ${response.status}` - ); + throw new ErrFetch(response.status, response.statusText); } } export function checkStatus(response: Response) { - if (response.status != 200) { + if (response.status >= 200 && response.status <= 299) { throw new ErrFetch(response.status, response.statusText); } } From 0255cbe10782c92c0ce7978dd2204457e30f2c52 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Mon, 11 Sep 2023 13:41:43 +0300 Subject: [PATCH 05/32] refactor: implement error handles for common methods Refs: #25 --- src/errors.ts | 12 ++++++++++++ src/utils.ts | 20 ++++++++++++++------ tests/traits-check.test.ts | 4 ++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index 7d644cc..1ec6864 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -62,3 +62,15 @@ export class ErrFailedOperation extends Error { super(`Failed to perform operation: ${method} : ${message}`); } } + +export class ErrMissingTrait extends Error { + public constructor(trait: string) { + super(`Missing trait: ${trait}`); + } +} + +export class ErrMissingValueForTrait extends Error { + public constructor(trait: string) { + super(`Missing value for trait: ${trait}`); + } +} diff --git a/src/utils.ts b/src/utils.ts index 35fbb49..959034a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,13 @@ import BigNumber from 'bignumber.js'; import { DataNft } from './datanft'; import { NftEnumType, NftType, Offer } from './interfaces'; -import { ErrFetch } from './errors'; +import { + ErrBadType, + ErrFetch, + ErrInvalidArgument, + ErrMissingTrait, + ErrMissingValueForTrait +} from './errors'; export function numberToPaddedHex(value: BigNumber.Value) { let hex = new BigNumber(value).toString(16); @@ -64,12 +70,14 @@ export async function checkTraitsUrl(traitsUrl: string) { const traitsResponse = await fetch(traitsUrl); const traits = await traitsResponse.json(); + checkStatus(traitsResponse); + if (!traits.description) { - throw new Error('Traits description is empty'); + throw new ErrMissingTrait(traits.description); } if (!Array.isArray(traits.attributes)) { - throw new Error('Traits attributes must be an array'); + throw new ErrMissingTrait(traits.attributes); } const requiredTraits = ['Creator', 'Data Preview URL']; @@ -81,13 +89,13 @@ export async function checkTraitsUrl(traitsUrl: string) { (attribute: any) => attribute.trait_type === requiredTrait ) ) { - throw new Error(`Missing required trait: ${requiredTrait}`); + throw new ErrMissingTrait(requiredTrait); } } for (const attribute of traitsAttributes) { if (!attribute.value) { - throw new Error(`Empty value for trait: ${attribute.trait_type}`); + throw new ErrMissingValueForTrait(attribute.trait_type); } } } @@ -533,7 +541,7 @@ export async function checkUrlIsUp(url: string, expectedHttpCodes: number[]) { } export function checkStatus(response: Response) { - if (response.status >= 200 && response.status <= 299) { + if (!(response.status >= 200 && response.status <= 299)) { throw new ErrFetch(response.status, response.statusText); } } diff --git a/tests/traits-check.test.ts b/tests/traits-check.test.ts index 4f40f59..7f0d8c9 100644 --- a/tests/traits-check.test.ts +++ b/tests/traits-check.test.ts @@ -15,8 +15,8 @@ describe('Traits strucutre test', () => { await checkTraitsUrl( 'https://ipfs.io/ipfs/bafybeicbmpiehja5rjk425ol4rmrorrg5xh62vcbeqigv3zjcrfk4rtggm/metadata.json' ); - } catch (error) { - expect(error).toStrictEqual(Error('Missing required trait: Creator')); + } catch (error: any) { + expect(error.message).toBe('Missing trait: Creator'); } }, 100000); }); From 3a6e3d527ad316dad6ccd89a8b864d503c30f1c7 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 13:17:45 +0300 Subject: [PATCH 06/32] fix: royalties should have two decimals Refs: #47 --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 354e4b8..a9c759d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -471,7 +471,7 @@ export function validateSpecificParamsMint(params: { typeof params.royalties === 'number' && !(params.royalties % 1 != 0) && // modulus checking. (10 % 1 != 0) EQ false, (10.5 % 1 != 0) EQ true, params.royalties >= 0 && - params.royalties <= 50 + params.royalties <= 5000 ) { royaltiesValid = true; } else { From 570d4f63f9ae1b54398d16ab69a0ee81a50489ad Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 13:28:50 +0300 Subject: [PATCH 07/32] chore: update dependecies Refs: #40 --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index b2facad..7889621 100644 --- a/package.json +++ b/package.json @@ -16,17 +16,17 @@ "author": "Itheum Protocol", "license": "GPL-3.0-only", "dependencies": { - "@multiversx/sdk-core": "12.6.0", - "@multiversx/sdk-network-providers": "1.5.0", - "bignumber.js": "^9.1.1", - "nft.storage": "^7.1.1" + "@multiversx/sdk-core": "12.8.0", + "@multiversx/sdk-network-providers": "2.0.0", + "bignumber.js": "9.1.2", + "nft.storage": "7.1.1" }, "devDependencies": { - "@types/jest": "^29.5.1", - "jest": "^29.5.0", - "ts-jest": "^29.1.0", - "tslint": "^6.1.3", - "typescript": "^5.0.4" + "@types/jest": "29.5.4", + "jest": "29.7.0", + "ts-jest": "29.1.1", + "tslint": "6.1.3", + "typescript": "5.2.2" }, "repository": { "type": "git", From c5639d5345ce43437632967dd331c6076386cd9e Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 14:58:51 +0300 Subject: [PATCH 08/32] refactor!: implement minter base class BREAKING CHANGE: NftMinter and SftMinter will inherit from Minter class. DataNftMinter -> SftMinter Refs: #37 --- src/index.ts | 2 ++ src/minter.ts | 54 +++++----------------------------------- src/nft-minter.ts | 30 ++++++++++++++++++++++ src/sft-minter.ts | 63 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 48 deletions(-) create mode 100644 src/nft-minter.ts create mode 100644 src/sft-minter.ts diff --git a/src/index.ts b/src/index.ts index 875bff2..82ea680 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,3 +5,5 @@ export * from './datanft'; export * from './utils'; export * from './utils'; export * from './minter'; +export * from './nft-minter'; +export * from './sft-minter'; diff --git a/src/minter.ts b/src/minter.ts index 7498fb8..e85f786 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -20,7 +20,6 @@ import { dataNftTokenIdentifier, imageService, itheumTokenIdentifier, - minterContractAddress, networkConfiguration } from './config'; import dataNftMintAbi from './abis/datanftmint.abi.json'; @@ -37,7 +36,7 @@ import { // ErrFailedOperation // } from './errors'; -export class DataNftMinter { +export class Minter { readonly contract: SmartContract; readonly chainID: string; readonly networkProvider: ApiNetworkProvider; @@ -49,7 +48,11 @@ export class DataNftMinter { * @param env 'devnet' | 'mainnet' | 'testnet' * @param timeout Timeout for the network provider (DEFAULT = 10000ms) */ - constructor(env: string, timeout: number = 10000) { + protected constructor( + env: string, + contractAddress: string, + timeout: number = 10000 + ) { this.env = env; const networkConfig = networkConfiguration[env as EnvironmentsEnum]; this.imageServiceUrl = imageService[env as EnvironmentsEnum]; @@ -60,7 +63,6 @@ export class DataNftMinter { timeout: timeout } ); - const contractAddress = minterContractAddress[env as EnvironmentsEnum]; this.contract = new SmartContract({ address: new Address(contractAddress), abi: AbiRegistry.create(dataNftMintAbi) @@ -74,50 +76,6 @@ export class DataNftMinter { return this.contract.getAddress(); } - /** - * Retrieves the minter smart contract requirements for the given user - * @param address the address of the user - * @param taxToken the tax token to be used for the minting (default = `ITHEUM` token identifier based on the {@link EnvironmentsEnum}) - */ - async viewMinterRequirements( - address: IAddress, - taxToken = itheumTokenIdentifier[this.env as EnvironmentsEnum] - ): Promise { - const interaction = this.contract.methodsExplicit.getUserDataOut([ - new AddressValue(address), - new TokenIdentifierValue(taxToken) - ]); - const query = interaction.buildQuery(); - const queryResponse = await this.networkProvider.queryContract(query); - const endpointDefinition = interaction.getEndpoint(); - const { firstValue, returnCode } = new ResultsParser().parseQueryResponse( - queryResponse, - endpointDefinition - ); - if (returnCode.isSuccess()) { - const returnValue = firstValue?.valueOf(); - const requirements: MinterRequirements = { - antiSpamTaxValue: returnValue.anti_spam_tax_value.toNumber(), - contractPaused: returnValue.is_paused, - maxRoyalties: returnValue.max_royalties.toNumber(), - minRoyalties: returnValue.min_royalties.toNumber(), - maxSupply: returnValue.max_supply.toNumber(), - mintTimeLimit: returnValue.mint_time_limit.toNumber(), - lastUserMintTime: returnValue.last_mint_time, - userWhitelistedForMint: returnValue.is_whitelisted, - contractWhitelistEnabled: returnValue.whitelist_enabled, - numberOfMintsForUser: returnValue.minted_per_user.toNumber(), - totalNumberOfMints: returnValue.total_minted.toNumber(), - addressFrozen: returnValue.frozen, - frozenNonces: returnValue.frozen_nonces.map((v: any) => v.toNumber()) - }; - return requirements; - } else { - throw new Error('Could not retrieve minter contract requirements'); - // throw new ErrContractQuery('Could not retrieve requirements'); - } - } - /** * Retrieves the smart contract pause state */ diff --git a/src/nft-minter.ts b/src/nft-minter.ts new file mode 100644 index 0000000..9618708 --- /dev/null +++ b/src/nft-minter.ts @@ -0,0 +1,30 @@ +import { + BooleanValue, + ContractCallPayloadBuilder, + ContractFunction, + IAddress, + Transaction +} from '@multiversx/sdk-core/out'; +import { Minter } from './minter'; + +export class NftMinter extends Minter { + constructor(env: string, contractAddress: string, timeout: number = 10000) { + super(env, contractAddress, timeout); + } + + pauseContract(senderAddress: IAddress, state: boolean): Transaction { + const pauseContractTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setIsPaused')) + .addArg(new BooleanValue(state)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return pauseContractTx; + } +} diff --git a/src/sft-minter.ts b/src/sft-minter.ts new file mode 100644 index 0000000..b982130 --- /dev/null +++ b/src/sft-minter.ts @@ -0,0 +1,63 @@ +import { + AddressValue, + IAddress, + ResultsParser, + TokenIdentifierValue +} from '@multiversx/sdk-core/out'; +import { Minter } from './minter'; +import { + EnvironmentsEnum, + itheumTokenIdentifier, + minterContractAddress +} from './config'; +import { MinterRequirements } from './interfaces'; + +export class SftMinter extends Minter { + constructor(env: string, timeout: number = 10000) { + super(env, minterContractAddress[env as EnvironmentsEnum], timeout); + } + + /** + * Retrieves the minter smart contract requirements for the given user + * @param address the address of the user + * @param taxToken the tax token to be used for the minting (default = `ITHEUM` token identifier based on the {@link EnvironmentsEnum}) + */ + async viewMinterRequirements( + address: IAddress, + taxToken = itheumTokenIdentifier[this.env as EnvironmentsEnum] + ): Promise { + const interaction = this.contract.methodsExplicit.getUserDataOut([ + new AddressValue(address), + new TokenIdentifierValue(taxToken) + ]); + const query = interaction.buildQuery(); + const queryResponse = await this.networkProvider.queryContract(query); + const endpointDefinition = interaction.getEndpoint(); + const { firstValue, returnCode } = new ResultsParser().parseQueryResponse( + queryResponse, + endpointDefinition + ); + if (returnCode.isSuccess()) { + const returnValue = firstValue?.valueOf(); + const requirements: MinterRequirements = { + antiSpamTaxValue: returnValue.anti_spam_tax_value.toNumber(), + contractPaused: returnValue.is_paused, + maxRoyalties: returnValue.max_royalties.toNumber(), + minRoyalties: returnValue.min_royalties.toNumber(), + maxSupply: returnValue.max_supply.toNumber(), + mintTimeLimit: returnValue.mint_time_limit.toNumber(), + lastUserMintTime: returnValue.last_mint_time, + userWhitelistedForMint: returnValue.is_whitelisted, + contractWhitelistEnabled: returnValue.whitelist_enabled, + numberOfMintsForUser: returnValue.minted_per_user.toNumber(), + totalNumberOfMints: returnValue.total_minted.toNumber(), + addressFrozen: returnValue.frozen, + frozenNonces: returnValue.frozen_nonces.map((v: any) => v.toNumber()) + }; + return requirements; + } else { + throw new Error('Could not retrieve minter contract requirements'); + // throw new ErrContractQuery('Could not retrieve requirements'); + } + } +} From 53938ef1fa5d0a0a5438fafad8c300c061f2d10b Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 15:06:48 +0300 Subject: [PATCH 09/32] feat: pause and unpause transactions implementation Refs: #37 --- src/abis/nftenterprise.abi.json | 1049 +++++++++++++++++++++++++++++++ src/nft-minter.ts | 26 +- 2 files changed, 1073 insertions(+), 2 deletions(-) create mode 100644 src/abis/nftenterprise.abi.json diff --git a/src/abis/nftenterprise.abi.json b/src/abis/nftenterprise.abi.json new file mode 100644 index 0000000..e266554 --- /dev/null +++ b/src/abis/nftenterprise.abi.json @@ -0,0 +1,1049 @@ +{ + "buildInfo": { + "rustc": { + "version": "1.71.0-nightly", + "commitHash": "7f94b314cead7059a71a265a8b64905ef2511796", + "commitDate": "2023-04-23", + "channel": "Nightly", + "short": "rustc 1.71.0-nightly (7f94b314c 2023-04-23)" + }, + "contractCrate": { + "name": "nftenterprise", + "version": "1.0.0" + }, + "framework": { + "name": "multiversx-sc", + "version": "0.43.2" + } + }, + "name": "DataNftMint", + "constructor": { + "inputs": [ + { + "name": "administrator", + "type": "optional
", + "multi_arg": true + } + ], + "outputs": [] + }, + "endpoints": [ + { + "name": "initializeContract", + "mutability": "mutable", + "payableInTokens": [ + "EGLD" + ], + "inputs": [ + { + "name": "collection_name", + "type": "bytes" + }, + { + "name": "token_ticker", + "type": "bytes" + }, + { + "name": "mint_time_limit", + "type": "u64" + }, + { + "name": "require_mint_tax", + "type": "bool" + }, + { + "name": "payment", + "type": "optional>", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "setLocalRoles", + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "updateAttributes", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ + { + "name": "attributes", + "type": "DataNftAttributes" + } + ], + "outputs": [] + }, + { + "name": "setTransferRole", + "mutability": "mutable", + "inputs": [ + { + "name": "address", + "type": "Address" + } + ], + "outputs": [] + }, + { + "name": "unsetTransferRole", + "mutability": "mutable", + "inputs": [ + { + "name": "address", + "type": "Address" + } + ], + "outputs": [] + }, + { + "name": "mint", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ + { + "name": "name", + "type": "bytes" + }, + { + "name": "media", + "type": "bytes" + }, + { + "name": "metadata", + "type": "bytes" + }, + { + "name": "data_marshal", + "type": "bytes" + }, + { + "name": "data_stream", + "type": "bytes" + }, + { + "name": "data_preview", + "type": "bytes" + }, + { + "name": "royalties", + "type": "BigUint" + }, + { + "name": "title", + "type": "bytes" + }, + { + "name": "description", + "type": "bytes" + } + ], + "outputs": [ + { + "type": "DataNftAttributes" + } + ] + }, + { + "name": "burn", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [], + "outputs": [] + }, + { + "name": "setIsPaused", + "mutability": "mutable", + "inputs": [ + { + "name": "is_paused", + "type": "bool" + } + ], + "outputs": [] + }, + { + "name": "setAntiSpamTax", + "mutability": "mutable", + "inputs": [ + { + "name": "token_id", + "type": "EgldOrEsdtTokenIdentifier" + }, + { + "name": "tax", + "type": "BigUint" + } + ], + "outputs": [] + }, + { + "name": "setTaxIsRequired", + "mutability": "mutable", + "inputs": [ + { + "name": "is_required", + "type": "bool" + } + ], + "outputs": [] + }, + { + "name": "setWhiteListEnabled", + "mutability": "mutable", + "inputs": [ + { + "name": "is_enabled", + "type": "bool" + } + ], + "outputs": [] + }, + { + "name": "setWhiteListSpots", + "mutability": "mutable", + "inputs": [ + { + "name": "whitelist", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "removeWhiteListSpots", + "mutability": "mutable", + "inputs": [ + { + "name": "whitelist", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "setMintTimeLimit", + "mutability": "mutable", + "inputs": [ + { + "name": "mint_time_limit", + "type": "u64" + } + ], + "outputs": [] + }, + { + "name": "setRoyaltiesLimits", + "mutability": "mutable", + "inputs": [ + { + "name": "min_royalties", + "type": "BigUint" + }, + { + "name": "max_royalties", + "type": "BigUint" + } + ], + "outputs": [] + }, + { + "name": "setAdministrator", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "administrator", + "type": "Address" + } + ], + "outputs": [] + }, + { + "name": "setTax", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "itheum_address", + "type": "Address" + }, + { + "name": "tax", + "type": "BigUint" + } + ], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "setClaimsAddress", + "mutability": "mutable", + "inputs": [ + { + "name": "address", + "type": "Address" + } + ], + "outputs": [] + }, + { + "name": "claimRoyalties", + "mutability": "mutable", + "inputs": [ + { + "name": "token_identifier", + "type": "EgldOrEsdtTokenIdentifier" + }, + { + "name": "nonce", + "type": "u64" + } + ], + "outputs": [] + }, + { + "name": "getTokenId", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "TokenIdentifier" + } + ] + }, + { + "name": "getMintedTokens", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "getMintTax", + "mutability": "readonly", + "inputs": [ + { + "name": "token", + "type": "EgldOrEsdtTokenIdentifier" + } + ], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "getTaxIsRequired", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "name": "getIsPaused", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "name": "getMaxRoyalties", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "getMinRoyalties", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "getMintedPerAddress", + "mutability": "readonly", + "inputs": [ + { + "name": "address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "mintTimeLimit", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "lastMintTime", + "mutability": "readonly", + "inputs": [ + { + "name": "address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "name": "getWhiteList", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ] + }, + { + "name": "getCollectionFrozenList", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ] + }, + { + "name": "getSftsFrozenForAddress", + "mutability": "readonly", + "inputs": [ + { + "name": "address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] + }, + { + "name": "getFrozenCount", + "mutability": "readonly", + "inputs": [ + { + "name": "address", + "type": "Address" + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "isWhiteListEnabled", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "name": "rolesAreSet", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "name": "getAddressesWithTransferRole", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ] + }, + { + "name": "getAddressesWithUpdateAttributesRole", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ] + }, + { + "name": "getAdministrator", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "Address" + } + ] + }, + { + "name": "getItheumTax", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "name": "getItheumTaxAddress", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "Address" + } + ] + }, + { + "name": "getClaimsAddress", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "Address" + } + ] + }, + { + "name": "getUserDataOut", + "mutability": "readonly", + "inputs": [ + { + "name": "address", + "type": "Address" + }, + { + "name": "tax_token", + "type": "EgldOrEsdtTokenIdentifier" + } + ], + "outputs": [ + { + "type": "UserDataOut" + } + ] + }, + { + "name": "pause", + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "unpause", + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "freeze", + "mutability": "mutable", + "inputs": [ + { + "name": "address", + "type": "Address" + } + ], + "outputs": [] + }, + { + "name": "unfreeze", + "mutability": "mutable", + "inputs": [ + { + "name": "address", + "type": "Address" + } + ], + "outputs": [] + }, + { + "name": "freezeSingleNFT", + "mutability": "mutable", + "inputs": [ + { + "name": "nonce", + "type": "u64" + }, + { + "name": "address", + "type": "Address" + } + ], + "outputs": [] + }, + { + "name": "unFreezeSingleNFT", + "mutability": "mutable", + "inputs": [ + { + "name": "nonce", + "type": "u64" + }, + { + "name": "address", + "type": "Address" + } + ], + "outputs": [] + }, + { + "name": "wipeSingleNFT", + "mutability": "mutable", + "inputs": [ + { + "name": "nonce", + "type": "u64" + }, + { + "name": "address", + "type": "Address" + } + ], + "outputs": [] + } + ], + "events": [ + { + "identifier": "mintPauseToggle", + "inputs": [ + { + "name": "pause_value", + "type": "bool", + "indexed": true + } + ] + }, + { + "identifier": "setTreasuryAddress", + "inputs": [ + { + "name": "treasury_address", + "type": "Address", + "indexed": true + } + ] + }, + { + "identifier": "whitelistEnableToggle", + "inputs": [ + { + "name": "enable_value", + "type": "bool", + "indexed": true + } + ] + }, + { + "identifier": "whitelistSpotSet", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + } + ] + }, + { + "identifier": "collectionFreezeListSpotSet", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + } + ] + }, + { + "identifier": "frozenSftsPerAddress", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + }, + { + "name": "nonce", + "type": "u64", + "indexed": true + } + ] + }, + { + "identifier": "unfrozenSftsPerAddress", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + }, + { + "name": "nonce", + "type": "u64", + "indexed": true + } + ] + }, + { + "identifier": "collectionFreezeListRemoved", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + } + ] + }, + { + "identifier": "whitelistSpotRemoved", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + } + ] + }, + { + "identifier": "setRoyaltiesLimits", + "inputs": [ + { + "name": "min_royalties", + "type": "BigUint", + "indexed": true + }, + { + "name": "max_royalties", + "type": "BigUint", + "indexed": true + } + ] + }, + { + "identifier": "antiSpamTaxSet", + "inputs": [ + { + "name": "token", + "type": "EgldOrEsdtTokenIdentifier", + "indexed": true + }, + { + "name": "amount", + "type": "BigUint", + "indexed": true + } + ] + }, + { + "identifier": "taxIsRequired", + "inputs": [ + { + "name": "tax_is_required", + "type": "bool", + "indexed": true + } + ] + }, + { + "identifier": "mintTimeLimitSet", + "inputs": [ + { + "name": "mint_time_limit", + "type": "u64", + "indexed": true + } + ] + }, + { + "identifier": "setAdministrator", + "inputs": [ + { + "name": "administrator", + "type": "Address", + "indexed": true + } + ] + }, + { + "identifier": "pauseCollection", + "inputs": [ + { + "name": "token_identifier", + "type": "TokenIdentifier", + "indexed": true + } + ] + }, + { + "identifier": "unpauseCollection", + "inputs": [ + { + "name": "token_identifier", + "type": "TokenIdentifier", + "indexed": true + } + ] + }, + { + "identifier": "freeze", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + }, + { + "name": "token_identifier", + "type": "TokenIdentifier", + "indexed": true + }, + { + "name": "nonce", + "type": "u64", + "indexed": true + } + ] + }, + { + "identifier": "unfreeze", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + }, + { + "name": "token_identifier", + "type": "TokenIdentifier", + "indexed": true + }, + { + "name": "nonce", + "type": "u64", + "indexed": true + } + ] + }, + { + "identifier": "wipe", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + }, + { + "name": "token_identifier", + "type": "TokenIdentifier", + "indexed": true + }, + { + "name": "nonce", + "type": "u64", + "indexed": true + } + ] + }, + { + "identifier": "burn", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + }, + { + "name": "token_identifier", + "type": "TokenIdentifier", + "indexed": true + }, + { + "name": "nonce", + "type": "u64", + "indexed": true + }, + { + "name": "amount", + "type": "BigUint", + "indexed": true + } + ] + }, + { + "identifier": "mint", + "inputs": [ + { + "name": "address", + "type": "Address", + "indexed": true + }, + { + "name": "amount", + "type": "BigUint", + "indexed": true + }, + { + "name": "token", + "type": "EgldOrEsdtTokenIdentifier", + "indexed": true + }, + { + "name": "price", + "type": "BigUint", + "indexed": true + } + ] + } + ], + "hasCallback": true, + "types": { + "DataNftAttributes": { + "type": "struct", + "fields": [ + { + "name": "data_stream_url", + "type": "bytes" + }, + { + "name": "data_preview_url", + "type": "bytes" + }, + { + "name": "data_marshal_url", + "type": "bytes" + }, + { + "name": "creator", + "type": "Address" + }, + { + "name": "creation_time", + "type": "u64" + }, + { + "name": "title", + "type": "bytes" + }, + { + "name": "description", + "type": "bytes" + } + ] + }, + "UserDataOut": { + "type": "struct", + "fields": [ + { + "name": "anti_spam_tax_value", + "type": "BigUint" + }, + { + "name": "is_paused", + "type": "bool" + }, + { + "name": "max_royalties", + "type": "BigUint" + }, + { + "name": "min_royalties", + "type": "BigUint" + }, + { + "name": "mint_time_limit", + "type": "u64" + }, + { + "name": "last_mint_time", + "type": "u64" + }, + { + "name": "whitelist_enabled", + "type": "bool" + }, + { + "name": "is_whitelisted", + "type": "bool" + }, + { + "name": "minted_per_user", + "type": "BigUint" + }, + { + "name": "total_minted", + "type": "BigUint" + }, + { + "name": "frozen", + "type": "bool" + }, + { + "name": "frozen_nonces", + "type": "List" + } + ] + } + } +} diff --git a/src/nft-minter.ts b/src/nft-minter.ts index 9618708..6f92895 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -12,12 +12,15 @@ export class NftMinter extends Minter { super(env, contractAddress, timeout); } - pauseContract(senderAddress: IAddress, state: boolean): Transaction { + /** Creates a pause transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + */ + pauseContract(senderAddress: IAddress): Transaction { const pauseContractTx = new Transaction({ value: 0, data: new ContractCallPayloadBuilder() .setFunction(new ContractFunction('setIsPaused')) - .addArg(new BooleanValue(state)) + .addArg(new BooleanValue(true)) .build(), receiver: this.contract.getAddress(), gasLimit: 10000000, @@ -27,4 +30,23 @@ export class NftMinter extends Minter { return pauseContractTx; } + + /** Creates a unpause transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + */ + unpauseContract(senderAddress: IAddress): Transaction { + const unpauseContractTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setIsPaused')) + .addArg(new BooleanValue(false)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return unpauseContractTx; + } } From 793f1e144cee2b95256918be160c675c4661480a Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 15:43:32 +0300 Subject: [PATCH 10/32] feat: nft minter contract admin endpoints implementation Refs: #37 --- src/nft-minter.ts | 298 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) diff --git a/src/nft-minter.ts b/src/nft-minter.ts index 6f92895..f9a5bb9 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -1,8 +1,12 @@ import { + Address, + AddressValue, + BigUIntValue, BooleanValue, ContractCallPayloadBuilder, ContractFunction, IAddress, + TokenIdentifierValue, Transaction } from '@multiversx/sdk-core/out'; import { Minter } from './minter'; @@ -12,6 +16,91 @@ export class NftMinter extends Minter { super(env, contractAddress, timeout); } + /** + * Creates a setLocalRoles transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + */ + setLocalRoles(senderAddress: IAddress): Transaction { + const setLocalRolesTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setLocalRoles')) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setLocalRolesTx; + } + + /** + * Creates a setTransferRoles transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param address The address to set the transfer roles + */ + setTransferRole(senderAddress: IAddress, address: IAddress): Transaction { + const setTransferRolesTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setTransferRole')) + .addArg(new AddressValue(address)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setTransferRolesTx; + } + + /** + * Creates an initialize contract transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param collectionName The name of the NFT collection + * @param tokenTicker The ticker of the NFT collection + * @param mintLimit(seconds)- The mint limit between mints + * @param requireMintTax - A boolean value to set if the mint tax is required or not + * @param options - If `requireMintTax` is true, the `options` object must contain the `taxTokenIdentifier` and `taxTokenAmount` + */ + initializeContract( + senderAddress: IAddress, + collectionName: string, + tokenTicker: string, + mintLimit: number, + requireMintTax: boolean, + options: { + taxTokenIdentifier: string; + taxTokenAmount: number; + } + ) { + // TODO implement + } + + updateAttributes() { + // TODO implement + } + + /** + * Creates an unsetTransferRoles transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param address The address to unset the transfer roles + */ + unsetTransferRole(senderAddress: IAddress, address: IAddress): Transaction { + const unsetTransferRolesTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('unsetTransferRole')) + .addArg(new AddressValue(address)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return unsetTransferRolesTx; + } + /** Creates a pause transaction for the contract * @param senderAddress The address of the sender, must be the admin of the contract */ @@ -49,4 +138,213 @@ export class NftMinter extends Minter { return unpauseContractTx; } + + /** Creates a set mint tax transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param is_required A boolean value to set if the mint tax is required or not + */ + setMintTaxIsRequired( + senderAddress: IAddress, + is_required: boolean + ): Transaction { + const setMintTaxIsRequiredTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setTaxIsRequired')) + .addArg(new BooleanValue(is_required)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return setMintTaxIsRequiredTx; + } + + /** Creates a set mint tax transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param tokenIdentifier The token identifier of the token to set the mint tax + * @param tax The tax to set for the token + */ + setMintTax( + senderAddress: IAddress, + tokenIdentifier: string, + tax: number + ): Transaction { + const setMintTaxTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setAntiSpamTax')) + .addArg(new TokenIdentifierValue(tokenIdentifier)) + .addArg(new BigUIntValue(tax)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setMintTaxTx; + } + + /** Creates a set mint tax transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param is_enabled A boolean value to set if whitelist is enabled or not + */ + setWhitelistIsEnabled( + senderAddress: IAddress, + is_enabled: boolean + ): Transaction { + const setWhitelistIsEnabledTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setWhiteListEnabled')) + .addArg(new BooleanValue(is_enabled)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setWhitelistIsEnabledTx; + } + + /** Creates a whitelist transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param addresses The addresses to whitelist + */ + + whitelist( + senderAddress: IAddress, + addresses: string[], + gasLimit = 0 + ): Transaction { + const whitelistTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setWhiteListSpots')) + .setArgs( + addresses.map((address) => new AddressValue(new Address(address))) + ) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000 + gasLimit, + sender: senderAddress, + chainID: this.chainID + }); + return whitelistTx; + } + + /** Creates a delist transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param addresses The addresses to delist + */ + delist( + senderAddress: IAddress, + addresses: string[], + gasLimit: 0 + ): Transaction { + const delistTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('delist')) + .setArgs( + addresses.map((address) => new AddressValue(new Address(address))) + ) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000 + gasLimit, + sender: senderAddress, + chainID: this.chainID + }); + return delistTx; + } + + /** Creates a set mint time limit transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param timeLimit(seconds) The time limit to set between mints + */ + setMintTimeLimit(senderAddress: IAddress, timeLimit: number): Transaction { + const setMintTimeLimitTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setMintTimeLimit')) + .addArg(new BigUIntValue(timeLimit)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setMintTimeLimitTx; + } + + /** Sets a new administrator for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param newAdministrator The address of the new administrator + */ + setAdministrator( + senderAddress: IAddress, + newAdministrator: IAddress + ): Transaction { + const setAdministratorTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setAdministrator')) + .addArg(new AddressValue(newAdministrator)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setAdministratorTx; + } + + /** Sets the claim address for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param claimsAddress The claims address + */ + setClaimsAddress( + senderAddress: IAddress, + claimsAddress: IAddress + ): Transaction { + const setClaimsAddressTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setClaimsAddress')) + .addArg(new AddressValue(claimsAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setClaimsAddressTx; + } + + /** Creates a claim royalties transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param tokenIdentifier The token identifier of the token to claim royalties + * @param nonce The nonce of the token to claim royalties (default: 0 for ESDT) + */ + claimRoyalties( + senderAddress: IAddress, + tokenIdentifier: string, + nonce = 0 + ): Transaction { + const claimRoyaltiesTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('claimRoyalties')) + .addArg(new TokenIdentifierValue(tokenIdentifier)) + .addArg(new BigUIntValue(nonce)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return claimRoyaltiesTx; + } } From 71b69cf5bf3914f16bf6e98e98dab05fe853b9d5 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 19:19:14 +0300 Subject: [PATCH 11/32] feat: updateAttributes && initializeContract methods implementation Refs: #37 --- src/abis/nftenterprise.abi.json | 24 +++++- src/nft-minter.ts | 129 +++++++++++++++++++++++++------- 2 files changed, 123 insertions(+), 30 deletions(-) diff --git a/src/abis/nftenterprise.abi.json b/src/abis/nftenterprise.abi.json index e266554..15334d0 100644 --- a/src/abis/nftenterprise.abi.json +++ b/src/abis/nftenterprise.abi.json @@ -73,8 +73,28 @@ ], "inputs": [ { - "name": "attributes", - "type": "DataNftAttributes" + "name": "data_marshal", + "type": "bytes" + }, + { + "name": "data_stream", + "type": "bytes" + }, + { + "name": "data_preview", + "type": "bytes" + }, + { + "name": "creator", + "type": "Address" + }, + { + "name": "title", + "type": "bytes" + }, + { + "name": "description", + "type": "bytes" } ], "outputs": [] diff --git a/src/nft-minter.ts b/src/nft-minter.ts index f9a5bb9..d551687 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -6,8 +6,10 @@ import { ContractCallPayloadBuilder, ContractFunction, IAddress, + StringValue, TokenIdentifierValue, - Transaction + Transaction, + U64Value } from '@multiversx/sdk-core/out'; import { Minter } from './minter'; @@ -16,6 +18,104 @@ export class NftMinter extends Minter { super(env, contractAddress, timeout); } + /** + * Creates an initialize contract transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param collectionName The name of the NFT collection + * @param tokenTicker The ticker of the NFT collection + * @param mintLimit(seconds)- The mint limit between mints + * @param requireMintTax - A boolean value to set if the mint tax is required or not + * @param options - If `requireMintTax` is true, the `options` object must contain the `taxTokenIdentifier` and `taxTokenAmount` + */ + initializeContract( + senderAddress: IAddress, + collectionName: string, + tokenTicker: string, + mintLimit: number, + requireMintTax: boolean, + options: { + taxTokenIdentifier: string; + taxTokenAmount: number; + } + ): Transaction { + let data; + if (requireMintTax && options) { + data = new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('initializeContract')) + .addArg(new StringValue(collectionName)) + .addArg(new StringValue(tokenTicker)) + .addArg(new BigUIntValue(mintLimit)) + .addArg(new BooleanValue(requireMintTax)) + .addArg(new TokenIdentifierValue(options.taxTokenIdentifier)) + .addArg(new BigUIntValue(options.taxTokenAmount)) + .build(); + } else { + data = new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('initializeContract')) + .addArg(new StringValue(collectionName)) + .addArg(new StringValue(tokenTicker)) + .addArg(new BigUIntValue(mintLimit)) + .addArg(new BooleanValue(requireMintTax)) + .build(); + } + + const initializeContractTx = new Transaction({ + value: 50000000000000000, + data: data, + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return initializeContractTx; + } + + /** + * Creates a updateAttributes transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param tokenIdentiifer The token identifier of the data nft to update attributes + * @param nonce The nonce of the token to update attributes + * @param attributes The new attributes to update + * @param quantity The quantity of the token to update attributes (default: 1) + */ + updateAttributes( + senderAddress: IAddress, + tokenIdentiifer: string, + nonce: number, + attributes: { + dataMarshalUrl: string; + dataStreamUrl: string; + dataPreviewUrl: string; + creator: IAddress; + title: string; + description: string; + }, + quantity = 1 + ): Transaction { + const updateAttributesTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('ESDTNFTTransfer')) + .addArg(new TokenIdentifierValue(tokenIdentiifer)) + .addArg(new U64Value(nonce)) + .addArg(new U64Value(quantity)) + .addArg(new AddressValue(this.contract.getAddress())) + .addArg(new StringValue('updateAttributes')) + .addArg(new StringValue(attributes.dataMarshalUrl)) + .addArg(new StringValue(attributes.dataStreamUrl)) + .addArg(new StringValue(attributes.dataPreviewUrl)) + .addArg(new AddressValue(attributes.creator)) + .addArg(new StringValue(attributes.title)) + .addArg(new StringValue(attributes.description)) + .build(), + receiver: senderAddress, + gasLimit: 12000000, + sender: senderAddress, + chainID: this.chainID + }); + return updateAttributesTx; + } + /** * Creates a setLocalRoles transaction for the contract * @param senderAddress The address of the sender, must be the admin of the contract @@ -54,33 +154,6 @@ export class NftMinter extends Minter { return setTransferRolesTx; } - /** - * Creates an initialize contract transaction for the contract - * @param senderAddress The address of the sender, must be the admin of the contract - * @param collectionName The name of the NFT collection - * @param tokenTicker The ticker of the NFT collection - * @param mintLimit(seconds)- The mint limit between mints - * @param requireMintTax - A boolean value to set if the mint tax is required or not - * @param options - If `requireMintTax` is true, the `options` object must contain the `taxTokenIdentifier` and `taxTokenAmount` - */ - initializeContract( - senderAddress: IAddress, - collectionName: string, - tokenTicker: string, - mintLimit: number, - requireMintTax: boolean, - options: { - taxTokenIdentifier: string; - taxTokenAmount: number; - } - ) { - // TODO implement - } - - updateAttributes() { - // TODO implement - } - /** * Creates an unsetTransferRoles transaction for the contract * @param senderAddress The address of the sender, must be the admin of the contract From 01b2a562f3a1c84d5534471f0d4ebf157df46799 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 19:35:25 +0300 Subject: [PATCH 12/32] refactor: abi file import in subclass and use in subclass constructor Refs: #37 --- .../{nftenterprise.abi.json => data-nft-lease.abi.json} | 2 +- src/minter.ts | 5 ++--- src/nft-minter.ts | 3 ++- src/sft-minter.ts | 8 +++++++- 4 files changed, 12 insertions(+), 6 deletions(-) rename src/abis/{nftenterprise.abi.json => data-nft-lease.abi.json} (99%) diff --git a/src/abis/nftenterprise.abi.json b/src/abis/data-nft-lease.abi.json similarity index 99% rename from src/abis/nftenterprise.abi.json rename to src/abis/data-nft-lease.abi.json index 15334d0..05adf82 100644 --- a/src/abis/nftenterprise.abi.json +++ b/src/abis/data-nft-lease.abi.json @@ -8,7 +8,7 @@ "short": "rustc 1.71.0-nightly (7f94b314c 2023-04-23)" }, "contractCrate": { - "name": "nftenterprise", + "name": "data-nft-lease", "version": "1.0.0" }, "framework": { diff --git a/src/minter.ts b/src/minter.ts index e85f786..08b8cd4 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -22,8 +22,6 @@ import { itheumTokenIdentifier, networkConfiguration } from './config'; -import dataNftMintAbi from './abis/datanftmint.abi.json'; -import { MinterRequirements } from './interfaces'; import { NFTStorage, File } from 'nft.storage'; import { checkTraitsUrl, @@ -51,6 +49,7 @@ export class Minter { protected constructor( env: string, contractAddress: string, + abiFile: any, timeout: number = 10000 ) { this.env = env; @@ -65,7 +64,7 @@ export class Minter { ); this.contract = new SmartContract({ address: new Address(contractAddress), - abi: AbiRegistry.create(dataNftMintAbi) + abi: AbiRegistry.create(abiFile) }); } diff --git a/src/nft-minter.ts b/src/nft-minter.ts index d551687..8ab6cec 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -12,10 +12,11 @@ import { U64Value } from '@multiversx/sdk-core/out'; import { Minter } from './minter'; +import dataNftLeaseAbi from './abis/data-nft-lease.abi.json'; export class NftMinter extends Minter { constructor(env: string, contractAddress: string, timeout: number = 10000) { - super(env, contractAddress, timeout); + super(env, contractAddress, dataNftLeaseAbi, timeout); } /** diff --git a/src/sft-minter.ts b/src/sft-minter.ts index b982130..28a049b 100644 --- a/src/sft-minter.ts +++ b/src/sft-minter.ts @@ -11,10 +11,16 @@ import { minterContractAddress } from './config'; import { MinterRequirements } from './interfaces'; +import dataNftMinterAbi from './abis/datanftmint.abi.json'; export class SftMinter extends Minter { constructor(env: string, timeout: number = 10000) { - super(env, minterContractAddress[env as EnvironmentsEnum], timeout); + super( + env, + minterContractAddress[env as EnvironmentsEnum], + dataNftMinterAbi, + timeout + ); } /** From fe44d017c6f8868c02d0fcdfdd2b6a18068f163e Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 20:00:48 +0300 Subject: [PATCH 13/32] refactor: abstract minter class --- src/minter.ts | 2 +- tests/minter.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/minter.ts b/src/minter.ts index 08b8cd4..0bb5a90 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -34,7 +34,7 @@ import { // ErrFailedOperation // } from './errors'; -export class Minter { +export abstract class Minter { readonly contract: SmartContract; readonly chainID: string; readonly networkProvider: ApiNetworkProvider; diff --git a/tests/minter.test.ts b/tests/minter.test.ts index 198b353..ce159d7 100644 --- a/tests/minter.test.ts +++ b/tests/minter.test.ts @@ -1,5 +1,5 @@ import { Address, Transaction } from '@multiversx/sdk-core/out'; -import { DataNftMinter, MinterRequirements } from '../src'; +import { DataNftMinter, Minter, MinterRequirements } from '../src'; describe('Data Nft Minter Test', () => { test('#getAddress', async () => { From 1094fe8d38cf13a0c780828080bb2ced1002e686 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 20:28:53 +0300 Subject: [PATCH 14/32] feat: collection management transaction implementation Refs: #37 --- src/nft-minter.ts | 157 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/src/nft-minter.ts b/src/nft-minter.ts index 8ab6cec..4c999bd 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -421,4 +421,161 @@ export class NftMinter extends Minter { }); return claimRoyaltiesTx; } + + /** + * Pause collection transaction + * @param senderAddress The address of the sender, must be the admin or owner of the contract + */ + pauseCollection(senderAddress: IAddress): Transaction { + const pauseCollectionTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('pause')) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return pauseCollectionTx; + } + + /** + * Unpause collection transaction + * @param senderAddress The address of the sender, must be the admin or owner of the contract + */ + unpauseCollection(senderAddress: IAddress): Transaction { + const unpauseCollectionTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('unpause')) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return unpauseCollectionTx; + } + + /** + * Freeze transaction + * @param senderAddress The address of the sender, must be the admin or owner of the contract + */ + freeze(senderAddress: IAddress, freezeAddress: IAddress): Transaction { + const freezeTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('freeze')) + .addArg(new AddressValue(freezeAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return freezeTx; + } + + /** + * Unfreeze transaction + * @param senderAddress The address of the sender, must be the admin or owner of the contract + */ + unfreeze(senderAddress: IAddress, unfreezeAddress: IAddress): Transaction { + const unfreezeTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('unfreeze')) + .addArg(new AddressValue(unfreezeAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return unfreezeTx; + } + + /** + * + * @param senderAddress The address of the sender, must be the admin or owner of the contract + * @param nonce The nonce of the token to freeze for `freezeAddress` + * @param freezeAddress The address to freeze + */ + freezeSingleNFT( + senderAddress: IAddress, + nonce: number, + freezeAddress: IAddress + ): Transaction { + const freezeSingleNFTTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('freezeSingleNFT')) + .addArg(new U64Value(nonce)) + .addArg(new AddressValue(freezeAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return freezeSingleNFTTx; + } + + /** + * + * @param senderAddress The address of the sender, must be the admin or owner of the contract + * @param nonce The nonce of the token to unfreeze for `unfreezeAddress` + * @param unfreezeAddress The address to unfreeze + */ + unFreezeSingleNFT( + senderAddress: IAddress, + nonce: number, + unfreezeAddress: IAddress + ): Transaction { + const unFreezeSingleNFTTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('unFreezeSingleNFT')) + .addArg(new U64Value(nonce)) + .addArg(new AddressValue(unfreezeAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return unFreezeSingleNFTTx; + } + + /** + * + * @param senderAddress The address of the sender, must be the admin or owner of the contract + * @param nonce The nonce of the token to wipe for `wipeAddress` + * @param wipeAddress The address to wipe from + * Important: This will wipe all NFTs from the address + * Note: The nonce must be freezed before wiping + */ + wipeSingleNFT( + senderAddress: IAddress, + nonce: number, + wipeAddress: IAddress + ): Transaction { + const wipeSingleNFTTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('wipeSingleNFT')) + .addArg(new U64Value(nonce)) + .addArg(new AddressValue(wipeAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return wipeSingleNFTTx; + } } From fa45512bd41263a54fe3cbadafca76a5b664de88 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 20:54:58 +0300 Subject: [PATCH 15/32] refactor: different mint implementation based on subclass Refs: #37 --- src/common/mint-utils.ts | 120 +++++++++++++++ src/{ => common}/utils.ts | 4 +- src/datanft.ts | 2 +- src/index.ts | 3 +- src/marketplace.ts | 2 +- src/minter.ts | 308 +------------------------------------- src/nft-minter.ts | 193 ++++++++++++++++++++++++ src/sft-minter.ts | 205 ++++++++++++++++++++++++- 8 files changed, 523 insertions(+), 314 deletions(-) create mode 100644 src/common/mint-utils.ts rename src/{ => common}/utils.ts (99%) diff --git a/src/common/mint-utils.ts b/src/common/mint-utils.ts new file mode 100644 index 0000000..c2d12ab --- /dev/null +++ b/src/common/mint-utils.ts @@ -0,0 +1,120 @@ +import { NFTStorage } from 'nft.storage'; + +export async function dataNFTDataStreamAdvertise( + dataNFTStreamUrl: string, + dataMarshalUrl: string +): Promise<{ dataNftHash: string; dataNftStreamUrlEncrypted: string }> { + const myHeaders = new Headers(); + myHeaders.append('cache-control', 'no-cache'); + myHeaders.append('Content-Type', 'application/json'); + + const requestOptions = { + method: 'POST', + headers: myHeaders, + body: JSON.stringify({ dataNFTStreamUrl }) + }; + + try { + const res = await fetch(`${dataMarshalUrl}/generate`, requestOptions); + const data = await res.json(); + + if (data && data.encryptedMessage && data.messageHash) { + return { + dataNftHash: data.messageHash, + dataNftStreamUrlEncrypted: data.encryptedMessage + }; + } else { + throw new Error('Issue with data marshal generate payload'); + // throw new ErrFailedOperation(this.dataNFTDataStreamAdvertise.name); + } + } catch (error) { + throw error; + // if (error instanceof Error) { + // throw new ErrFailedOperation( + // this.dataNFTDataStreamAdvertise.name, + // error + // ); + // } else { + // throw new ErrFailedOperation(this.dataNFTDataStreamAdvertise.name); + // } + } +} + +export async function storeToIpfs( + storageToken: string, + traits: File, + image: File +): Promise<{ imageOnIpfsUrl: string; metadataOnIpfsUrl: string }> { + let res; + try { + const nftstorage = new NFTStorage({ + token: storageToken + }); + const dir = [image, traits]; + res = await nftstorage.storeDirectory(dir); + } catch (error) { + throw error; + // throw new ErrFailedOperation(this.storeToIpfs.name); + } + return { + imageOnIpfsUrl: `https://ipfs.io/ipfs/${res}/image.png`, + metadataOnIpfsUrl: `https://ipfs.io/ipfs/${res}/metadata.json` + }; +} + +export function createIpfsMetadata( + traits: string, + datasetTitle: string, + datasetDescription: string, + dataNFTStreamPreviewUrl: string, + address: string +) { + const metadata = { + description: `${datasetTitle} : ${datasetDescription}`, + attributes: [] as object[] + }; + const attributes = traits + .split(',') + .filter((element) => element.trim() !== ''); + const metadataAttributes = []; + for (const attribute of attributes) { + const [key, value] = attribute.split(':'); + const trait = { trait_type: key.trim(), value: value.trim() }; + metadataAttributes.push(trait); + } + metadataAttributes.push({ + trait_type: 'Data Preview URL', + value: dataNFTStreamPreviewUrl + }); + metadataAttributes.push({ trait_type: 'Creator', value: address }); + metadata.attributes = metadataAttributes; + return metadata; +} + +export async function createFileFromUrl( + url: string, + datasetTitle: string, + datasetDescription: string, + dataNFTStreamPreviewUrl: string, + address: string +) { + let res: any = ''; + let data: any = ''; + let _imageFile: File = new File([], ''); + if (url) { + res = await fetch(url); + data = await res.blob(); + _imageFile = new File([data], 'image.png', { type: 'image/png' }); + } + const traits = createIpfsMetadata( + res.headers.get('x-nft-traits') || '', + datasetTitle, + datasetDescription, + dataNFTStreamPreviewUrl, + address + ); + const _traitsFile = new File([JSON.stringify(traits)], 'metadata.json', { + type: 'application/json' + }); + return { image: _imageFile, traits: _traitsFile }; +} diff --git a/src/utils.ts b/src/common/utils.ts similarity index 99% rename from src/utils.ts rename to src/common/utils.ts index a9c759d..46e86e6 100644 --- a/src/utils.ts +++ b/src/common/utils.ts @@ -1,6 +1,6 @@ import BigNumber from 'bignumber.js'; -import { DataNft } from './datanft'; -import { NftEnumType, NftType, Offer } from './interfaces'; +import { DataNft } from '../datanft'; +import { NftEnumType, NftType, Offer } from '../interfaces'; export function numberToPaddedHex(value: BigNumber.Value) { let hex = new BigNumber(value).toString(16); diff --git a/src/datanft.ts b/src/datanft.ts index 9e47cc1..50d85d1 100644 --- a/src/datanft.ts +++ b/src/datanft.ts @@ -16,7 +16,7 @@ import { parseDataNft, validateSpecificParamsViewData, checkStatus -} from './utils'; +} from './common/utils'; import minterAbi from './abis/datanftmint.abi.json'; import { NftType, ViewDataReturnType } from './interfaces'; // import { diff --git a/src/index.ts b/src/index.ts index 82ea680..8b6f10e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,7 @@ export * from './config'; export * from './marketplace'; export * from './interfaces'; export * from './datanft'; -export * from './utils'; -export * from './utils'; +export * from './common/utils'; export * from './minter'; export * from './nft-minter'; export * from './sft-minter'; diff --git a/src/marketplace.ts b/src/marketplace.ts index a3c5b38..ce84ce2 100644 --- a/src/marketplace.ts +++ b/src/marketplace.ts @@ -25,7 +25,7 @@ import { } from './config'; import dataMarketAbi from './abis/data_market.abi.json'; import { MarketplaceRequirements, Offer } from './interfaces'; -import { parseOffer } from './utils'; +import { parseOffer } from './common/utils'; // import { ErrContractQuery } from './errors'; export class DataNftMarket { diff --git a/src/minter.ts b/src/minter.ts index 0bb5a90..14d5508 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -27,7 +27,7 @@ import { checkTraitsUrl, checkUrlIsUp, validateSpecificParamsMint -} from './utils'; +} from './common/utils'; // import { // ErrArgumentNotSet, // ErrContractQuery, @@ -128,310 +128,4 @@ export abstract class Minter { }); return burnTx; } - - /** - * 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 - * - * For more information, see the [README documentation](https://github.com/Itheum/sdk-mx-data-nft#create-a-mint-transaction). - * - * @param senderAddress the address of the user - * @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 royalties the royalties to be set for the Data NFT-FT. A number between 0 and 50. This equates to a % value. e.g. 10% - * @param supply the supply of the Data NFT-FT. A number between 1 and 1000. - * @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 antiSpamTax the anti spam tax to be set for the Data NFT-FT with decimals. Needs to be greater than 0 and should be obtained in real time via {@link viewMinterRequirements} prior to calling mint. - * @param options [optional] below parameters are all optional - * - 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 - * - antiSpamTokenIdentifier: the anti spam token identifier to be used for the minting (default = `ITHEUM` token identifier based on the {@link EnvironmentsEnum}) - * - */ - async mint( - senderAddress: IAddress, - tokenName: string, - dataMarshalUrl: string, - dataStreamUrl: string, - dataPreviewUrl: string, - royalties: number, - supply: number, - datasetTitle: string, - datasetDescription: string, - antiSpamTax: number, - options?: { - imageUrl?: string; - traitsUrl?: string; - nftStorageToken?: string; - antiSpamTokenIdentifier?: string; - } - ): Promise { - const { - imageUrl, - traitsUrl, - nftStorageToken, - antiSpamTokenIdentifier = itheumTokenIdentifier[ - this.env as EnvironmentsEnum - ] - } = options ?? {}; - - // S: run any format specific validation - const { allPassed, validationMessages } = validateSpecificParamsMint({ - senderAddress, - tokenName, - royalties, - supply, - datasetTitle, - datasetDescription, - antiSpamTax, - _mandatoryParamsList: [ - 'senderAddress', - 'tokenName', - 'royalties', - 'supply', - 'datasetTitle', - 'datasetDescription', - 'antiSpamTax' - ] - }); - - if (!allPassed) { - throw new Error(`Params have validation issues = ${validationMessages}`); - // throw new ErrFailedOperation( - // this.mint.name, - // new Error(`params have validation issues = ${validationMessages}`) - // ); - } - // E: run any format specific validation... - - // deep validate all mandatory URLs - try { - await checkUrlIsUp(dataStreamUrl, [200, 403]); - await checkUrlIsUp(dataPreviewUrl, [200]); - await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation(this.mint.name, error); - // } else { - // throw new ErrFailedOperation(this.mint.name); - // } - } - - let imageOnIpfsUrl: string; - let metadataOnIpfsUrl: string; - - const { dataNftHash, dataNftStreamUrlEncrypted } = - await this.dataNFTDataStreamAdvertise(dataStreamUrl, dataMarshalUrl); - - if (!imageUrl) { - if (!nftStorageToken) { - throw new Error( - 'NFT Storage token is required when not using custom image and traits' - ); - // throw new ErrArgumentNotSet( - // 'nftStorageToken', - // 'NFT Storage token is required when not using custom image and traits' - // ); - } - const { image, traits } = await this.createFileFromUrl( - `${this.imageServiceUrl}/v1/generateNFTArt?hash=${dataNftHash}`, - datasetTitle, - datasetDescription, - dataPreviewUrl, - senderAddress.bech32() - ); - - const { - imageOnIpfsUrl: imageIpfsUrl, - metadataOnIpfsUrl: metadataIpfsUrl - } = await this.storeToIpfs(nftStorageToken, traits, image); - - imageOnIpfsUrl = imageIpfsUrl; - metadataOnIpfsUrl = metadataIpfsUrl; - } else { - if (!traitsUrl) { - throw new Error('Traits URL is required when using custom image'); - // throw new ErrArgumentNotSet( - // 'traitsUrl', - // 'Traits URL is required when using custom image' - // ); - } - - await checkTraitsUrl(traitsUrl); - - imageOnIpfsUrl = imageUrl; - metadataOnIpfsUrl = traitsUrl; - } - - let data; - if (antiSpamTax > 0) { - data = new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('ESDTTransfer')) - .addArg(new TokenIdentifierValue(antiSpamTokenIdentifier)) - .addArg(new BigUIntValue(antiSpamTax)) - .addArg(new StringValue('mint')) - .addArg(new StringValue(tokenName)) - .addArg(new StringValue(imageOnIpfsUrl)) - .addArg(new StringValue(metadataOnIpfsUrl)) - .addArg(new StringValue(dataMarshalUrl)) - .addArg(new StringValue(dataNftStreamUrlEncrypted)) - .addArg(new StringValue(dataPreviewUrl)) - .addArg(new U64Value(royalties)) - .addArg(new U64Value(supply)) - .addArg(new StringValue(datasetTitle)) - .addArg(new StringValue(datasetDescription)) - .build(); - } else { - data = new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('mint')) - .addArg(new StringValue(tokenName)) - .addArg(new StringValue(imageOnIpfsUrl)) - .addArg(new StringValue(metadataOnIpfsUrl)) - .addArg(new StringValue(dataMarshalUrl)) - .addArg(new StringValue(dataNftStreamUrlEncrypted)) - .addArg(new StringValue(dataPreviewUrl)) - .addArg(new U64Value(royalties)) - .addArg(new U64Value(supply)) - .addArg(new StringValue(datasetTitle)) - .addArg(new StringValue(datasetDescription)) - .build(); - } - - const mintTx = new Transaction({ - data, - sender: senderAddress, - receiver: this.contract.getAddress(), - gasLimit: 60000000, - chainID: this.chainID - }); - - return mintTx; - } - - private async dataNFTDataStreamAdvertise( - dataNFTStreamUrl: string, - dataMarshalUrl: string - ): Promise<{ dataNftHash: string; dataNftStreamUrlEncrypted: string }> { - const myHeaders = new Headers(); - myHeaders.append('cache-control', 'no-cache'); - myHeaders.append('Content-Type', 'application/json'); - - const requestOptions = { - method: 'POST', - headers: myHeaders, - body: JSON.stringify({ dataNFTStreamUrl }) - }; - - try { - const res = await fetch(`${dataMarshalUrl}/generate`, requestOptions); - const data = await res.json(); - - if (data && data.encryptedMessage && data.messageHash) { - return { - dataNftHash: data.messageHash, - dataNftStreamUrlEncrypted: data.encryptedMessage - }; - } else { - throw new Error('Issue with data marshal generate payload'); - // throw new ErrFailedOperation(this.dataNFTDataStreamAdvertise.name); - } - } catch (error) { - throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation( - // this.dataNFTDataStreamAdvertise.name, - // error - // ); - // } else { - // throw new ErrFailedOperation(this.dataNFTDataStreamAdvertise.name); - // } - } - } - - private async storeToIpfs( - storageToken: string, - traits: File, - image: File - ): Promise<{ imageOnIpfsUrl: string; metadataOnIpfsUrl: string }> { - let res; - try { - const nftstorage = new NFTStorage({ - token: storageToken - }); - const dir = [image, traits]; - res = await nftstorage.storeDirectory(dir); - } catch (error) { - throw error; - // throw new ErrFailedOperation(this.storeToIpfs.name); - } - return { - imageOnIpfsUrl: `https://ipfs.io/ipfs/${res}/image.png`, - metadataOnIpfsUrl: `https://ipfs.io/ipfs/${res}/metadata.json` - }; - } - - private createIpfsMetadata( - traits: string, - datasetTitle: string, - datasetDescription: string, - dataNFTStreamPreviewUrl: string, - address: string - ) { - const metadata = { - description: `${datasetTitle} : ${datasetDescription}`, - attributes: [] as object[] - }; - const attributes = traits - .split(',') - .filter((element) => element.trim() !== ''); - const metadataAttributes = []; - for (const attribute of attributes) { - const [key, value] = attribute.split(':'); - const trait = { trait_type: key.trim(), value: value.trim() }; - metadataAttributes.push(trait); - } - metadataAttributes.push({ - trait_type: 'Data Preview URL', - value: dataNFTStreamPreviewUrl - }); - metadataAttributes.push({ trait_type: 'Creator', value: address }); - metadata.attributes = metadataAttributes; - return metadata; - } - - private async createFileFromUrl( - url: string, - datasetTitle: string, - datasetDescription: string, - dataNFTStreamPreviewUrl: string, - address: string - ) { - let res: any = ''; - let data: any = ''; - let _imageFile: File = new File([], ''); - if (url) { - res = await fetch(url); - data = await res.blob(); - _imageFile = new File([data], 'image.png', { type: 'image/png' }); - } - const traits = this.createIpfsMetadata( - res.headers.get('x-nft-traits') || '', - datasetTitle, - datasetDescription, - dataNFTStreamPreviewUrl, - address - ); - const _traitsFile = new File([JSON.stringify(traits)], 'metadata.json', { - type: 'application/json' - }); - return { image: _imageFile, traits: _traitsFile }; - } } diff --git a/src/nft-minter.ts b/src/nft-minter.ts index 4c999bd..049dc54 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -13,6 +13,16 @@ import { } from '@multiversx/sdk-core/out'; import { Minter } from './minter'; import dataNftLeaseAbi from './abis/data-nft-lease.abi.json'; +import { + checkTraitsUrl, + checkUrlIsUp, + validateSpecificParamsMint +} from './common/utils'; +import { + createFileFromUrl, + dataNFTDataStreamAdvertise, + storeToIpfs +} from './common/mint-utils'; export class NftMinter extends Minter { constructor(env: string, contractAddress: string, timeout: number = 10000) { @@ -117,6 +127,189 @@ export class NftMinter extends Minter { return updateAttributesTx; } + /** + * 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 + * + * For more information, see the [README documentation](https://github.com/Itheum/sdk-mx-data-nft#create-a-mint-transaction). + * + * @param senderAddress the address of the user + * @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 royalties the royalties to be set for the Data NFT-FT. A number between 0 and 50. This equates to a % value. e.g. 10% + * @param supply the supply of the Data NFT-FT. A number between 1 and 1000. + * @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 + * - antiSpamTokenIdentifier: the anti spam token identifier to be used for the minting (default = `ITHEUM` token identifier based on the {@link EnvironmentsEnum}) + * - antiSpamTax: the anti spam tax to be set for the Data NFT-FT with decimals. Needs to be greater than 0 and should be obtained in real time via {@link viewMinterRequirements} prior to calling mint. + */ + async mint( + senderAddress: IAddress, + tokenName: string, + dataMarshalUrl: string, + dataStreamUrl: string, + dataPreviewUrl: string, + royalties: number, + supply: number, + datasetTitle: string, + datasetDescription: string, + options?: { + imageUrl?: string; + traitsUrl?: string; + nftStorageToken?: string; + antiSpamTokenIdentifier?: string; + antiSpamTax?: number; + } + ): Promise { + const { + imageUrl, + traitsUrl, + nftStorageToken, + antiSpamTokenIdentifier, + antiSpamTax + } = options ?? {}; + + // S: run any format specific validation + const { allPassed, validationMessages } = validateSpecificParamsMint({ + senderAddress, + tokenName, + royalties, + supply, + datasetTitle, + datasetDescription, + _mandatoryParamsList: [ + 'senderAddress', + 'tokenName', + 'royalties', + 'supply', + 'datasetTitle', + 'datasetDescription' + ] + }); + + if (!allPassed) { + throw new Error(`Params have validation issues = ${validationMessages}`); + // throw new ErrFailedOperation( + // this.mint.name, + // new Error(`params have validation issues = ${validationMessages}`) + // ); + } + // E: run any format specific validation... + + // deep validate all mandatory URLs + try { + await checkUrlIsUp(dataStreamUrl, [200, 403]); + await checkUrlIsUp(dataPreviewUrl, [200]); + await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); + } catch (error) { + throw error; + // if (error instanceof Error) { + // throw new ErrFailedOperation(this.mint.name, error); + // } else { + // throw new ErrFailedOperation(this.mint.name); + // } + } + + let imageOnIpfsUrl: string; + let metadataOnIpfsUrl: string; + + const { dataNftHash, dataNftStreamUrlEncrypted } = + await dataNFTDataStreamAdvertise(dataStreamUrl, dataMarshalUrl); + + if (!imageUrl) { + if (!nftStorageToken) { + throw new Error( + 'NFT Storage token is required when not using custom image and traits' + ); + // throw new ErrArgumentNotSet( + // 'nftStorageToken', + // 'NFT Storage token is required when not using custom image and traits' + // ); + } + const { image, traits } = await createFileFromUrl( + `${this.imageServiceUrl}/v1/generateNFTArt?hash=${dataNftHash}`, + datasetTitle, + datasetDescription, + dataPreviewUrl, + senderAddress.bech32() + ); + + const { + imageOnIpfsUrl: imageIpfsUrl, + metadataOnIpfsUrl: metadataIpfsUrl + } = await storeToIpfs(nftStorageToken, traits, image); + + imageOnIpfsUrl = imageIpfsUrl; + metadataOnIpfsUrl = metadataIpfsUrl; + } else { + if (!traitsUrl) { + throw new Error('Traits URL is required when using custom image'); + // throw new ErrArgumentNotSet( + // 'traitsUrl', + // 'Traits URL is required when using custom image' + // ); + } + + await checkTraitsUrl(traitsUrl); + + imageOnIpfsUrl = imageUrl; + metadataOnIpfsUrl = traitsUrl; + } + + let data; + if (antiSpamTax && antiSpamTokenIdentifier && antiSpamTax > 0) { + data = new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('ESDTTransfer')) + .addArg(new TokenIdentifierValue(antiSpamTokenIdentifier)) + .addArg(new BigUIntValue(antiSpamTax)) + .addArg(new StringValue('mint')) + .addArg(new StringValue(tokenName)) + .addArg(new StringValue(imageOnIpfsUrl)) + .addArg(new StringValue(metadataOnIpfsUrl)) + .addArg(new StringValue(dataMarshalUrl)) + .addArg(new StringValue(dataNftStreamUrlEncrypted)) + .addArg(new StringValue(dataPreviewUrl)) + .addArg(new U64Value(royalties)) + .addArg(new U64Value(supply)) + .addArg(new StringValue(datasetTitle)) + .addArg(new StringValue(datasetDescription)) + .build(); + } else { + data = new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('mint')) + .addArg(new StringValue(tokenName)) + .addArg(new StringValue(imageOnIpfsUrl)) + .addArg(new StringValue(metadataOnIpfsUrl)) + .addArg(new StringValue(dataMarshalUrl)) + .addArg(new StringValue(dataNftStreamUrlEncrypted)) + .addArg(new StringValue(dataPreviewUrl)) + .addArg(new U64Value(royalties)) + .addArg(new U64Value(supply)) + .addArg(new StringValue(datasetTitle)) + .addArg(new StringValue(datasetDescription)) + .build(); + } + + const mintTx = new Transaction({ + data, + sender: senderAddress, + receiver: this.contract.getAddress(), + gasLimit: 60000000, + chainID: this.chainID + }); + + return mintTx; + } + /** * Creates a setLocalRoles transaction for the contract * @param senderAddress The address of the sender, must be the admin of the contract diff --git a/src/sft-minter.ts b/src/sft-minter.ts index 28a049b..511bfd3 100644 --- a/src/sft-minter.ts +++ b/src/sft-minter.ts @@ -1,8 +1,14 @@ import { AddressValue, + BigUIntValue, + ContractCallPayloadBuilder, + ContractFunction, IAddress, ResultsParser, - TokenIdentifierValue + StringValue, + TokenIdentifierValue, + Transaction, + U64Value } from '@multiversx/sdk-core/out'; import { Minter } from './minter'; import { @@ -12,6 +18,16 @@ import { } from './config'; import { MinterRequirements } from './interfaces'; import dataNftMinterAbi from './abis/datanftmint.abi.json'; +import { + checkTraitsUrl, + checkUrlIsUp, + validateSpecificParamsMint +} from './common/utils'; +import { + createFileFromUrl, + dataNFTDataStreamAdvertise, + storeToIpfs +} from './common/mint-utils'; export class SftMinter extends Minter { constructor(env: string, timeout: number = 10000) { @@ -66,4 +82,191 @@ export class SftMinter extends Minter { // throw new ErrContractQuery('Could not retrieve requirements'); } } + + /** + * 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 + * + * For more information, see the [README documentation](https://github.com/Itheum/sdk-mx-data-nft#create-a-mint-transaction). + * + * @param senderAddress the address of the user + * @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 royalties the royalties to be set for the Data NFT-FT. A number between 0 and 50. This equates to a % value. e.g. 10% + * @param supply the supply of the Data NFT-FT. A number between 1 and 1000. + * @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 antiSpamTax the anti spam tax to be set for the Data NFT-FT with decimals. Needs to be greater than 0 and should be obtained in real time via {@link viewMinterRequirements} prior to calling mint. + * @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 + * - antiSpamTokenIdentifier: the anti spam token identifier to be used for the minting (default = `ITHEUM` token identifier based on the {@link EnvironmentsEnum}) + * + */ + async mint( + senderAddress: IAddress, + tokenName: string, + dataMarshalUrl: string, + dataStreamUrl: string, + dataPreviewUrl: string, + royalties: number, + supply: number, + datasetTitle: string, + datasetDescription: string, + antiSpamTax: number, + options?: { + imageUrl?: string; + traitsUrl?: string; + nftStorageToken?: string; + antiSpamTokenIdentifier?: string; + } + ): Promise { + const { + imageUrl, + traitsUrl, + nftStorageToken, + antiSpamTokenIdentifier = itheumTokenIdentifier[ + this.env as EnvironmentsEnum + ] + } = options ?? {}; + + // S: run any format specific validation + const { allPassed, validationMessages } = validateSpecificParamsMint({ + senderAddress, + tokenName, + royalties, + supply, + datasetTitle, + datasetDescription, + antiSpamTax, + _mandatoryParamsList: [ + 'senderAddress', + 'tokenName', + 'royalties', + 'supply', + 'datasetTitle', + 'datasetDescription', + 'antiSpamTax' + ] + }); + + if (!allPassed) { + throw new Error(`Params have validation issues = ${validationMessages}`); + // throw new ErrFailedOperation( + // this.mint.name, + // new Error(`params have validation issues = ${validationMessages}`) + // ); + } + // E: run any format specific validation... + + // deep validate all mandatory URLs + try { + await checkUrlIsUp(dataStreamUrl, [200, 403]); + await checkUrlIsUp(dataPreviewUrl, [200]); + await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); + } catch (error) { + throw error; + // if (error instanceof Error) { + // throw new ErrFailedOperation(this.mint.name, error); + // } else { + // throw new ErrFailedOperation(this.mint.name); + // } + } + + let imageOnIpfsUrl: string; + let metadataOnIpfsUrl: string; + + const { dataNftHash, dataNftStreamUrlEncrypted } = + await dataNFTDataStreamAdvertise(dataStreamUrl, dataMarshalUrl); + + if (!imageUrl) { + if (!nftStorageToken) { + throw new Error( + 'NFT Storage token is required when not using custom image and traits' + ); + // throw new ErrArgumentNotSet( + // 'nftStorageToken', + // 'NFT Storage token is required when not using custom image and traits' + // ); + } + const { image, traits } = await createFileFromUrl( + `${this.imageServiceUrl}/v1/generateNFTArt?hash=${dataNftHash}`, + datasetTitle, + datasetDescription, + dataPreviewUrl, + senderAddress.bech32() + ); + + const { + imageOnIpfsUrl: imageIpfsUrl, + metadataOnIpfsUrl: metadataIpfsUrl + } = await storeToIpfs(nftStorageToken, traits, image); + + imageOnIpfsUrl = imageIpfsUrl; + metadataOnIpfsUrl = metadataIpfsUrl; + } else { + if (!traitsUrl) { + throw new Error('Traits URL is required when using custom image'); + // throw new ErrArgumentNotSet( + // 'traitsUrl', + // 'Traits URL is required when using custom image' + // ); + } + + await checkTraitsUrl(traitsUrl); + + imageOnIpfsUrl = imageUrl; + metadataOnIpfsUrl = traitsUrl; + } + + let data; + if (antiSpamTax > 0) { + data = new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('ESDTTransfer')) + .addArg(new TokenIdentifierValue(antiSpamTokenIdentifier)) + .addArg(new BigUIntValue(antiSpamTax)) + .addArg(new StringValue('mint')) + .addArg(new StringValue(tokenName)) + .addArg(new StringValue(imageOnIpfsUrl)) + .addArg(new StringValue(metadataOnIpfsUrl)) + .addArg(new StringValue(dataMarshalUrl)) + .addArg(new StringValue(dataNftStreamUrlEncrypted)) + .addArg(new StringValue(dataPreviewUrl)) + .addArg(new U64Value(royalties)) + .addArg(new U64Value(supply)) + .addArg(new StringValue(datasetTitle)) + .addArg(new StringValue(datasetDescription)) + .build(); + } else { + data = new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('mint')) + .addArg(new StringValue(tokenName)) + .addArg(new StringValue(imageOnIpfsUrl)) + .addArg(new StringValue(metadataOnIpfsUrl)) + .addArg(new StringValue(dataMarshalUrl)) + .addArg(new StringValue(dataNftStreamUrlEncrypted)) + .addArg(new StringValue(dataPreviewUrl)) + .addArg(new U64Value(royalties)) + .addArg(new U64Value(supply)) + .addArg(new StringValue(datasetTitle)) + .addArg(new StringValue(datasetDescription)) + .build(); + } + + const mintTx = new Transaction({ + data, + sender: senderAddress, + receiver: this.contract.getAddress(), + gasLimit: 60000000, + chainID: this.chainID + }); + + return mintTx; + } } From d86853491b6d68c6d87a3ff504116b2569c3a654 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Thu, 14 Sep 2023 21:00:27 +0300 Subject: [PATCH 16/32] refactor: shared methods implemented in base class Refs :#37 --- .gitignore | 1 + package-lock.json | 1046 +++++++++++++++++++++++++++------------------ package.json | 1 + src/index.ts | 2 - src/minter.ts | 430 +++++++++++++++++++ src/nft-minter.ts | 352 --------------- src/sft-minter.ts | 119 ++++-- 7 files changed, 1135 insertions(+), 816 deletions(-) diff --git a/.gitignore b/.gitignore index cfca13f..4f2b86e 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ yarn-error.log* coverage +docs \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 47f9f4b..02a6b36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,26 @@ { "name": "@itheum/sdk-mx-data-nft", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@itheum/sdk-mx-data-nft", - "version": "1.1.0", + "version": "1.2.0", "license": "GPL-3.0-only", "dependencies": { - "@multiversx/sdk-core": "12.6.0", - "@multiversx/sdk-network-providers": "1.5.0", - "bignumber.js": "^9.1.1", - "nft.storage": "^7.1.1" + "@multiversx/sdk-core": "12.8.0", + "@multiversx/sdk-network-providers": "2.0.0", + "bignumber.js": "9.1.2", + "nft.storage": "7.1.1" }, "devDependencies": { - "@types/jest": "^29.5.1", - "jest": "^29.5.0", - "ts-jest": "^29.1.0", - "tslint": "^6.1.3", - "typescript": "^5.0.4" + "@types/jest": "29.5.4", + "jest": "29.7.0", + "ts-jest": "29.1.1", + "tslint": "6.1.3", + "typedoc": "^0.25.1", + "typescript": "5.2.2" } }, "node_modules/@ampproject/remapping": { @@ -196,9 +197,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -418,12 +419,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", - "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -520,12 +521,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", - "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -651,16 +652,16 @@ } }, "node_modules/@jest/console": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", - "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -668,37 +669,37 @@ } }, "node_modules/@jest/core": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", - "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/reporters": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-resolve-dependencies": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "jest-watcher": "^29.5.0", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -715,89 +716,89 @@ } }, "node_modules/@jest/environment": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", - "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "dependencies": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "dependencies": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", - "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", - "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", - "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -805,13 +806,13 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -829,25 +830,74 @@ } } }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", + "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, @@ -856,13 +906,13 @@ } }, "node_modules/@jest/test-result": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", - "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -871,14 +921,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", - "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "dependencies": { - "@jest/test-result": "^29.5.0", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -886,22 +936,22 @@ } }, "node_modules/@jest/transform": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", - "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -912,12 +962,12 @@ } }, "node_modules/@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -992,9 +1042,9 @@ } }, "node_modules/@multiversx/sdk-core": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-core/-/sdk-core-12.6.0.tgz", - "integrity": "sha512-aJjSn8EzaYXalUuvAuxOTPtv8GDbimJlSYNRLjCsToyfwGAyS9cRllmq2d1I2nlrR89YgAsOL9qp+sI5DA7yBw==", + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-core/-/sdk-core-12.8.0.tgz", + "integrity": "sha512-+XtnwQ/dUaXrpu/GCNud7r5AaQtNKRVqeKnjVUoKKHr14ohXbg9FW2Xx7IrJvehEMupjdx+6phR+nAXR60bIbQ==", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", "bech32": "1.1.4", @@ -1043,9 +1093,9 @@ } }, "node_modules/@multiversx/sdk-network-providers": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-network-providers/-/sdk-network-providers-1.5.0.tgz", - "integrity": "sha512-GgmpOfwdeK8QvBCVJ96/L2ATNax7/rdSDvPLlmppKHVuFAj56/EqGnrLuktNPRWBsZse+7DMoS38kGtN77AwJQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-network-providers/-/sdk-network-providers-2.0.0.tgz", + "integrity": "sha512-87QlwC2kaNtywDv2IVX/cavRfR4D5N7XgqOvsZTyMXRVbfWOUlDocrRr5BdvmC1gDdSOQH++nFEDHiMfnKmRng==", "dependencies": { "axios": "0.24.0", "bech32": "1.1.4", @@ -1138,33 +1188,33 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "dependencies": { - "@sinonjs/commons": "^2.0.0" + "@sinonjs/commons": "^3.0.0" } }, "node_modules/@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -1194,12 +1244,12 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", "dev": true, "dependencies": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "node_modules/@types/graceful-fs": { @@ -1236,9 +1286,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.1.tgz", - "integrity": "sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ==", + "version": "29.5.4", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.4.tgz", + "integrity": "sha512-PhglGmhWeD46FYOVLt3X7TiWjzwuVGW9wG/4qocPevXMjCmrIc5b6db9WjeGE4QYVpUAWMDv3v0IiBwObY289A==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -1270,12 +1320,6 @@ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -1405,6 +1449,12 @@ "node": ">=8" } }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1471,15 +1521,15 @@ } }, "node_modules/babel-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", - "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "dependencies": { - "@jest/transform": "^29.5.0", + "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", + "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -1508,9 +1558,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", - "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", @@ -1546,12 +1596,12 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", - "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^29.5.0", + "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { @@ -1596,9 +1646,9 @@ "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" }, "node_modules/bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "engines": { "node": "*" } @@ -1891,9 +1941,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, "node_modules/cliui": { @@ -1921,9 +1971,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "node_modules/color-convert": { @@ -1961,6 +2011,27 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2031,10 +2102,18 @@ } }, "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } }, "node_modules/deepmerge": { "version": "4.3.1", @@ -2064,9 +2143,9 @@ } }, "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -2210,16 +2289,16 @@ } }, "node_modules/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -2303,9 +2382,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -3000,17 +3079,17 @@ } }, "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/istanbul-lib-source-maps": { @@ -3028,9 +3107,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -3121,15 +3200,15 @@ } }, "node_modules/jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.5.0" + "jest-cli": "^29.7.0" }, "bin": { "jest": "bin/jest.js" @@ -3147,12 +3226,13 @@ } }, "node_modules/jest-changed-files": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", - "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "dependencies": { "execa": "^5.0.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0" }, "engines": { @@ -3160,28 +3240,28 @@ } }, "node_modules/jest-circus": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", - "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "dedent": "^0.7.0", + "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.5.0", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -3191,22 +3271,21 @@ } }, "node_modules/jest-cli": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", - "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "dependencies": { - "@jest/core": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", + "create-jest": "^29.7.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "prompts": "^2.0.1", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "bin": { @@ -3225,31 +3304,31 @@ } }, "node_modules/jest-config": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", - "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.5.0", - "jest-environment-node": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -3270,24 +3349,24 @@ } }, "node_modules/jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", - "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" @@ -3297,62 +3376,62 @@ } }, "node_modules/jest-each": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", - "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", - "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", - "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -3364,46 +3443,46 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", - "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", - "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -3412,14 +3491,14 @@ } }, "node_modules/jest-mock": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", - "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.5.0" + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3443,26 +3522,26 @@ } }, "node_modules/jest-regex-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", - "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -3472,43 +3551,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", - "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.5.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", - "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/environment": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-leak-detector": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-resolve": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-util": "^29.5.0", - "jest-watcher": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -3517,31 +3596,31 @@ } }, "node_modules/jest-runtime": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", - "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/globals": "^29.5.0", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -3550,34 +3629,31 @@ } }, "node_modules/jest-snapshot": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", - "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.5.0", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^29.5.0", - "semver": "^7.3.5" + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3596,9 +3672,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3617,12 +3693,12 @@ "dev": true }, "node_modules/jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -3634,17 +3710,17 @@ } }, "node_modules/jest-validate": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", - "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^29.5.0" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3663,18 +3739,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", - "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "dependencies": { - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "string-length": "^4.0.1" }, "engines": { @@ -3682,13 +3758,13 @@ } }, "node_modules/jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -3774,6 +3850,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/keccak": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", @@ -3850,21 +3932,60 @@ "yallist": "^3.0.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -3891,6 +4012,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/meow": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", @@ -4458,12 +4591,12 @@ } }, "node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -4522,9 +4655,9 @@ } }, "node_modules/pure-rand": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", - "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.3.tgz", + "integrity": "sha512-KddyFewCsO0j3+np81IQ+SweXLDnDQTs5s67BOnrYmYe/yNmUhttQyGsYzy8yUnoljGAQ9sl38YB4vH8ur7Y+w==", "dev": true, "funding": [ { @@ -4805,6 +4938,18 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.4.tgz", + "integrity": "sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==", + "dev": true, + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -5092,9 +5237,9 @@ } }, "node_modules/ts-jest": { - "version": "29.1.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz", - "integrity": "sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==", + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -5103,7 +5248,7 @@ "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" }, "bin": { @@ -5147,9 +5292,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -5326,17 +5471,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typedoc": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.1.tgz", + "integrity": "sha512-c2ye3YUtGIadxN2O6YwPEXgrZcvhlZ6HlhWZ8jQRNzwLPn2ylhdGqdR8HbyDRyALP8J6lmSANILCkkIdNPFxqA==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } }, "node_modules/uint8arrays": { @@ -5428,6 +5618,18 @@ "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -5556,9 +5758,9 @@ "dev": true }, "node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { "cliui": "^8.0.1", diff --git a/package.json b/package.json index 7889621..3c3e919 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "jest": "29.7.0", "ts-jest": "29.1.1", "tslint": "6.1.3", + "typedoc": "^0.25.1", "typescript": "5.2.2" }, "repository": { diff --git a/src/index.ts b/src/index.ts index 8b6f10e..a2d00eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,6 @@ -export * from './config'; export * from './marketplace'; export * from './interfaces'; export * from './datanft'; -export * from './common/utils'; export * from './minter'; export * from './nft-minter'; export * from './sft-minter'; diff --git a/src/minter.ts b/src/minter.ts index 14d5508..0a06e32 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -28,6 +28,7 @@ import { checkUrlIsUp, validateSpecificParamsMint } from './common/utils'; +import { MinterRequirements } from './interfaces'; // import { // ErrArgumentNotSet, // ErrContractQuery, @@ -98,6 +99,53 @@ export abstract class Minter { } } + // [TO DO] Implement other general views between sft and nft minter + // [TO DO] Implement all general methods between sft and nft minter ? + + /** + * Retrieves the minter smart contract requirements for the given user + * @param address the address of the user + * @param taxToken the tax token to be used for the minting (default = `ITHEUM` token identifier based on the {@link EnvironmentsEnum}) + */ + async viewMinterRequirements( + address: IAddress, + taxToken = itheumTokenIdentifier[this.env as EnvironmentsEnum] + ): Promise { + const interaction = this.contract.methodsExplicit.getUserDataOut([ + new AddressValue(address), + new TokenIdentifierValue(taxToken) + ]); + const query = interaction.buildQuery(); + const queryResponse = await this.networkProvider.queryContract(query); + const endpointDefinition = interaction.getEndpoint(); + const { firstValue, returnCode } = new ResultsParser().parseQueryResponse( + queryResponse, + endpointDefinition + ); + if (returnCode.isSuccess()) { + const returnValue = firstValue?.valueOf(); + const requirements: MinterRequirements = { + antiSpamTaxValue: returnValue.anti_spam_tax_value.toNumber(), + contractPaused: returnValue.is_paused, + maxRoyalties: returnValue.max_royalties.toNumber(), + minRoyalties: returnValue.min_royalties.toNumber(), + maxSupply: returnValue.max_supply.toNumber(), + mintTimeLimit: returnValue.mint_time_limit.toNumber(), + lastUserMintTime: returnValue.last_mint_time, + userWhitelistedForMint: returnValue.is_whitelisted, + contractWhitelistEnabled: returnValue.whitelist_enabled, + numberOfMintsForUser: returnValue.minted_per_user.toNumber(), + totalNumberOfMints: returnValue.total_minted.toNumber(), + addressFrozen: returnValue.frozen, + frozenNonces: returnValue.frozen_nonces.map((v: any) => v.toNumber()) + }; + return requirements; + } else { + throw new Error('Could not retrieve minter contract requirements'); + // throw new ErrContractQuery('Could not retrieve requirements'); + } + } + /** * Creates a `burn` transaction * @param senderAddress the address of the user @@ -128,4 +176,386 @@ export abstract class Minter { }); return burnTx; } + + /** + * Creates a setLocalRoles transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + */ + setLocalRoles(senderAddress: IAddress): Transaction { + const setLocalRolesTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setLocalRoles')) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setLocalRolesTx; + } + + /** Creates a pause transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + */ + pauseContract(senderAddress: IAddress): Transaction { + const pauseContractTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setIsPaused')) + .addArg(new BooleanValue(true)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return pauseContractTx; + } + + /** Creates a unpause transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + */ + unpauseContract(senderAddress: IAddress): Transaction { + const unpauseContractTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setIsPaused')) + .addArg(new BooleanValue(false)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return unpauseContractTx; + } + + /** Creates a set mint tax transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param tokenIdentifier The token identifier of the token to set the mint tax + * @param tax The tax to set for the token + */ + setMintTax( + senderAddress: IAddress, + tokenIdentifier: string, + tax: number + ): Transaction { + const setMintTaxTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setAntiSpamTax')) + .addArg(new TokenIdentifierValue(tokenIdentifier)) + .addArg(new BigUIntValue(tax)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setMintTaxTx; + } + + /** + * + * @param senderAddress The address of the sender, must be the admin of the contract + * @param minRoyalties The minimum royalties to set for minting + * @param maxRoyalties The maximum royalties to set for minting + * + * Remarks: The royalties are set in percentage (e.g. 100% = 10000) + */ + setRoyaltiesLimits( + senderAddress: IAddress, + minRoyalties: number, + maxRoyalties: number + ): Transaction { + const setRoyaltiesLimitsTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setRoyaltiesLimits')) + .addArg(new BigUIntValue(minRoyalties)) + .addArg(new BigUIntValue(maxRoyalties)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setRoyaltiesLimitsTx; + } + + /** Creates a set mint tax transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param is_enabled A boolean value to set if whitelist is enabled or not + */ + setWhitelistIsEnabled( + senderAddress: IAddress, + is_enabled: boolean + ): Transaction { + const setWhitelistIsEnabledTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setWhiteListEnabled')) + .addArg(new BooleanValue(is_enabled)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setWhitelistIsEnabledTx; + } + + /** Creates a whitelist transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param addresses The addresses to whitelist + */ + + whitelist( + senderAddress: IAddress, + addresses: string[], + gasLimit = 0 + ): Transaction { + const whitelistTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setWhiteListSpots')) + .setArgs( + addresses.map((address) => new AddressValue(new Address(address))) + ) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000 + gasLimit, + sender: senderAddress, + chainID: this.chainID + }); + return whitelistTx; + } + + /** Creates a delist transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param addresses The addresses to delist + */ + delist( + senderAddress: IAddress, + addresses: string[], + gasLimit: 0 + ): Transaction { + const delistTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('removeWhiteListSpots')) + .setArgs( + addresses.map((address) => new AddressValue(new Address(address))) + ) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000 + gasLimit, + sender: senderAddress, + chainID: this.chainID + }); + return delistTx; + } + + /** Creates a set mint time limit transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param timeLimit(seconds) The time limit to set between mints + */ + setMintTimeLimit(senderAddress: IAddress, timeLimit: number): Transaction { + const setMintTimeLimitTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setMintTimeLimit')) + .addArg(new U64Value(timeLimit)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setMintTimeLimitTx; + } + + /** Sets a new administrator for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param newAdministrator The address of the new administrator + */ + setAdministrator( + senderAddress: IAddress, + newAdministrator: IAddress + ): Transaction { + const setAdministratorTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setAdministrator')) + .addArg(new AddressValue(newAdministrator)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setAdministratorTx; + } + + // Collection management methods + + /** + * Pause collection transaction + * @param senderAddress The address of the sender, must be the admin or owner of the contract + */ + pauseCollection(senderAddress: IAddress): Transaction { + const pauseCollectionTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('pause')) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return pauseCollectionTx; + } + + /** + * Unpause collection transaction + * @param senderAddress The address of the sender, must be the admin or owner of the contract + */ + unpauseCollection(senderAddress: IAddress): Transaction { + const unpauseCollectionTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('unpause')) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return unpauseCollectionTx; + } + + /** + * Freeze transaction + * @param senderAddress The address of the sender, must be the admin or owner of the contract + */ + freeze(senderAddress: IAddress, freezeAddress: IAddress): Transaction { + const freezeTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('freeze')) + .addArg(new AddressValue(freezeAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return freezeTx; + } + + /** + * Unfreeze transaction + * @param senderAddress The address of the sender, must be the admin or owner of the contract + */ + unfreeze(senderAddress: IAddress, unfreezeAddress: IAddress): Transaction { + const unfreezeTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('unfreeze')) + .addArg(new AddressValue(unfreezeAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + + return unfreezeTx; + } + + /** + * + * @param senderAddress The address of the sender, must be the admin or owner of the contract + * @param nonce The nonce of the token to freeze for `freezeAddress` + * @param freezeAddress The address to freeze + */ + freezeSingleNFT( + senderAddress: IAddress, + nonce: number, + freezeAddress: IAddress + ): Transaction { + const freezeSingleNFTTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('freezeSingleNFT')) + .addArg(new U64Value(nonce)) + .addArg(new AddressValue(freezeAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return freezeSingleNFTTx; + } + + /** + * + * @param senderAddress The address of the sender, must be the admin or owner of the contract + * @param nonce The nonce of the token to unfreeze for `unfreezeAddress` + * @param unfreezeAddress The address to unfreeze + */ + unFreezeSingleNFT( + senderAddress: IAddress, + nonce: number, + unfreezeAddress: IAddress + ): Transaction { + const unFreezeSingleNFTTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('unFreezeSingleNFT')) + .addArg(new U64Value(nonce)) + .addArg(new AddressValue(unfreezeAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return unFreezeSingleNFTTx; + } + + /** + * + * @param senderAddress The address of the sender, must be the admin or owner of the contract + * @param nonce The nonce of the token to wipe for `wipeAddress` + * @param wipeAddress The address to wipe from + * Important: This will wipe all NFTs from the address + * Note: The nonce must be freezed before wiping + */ + wipeSingleNFT( + senderAddress: IAddress, + nonce: number, + wipeAddress: IAddress + ): Transaction { + const wipeSingleNFTTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('wipeSingleNFT')) + .addArg(new U64Value(nonce)) + .addArg(new AddressValue(wipeAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return wipeSingleNFTTx; + } } diff --git a/src/nft-minter.ts b/src/nft-minter.ts index 049dc54..17a49ba 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -310,24 +310,6 @@ export class NftMinter extends Minter { return mintTx; } - /** - * Creates a setLocalRoles transaction for the contract - * @param senderAddress The address of the sender, must be the admin of the contract - */ - setLocalRoles(senderAddress: IAddress): Transaction { - const setLocalRolesTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('setLocalRoles')) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - return setLocalRolesTx; - } - /** * Creates a setTransferRoles transaction for the contract * @param senderAddress The address of the sender, must be the admin of the contract @@ -368,44 +350,6 @@ export class NftMinter extends Minter { return unsetTransferRolesTx; } - /** Creates a pause transaction for the contract - * @param senderAddress The address of the sender, must be the admin of the contract - */ - pauseContract(senderAddress: IAddress): Transaction { - const pauseContractTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('setIsPaused')) - .addArg(new BooleanValue(true)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - - return pauseContractTx; - } - - /** Creates a unpause transaction for the contract - * @param senderAddress The address of the sender, must be the admin of the contract - */ - unpauseContract(senderAddress: IAddress): Transaction { - const unpauseContractTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('setIsPaused')) - .addArg(new BooleanValue(false)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - - return unpauseContractTx; - } - /** Creates a set mint tax transaction for the contract * @param senderAddress The address of the sender, must be the admin of the contract * @param is_required A boolean value to set if the mint tax is required or not @@ -429,145 +373,6 @@ export class NftMinter extends Minter { return setMintTaxIsRequiredTx; } - /** Creates a set mint tax transaction for the contract - * @param senderAddress The address of the sender, must be the admin of the contract - * @param tokenIdentifier The token identifier of the token to set the mint tax - * @param tax The tax to set for the token - */ - setMintTax( - senderAddress: IAddress, - tokenIdentifier: string, - tax: number - ): Transaction { - const setMintTaxTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('setAntiSpamTax')) - .addArg(new TokenIdentifierValue(tokenIdentifier)) - .addArg(new BigUIntValue(tax)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - return setMintTaxTx; - } - - /** Creates a set mint tax transaction for the contract - * @param senderAddress The address of the sender, must be the admin of the contract - * @param is_enabled A boolean value to set if whitelist is enabled or not - */ - setWhitelistIsEnabled( - senderAddress: IAddress, - is_enabled: boolean - ): Transaction { - const setWhitelistIsEnabledTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('setWhiteListEnabled')) - .addArg(new BooleanValue(is_enabled)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - return setWhitelistIsEnabledTx; - } - - /** Creates a whitelist transaction for the contract - * @param senderAddress The address of the sender, must be the admin of the contract - * @param addresses The addresses to whitelist - */ - - whitelist( - senderAddress: IAddress, - addresses: string[], - gasLimit = 0 - ): Transaction { - const whitelistTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('setWhiteListSpots')) - .setArgs( - addresses.map((address) => new AddressValue(new Address(address))) - ) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000 + gasLimit, - sender: senderAddress, - chainID: this.chainID - }); - return whitelistTx; - } - - /** Creates a delist transaction for the contract - * @param senderAddress The address of the sender, must be the admin of the contract - * @param addresses The addresses to delist - */ - delist( - senderAddress: IAddress, - addresses: string[], - gasLimit: 0 - ): Transaction { - const delistTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('delist')) - .setArgs( - addresses.map((address) => new AddressValue(new Address(address))) - ) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000 + gasLimit, - sender: senderAddress, - chainID: this.chainID - }); - return delistTx; - } - - /** Creates a set mint time limit transaction for the contract - * @param senderAddress The address of the sender, must be the admin of the contract - * @param timeLimit(seconds) The time limit to set between mints - */ - setMintTimeLimit(senderAddress: IAddress, timeLimit: number): Transaction { - const setMintTimeLimitTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('setMintTimeLimit')) - .addArg(new BigUIntValue(timeLimit)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - return setMintTimeLimitTx; - } - - /** Sets a new administrator for the contract - * @param senderAddress The address of the sender, must be the admin of the contract - * @param newAdministrator The address of the new administrator - */ - setAdministrator( - senderAddress: IAddress, - newAdministrator: IAddress - ): Transaction { - const setAdministratorTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('setAdministrator')) - .addArg(new AddressValue(newAdministrator)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - return setAdministratorTx; - } - /** Sets the claim address for the contract * @param senderAddress The address of the sender, must be the admin of the contract * @param claimsAddress The claims address @@ -614,161 +419,4 @@ export class NftMinter extends Minter { }); return claimRoyaltiesTx; } - - /** - * Pause collection transaction - * @param senderAddress The address of the sender, must be the admin or owner of the contract - */ - pauseCollection(senderAddress: IAddress): Transaction { - const pauseCollectionTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('pause')) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - return pauseCollectionTx; - } - - /** - * Unpause collection transaction - * @param senderAddress The address of the sender, must be the admin or owner of the contract - */ - unpauseCollection(senderAddress: IAddress): Transaction { - const unpauseCollectionTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('unpause')) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - - return unpauseCollectionTx; - } - - /** - * Freeze transaction - * @param senderAddress The address of the sender, must be the admin or owner of the contract - */ - freeze(senderAddress: IAddress, freezeAddress: IAddress): Transaction { - const freezeTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('freeze')) - .addArg(new AddressValue(freezeAddress)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - - return freezeTx; - } - - /** - * Unfreeze transaction - * @param senderAddress The address of the sender, must be the admin or owner of the contract - */ - unfreeze(senderAddress: IAddress, unfreezeAddress: IAddress): Transaction { - const unfreezeTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('unfreeze')) - .addArg(new AddressValue(unfreezeAddress)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - - return unfreezeTx; - } - - /** - * - * @param senderAddress The address of the sender, must be the admin or owner of the contract - * @param nonce The nonce of the token to freeze for `freezeAddress` - * @param freezeAddress The address to freeze - */ - freezeSingleNFT( - senderAddress: IAddress, - nonce: number, - freezeAddress: IAddress - ): Transaction { - const freezeSingleNFTTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('freezeSingleNFT')) - .addArg(new U64Value(nonce)) - .addArg(new AddressValue(freezeAddress)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - return freezeSingleNFTTx; - } - - /** - * - * @param senderAddress The address of the sender, must be the admin or owner of the contract - * @param nonce The nonce of the token to unfreeze for `unfreezeAddress` - * @param unfreezeAddress The address to unfreeze - */ - unFreezeSingleNFT( - senderAddress: IAddress, - nonce: number, - unfreezeAddress: IAddress - ): Transaction { - const unFreezeSingleNFTTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('unFreezeSingleNFT')) - .addArg(new U64Value(nonce)) - .addArg(new AddressValue(unfreezeAddress)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - return unFreezeSingleNFTTx; - } - - /** - * - * @param senderAddress The address of the sender, must be the admin or owner of the contract - * @param nonce The nonce of the token to wipe for `wipeAddress` - * @param wipeAddress The address to wipe from - * Important: This will wipe all NFTs from the address - * Note: The nonce must be freezed before wiping - */ - wipeSingleNFT( - senderAddress: IAddress, - nonce: number, - wipeAddress: IAddress - ): Transaction { - const wipeSingleNFTTx = new Transaction({ - value: 0, - data: new ContractCallPayloadBuilder() - .setFunction(new ContractFunction('wipeSingleNFT')) - .addArg(new U64Value(nonce)) - .addArg(new AddressValue(wipeAddress)) - .build(), - receiver: this.contract.getAddress(), - gasLimit: 10000000, - sender: senderAddress, - chainID: this.chainID - }); - return wipeSingleNFTTx; - } } diff --git a/src/sft-minter.ts b/src/sft-minter.ts index 511bfd3..b0c228e 100644 --- a/src/sft-minter.ts +++ b/src/sft-minter.ts @@ -39,48 +39,87 @@ export class SftMinter extends Minter { ); } + // [TO DO] Initialize the contract for sft minter + /** - * Retrieves the minter smart contract requirements for the given user - * @param address the address of the user - * @param taxToken the tax token to be used for the minting (default = `ITHEUM` token identifier based on the {@link EnvironmentsEnum}) + * Creates an initialize contract transaction for the contract + * @param senderAddress The address of the sender, must be the admin of the contract + * @param collectionName The name of the NFT collection + * @param tokenTicker The ticker of the NFT collection + * @param antiSpamTaxTokenIdentifier The token identifier of the anti spam token + * @param antiSpamTaxTokenAmount The amount of anti spam token to be used for minting as tax + * @param mintLimit(seconds)- The mint limit between mints + * @param treasury_address The address of the treasury to collect the anti spam tax */ - async viewMinterRequirements( - address: IAddress, - taxToken = itheumTokenIdentifier[this.env as EnvironmentsEnum] - ): Promise { - const interaction = this.contract.methodsExplicit.getUserDataOut([ - new AddressValue(address), - new TokenIdentifierValue(taxToken) - ]); - const query = interaction.buildQuery(); - const queryResponse = await this.networkProvider.queryContract(query); - const endpointDefinition = interaction.getEndpoint(); - const { firstValue, returnCode } = new ResultsParser().parseQueryResponse( - queryResponse, - endpointDefinition - ); - if (returnCode.isSuccess()) { - const returnValue = firstValue?.valueOf(); - const requirements: MinterRequirements = { - antiSpamTaxValue: returnValue.anti_spam_tax_value.toNumber(), - contractPaused: returnValue.is_paused, - maxRoyalties: returnValue.max_royalties.toNumber(), - minRoyalties: returnValue.min_royalties.toNumber(), - maxSupply: returnValue.max_supply.toNumber(), - mintTimeLimit: returnValue.mint_time_limit.toNumber(), - lastUserMintTime: returnValue.last_mint_time, - userWhitelistedForMint: returnValue.is_whitelisted, - contractWhitelistEnabled: returnValue.whitelist_enabled, - numberOfMintsForUser: returnValue.minted_per_user.toNumber(), - totalNumberOfMints: returnValue.total_minted.toNumber(), - addressFrozen: returnValue.frozen, - frozenNonces: returnValue.frozen_nonces.map((v: any) => v.toNumber()) - }; - return requirements; - } else { - throw new Error('Could not retrieve minter contract requirements'); - // throw new ErrContractQuery('Could not retrieve requirements'); - } + initializeContract( + senderAddress: IAddress, + collectionName: string, + tokenTicker: string, + antiSpamTaxTokenIdentifier: string, + antiSpamTaxTokenAmount: number, + mintLimit: number, + treasury_address: IAddress + ): Transaction { + const initializeContractTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('initializeContract')) + .addArg(new StringValue(collectionName)) + .addArg(new StringValue(tokenTicker)) + .addArg(new TokenIdentifierValue(antiSpamTaxTokenIdentifier)) + .addArg(new BigUIntValue(antiSpamTaxTokenAmount)) + .addArg(new U64Value(mintLimit)) + .addArg(new AddressValue(treasury_address)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return initializeContractTx; + } + + /** + * + * @param senderAddress The address of the sender, must be the admin of the contract + * @param treasuryAddress The address of the treasury to collect the anti spam tax + */ + setTreasuryAddress( + senderAddress: IAddress, + treasuryAddress: IAddress + ): Transaction { + const setTreasuryAddressTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setTreasuryAddress')) + .addArg(new AddressValue(treasuryAddress)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setTreasuryAddressTx; + } + + /** + * + * @param senderAddress The address of the sender, must be the admin of the contract + * @param maxSupply The maximum supply that can be minted + */ + setMaxSupply(senderAddress: IAddress, maxSupply: number): Transaction { + const setMaxSupplyTx = new Transaction({ + value: 0, + data: new ContractCallPayloadBuilder() + .setFunction(new ContractFunction('setMaxSupply')) + .addArg(new BigUIntValue(maxSupply)) + .build(), + receiver: this.contract.getAddress(), + gasLimit: 10000000, + sender: senderAddress, + chainID: this.chainID + }); + return setMaxSupplyTx; } /** From f9a3b5cf723a5eb321990cda3bc3e2c250bed13b Mon Sep 17 00:00:00 2001 From: Bucur David Date: Thu, 14 Sep 2023 21:30:04 +0300 Subject: [PATCH 17/32] view method for contract configuration Refs: #37 --- src/abis/data-nft-lease.abi.json | 59 ++++++++++++++++++++++++++++++++ src/interfaces.ts | 14 ++++++++ src/nft-minter.ts | 39 +++++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/src/abis/data-nft-lease.abi.json b/src/abis/data-nft-lease.abi.json index 05adf82..e9a31cf 100644 --- a/src/abis/data-nft-lease.abi.json +++ b/src/abis/data-nft-lease.abi.json @@ -605,6 +605,16 @@ } ] }, + { + "name": "getContractConfiguration", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "ContractConfiguration" + } + ] + }, { "name": "pause", "mutability": "mutable", @@ -979,6 +989,55 @@ ], "hasCallback": true, "types": { + "ContractConfiguration": { + "type": "struct", + "fields": [ + { + "name": "token_identifier", + "type": "TokenIdentifier" + }, + { + "name": "minted_tokens", + "type": "BigUint" + }, + { + "name": "tax_required", + "type": "bool" + }, + { + "name": "is_paused", + "type": "bool" + }, + { + "name": "max_royalties", + "type": "BigUint" + }, + { + "name": "min_royalties", + "type": "BigUint" + }, + { + "name": "mint_time_limit", + "type": "u64" + }, + { + "name": "is_whitelist_enabled", + "type": "bool" + }, + { + "name": "roles_are_set", + "type": "bool" + }, + { + "name": "claims_address", + "type": "Address" + }, + { + "name": "administrator_address", + "type": "Address" + } + ] + }, "DataNftAttributes": { "type": "struct", "fields": [ diff --git a/src/interfaces.ts b/src/interfaces.ts index b8c45c2..0d20b4b 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -92,3 +92,17 @@ export interface ViewDataReturnType { contentType: string; error?: string; } + +export interface ContractConfiguration { + tokenIdentifier: string; + mintedTokens: number; + isTaxRequired: boolean; + isContractPaused: boolean; + maxRoyalties: number; + minRoyalties: number; + mintTimeLimit: number; + isWhitelistEnabled: boolean; + rolesAreSet: boolean; + claimsAddress: string; + administratorAddress: string; +} diff --git a/src/nft-minter.ts b/src/nft-minter.ts index 17a49ba..e4a4424 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -6,6 +6,7 @@ import { ContractCallPayloadBuilder, ContractFunction, IAddress, + ResultsParser, StringValue, TokenIdentifierValue, Transaction, @@ -23,6 +24,7 @@ import { dataNFTDataStreamAdvertise, storeToIpfs } from './common/mint-utils'; +import { ContractConfiguration } from './interfaces'; export class NftMinter extends Minter { constructor(env: string, contractAddress: string, timeout: number = 10000) { @@ -419,4 +421,41 @@ export class NftMinter extends Minter { }); return claimRoyaltiesTx; } + + /** + * Retrieves the smart contract configuration + */ + async viewContractConfiguration(): Promise { + const interaction = + this.contract.methodsExplicit.getContractConfiguration(); + const query = interaction.buildQuery(); + const queryResponse = await this.networkProvider.queryContract(query); + const endpointDefinition = interaction.getEndpoint(); + const { firstValue, returnCode } = new ResultsParser().parseQueryResponse( + queryResponse, + endpointDefinition + ); + if (returnCode.isSuccess()) { + const returnValue = firstValue?.valueOf(); + const contractConfiguration: ContractConfiguration = { + tokenIdentifier: returnValue?.token_identifier.toString(), + mintedTokens: returnValue?.minted_tokens.toNumber(), + isTaxRequired: returnValue?.tax_required as boolean, + maxRoyalties: returnValue?.max_royalties.toNumber(), + minRoyalties: returnValue?.min_royalties.toNumber(), + mintTimeLimit: returnValue?.mint_time_limit.toNumber(), + isWhitelistEnabled: returnValue?.whitelist_enabled as boolean, + isContractPaused: returnValue?.is_paused as boolean, + rolesAreSet: returnValue?.roles_are_set as boolean, + claimsAddress: returnValue?.claims_address.toString(), + administratorAddress: returnValue?.administrator_address.toString() + }; + return contractConfiguration; + } else { + throw new Error('Error while retrieving the contract pause state'); + // throw new ErrContractQuery( + // 'Error while retrieving the contract pause state' + // ); + } + } } From 53bc4b16203abb5748c9c7874eaefe2f0c9f729e Mon Sep 17 00:00:00 2001 From: Bucur David Date: Thu, 14 Sep 2023 21:50:12 +0300 Subject: [PATCH 18/32] view methods of the contracts Refs: #37 --- src/minter.ts | 75 +++++++++++++++++++++++++++++++++++++++++++++++ src/nft-minter.ts | 58 ++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/src/minter.ts b/src/minter.ts index 0a06e32..77a830f 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -146,6 +146,81 @@ export abstract class Minter { } } + /** + * Retrieves the minter whitelist + */ + async viewWhitelist(): Promise { + const interaction = this.contract.methodsExplicit.getWhiteList(); + const query = interaction.buildQuery(); + const queryResponse = await this.networkProvider.queryContract(query); + const endpointDefinition = interaction.getEndpoint(); + const { firstValue, returnCode } = new ResultsParser().parseQueryResponse( + queryResponse, + endpointDefinition + ); + if (returnCode.isSuccess()) { + const returnValue = firstValue?.valueOf(); + const whitelist: string[] = returnValue.map((addres: any) => + addres.toString() + ); + return whitelist; + } else { + throw new Error('Could not retrieve minter whitelist'); + // throw new ErrContractQuery('Could not retrieve requirements'); + } + } + + /** + * Retrives a list of addresses that are frozen for collection + */ + async viewCollectionFrozenAddresses(): Promise { + const interaction = this.contract.methodsExplicit.getCollectionFrozenList(); + const query = interaction.buildQuery(); + const queryResponse = await this.networkProvider.queryContract(query); + const endpointDefinition = interaction.getEndpoint(); + const { firstValue, returnCode } = new ResultsParser().parseQueryResponse( + queryResponse, + endpointDefinition + ); + if (returnCode.isSuccess()) { + const returnValue = firstValue?.valueOf(); + const frozenAddresses: string[] = returnValue.map((addres: any) => + addres.toString() + ); + return frozenAddresses; + } else { + throw new Error('Could not retrieve frozen addresses'); + // throw new ErrContractQuery('Could not retrieve requirements'); + } + } + + /** + * Retrives a list of nonces that are frozen for address + * @param address The address to check + */ + async viewAddressFrozenNonces(address: IAddress): Promise { + const interaction = this.contract.methodsExplicit.getSftsFrozenForAddress([ + new AddressValue(address) + ]); + const query = interaction.buildQuery(); + const queryResponse = await this.networkProvider.queryContract(query); + const endpointDefinition = interaction.getEndpoint(); + const { firstValue, returnCode } = new ResultsParser().parseQueryResponse( + queryResponse, + endpointDefinition + ); + if (returnCode.isSuccess()) { + const returnValue = firstValue?.valueOf(); + const frozenNonces: number[] = returnValue.map((nonce: any) => + nonce.toNumber() + ); + return frozenNonces; + } else { + throw new Error('Could not retrieve frozen nonces'); + // throw new ErrContractQuery('Could not retrieve requirements'); + } + } + /** * Creates a `burn` transaction * @param senderAddress the address of the user diff --git a/src/nft-minter.ts b/src/nft-minter.ts index e4a4424..c523f59 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -458,4 +458,62 @@ export class NftMinter extends Minter { // ); } } + + /** + * Retrieves the addresses with transfer roles for contract collection + */ + async viewTransferRoles(): Promise { + const interaction = + this.contract.methodsExplicit.getAddressesWithTransferRole(); + const query = interaction.buildQuery(); + const queryResponse = await this.networkProvider.queryContract(query); + const endpointDefinition = interaction.getEndpoint(); + const { firstValue, returnCode } = new ResultsParser().parseQueryResponse( + queryResponse, + endpointDefinition + ); + if (returnCode.isSuccess()) { + const returnValue = firstValue?.valueOf(); + const addressesWithTransferRole: string[] = returnValue?.map( + (address: any) => address.toString() + ); + return addressesWithTransferRole; + } else { + throw new Error( + 'Error while retrieving the addresses with transfer roles' + ); + // throw new ErrContractQuery( + // 'Error while retrieving the addresses with transfer roles' + // ); + } + } + + /** + * Retrieves the addresss with update attributes roles for contract collection + */ + async viewUpdateAttributesRoles(): Promise { + const interaction = + this.contract.methodsExplicit.getAddressesWithUpdateAttributesRole(); + const query = interaction.buildQuery(); + const queryResponse = await this.networkProvider.queryContract(query); + const endpointDefinition = interaction.getEndpoint(); + const { firstValue, returnCode } = new ResultsParser().parseQueryResponse( + queryResponse, + endpointDefinition + ); + if (returnCode.isSuccess()) { + const returnValue = firstValue?.valueOf(); + const addressesWithUpdateAttributesRole: string[] = returnValue?.map( + (address: any) => address.toString() + ); + return addressesWithUpdateAttributesRole; + } else { + throw new Error( + 'Error while retrieving the addresses with update attributes roles' + ); + // throw new ErrContractQuery( + // 'Error while retrieving the addresses with update attributes roles' + // ); + } + } } From 09ed5bec49aef30a8cf66a59afdba7287e760be8 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Thu, 14 Sep 2023 21:53:34 +0300 Subject: [PATCH 19/32] docs: constructor documentation for subclasses Refs: #37 --- src/minter.ts | 5 ----- src/nft-minter.ts | 6 ++++++ src/sft-minter.ts | 5 +++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/minter.ts b/src/minter.ts index 77a830f..5506409 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -42,11 +42,6 @@ export abstract class Minter { readonly env: string; readonly imageServiceUrl: string; - /** - * Creates a new instance of the `DataNftMinter` which can be used to interact with the Data NFT-FT minter smart contract - * @param env 'devnet' | 'mainnet' | 'testnet' - * @param timeout Timeout for the network provider (DEFAULT = 10000ms) - */ protected constructor( env: string, contractAddress: string, diff --git a/src/nft-minter.ts b/src/nft-minter.ts index c523f59..bc45b1f 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -27,6 +27,12 @@ import { import { ContractConfiguration } from './interfaces'; export class NftMinter extends Minter { + /** + * Creates a new instance of the `NftMinter` class, which is used to interact with the factory generated smart contract. + * @param env 'devnet' | 'mainnet' | 'testnet' + * @param contractAddress The address of the factory generated smart contract + * @param timeout Timeout for the network provider (DEFAULT = 10000ms) + */ constructor(env: string, contractAddress: string, timeout: number = 10000) { super(env, contractAddress, dataNftLeaseAbi, timeout); } diff --git a/src/sft-minter.ts b/src/sft-minter.ts index b0c228e..e41e792 100644 --- a/src/sft-minter.ts +++ b/src/sft-minter.ts @@ -30,6 +30,11 @@ import { } from './common/mint-utils'; export class SftMinter extends Minter { + /** + * 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' + * @param timeout Timeout for the network provider (DEFAULT = 10000ms) + */ constructor(env: string, timeout: number = 10000) { super( env, From 96285281fe219e54938243304ebfee491e9e4cf6 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Mon, 18 Sep 2023 17:42:43 +0300 Subject: [PATCH 20/32] refactor: gas limits and types Refs: #37 --- src/index.ts | 1 + src/minter.ts | 49 ++++++++++++++++++----------------------------- src/nft-minter.ts | 20 +++++++++---------- 3 files changed, 30 insertions(+), 40 deletions(-) diff --git a/src/index.ts b/src/index.ts index a2d00eb..0159174 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './marketplace'; +export * from './config'; export * from './interfaces'; export * from './datanft'; export * from './minter'; diff --git a/src/minter.ts b/src/minter.ts index 5506409..a9243f6 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -22,18 +22,7 @@ import { itheumTokenIdentifier, networkConfiguration } from './config'; -import { NFTStorage, File } from 'nft.storage'; -import { - checkTraitsUrl, - checkUrlIsUp, - validateSpecificParamsMint -} from './common/utils'; import { MinterRequirements } from './interfaces'; -// import { -// ErrArgumentNotSet, -// ErrContractQuery, -// ErrFailedOperation -// } from './errors'; export abstract class Minter { readonly contract: SmartContract; @@ -258,7 +247,7 @@ export abstract class Minter { .setFunction(new ContractFunction('setLocalRoles')) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 90000000, sender: senderAddress, chainID: this.chainID }); @@ -276,7 +265,7 @@ export abstract class Minter { .addArg(new BooleanValue(true)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 6000000, sender: senderAddress, chainID: this.chainID }); @@ -295,7 +284,7 @@ export abstract class Minter { .addArg(new BooleanValue(false)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 6000000, sender: senderAddress, chainID: this.chainID }); @@ -321,7 +310,7 @@ export abstract class Minter { .addArg(new BigUIntValue(tax)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 6000000, sender: senderAddress, chainID: this.chainID }); @@ -349,7 +338,7 @@ export abstract class Minter { .addArg(new BigUIntValue(maxRoyalties)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 6000000, sender: senderAddress, chainID: this.chainID }); @@ -371,7 +360,7 @@ export abstract class Minter { .addArg(new BooleanValue(is_enabled)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 6000000, sender: senderAddress, chainID: this.chainID }); @@ -386,7 +375,7 @@ export abstract class Minter { whitelist( senderAddress: IAddress, addresses: string[], - gasLimit = 0 + extraGas = 0 ): Transaction { const whitelistTx = new Transaction({ value: 0, @@ -397,7 +386,7 @@ export abstract class Minter { ) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000 + gasLimit, + gasLimit: 50000000 + extraGas, sender: senderAddress, chainID: this.chainID }); @@ -411,7 +400,7 @@ export abstract class Minter { delist( senderAddress: IAddress, addresses: string[], - gasLimit: 0 + extraGas = 0 ): Transaction { const delistTx = new Transaction({ value: 0, @@ -422,7 +411,7 @@ export abstract class Minter { ) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000 + gasLimit, + gasLimit: 50000000 + extraGas, sender: senderAddress, chainID: this.chainID }); @@ -441,7 +430,7 @@ export abstract class Minter { .addArg(new U64Value(timeLimit)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 6000000, sender: senderAddress, chainID: this.chainID }); @@ -463,7 +452,7 @@ export abstract class Minter { .addArg(new AddressValue(newAdministrator)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 6000000, sender: senderAddress, chainID: this.chainID }); @@ -483,7 +472,7 @@ export abstract class Minter { .setFunction(new ContractFunction('pause')) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 100000000, sender: senderAddress, chainID: this.chainID }); @@ -501,7 +490,7 @@ export abstract class Minter { .setFunction(new ContractFunction('unpause')) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 100000000, sender: senderAddress, chainID: this.chainID }); @@ -521,7 +510,7 @@ export abstract class Minter { .addArg(new AddressValue(freezeAddress)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 100000000, sender: senderAddress, chainID: this.chainID }); @@ -541,7 +530,7 @@ export abstract class Minter { .addArg(new AddressValue(unfreezeAddress)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 100000000, sender: senderAddress, chainID: this.chainID }); @@ -568,7 +557,7 @@ export abstract class Minter { .addArg(new AddressValue(freezeAddress)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 100000000, sender: senderAddress, chainID: this.chainID }); @@ -594,7 +583,7 @@ export abstract class Minter { .addArg(new AddressValue(unfreezeAddress)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 100000000, sender: senderAddress, chainID: this.chainID }); @@ -622,7 +611,7 @@ export abstract class Minter { .addArg(new AddressValue(wipeAddress)) .build(), receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 100000000, sender: senderAddress, chainID: this.chainID }); diff --git a/src/nft-minter.ts b/src/nft-minter.ts index bc45b1f..11b9073 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -52,7 +52,7 @@ export class NftMinter extends Minter { tokenTicker: string, mintLimit: number, requireMintTax: boolean, - options: { + options?: { taxTokenIdentifier: string; taxTokenAmount: number; } @@ -82,7 +82,7 @@ export class NftMinter extends Minter { value: 50000000000000000, data: data, receiver: this.contract.getAddress(), - gasLimit: 10000000, + gasLimit: 50000000, sender: senderAddress, chainID: this.chainID }); @@ -150,7 +150,6 @@ export class NftMinter extends Minter { * @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 royalties the royalties to be set for the Data NFT-FT. A number between 0 and 50. This equates to a % value. e.g. 10% - * @param supply the supply of the Data NFT-FT. A number between 1 and 1000. * @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 @@ -167,7 +166,6 @@ export class NftMinter extends Minter { dataStreamUrl: string, dataPreviewUrl: string, royalties: number, - supply: number, datasetTitle: string, datasetDescription: string, options?: { @@ -191,14 +189,12 @@ export class NftMinter extends Minter { senderAddress, tokenName, royalties, - supply, datasetTitle, datasetDescription, _mandatoryParamsList: [ 'senderAddress', 'tokenName', 'royalties', - 'supply', 'datasetTitle', 'datasetDescription' ] @@ -274,7 +270,12 @@ export class NftMinter extends Minter { } let data; - if (antiSpamTax && antiSpamTokenIdentifier && antiSpamTax > 0) { + if ( + antiSpamTax && + antiSpamTokenIdentifier && + antiSpamTokenIdentifier != 'EGLD' && + antiSpamTax > 0 + ) { data = new ContractCallPayloadBuilder() .setFunction(new ContractFunction('ESDTTransfer')) .addArg(new TokenIdentifierValue(antiSpamTokenIdentifier)) @@ -287,7 +288,6 @@ export class NftMinter extends Minter { .addArg(new StringValue(dataNftStreamUrlEncrypted)) .addArg(new StringValue(dataPreviewUrl)) .addArg(new U64Value(royalties)) - .addArg(new U64Value(supply)) .addArg(new StringValue(datasetTitle)) .addArg(new StringValue(datasetDescription)) .build(); @@ -301,13 +301,13 @@ export class NftMinter extends Minter { .addArg(new StringValue(dataNftStreamUrlEncrypted)) .addArg(new StringValue(dataPreviewUrl)) .addArg(new U64Value(royalties)) - .addArg(new U64Value(supply)) .addArg(new StringValue(datasetTitle)) .addArg(new StringValue(datasetDescription)) .build(); } const mintTx = new Transaction({ + value: antiSpamTokenIdentifier == 'EGLD' ? antiSpamTax : 0, data, sender: senderAddress, receiver: this.contract.getAddress(), @@ -450,7 +450,7 @@ export class NftMinter extends Minter { maxRoyalties: returnValue?.max_royalties.toNumber(), minRoyalties: returnValue?.min_royalties.toNumber(), mintTimeLimit: returnValue?.mint_time_limit.toNumber(), - isWhitelistEnabled: returnValue?.whitelist_enabled as boolean, + isWhitelistEnabled: returnValue?.is_whitelist_enabled as boolean, isContractPaused: returnValue?.is_paused as boolean, rolesAreSet: returnValue?.roles_are_set as boolean, claimsAddress: returnValue?.claims_address.toString(), From ca226e2aee9b863e5a8957992c8481361178943b Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 20 Sep 2023 16:25:45 +0300 Subject: [PATCH 21/32] feat: claims address on initialize method Refs: #37 --- src/nft-minter.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nft-minter.ts b/src/nft-minter.ts index 11b9073..ede56c0 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -52,6 +52,7 @@ export class NftMinter extends Minter { tokenTicker: string, mintLimit: number, requireMintTax: boolean, + claimsAddress: IAddress, options?: { taxTokenIdentifier: string; taxTokenAmount: number; @@ -65,6 +66,7 @@ export class NftMinter extends Minter { .addArg(new StringValue(tokenTicker)) .addArg(new BigUIntValue(mintLimit)) .addArg(new BooleanValue(requireMintTax)) + .addArg(new AddressValue(claimsAddress)) .addArg(new TokenIdentifierValue(options.taxTokenIdentifier)) .addArg(new BigUIntValue(options.taxTokenAmount)) .build(); @@ -75,6 +77,7 @@ export class NftMinter extends Minter { .addArg(new StringValue(tokenTicker)) .addArg(new BigUIntValue(mintLimit)) .addArg(new BooleanValue(requireMintTax)) + .addArg(new AddressValue(claimsAddress)) .build(); } From 895ad3391dafd8c9a826ee78ed52f3879e1c721f Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 20 Sep 2023 17:46:32 +0300 Subject: [PATCH 22/32] refactor: update abi Refs: #37 --- README.md | 131 ------------------------------- src/abis/data-nft-lease.abi.json | 4 + 2 files changed, 4 insertions(+), 131 deletions(-) diff --git a/README.md b/README.md index 4ae1957..17311a2 100644 --- a/README.md +++ b/README.md @@ -87,137 +87,6 @@ dataNft.viewDataViaMVXNativeAuth({ }); // optional params "stream" (stream out data instead of downloading file), "fwdAllHeaders"/"fwdHeaderKeys" can be used to pass on the headers like Authorization to origin Data Stream servers ``` -### 2. Interacting with Data NFT Minter - -```typescript -import { DataNftMinter } from '@itheum/sdk-mx-data-nft'; - -const dataNftMinter = new DataNftMinter('devnet' | 'testnet' | 'mainnet'); - -// View minter smart contract requirements -const requirements = await dataNftMinter.viewMinterRequirements( - new Address('erd1...') -); - -// View contract pause state -const result = await dataNftMinter.viewContractPauseState(); -``` - -#### Create a mint transaction - -Method 1: Mint a new Data NFT with Ithuem generated image and traits. -Currently only supports [nft.storage](https://nft.storage/docs/quickstart/#get-an-api-token). - -```typescript -const transaction = await dataNftMinter.mint( - new Address('erd1...'), - 'TEST-TOKEN', - 'https://marshal.com', - 'https://streamdata.com', - 'https://previewdata', - 15, - 1000, - 'Test Title', - 'Test Description', - 10000000000, - { - nftStorageToken: 'API TOKEN' - } -); -``` - -Method 2: Mint a new Data NFT with custom image and traits. -Traits should be compliant with the Itheum [traits structure](#traits-structure). - -```typescript -const transaction = await dataNftMinter.mint( - new Address('erd1'), - 'TEST-TOKEN', - 'https://marshal.com', - 'https://streamdata.com', - 'https://previewdata', - 15, - 1000, - 'Test Title', - 'Test Description', - 10000000000, - { - imageUrl: 'https://imageurl.com', - traitsUrl: 'https://traitsurl.com' - } -); -``` - -#### Create a burn transaction - -```typescript -const transaction = await dataNftMarket.burn( - new Address('erd1'), - dataNftNonce, - quantityToBurn -); -``` - -### 3. Interacting with Data NFT Marketplace - -```typescript -import { DataNftMarket } from '@itheum/sdk-mx-data-nft'; - -const dataNftMarket = new DataNftMarket('devnet' | 'testnet' | 'mainnet'); - -// View requirements -const result = await dataNftMarket.viewRequirements(); - -// View address listed offers -const result = await dataNftMarket.viewAddressListedOffers(new Address('')); - -// View address paged offers -const result = await dataNftMarket.viewAddressPagedOffers( - 1, - 10, - new Address('') -); - -// View address total offers -const result = await dataNftMarket.viewAddressTotalOffers(new Address('')); - -// View address cancelled offers -const result = await dataNftMarket.viewAddressCancelledOffers(new Address('')); - -// View offers paged -const result = await dataNftMarket.viewPagedOffers(1, 10); - -// View offers -const result = await dataNftMarket.viewOffers(); - -// View number of offers listed -const result = await dataNftMarket.viewNumberOfOffers(); - -// View contract pause state -const result = await dataNftMarket.viewContractPauseState(); - -// View last valid offer id -const result = await dataNftMarket.viewLastValidOfferId(); - -// Create addOffer transaction -const result = dataNftMarket.addOffer(new Address(''), '', 0, 0, '', 0, 0, 0); - -// Create acceptOffer transaction -const result = dataNftMarket.acceptOffer(new Address(''), 0, 0, 0); - -// Create cancelOffer transaction -const result = dataNftMarket.cancelOffer(new Address(''), 0); - -// Create cancelOffer transaction without sending the funds back to the owner -const result = dataNftMarket.cancelOffer(new Address(''), 0, false); - -// Create withdrawFromCancelledOffer transaction -const result = dataNftMarket.withdrawCancelledOffer(new Address(''), 0); - -// Create changeOfferPrice transaction -const result = dataNftMarket.changeOfferPrice(new Address(''), 0, 0); -``` - ### Traits structure Items below marked "required" are the "minimum" required for it to be compatible with the Itheum protocol. You can add any additional traits you may need for your own reasons. diff --git a/src/abis/data-nft-lease.abi.json b/src/abis/data-nft-lease.abi.json index e9a31cf..72d5db2 100644 --- a/src/abis/data-nft-lease.abi.json +++ b/src/abis/data-nft-lease.abi.json @@ -51,6 +51,10 @@ "name": "require_mint_tax", "type": "bool" }, + { + "name": "claims_address", + "type": "Address" + }, { "name": "payment", "type": "optional>", From 7e2459b5c8ec173dde69f681df79d9b5e3c03766 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 20 Sep 2023 20:08:55 +0300 Subject: [PATCH 23/32] feat: devnet2 support Refs: #37 --- src/config.ts | 13 +++++++++++++ src/minter.ts | 5 +++++ src/nft-minter.ts | 2 +- src/sft-minter.ts | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/config.ts b/src/config.ts index efe9e28..edf65a8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,6 @@ export enum EnvironmentsEnum { devnet = 'devnet', + devnet2 = 'devnet2', testnet = 'testnet', mainnet = 'mainnet' } @@ -14,6 +15,11 @@ const devnetNetworkConfig: Config = { networkProvider: 'https://devnet-api.multiversx.com' }; +const devnet2NetworkConfig: Config = { + chainID: 'D', + networkProvider: 'https://devnet2-api.multiversx.com' +}; + const mainnetNetworkConfig: Config = { chainID: '1', networkProvider: 'https://api.multiversx.com' @@ -26,12 +32,14 @@ const testnetNetworkConfig: Config = { export const itheumTokenIdentifier: { [key in EnvironmentsEnum]: string } = { devnet: 'ITHEUM-a61317', + devnet2: '', mainnet: 'ITHEUM-df6f26', testnet: '' }; export const dataNftTokenIdentifier: { [key in EnvironmentsEnum]: string } = { devnet: 'DATANFTFT4-3ba099', + devnet2: '', mainnet: 'DATANFTFT-e936d4', testnet: '' }; //[future] list of whitelisted tokens as Data NFTs @@ -39,30 +47,35 @@ export const dataNftTokenIdentifier: { [key in EnvironmentsEnum]: string } = { export const marketPlaceContractAddress: { [key in EnvironmentsEnum]: string } = { devnet: 'erd1qqqqqqqqqqqqqpgqrwtl03qdxjv2e52ta5ry4rg0z7l95neqfsxsp4y4xh', + devnet2: '', mainnet: 'erd1qqqqqqqqqqqqqpgqay2r64l9nhhvmaqw4qanywfd0954w2m3c77qm7drxc', testnet: '' }; export const minterContractAddress: { [key in EnvironmentsEnum]: string } = { devnet: 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm', + devnet2: '', mainnet: 'erd1qqqqqqqqqqqqqpgqmuzgkurn657afd3r2aldqy2snsknwvrhc77q3lj8l6', testnet: '' }; export const apiConfiguration: { [key in EnvironmentsEnum]: string } = { devnet: 'https://devnet-api.multiversx.com', + devnet2: 'https://devnet2-api.multiversx.com', mainnet: 'https://api.multiversx.com', testnet: 'https://testnet-api.multiversx.com' }; export const networkConfiguration: { [key in EnvironmentsEnum]: Config } = { devnet: devnetNetworkConfig, + devnet2: devnet2NetworkConfig, mainnet: mainnetNetworkConfig, testnet: testnetNetworkConfig }; export const imageService: { [key in EnvironmentsEnum]: string } = { devnet: 'https://api.itheumcloud-stg.com/datadexapi', + devnet2: 'https://api.itheumcloud-stg.com/datadexapi', mainnet: 'https://api.itheumcloud.com/datadexapi', testnet: '' }; diff --git a/src/minter.ts b/src/minter.ts index a9243f6..ee0bd1e 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -37,6 +37,11 @@ export abstract class Minter { abiFile: any, timeout: number = 10000 ) { + if (!(env in EnvironmentsEnum)) { + throw new Error( + `Invalid environment: ${env}, Expected: 'devnet' | 'devnet2' | 'mainnet' | 'testnet'` + ); + } this.env = env; const networkConfig = networkConfiguration[env as EnvironmentsEnum]; this.imageServiceUrl = imageService[env as EnvironmentsEnum]; diff --git a/src/nft-minter.ts b/src/nft-minter.ts index ede56c0..c967183 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -29,7 +29,7 @@ import { ContractConfiguration } from './interfaces'; export class NftMinter extends Minter { /** * Creates a new instance of the `NftMinter` class, which is used to interact with the factory generated smart contract. - * @param env 'devnet' | 'mainnet' | 'testnet' + * @param env 'devnet' | 'devnet2'| 'mainnet' | 'testnet' * @param contractAddress The address of the factory generated smart contract * @param timeout Timeout for the network provider (DEFAULT = 10000ms) */ diff --git a/src/sft-minter.ts b/src/sft-minter.ts index e41e792..ec4a81e 100644 --- a/src/sft-minter.ts +++ b/src/sft-minter.ts @@ -32,7 +32,7 @@ import { export class SftMinter extends Minter { /** * 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' + * @param env 'devnet' | 'devnet2' | 'mainnet' | 'testnet' * @param timeout Timeout for the network provider (DEFAULT = 10000ms) */ constructor(env: string, timeout: number = 10000) { From 9523077fa4e2f5523f11fbb6ecdecb51ecf828f5 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 20 Sep 2023 21:06:28 +0300 Subject: [PATCH 24/32] refactor: error handling Refs: #25 --- src/datanft.ts | 1 - src/errors.ts | 2 +- src/minter.ts | 30 ++++++++++++++---------------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/datanft.ts b/src/datanft.ts index af849f5..0a63dc1 100644 --- a/src/datanft.ts +++ b/src/datanft.ts @@ -11,7 +11,6 @@ import { networkConfiguration } from './config'; import { - checkStatus, createNftIdentifier, numberToPaddedHex, parseDataNft, diff --git a/src/errors.ts b/src/errors.ts index 1ec6864..ebcccdc 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -47,7 +47,7 @@ export class ErrAttributeNotSet extends Error { export class ErrContractQuery extends Error { public constructor(method: string, message?: string) { - super(`Failed to query contract: Method: ${method} : ${message}`); + super(`Failed to query contract with method: ${method} : ${message}`); } } diff --git a/src/minter.ts b/src/minter.ts index 6139edb..33d039d 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -23,7 +23,7 @@ import { networkConfiguration } from './config'; import { MinterRequirements } from './interfaces'; -import { ErrContractQuery } from './errors'; +import { ErrContractQuery, ErrNetworkConfig } from './errors'; export abstract class Minter { readonly contract: SmartContract; @@ -39,7 +39,7 @@ export abstract class Minter { timeout: number = 10000 ) { if (!(env in EnvironmentsEnum)) { - throw new Error( + throw new ErrNetworkConfig( `Invalid environment: ${env}, Expected: 'devnet' | 'devnet2' | 'mainnet' | 'testnet'` ); } @@ -82,16 +82,11 @@ export abstract class Minter { const returnValue = firstValue?.valueOf(); return new BooleanValue(returnValue).valueOf(); } else { - throw new Error('Error while retrieving the contract pause state'); - // throw new ErrContractQuery( - // 'Error while retrieving the contract pause state' - // ); + throw new ErrContractQuery( + 'Error while retrieving the contract pause state' + ); } } - - // [TO DO] Implement other general views between sft and nft minter - // [TO DO] Implement all general methods between sft and nft minter ? - /** * Retrieves the minter smart contract requirements for the given user * @param address the address of the user @@ -157,8 +152,7 @@ export abstract class Minter { ); return whitelist; } else { - throw new Error('Could not retrieve minter whitelist'); - // throw new ErrContractQuery('Could not retrieve requirements'); + throw new ErrContractQuery('viewWhitelist', returnCode.toString()); } } @@ -181,8 +175,10 @@ export abstract class Minter { ); return frozenAddresses; } else { - throw new Error('Could not retrieve frozen addresses'); - // throw new ErrContractQuery('Could not retrieve requirements'); + throw new ErrContractQuery( + 'viewCollectionFrozenAddresses', + returnCode.toString() + ); } } @@ -208,8 +204,10 @@ export abstract class Minter { ); return frozenNonces; } else { - throw new Error('Could not retrieve frozen nonces'); - // throw new ErrContractQuery('Could not retrieve requirements'); + throw new ErrContractQuery( + 'viewAddressFrozenNonces', + returnCode.toString() + ); } } From 0d1eed7858a3c0bcd5fea665750694b41c7c206c Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 20 Sep 2023 21:21:57 +0300 Subject: [PATCH 25/32] refactor: error handling Refs: #25 --- src/datanft.ts | 6 ++++-- src/sft-minter.ts | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/datanft.ts b/src/datanft.ts index 0a63dc1..1585879 100644 --- a/src/datanft.ts +++ b/src/datanft.ts @@ -164,7 +164,9 @@ export class DataNft { return dataNfts; } } catch (error: any) { - throw new ErrDataNftCreate('Response could not be parsed'); + throw new ErrDataNftCreate( + 'Payload could not be parsed: ' + error.message + ); } } @@ -216,8 +218,8 @@ export class DataNft { checkStatus(res); const data = await res.json(); - const dataNfts: DataNft[] = this.createFromApiResponseOrBulk(data); + const dataNfts: DataNft[] = this.createFromApiResponseOrBulk(data); return dataNfts; } diff --git a/src/sft-minter.ts b/src/sft-minter.ts index ec4a81e..aa24a43 100644 --- a/src/sft-minter.ts +++ b/src/sft-minter.ts @@ -16,7 +16,6 @@ import { itheumTokenIdentifier, minterContractAddress } from './config'; -import { MinterRequirements } from './interfaces'; import dataNftMinterAbi from './abis/datanftmint.abi.json'; import { checkTraitsUrl, From 926c41e8010de7efd569caa6e115d6aca473de76 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 20 Sep 2023 21:27:32 +0300 Subject: [PATCH 26/32] tests: update minter class name Refs: #38 --- tests/environment.test.ts | 8 ++++---- tests/minter.test.ts | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/environment.test.ts b/tests/environment.test.ts index 279a470..0b431c0 100644 --- a/tests/environment.test.ts +++ b/tests/environment.test.ts @@ -1,5 +1,5 @@ import { ApiNetworkProvider } from '@multiversx/sdk-network-providers/out'; -import { DataNftMarket, DataNftMinter } from '../src/index'; +import { DataNftMarket, SftMinter } from '../src/index'; describe('testing environment market', () => { test('#devnet-default', async () => { @@ -49,7 +49,7 @@ describe('testing environment market', () => { describe('testing environment minter', () => { test('#devnet-default', async () => { - const datanft = new DataNftMinter('devnet'); + const datanft = new SftMinter('devnet'); expect(datanft.chainID).toStrictEqual('D'); expect(datanft.networkProvider).toStrictEqual( @@ -71,7 +71,7 @@ describe('testing environment minter', () => { }); test('#devnet-custom-timeout', async () => { - const datanft = new DataNftMinter('devnet', 5000); + const datanft = new SftMinter('devnet', 5000); expect(datanft.chainID).toStrictEqual('D'); expect(datanft.networkProvider).toStrictEqual( @@ -82,7 +82,7 @@ describe('testing environment minter', () => { }); test('#mainnet-custom-timeout', async () => { - const datanft = new DataNftMinter('mainnet', 5000); + const datanft = new SftMinter('mainnet', 5000); expect(datanft.chainID).toStrictEqual('1'); expect(datanft.networkProvider).toStrictEqual( diff --git a/tests/minter.test.ts b/tests/minter.test.ts index ce159d7..11ad11a 100644 --- a/tests/minter.test.ts +++ b/tests/minter.test.ts @@ -1,9 +1,9 @@ import { Address, Transaction } from '@multiversx/sdk-core/out'; -import { DataNftMinter, Minter, MinterRequirements } from '../src'; +import { SftMinter, Minter, MinterRequirements } from '../src'; describe('Data Nft Minter Test', () => { test('#getAddress', async () => { - const dataNftMarket = new DataNftMinter('devnet'); + const dataNftMarket = new SftMinter('devnet'); expect(dataNftMarket.getContractAddress()).toBeInstanceOf(Address); expect(dataNftMarket.getContractAddress().bech32()).toStrictEqual( 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' @@ -11,7 +11,7 @@ describe('Data Nft Minter Test', () => { }); test('#viewMinterRequirements', async () => { - const dataNftMarket = new DataNftMinter('devnet'); + const dataNftMarket = new SftMinter('devnet'); const result = await dataNftMarket.viewMinterRequirements( new Address( @@ -22,7 +22,7 @@ describe('Data Nft Minter Test', () => { }); test('#burn', async () => { - const dataNftMarket = new DataNftMinter('devnet'); + const dataNftMarket = new SftMinter('devnet'); const result = await dataNftMarket.burn( new Address( @@ -35,7 +35,7 @@ describe('Data Nft Minter Test', () => { }); test('#viewContractpauseState', async () => { - const dataNftMarket = new DataNftMinter('devnet'); + const dataNftMarket = new SftMinter('devnet'); const result = await dataNftMarket.viewContractPauseState(); expect(typeof result).toBe('boolean'); From 7427c5403864b1b0cfbdb35a4e80dc9e074c1a9c Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 20 Sep 2023 22:02:48 +0300 Subject: [PATCH 27/32] refactor: consistent address parameter Refs: #25 --- README.md | 127 ++++++++++++++++++++++++++++++++++++++++ src/minter.ts | 4 +- src/nft-minter.ts | 6 +- src/sft-minter.ts | 3 +- tests/nftminter.test.ts | 121 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 tests/nftminter.test.ts diff --git a/README.md b/README.md index 17311a2..7f40e7d 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,133 @@ dataNft.viewDataViaMVXNativeAuth({ }); // optional params "stream" (stream out data instead of downloading file), "fwdAllHeaders"/"fwdHeaderKeys" can be used to pass on the headers like Authorization to origin Data Stream servers ``` +### 2. Interacting with Data NFT Minter + +```typescript +import { DataNftMinter } from '@itheum/sdk-mx-data-nft'; + +const dataNftMinter = new DataNftMinter('devnet' | 'testnet' | 'mainnet'); + +// View minter smart contract requirements +const requirements = await dataNftMinter.viewMinterRequirements( + new Address('erd1...') +); + +// View contract pause state +const result = await dataNftMinter.viewContractPauseState(); +``` + +#### Create a mint transaction + +Method 1: Mint a new Data NFT with Ithuem generated image and traits. +Currently only supports [nft.storage](https://nft.storage/docs/quickstart/#get-an-api-token). + +```typescript +const transaction = await nftMinter.mint( + new Address('erd1...'), + 'TEST-TOKEN', + 'https://marshal.com', + 'https://streamdata.com', + 'https://previewdata', + 1000, + 'Test Title', + 'Test Description', + { + nftStorageToken: 'API TOKEN' + } +); +``` + +Method 2: Mint a new Data NFT with custom image and traits. +Traits should be compliant with the Itheum [traits structure](#traits-structure). + +```typescript +const transaction = await nftMinter.mint( + new Address('erd1'), + 'TEST-TOKEN', + 'https://marshal.com', + 'https://streamdata.com', + 'https://previewdata', + 1000, + 'Test Title', + 'Test Description', + { + imageUrl: 'https://imageurl.com', + traitsUrl: 'https://traitsurl.com' + } +); +``` + +#### Create a burn transaction + +```typescript +const transaction = await dataNftMarket.burn( + new Address('erd1'), + dataNftNonce, + quantityToBurn +); +``` + +### 3. Interacting with Data NFT Marketplace + +```typescript +import { DataNftMarket } from '@itheum/sdk-mx-data-nft'; + +const dataNftMarket = new DataNftMarket('devnet' | 'testnet' | 'mainnet'); + +// View requirements +const result = await dataNftMarket.viewRequirements(); + +// View address listed offers +const result = await dataNftMarket.viewAddressListedOffers(new Address('')); + +// View address paged offers +const result = await dataNftMarket.viewAddressPagedOffers( + 1, + 10, + new Address('') +); + +// View address total offers +const result = await dataNftMarket.viewAddressTotalOffers(new Address('')); + +// View address cancelled offers +const result = await dataNftMarket.viewAddressCancelledOffers(new Address('')); + +// View offers paged +const result = await dataNftMarket.viewPagedOffers(1, 10); + +// View offers +const result = await dataNftMarket.viewOffers(); + +// View number of offers listed +const result = await dataNftMarket.viewNumberOfOffers(); + +// View contract pause state +const result = await dataNftMarket.viewContractPauseState(); + +// View last valid offer id +const result = await dataNftMarket.viewLastValidOfferId(); + +// Create addOffer transaction +const result = dataNftMarket.addOffer(new Address(''), '', 0, 0, '', 0, 0, 0); + +// Create acceptOffer transaction +const result = dataNftMarket.acceptOffer(new Address(''), 0, 0, 0); + +// Create cancelOffer transaction +const result = dataNftMarket.cancelOffer(new Address(''), 0); + +// Create cancelOffer transaction without sending the funds back to the owner +const result = dataNftMarket.cancelOffer(new Address(''), 0, false); + +// Create withdrawFromCancelledOffer transaction +const result = dataNftMarket.withdrawCancelledOffer(new Address(''), 0); + +// Create changeOfferPrice transaction +const result = dataNftMarket.changeOfferPrice(new Address(''), 0, 0); +``` + ### Traits structure Items below marked "required" are the "minimum" required for it to be compatible with the Itheum protocol. You can add any additional traits you may need for your own reasons. diff --git a/src/minter.ts b/src/minter.ts index 33d039d..a59aa13 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -34,7 +34,7 @@ export abstract class Minter { protected constructor( env: string, - contractAddress: string, + contractAddress: IAddress, abiFile: any, timeout: number = 10000 ) { @@ -54,7 +54,7 @@ export abstract class Minter { } ); this.contract = new SmartContract({ - address: new Address(contractAddress), + address: contractAddress, abi: AbiRegistry.create(abiFile) }); } diff --git a/src/nft-minter.ts b/src/nft-minter.ts index c967183..b3aba84 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -33,7 +33,7 @@ export class NftMinter extends Minter { * @param contractAddress The address of the factory generated smart contract * @param timeout Timeout for the network provider (DEFAULT = 10000ms) */ - constructor(env: string, contractAddress: string, timeout: number = 10000) { + constructor(env: string, contractAddress: IAddress, timeout: number = 10000) { super(env, contractAddress, dataNftLeaseAbi, timeout); } @@ -143,7 +143,7 @@ export class NftMinter extends Minter { * * 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 + * 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](https://github.com/Itheum/sdk-mx-data-nft#traits-structure) structure * * For more information, see the [README documentation](https://github.com/Itheum/sdk-mx-data-nft#create-a-mint-transaction). * @@ -159,7 +159,7 @@ export class NftMinter extends Minter { * - 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 - * - antiSpamTokenIdentifier: the anti spam token identifier to be used for the minting (default = `ITHEUM` token identifier based on the {@link EnvironmentsEnum}) + * - antiSpamTokenIdentifier: the anti spam token identifier to be used for the minting * - antiSpamTax: the anti spam tax to be set for the Data NFT-FT with decimals. Needs to be greater than 0 and should be obtained in real time via {@link viewMinterRequirements} prior to calling mint. */ async mint( diff --git a/src/sft-minter.ts b/src/sft-minter.ts index aa24a43..99604fb 100644 --- a/src/sft-minter.ts +++ b/src/sft-minter.ts @@ -1,4 +1,5 @@ import { + Address, AddressValue, BigUIntValue, ContractCallPayloadBuilder, @@ -37,7 +38,7 @@ export class SftMinter extends Minter { constructor(env: string, timeout: number = 10000) { super( env, - minterContractAddress[env as EnvironmentsEnum], + new Address(minterContractAddress[env as EnvironmentsEnum]), dataNftMinterAbi, timeout ); diff --git a/tests/nftminter.test.ts b/tests/nftminter.test.ts new file mode 100644 index 0000000..35a74a4 --- /dev/null +++ b/tests/nftminter.test.ts @@ -0,0 +1,121 @@ +import { Address, Transaction } from '@multiversx/sdk-core/out'; +import { NftMinter } from '../src'; + +describe('Nft minter test', () => { + test('#initialize minter', async () => { + const factoryGeneratedContract = new Address( + 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' + ); + const nftMinter = new NftMinter('devnet', factoryGeneratedContract); + + expect(nftMinter).toBeInstanceOf(NftMinter); + }); + + test('#initialize minter contract transaction', async () => { + const factoryGeneratedContract = new Address( + 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' + ); + const nftMinter = new NftMinter('devnet', factoryGeneratedContract); + + const adminOfContract = new Address( + 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' + ); + + const initTx = nftMinter.initializeContract( + adminOfContract, + 'Collection-Name-To-Mint', + 'CollectionTicker', + 0, + false, + new Address( + 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' + ) + ); + expect(initTx).toBeInstanceOf(Transaction); + }); + + test('#mint nft using itheum generated image', async () => { + const factoryGeneratedContract = new Address( + 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' + ); + const nftMinter = new NftMinter('devnet', factoryGeneratedContract); + + const senderAddress = new Address( + 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' + ); + + const mintTx = await nftMinter.mint( + senderAddress, + 'TokenName', + 'https://d37x5igq4vw5mq.cloudfront.net/datamarshalapi/achilles/v1', + 'https://raw.githubusercontent.com/Itheum/data-assets/main/Health/H1__Signs_of_Anxiety_in_American_Households_due_to_Covid19/dataset.json', + 'https://itheumapi.com/programReadingPreview/70dc6bd0-59b0-11e8-8d54-2d562f6cba54', + 1000, + 'Title for token', + 'Description for token', + { + nftStorageToken: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6ZXRocjoweDMzQjZGNzhmOTZmZjVmMGIwMUJFNzJmZTQ0NDRmMjBCYzhkOEQ0REUiLCJpc3MiOiJuZnQtc3RvcmFnZSIsImlhdCI6MTY5MTc0NDc5NDY5MCwibmFtZSI6InRlc3QifQ.lTjq16CgrDipiVClOrbWNt0A0zYkJ9YGVeDz1TlGqQ0' + } + ); + expect(mintTx).toBeInstanceOf(Transaction); + }, 20000); + + test('#mint nft using your image and metadata', async () => { + const factoryGeneratedContract = new Address( + 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' + ); + const nftMinter = new NftMinter('devnet', factoryGeneratedContract); + + const senderAddress = new Address( + 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' + ); + + const mintTx = await nftMinter.mint( + senderAddress, + 'TokenName', + 'https://d37x5igq4vw5mq.cloudfront.net/datamarshalapi/achilles/v1', + 'https://raw.githubusercontent.com/Itheum/data-assets/main/Health/H1__Signs_of_Anxiety_in_American_Households_due_to_Covid19/dataset.json', + 'https://itheumapi.com/programReadingPreview/70dc6bd0-59b0-11e8-8d54-2d562f6cba54', + 1000, + 'Title for token', + 'Description for token', + { + imageUrl: + 'https://ipfs.io/ipfs/bafybeih7bvpcfj42nawm7g4bkbu25cqxbhlzth5sxm6qjwis3tke23p7ty/image.png', + traitsUrl: + 'https://ipfs.io/ipfs/bafybeih7bvpcfj42nawm7g4bkbu25cqxbhlzth5sxm6qjwis3tke23p7ty/metadata.json' + } + ); + expect(mintTx).toBeInstanceOf(Transaction); + }, 20000); + + test('#mint nft using tax for minting', async () => { + const factoryGeneratedContract = new Address( + 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' + ); + const nftMinter = new NftMinter('devnet', factoryGeneratedContract); + + const senderAddress = new Address( + 'erd1qqqqqqqqqqqqqpgqpd9qxrq5a03jrneafmlmckmlj5zgdj55fsxsqa7jsm' + ); + const mintTx = await nftMinter.mint( + senderAddress, + 'TokenName', + 'https://d37x5igq4vw5mq.cloudfront.net/datamarshalapi/achilles/v1', + 'https://raw.githubusercontent.com/Itheum/data-assets/main/Health/H1__Signs_of_Anxiety_in_American_Households_due_to_Covid19/dataset.json', + 'https://itheumapi.com/programReadingPreview/70dc6bd0-59b0-11e8-8d54-2d562f6cba54', + 1000, + 'Title for token', + 'Description for token', + { + nftStorageToken: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6ZXRocjoweDMzQjZGNzhmOTZmZjVmMGIwMUJFNzJmZTQ0NDRmMjBCYzhkOEQ0REUiLCJpc3MiOiJuZnQtc3RvcmFnZSIsImlhdCI6MTY5MTc0NDc5NDY5MCwibmFtZSI6InRlc3QifQ.lTjq16CgrDipiVClOrbWNt0A0zYkJ9YGVeDz1TlGqQ0', + antiSpamTax: 1000000000000000000, + antiSpamTokenIdentifier: 'EGLD' + } + ); + + expect(mintTx).toBeInstanceOf(Transaction); + }); +}); From a6339cb002e322d1675deb36dadb3e9f97074d2a Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 20 Sep 2023 22:10:08 +0300 Subject: [PATCH 28/32] refactor: error handling and File import Refs: #25 --- src/common/mint-utils.ts | 2 +- src/errors.ts | 5 +++++ src/nft-minter.ts | 8 +++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/common/mint-utils.ts b/src/common/mint-utils.ts index c2d12ab..a0cb354 100644 --- a/src/common/mint-utils.ts +++ b/src/common/mint-utils.ts @@ -1,4 +1,4 @@ -import { NFTStorage } from 'nft.storage'; +import { NFTStorage, File } from 'nft.storage'; export async function dataNFTDataStreamAdvertise( dataNFTStreamUrl: string, diff --git a/src/errors.ts b/src/errors.ts index ebcccdc..9f972d2 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -7,6 +7,11 @@ export class ErrNetworkConfig extends Error { } } +export class ErrArgumentNotSet extends Error { + public constructor(argument: string, message?: string) { + super(`Argument "${argument}" is not set. ${message}`); + } +} export class ErrInvalidArgument extends Error { public constructor(message: string) { super(`Invalid argument: ${message}`); diff --git a/src/nft-minter.ts b/src/nft-minter.ts index b3aba84..599b9cc 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -25,6 +25,7 @@ import { storeToIpfs } from './common/mint-utils'; import { ContractConfiguration } from './interfaces'; +import { ErrArgumentNotSet } from './errors'; export class NftMinter extends Minter { /** @@ -234,13 +235,10 @@ export class NftMinter extends Minter { if (!imageUrl) { if (!nftStorageToken) { - throw new Error( + throw new ErrArgumentNotSet( + 'nftStorageToken', 'NFT Storage token is required when not using custom image and traits' ); - // throw new ErrArgumentNotSet( - // 'nftStorageToken', - // 'NFT Storage token is required when not using custom image and traits' - // ); } const { image, traits } = await createFileFromUrl( `${this.imageServiceUrl}/v1/generateNFTArt?hash=${dataNftHash}`, From 61a5b65db1585da94c89c02669c732897b9259a3 Mon Sep 17 00:00:00 2001 From: Damian Date: Thu, 21 Sep 2023 00:11:25 +0300 Subject: [PATCH 29/32] set typedoc v --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c3e919..c9bcd92 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "jest": "29.7.0", "ts-jest": "29.1.1", "tslint": "6.1.3", - "typedoc": "^0.25.1", + "typedoc": "0.25.1", "typescript": "5.2.2" }, "repository": { From df8871467851696ee10006d1fba7b5fe2313c87e Mon Sep 17 00:00:00 2001 From: Bucur David Date: Thu, 21 Sep 2023 15:22:30 +0300 Subject: [PATCH 30/32] refactor: remove comment code and error handling --- src/common/mint-utils.ts | 12 +----- src/minter.ts | 3 +- src/nft-minter.ts | 47 ++++++++------------- src/sft-minter.ts | 28 +++--------- tests/{minter.test.ts => sftminter.test.ts} | 0 5 files changed, 27 insertions(+), 63 deletions(-) rename tests/{minter.test.ts => sftminter.test.ts} (100%) diff --git a/src/common/mint-utils.ts b/src/common/mint-utils.ts index a0cb354..8d8c3af 100644 --- a/src/common/mint-utils.ts +++ b/src/common/mint-utils.ts @@ -24,19 +24,10 @@ export async function dataNFTDataStreamAdvertise( dataNftStreamUrlEncrypted: data.encryptedMessage }; } else { - throw new Error('Issue with data marshal generate payload'); - // throw new ErrFailedOperation(this.dataNFTDataStreamAdvertise.name); + throw new Error('Issue with data marshal generating payload'); } } catch (error) { throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation( - // this.dataNFTDataStreamAdvertise.name, - // error - // ); - // } else { - // throw new ErrFailedOperation(this.dataNFTDataStreamAdvertise.name); - // } } } @@ -54,7 +45,6 @@ export async function storeToIpfs( res = await nftstorage.storeDirectory(dir); } catch (error) { throw error; - // throw new ErrFailedOperation(this.storeToIpfs.name); } return { imageOnIpfsUrl: `https://ipfs.io/ipfs/${res}/image.png`, diff --git a/src/minter.ts b/src/minter.ts index a59aa13..1382dd8 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -83,7 +83,8 @@ export abstract class Minter { return new BooleanValue(returnValue).valueOf(); } else { throw new ErrContractQuery( - 'Error while retrieving the contract pause state' + 'viewContractPauseState', + returnCode.toString() ); } } diff --git a/src/nft-minter.ts b/src/nft-minter.ts index 599b9cc..810a142 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -25,7 +25,11 @@ import { storeToIpfs } from './common/mint-utils'; import { ContractConfiguration } from './interfaces'; -import { ErrArgumentNotSet } from './errors'; +import { + ErrArgumentNotSet, + ErrAttributeNotSet, + ErrContractQuery +} from './errors'; export class NftMinter extends Minter { /** @@ -206,10 +210,6 @@ export class NftMinter extends Minter { if (!allPassed) { throw new Error(`Params have validation issues = ${validationMessages}`); - // throw new ErrFailedOperation( - // this.mint.name, - // new Error(`params have validation issues = ${validationMessages}`) - // ); } // E: run any format specific validation... @@ -220,11 +220,6 @@ export class NftMinter extends Minter { await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); } catch (error) { throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation(this.mint.name, error); - // } else { - // throw new ErrFailedOperation(this.mint.name); - // } } let imageOnIpfsUrl: string; @@ -257,11 +252,10 @@ export class NftMinter extends Minter { metadataOnIpfsUrl = metadataIpfsUrl; } else { if (!traitsUrl) { - throw new Error('Traits URL is required when using custom image'); - // throw new ErrArgumentNotSet( - // 'traitsUrl', - // 'Traits URL is required when using custom image' - // ); + throw new ErrArgumentNotSet( + 'traitsUrl', + 'Traits URL is required when using custom image' + ); } await checkTraitsUrl(traitsUrl); @@ -459,10 +453,10 @@ export class NftMinter extends Minter { }; return contractConfiguration; } else { - throw new Error('Error while retrieving the contract pause state'); - // throw new ErrContractQuery( - // 'Error while retrieving the contract pause state' - // ); + throw new ErrContractQuery( + 'viewContractConfiguration', + returnCode.toString() + ); } } @@ -486,12 +480,7 @@ export class NftMinter extends Minter { ); return addressesWithTransferRole; } else { - throw new Error( - 'Error while retrieving the addresses with transfer roles' - ); - // throw new ErrContractQuery( - // 'Error while retrieving the addresses with transfer roles' - // ); + throw new ErrContractQuery('viewTransferRoles', returnCode.toString()); } } @@ -515,12 +504,10 @@ export class NftMinter extends Minter { ); return addressesWithUpdateAttributesRole; } else { - throw new Error( - 'Error while retrieving the addresses with update attributes roles' + throw new ErrContractQuery( + 'viewUpdateAttributesRoles', + returnCode.toString() ); - // throw new ErrContractQuery( - // 'Error while retrieving the addresses with update attributes roles' - // ); } } } diff --git a/src/sft-minter.ts b/src/sft-minter.ts index 99604fb..43b2db1 100644 --- a/src/sft-minter.ts +++ b/src/sft-minter.ts @@ -28,6 +28,7 @@ import { dataNFTDataStreamAdvertise, storeToIpfs } from './common/mint-utils'; +import { ErrArgumentNotSet } from './errors'; export class SftMinter extends Minter { /** @@ -44,8 +45,6 @@ export class SftMinter extends Minter { ); } - // [TO DO] Initialize the contract for sft minter - /** * Creates an initialize contract transaction for the contract * @param senderAddress The address of the sender, must be the admin of the contract @@ -202,10 +201,6 @@ export class SftMinter extends Minter { if (!allPassed) { throw new Error(`Params have validation issues = ${validationMessages}`); - // throw new ErrFailedOperation( - // this.mint.name, - // new Error(`params have validation issues = ${validationMessages}`) - // ); } // E: run any format specific validation... @@ -216,11 +211,6 @@ export class SftMinter extends Minter { await checkUrlIsUp(dataMarshalUrl + '/health-check', [200]); } catch (error) { throw error; - // if (error instanceof Error) { - // throw new ErrFailedOperation(this.mint.name, error); - // } else { - // throw new ErrFailedOperation(this.mint.name); - // } } let imageOnIpfsUrl: string; @@ -231,13 +221,10 @@ export class SftMinter extends Minter { if (!imageUrl) { if (!nftStorageToken) { - throw new Error( + throw new ErrArgumentNotSet( + 'nftStorageToken', 'NFT Storage token is required when not using custom image and traits' ); - // throw new ErrArgumentNotSet( - // 'nftStorageToken', - // 'NFT Storage token is required when not using custom image and traits' - // ); } const { image, traits } = await createFileFromUrl( `${this.imageServiceUrl}/v1/generateNFTArt?hash=${dataNftHash}`, @@ -256,11 +243,10 @@ export class SftMinter extends Minter { metadataOnIpfsUrl = metadataIpfsUrl; } else { if (!traitsUrl) { - throw new Error('Traits URL is required when using custom image'); - // throw new ErrArgumentNotSet( - // 'traitsUrl', - // 'Traits URL is required when using custom image' - // ); + throw new ErrArgumentNotSet( + 'traitsUrl', + 'Traits URL is required when using custom image' + ); } await checkTraitsUrl(traitsUrl); diff --git a/tests/minter.test.ts b/tests/sftminter.test.ts similarity index 100% rename from tests/minter.test.ts rename to tests/sftminter.test.ts From e257cc549e8214d3364efc462eaebce4c9b4a1cf Mon Sep 17 00:00:00 2001 From: Bucur David Date: Thu, 21 Sep 2023 15:27:10 +0300 Subject: [PATCH 31/32] test: increase timeout --- tests/nftminter.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/nftminter.test.ts b/tests/nftminter.test.ts index 35a74a4..36128e5 100644 --- a/tests/nftminter.test.ts +++ b/tests/nftminter.test.ts @@ -59,7 +59,7 @@ describe('Nft minter test', () => { } ); expect(mintTx).toBeInstanceOf(Transaction); - }, 20000); + }, 40000); test('#mint nft using your image and metadata', async () => { const factoryGeneratedContract = new Address( @@ -88,7 +88,7 @@ describe('Nft minter test', () => { } ); expect(mintTx).toBeInstanceOf(Transaction); - }, 20000); + }, 40000); test('#mint nft using tax for minting', async () => { const factoryGeneratedContract = new Address( @@ -117,5 +117,5 @@ describe('Nft minter test', () => { ); expect(mintTx).toBeInstanceOf(Transaction); - }); + }, 40000); }); From f17963f46a25655f33b6f37905f787dfcd2cfed3 Mon Sep 17 00:00:00 2001 From: Mark Paul Date: Fri, 22 Sep 2023 20:00:11 +1000 Subject: [PATCH 32/32] v2.0.0-alpha.1 is ready. fixed some typos. tested locally via integration --- README.md | 2 +- package-lock.json | 6 +++--- package.json | 2 +- src/marketplace.ts | 4 ++-- src/minter.ts | 6 +++--- src/nft-minter.ts | 8 ++++---- src/sft-minter.ts | 1 - 7 files changed, 14 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7f40e7d..6068d13 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This SDK is currently focused on interacting with the Itheum's Data NFT technolo ### Dev Testing -- Only simple dev testing added. First **Build** as below and then run `npm run devtest` and work on the test.mjs file for live reload +- Only simple dev testing added. First **Build** as below and then run `npm run test` and work on the test.mjs file for live reload ### Build diff --git a/package-lock.json b/package-lock.json index 02a6b36..d1afaba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@itheum/sdk-mx-data-nft", - "version": "1.2.0", + "version": "2.0.0-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@itheum/sdk-mx-data-nft", - "version": "1.2.0", + "version": "2.0.0-alpha.1", "license": "GPL-3.0-only", "dependencies": { "@multiversx/sdk-core": "12.8.0", @@ -19,7 +19,7 @@ "jest": "29.7.0", "ts-jest": "29.1.1", "tslint": "6.1.3", - "typedoc": "^0.25.1", + "typedoc": "0.25.1", "typescript": "5.2.2" } }, diff --git a/package.json b/package.json index c9bcd92..61ca6d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@itheum/sdk-mx-data-nft", - "version": "1.2.0", + "version": "2.0.0-alpha.1", "description": "SDK for Itheum's Data NFT Technology on MultiversX Blockchain", "main": "out/index.js", "types": "out/index.d.js", diff --git a/src/marketplace.ts b/src/marketplace.ts index 23c8660..84d24e8 100644 --- a/src/marketplace.ts +++ b/src/marketplace.ts @@ -64,7 +64,7 @@ export class DataNftMarket { } /** - * Retrives the address of the marketplace smart contract based on the environment + * Retrieves the address of the marketplace smart contract based on the environment */ getContractAddress(): IAddress { return this.contract.getAddress(); @@ -222,7 +222,7 @@ export class DataNftMarket { } /** - * Retrives an array of `Offer` objects. + * Retrieves an array of `Offer` objects. */ async viewOffers(): Promise { const interaction = this.contract.methodsExplicit.getOffers(); diff --git a/src/minter.ts b/src/minter.ts index 1382dd8..0e42d96 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -60,7 +60,7 @@ export abstract class Minter { } /** - * Retrives the address of the minter smart contract based on the environment + * Retrieves the address of the minter smart contract based on the environment */ getContractAddress(): IAddress { return this.contract.getAddress(); @@ -158,7 +158,7 @@ export abstract class Minter { } /** - * Retrives a list of addresses that are frozen for collection + * Retrieves a list of addresses that are frozen for collection */ async viewCollectionFrozenAddresses(): Promise { const interaction = this.contract.methodsExplicit.getCollectionFrozenList(); @@ -184,7 +184,7 @@ export abstract class Minter { } /** - * Retrives a list of nonces that are frozen for address + * Retrieves a list of nonces that are frozen for address * @param address The address to check */ async viewAddressFrozenNonces(address: IAddress): Promise { diff --git a/src/nft-minter.ts b/src/nft-minter.ts index 810a142..2a2ec2d 100644 --- a/src/nft-minter.ts +++ b/src/nft-minter.ts @@ -100,14 +100,14 @@ export class NftMinter extends Minter { /** * Creates a updateAttributes transaction for the contract * @param senderAddress The address of the sender, must be the admin of the contract - * @param tokenIdentiifer The token identifier of the data nft to update attributes + * @param tokenIdentifier The token identifier of the data nft to update attributes * @param nonce The nonce of the token to update attributes * @param attributes The new attributes to update * @param quantity The quantity of the token to update attributes (default: 1) */ updateAttributes( senderAddress: IAddress, - tokenIdentiifer: string, + tokenIdentifier: string, nonce: number, attributes: { dataMarshalUrl: string; @@ -123,7 +123,7 @@ export class NftMinter extends Minter { value: 0, data: new ContractCallPayloadBuilder() .setFunction(new ContractFunction('ESDTNFTTransfer')) - .addArg(new TokenIdentifierValue(tokenIdentiifer)) + .addArg(new TokenIdentifierValue(tokenIdentifier)) .addArg(new U64Value(nonce)) .addArg(new U64Value(quantity)) .addArg(new AddressValue(this.contract.getAddress())) @@ -485,7 +485,7 @@ export class NftMinter extends Minter { } /** - * Retrieves the addresss with update attributes roles for contract collection + * Retrieves the address with update attributes roles for contract collection */ async viewUpdateAttributesRoles(): Promise { const interaction = diff --git a/src/sft-minter.ts b/src/sft-minter.ts index 43b2db1..08c637e 100644 --- a/src/sft-minter.ts +++ b/src/sft-minter.ts @@ -5,7 +5,6 @@ import { ContractCallPayloadBuilder, ContractFunction, IAddress, - ResultsParser, StringValue, TokenIdentifierValue, Transaction,