From ce1d727bb9f54de26c1fbea320df407412a95038 Mon Sep 17 00:00:00 2001 From: Mark Paul Date: Tue, 12 Sep 2023 21:02:34 +1000 Subject: [PATCH 01/10] v1.1.0 version bump --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6c1422..47f9f4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@itheum/sdk-mx-data-nft", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@itheum/sdk-mx-data-nft", - "version": "1.0.0", + "version": "1.1.0", "license": "GPL-3.0-only", "dependencies": { "@multiversx/sdk-core": "12.6.0", diff --git a/package.json b/package.json index 78df9ff..268ac23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@itheum/sdk-mx-data-nft", - "version": "1.0.0", + "version": "1.1.0", "description": "SDK for Itheum's Data NFT Technology on MultiversX Blockchain", "main": "out/index.js", "types": "out/index.d.js", From 97d3bc08b896a5eba30781b464865abf792d634e Mon Sep 17 00:00:00 2001 From: Mark Paul Date: Tue, 12 Sep 2023 23:37:16 +1000 Subject: [PATCH 02/10] nested streams support #39, catch and pass through data marshal error #42 --- README.md | 4 ++-- package.json | 2 +- src/datanft.ts | 45 ++++++++++++++++++++++++++++++++++++++++++++- src/utils.ts | 38 +++++++++++++++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5c9961b..4ae1957 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ const dataNft = new DataNft({ // Create a new DataNft object from API const nonce = 1; -const nft = await DataNft.createFromApi(nonce); +const nft = await DataNft.createFromApi({ nonce }); // Create a new DataNft object from API Response const response = await fetch('https://devnet-api.multiversx.com/address/nfts'); @@ -60,7 +60,7 @@ const dataNfts = []; dataNfts = await DataNft.ownedByAddress(address); // Retrieves the specific DataNft -const dataNft = DataNft.createFromApi(nonce); +const dataNft = DataNft.createFromApi({ nonce }); // (A) Get a message from the Data Marshal node for your to sign to prove ownership const message = await dataNft.messageToSign(); diff --git a/package.json b/package.json index 268ac23..b2facad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@itheum/sdk-mx-data-nft", - "version": "1.1.0", + "version": "1.2.0", "description": "SDK for Itheum's Data NFT Technology on MultiversX Blockchain", "main": "out/index.js", "types": "out/index.d.js", diff --git a/src/datanft.ts b/src/datanft.ts index 15471f4..9e47cc1 100644 --- a/src/datanft.ts +++ b/src/datanft.ts @@ -14,7 +14,8 @@ import { createNftIdentifier, numberToPaddedHex, parseDataNft, - validateSpecificParamsViewData + validateSpecificParamsViewData, + checkStatus } from './utils'; import minterAbi from './abis/datanftmint.abi.json'; import { NftType, ViewDataReturnType } from './interfaces'; @@ -267,6 +268,7 @@ export class DataNft { * @param fwdAllHeaders [optional] Forward all request headers to the Origin Data Stream server. * @param fwdHeaderKeys [optional] Forward only selected headers to the Origin Data Stream server. Has priority over fwdAllHeaders param. A comma separated lowercase string with less than 5 items. e.g. cookie,authorization * @param fwdHeaderMapLookup [optional] Used with fwdHeaderKeys to set a front-end client side lookup map of headers the SDK uses to setup the forward. e.g. { cookie : "xyz", authorization : "Bearer zxy" }. Note that these are case-sensitive and need to match fwdHeaderKeys exactly. + * @param nestedIdxToStream [optional] If you are accessing a "nested stream", this is the index of the nested item you want drill into and fetch */ async viewData(p: { signedMessage: string; @@ -277,6 +279,7 @@ export class DataNft { fwdHeaderMapLookup?: { [key: string]: any; }; + nestedIdxToStream?: number; }): Promise { DataNft.ensureNetworkConfigSet(); if (!this.dataMarshal) { @@ -298,6 +301,7 @@ export class DataNft { fwdAllHeaders: p.fwdAllHeaders, fwdHeaderKeys: p.fwdHeaderKeys, fwdHeaderMapLookup: p.fwdHeaderMapLookup, + nestedIdxToStream: p.nestedIdxToStream, _mandatoryParamsList: ['signedMessage', 'signableMessage'] }); @@ -354,6 +358,10 @@ export class DataNft { url += p.fwdAllHeaders ? '&fwdAllHeaders=1' : ''; } + if (typeof p.nestedIdxToStream !== 'undefined') { + url += `&nestedIdxToStream=${p.nestedIdxToStream}`; + } + if (typeof p.fwdHeaderKeys !== 'undefined') { url += `&fwdHeaderKeys=${p.fwdHeaderKeys}`; @@ -375,6 +383,20 @@ export class DataNft { const contentType = response.headers.get('content-type'); const data = await response.blob(); + // if the marshal returned a error, we should throw it here so that the SDK integrator can handle it + // ... if we don't, the marshal error response is just passed through as a normal data stream response + // ... and the user won't know what went wrong + try { + checkStatus(response); + } catch (e: any) { + // as it's a data marshal error, we get it's payload which is in JSON and send that thrown as text + const errorPayload = await (data as Blob).text(); + + throw new Error( + `${e.toString()}. Detailed error trace follows : ${errorPayload}` + ); + } + return { data: data, contentType: contentType || '' @@ -396,6 +418,7 @@ export class DataNft { * @param fwdHeaderKeys [optional] Forward only selected headers to the Origin Data Stream server. Has priority over fwdAllHeaders param. A comma separated lowercase string with less than 5 items. e.g. cookie,authorization * @param fwdAllHeaders [optional] Forward all request headers to the Origin Data Stream server. * @param stream [optional] Instead of auto-downloading if possible, request if data should always be streamed or not.i.e true=stream, false/undefined=default behavior + * @param nestedIdxToStream [optional] If you are accessing a "nested stream", this is the index of the nested item you want drill into and fetch */ async viewDataViaMVXNativeAuth(p: { mvxNativeAuthOrigins: string[]; @@ -406,6 +429,7 @@ export class DataNft { fwdHeaderKeys?: string; fwdAllHeaders?: boolean; stream?: boolean; + nestedIdxToStream?: number; }): Promise { try { // S: run any format specific validation @@ -416,6 +440,7 @@ export class DataNft { fwdHeaderMapLookup: p.fwdHeaderMapLookup, fwdAllHeaders: p.fwdAllHeaders, stream: p.stream, + nestedIdxToStream: p.nestedIdxToStream, _fwdHeaderMapLookupMustContainBearerAuthHeader: true, _mandatoryParamsList: [ 'mvxNativeAuthOrigins', @@ -466,6 +491,10 @@ export class DataNft { url += p.fwdAllHeaders ? '&fwdAllHeaders=1' : ''; } + if (typeof p.nestedIdxToStream !== 'undefined') { + url += `&nestedIdxToStream=${p.nestedIdxToStream}`; + } + // if fwdHeaderMapLookup exists, send these headers and values to the data marshal for forwarding if ( typeof p.fwdHeaderMapLookup !== 'undefined' && @@ -495,6 +524,20 @@ export class DataNft { const contentType = response.headers.get('content-type'); const data = await response.blob(); + // if the marshal returned a error, we should throw it here so that the SDK integrator can handle it + // ... if we don't, the marshal error response is just passed through as a normal data stream response + // ... and the user won't know what went wrong + try { + checkStatus(response); + } catch (e: any) { + // as it's a data marshal error, we get it's payload which is in JSON and send that thrown as text + const errorPayload = await (data as Blob).text(); + + throw new Error( + `${e.toString()}. Detailed error trace follows : ${errorPayload}` + ); + } + return { data: data, contentType: contentType || '' diff --git a/src/utils.ts b/src/utils.ts index 4b39857..354e4b8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -102,6 +102,7 @@ export function validateSpecificParamsViewData(params: { mvxNativeAuthMaxExpirySeconds?: number | undefined; mvxNativeAuthOrigins?: string[] | undefined; fwdHeaderMapLookup?: any; + nestedIdxToStream?: number | undefined; _fwdHeaderMapLookupMustContainBearerAuthHeader?: boolean | undefined; _mandatoryParamsList: string[]; // a pure JS fallback way to validate mandatory params, as typescript rules for mandatory can be bypassed by client app }): { @@ -301,6 +302,32 @@ export function validateSpecificParamsViewData(params: { } } + // nestedIdxToStream test + let nestedIdxToStreamValid = true; + + if ( + params.nestedIdxToStream !== undefined || + params._mandatoryParamsList.includes('nestedIdxToStream') + ) { + nestedIdxToStreamValid = false; + + const nestedIdxToStreamToInt = + params.nestedIdxToStream !== undefined + ? parseInt(params.nestedIdxToStream.toString(), 10) + : null; + + if ( + nestedIdxToStreamToInt !== null && + !isNaN(nestedIdxToStreamToInt) && + nestedIdxToStreamToInt >= 0 + ) { + nestedIdxToStreamValid = true; + } else { + validationMessages += + '[nestedIdxToStream needs to be a number more than 0]'; + } + } + if ( !signedMessageValid || !signableMessageValid || @@ -309,7 +336,8 @@ export function validateSpecificParamsViewData(params: { !fwdHeaderKeysIsValid || !fwdHeaderMapLookupIsValid || !mvxNativeAuthMaxExpirySecondsValid || - !mvxNativeAuthOriginsIsValid + !mvxNativeAuthOriginsIsValid || + !nestedIdxToStreamValid ) { allPassed = false; } @@ -532,3 +560,11 @@ export async function checkUrlIsUp(url: string, expectedHttpCodes: number[]) { ); } } + +export function checkStatus(response: Response) { + if (!(response.status >= 200 && response.status <= 299)) { + throw new Error( + `Response returned non-success HTTP code. status = ${response?.status} statusText = ${response?.statusText}` + ); + } +} From d0eff29970cafbcc99f6b2b8c33f15ba7a5de9a6 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 09:12:52 +0300 Subject: [PATCH 03/10] fix: wrong import for File --- src/minter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/minter.ts b/src/minter.ts index 710b13c..7498fb8 100644 --- a/src/minter.ts +++ b/src/minter.ts @@ -25,8 +25,7 @@ import { } from './config'; import dataNftMintAbi from './abis/datanftmint.abi.json'; import { MinterRequirements } from './interfaces'; -import { NFTStorage } from 'nft.storage'; -import { File } from '@web-std/file'; +import { NFTStorage, File } from 'nft.storage'; import { checkTraitsUrl, checkUrlIsUp, From 3a6e3d527ad316dad6ccd89a8b864d503c30f1c7 Mon Sep 17 00:00:00 2001 From: Bucur David Date: Wed, 13 Sep 2023 13:17:45 +0300 Subject: [PATCH 04/10] 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 05/10] 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 06/10] 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 07/10] 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 08/10] 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 09/10] 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 10/10] 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 + ); } /**