From e43c4ce858abfac1ff286bc72c318142ec2469bf Mon Sep 17 00:00:00 2001 From: Bonnie Date: Wed, 8 Jan 2025 18:13:11 +0800 Subject: [PATCH] Refactor derivative data validation --- packages/core-sdk/src/resources/ipAsset.ts | 246 +++++++++--------- .../core-sdk/src/types/resources/ipAsset.ts | 45 ++-- .../core-sdk/src/utils/licenseTermsHelper.ts | 3 +- .../test/unit/resources/ipAsset.test.ts | 157 ++++++++++- 4 files changed, 288 insertions(+), 163 deletions(-) diff --git a/packages/core-sdk/src/resources/ipAsset.ts b/packages/core-sdk/src/resources/ipAsset.ts index 96c054a3..cad295a1 100644 --- a/packages/core-sdk/src/resources/ipAsset.ts +++ b/packages/core-sdk/src/resources/ipAsset.ts @@ -57,6 +57,7 @@ import { MintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokensRequest, MintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokensResponse, MintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokensResponse, + InternalDerivativeData, } from "../types/resources/ipAsset"; import { AccessControllerClient, @@ -76,6 +77,7 @@ import { LicenseRegistryReadOnlyClient, LicenseTokenReadOnlyClient, LicensingModuleClient, + LicensingModuleRegisterDerivativeRequest, Multicall3Client, PiLicenseTemplateClient, RegistrationWorkflowsClient, @@ -83,6 +85,8 @@ import { RegistrationWorkflowsRegisterIpRequest, RoyaltyModuleEventClient, RoyaltyTokenDistributionWorkflowsClient, + RoyaltyTokenDistributionWorkflowsMintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokensRequest, + RoyaltyTokenDistributionWorkflowsRegisterIpAndMakeDerivativeAndDeployRoyaltyVaultRequest, SimpleWalletClient, coreMetadataModuleAbi, ipAccountImplAbi, @@ -419,7 +423,7 @@ export class IPAssetClient { request: RegisterDerivativeRequest, ): Promise { try { - const req = { + const object: LicensingModuleRegisterDerivativeRequest = { childIpId: getAddress(request.childIpId, "request.childIpId"), parentIpIds: request.parentIpIds, licenseTermsIds: request.licenseTermsIds.map((id) => BigInt(id)), @@ -432,59 +436,17 @@ export class IPAssetClient { maxRts: Number(request.maxRts), maxRevenueShare: getRevenueShare(request.maxRevenueShare), } as const; - if (req.maxMintingFee < 0) { - throw new Error(`The maxMintingFee must be greater than 0.`); - } - if (req.maxRts < 0 || req.maxRts > MAX_ROYALTY_TOKEN) { - throw new Error(`The maxRts must be greater than 0 and less than ${MAX_ROYALTY_TOKEN}.`); - } - const isChildIpIdRegistered = await this.isRegistered(req.childIpId); + const isChildIpIdRegistered = await this.isRegistered(object.childIpId); if (!isChildIpIdRegistered) { - throw new Error(`The child IP with id ${req.childIpId} is not registered.`); - } - if (req.parentIpIds.length !== req.licenseTermsIds.length) { - throw new Error("Parent IP IDs and License terms IDs must be provided in pairs."); - } - for (const [i, parentId] of req.parentIpIds.entries()) { - const isParentIpIdRegistered = await this.isRegistered( - getAddress(parentId, "request.parentIpIds"), - ); - if (!isParentIpIdRegistered) { - throw new Error(`The parent IP with id ${parentId} is not registered.`); - } - const { royaltyPercent } = await this.licenseRegistryReadOnlyClient.getRoyaltyPercent({ - ipId: getAddress(parentId, "request.parentIpIds"), - licenseTemplate: req.licenseTemplate, - licenseTermsId: req.licenseTermsIds[i], - }); - if (req.maxRevenueShare !== 0 && royaltyPercent > req.maxRevenueShare) { - throw new Error( - `The royalty percent for the parent IP with id ${parentId} is greater than the maximum revenue share ${req.maxRevenueShare}.`, - ); - } + throw new Error(`The child IP with id ${object.childIpId} is not registered.`); } - - for (let i = 0; i < request.parentIpIds.length; i++) { - const isAttachedLicenseTerms = - await this.licenseRegistryReadOnlyClient.hasIpAttachedLicenseTerms({ - ipId: getAddress(request.parentIpIds[i], "request.parentIpIds"), - licenseTemplate: - (request.licenseTemplate && - getAddress(request.licenseTemplate, "request.licenseTemplate")) || - this.licenseTemplateClient.address, - licenseTermsId: BigInt(request.licenseTermsIds[i]), - }); - if (!isAttachedLicenseTerms) { - throw new Error( - `License terms id ${request.licenseTermsIds[i]} must be attached to the parent ipId ${request.parentIpIds[i]} before registering derivative.`, - ); - } - } - + await this.validateDerivativeData({ + ...object, + }); if (request.txOptions?.encodedTxDataOnly) { - return { encodedTxData: this.licensingModuleClient.registerDerivativeEncode(req) }; + return { encodedTxData: this.licensingModuleClient.registerDerivativeEncode(object) }; } else { - const txHash = await this.licensingModuleClient.registerDerivative(req); + const txHash = await this.licensingModuleClient.registerDerivative(object); if (request.txOptions?.waitForTransaction) { await this.rpcClient.waitForTransactionReceipt({ ...request.txOptions, @@ -508,7 +470,7 @@ export class IPAssetClient { * @param {Array} request.args.parentIpIds The parent IP IDs. * @param {Array} request.args.licenseTermsIds The IDs of the license terms that the parent IP supports. * @param request.args.maxMintingFee The maximum minting fee that the caller is willing to pay. if set to 0 then no limit. - * @param request.args.maxRts The maximum number of royalty tokens that can be distributed to the external royalty policies (max: 100000000). + * @param request.args.maxRts The maximum number of royalty tokens that can be distributed to the external royalty policies (max: 100,000,000). * @param request.args.maxRevenueShare The maximum revenue share percentage allowed for minting the License Tokens. Must be between 0 and 100,000,000 (where 100,000,000 represents 100%). * @param request.deadline [Optional] The deadline for the signature in seconds, default is 1000s. * @param request.txOptions [Optional] This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property, without encodedTxDataOnly option. @@ -1735,60 +1697,44 @@ export class IPAssetClient { }, ], }); - const req = { - nftContract: request.nftContract, - tokenId: BigInt(request.tokenId), - ipMetadata: this.getIpMetadata(request.ipMetadata), - derivData: { - ...request.derivData, - licenseTemplate: - (request.derivData.licenseTemplate && - getAddress(request.derivData.licenseTemplate, "request.derivData.licenseTemplate")) || - this.licenseTemplateClient.address, - royaltyContext: zeroAddress, - maxMintingFee: BigInt(request.derivData.maxMintingFee), - maxRts: Number(request.derivData.maxRts), - maxRevenueShare: getRevenueShare(request.derivData.maxRevenueShare), - }, - sigMetadataAndRegister: { - signer: this.wallet.account!.address, - deadline: calculatedDeadline, - signature: signature, - }, - } as const; - if (req.derivData.maxRts < 0 || req.derivData.maxRts > MAX_ROYALTY_TOKEN) { - throw new Error(`The maxRts must be greater than 0 and less than ${MAX_ROYALTY_TOKEN}.`); - } - if (req.derivData.maxMintingFee < 0) { - throw new Error(`The maxMintingFee must be greater than 0.`); - } + const object: RoyaltyTokenDistributionWorkflowsRegisterIpAndMakeDerivativeAndDeployRoyaltyVaultRequest = + { + nftContract: request.nftContract, + tokenId: BigInt(request.tokenId), + ipMetadata: this.getIpMetadata(request.ipMetadata), + derivData: { + ...request.derivData, + licenseTemplate: + (request.derivData.licenseTemplate && + getAddress( + request.derivData.licenseTemplate, + "request.derivData.licenseTemplate", + )) || + this.licenseTemplateClient.address, + royaltyContext: zeroAddress, + maxMintingFee: BigInt(request.derivData.maxMintingFee), + maxRts: Number(request.derivData.maxRts), + maxRevenueShare: getRevenueShare(request.derivData.maxRevenueShare), + parentIpIds: request.derivData.parentIpIds.map((id) => + getAddress(id, "request.derivData.parentIpIds"), + ), + licenseTermsIds: request.derivData.licenseTermsIds.map((id) => BigInt(id)), + }, + sigMetadataAndRegister: { + signer: this.wallet.account!.address, + deadline: calculatedDeadline, + signature: signature, + }, + } as const; const { royaltyShares, totalAmount } = this.getRoyaltyShares(request.royaltyShares); const isRegistered = await this.isRegistered(ipIdAddress); if (isRegistered) { throw new Error(`The NFT with id ${request.tokenId} is already registered as IP.`); } - if (request.derivData.parentIpIds.length !== request.derivData.licenseTermsIds.length) { - throw new Error("Parent IP IDs and License terms IDs must be provided in pairs."); - } - for (const [i, parentId] of request.derivData.parentIpIds.entries()) { - const isParentIpRegistered = await this.isRegistered(parentId); - if (!isParentIpRegistered) { - throw new Error(`The parent IP with id ${parentId} is not registered.`); - } - const { royaltyPercent } = await this.licenseRegistryReadOnlyClient.getRoyaltyPercent({ - ipId: getAddress(parentId, "request.parentIpIds"), - licenseTemplate: req.derivData.licenseTemplate, - licenseTermsId: req.derivData.licenseTermsIds[i], - }); - if (req.derivData.maxRevenueShare !== 0 && royaltyPercent > req.derivData.maxRevenueShare) { - throw new Error( - `The royalty percent for the parent IP with id ${parentId} is greater than the maximum revenue share ${req.derivData.maxRevenueShare}.`, - ); - } - } + await this.validateDerivativeData(object.derivData); const txHash = await this.royaltyTokenDistributionWorkflowsClient.registerIpAndMakeDerivativeAndDeployRoyaltyVault( - req, + object, ); const txReceipt = await this.rpcClient.waitForTransactionReceipt({ ...request.txOptions, @@ -1925,8 +1871,8 @@ export class IPAssetClient { * @param request.derivData.licenseTemplate [Optional] The address of the license template to be used for the linking, default value is Programmable IP License. * @param {Array} request.derivData.licenseTermsIds The IDs of the license terms to be used for the linking. * @param request.derivData.maxMintingFee The maximum minting fee that the caller is willing to pay. if set to 0 then no limit. - * @param request.derivData.maxRts The maximum number of royalty tokens that can be distributed to the external royalty policies. - * @param request.derivData.maxRevenueShare The maximum revenue share percentage allowed for minting the License Tokens. + * @param request.derivData.maxRts The maximum number of royalty tokens that can be distributed to the external royalty policies (max: 100,000,000). + * @param request.derivData.maxRevenueShare The maximum revenue share percentage allowed for minting the License Tokens. Must be between 0 and 100,000,000 (where 100,000,000 represents 100%). * @param {Object} request.ipMetadata - [Optional] The desired metadata for the newly minted NFT and newly registered IP. * @param request.ipMetadata.ipMetadataURI [Optional] The URI of the metadata for the IP. * @param request.ipMetadata.ipMetadataHash [Optional] The hash of the metadata for the IP. @@ -1959,32 +1905,38 @@ export class IPAssetClient { licenseTerms.push(licenseTermsId); } const { royaltyShares } = this.getRoyaltyShares(request.royaltyShares); + const object: RoyaltyTokenDistributionWorkflowsMintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokensRequest = + { + spgNftContract: getAddress(request.spgNftContract, "request.spgNftContract"), + recipient: + (request.recipient && getAddress(request.recipient, "request.recipient")) || + this.wallet.account!.address, + ipMetadata: this.getIpMetadata(request.ipMetadata), + derivData: { + ...request.derivData, + licenseTemplate: + (request.derivData.licenseTemplate && + getAddress( + request.derivData.licenseTemplate, + "request.derivData.licenseTemplate", + )) || + this.licenseTemplateClient.address, + royaltyContext: zeroAddress, + licenseTermsIds: licenseTerms, + maxMintingFee: BigInt(request.derivData.maxMintingFee), + maxRts: Number(request.derivData.maxRts), + maxRevenueShare: getRevenueShare(request.derivData.maxRevenueShare), + parentIpIds: request.derivData.parentIpIds.map((id) => + getAddress(id, "request.derivData.parentIpIds"), + ), + }, + royaltyShares: royaltyShares, + allowDuplicates: request.allowDuplicates, + }; + await this.validateDerivativeData(object.derivData); const txHash = await this.royaltyTokenDistributionWorkflowsClient.mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens( - { - spgNftContract: getAddress(request.spgNftContract, "request.spgNftContract"), - recipient: - (request.recipient && getAddress(request.recipient, "request.recipient")) || - this.wallet.account!.address, - ipMetadata: this.getIpMetadata(request.ipMetadata), - derivData: { - ...request.derivData, - licenseTemplate: - (request.derivData.licenseTemplate && - getAddress( - request.derivData.licenseTemplate, - "request.derivData.licenseTemplate", - )) || - this.licenseTemplateClient.address, - royaltyContext: zeroAddress, - licenseTermsIds: licenseTerms, - maxMintingFee: BigInt(request.derivData.maxMintingFee), - maxRts: Number(request.derivData.maxRts), - maxRevenueShare: Number(request.derivData.maxRevenueShare), - }, - royaltyShares: royaltyShares, - allowDuplicates: request.allowDuplicates, - }, + object, ); if (request.txOptions?.waitForTransaction) { const txReceipt = await this.rpcClient.waitForTransactionReceipt({ @@ -2149,4 +2101,50 @@ export class IPAssetClient { const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; return getDeadline(blockTimestamp, requestDeadline); } + + private async validateDerivativeData(derivativeData: InternalDerivativeData) { + if (derivativeData.parentIpIds.length === 0) { + throw new Error("The parent IP IDs must be provided."); + } + if (derivativeData.licenseTermsIds.length === 0) { + throw new Error("The license terms IDs must be provided."); + } + if (derivativeData.parentIpIds.length !== derivativeData.licenseTermsIds.length) { + throw new Error("The number of parent IP IDs must match the number of license terms IDs."); + } + if (derivativeData.maxRts < 0 || derivativeData.maxRts > MAX_ROYALTY_TOKEN) { + throw new Error(`The maxRts must be greater than 0 and less than ${MAX_ROYALTY_TOKEN}.`); + } + if (derivativeData.maxMintingFee < 0) { + throw new Error(`The maxMintingFee must be greater than 0.`); + } + for (let i = 0; i < derivativeData.parentIpIds.length; i++) { + const parentId = derivativeData.parentIpIds[i]; + const isParentIpRegistered = await this.isRegistered(parentId); + if (!isParentIpRegistered) { + throw new Error(`The parent IP with id ${parentId} is not registered.`); + } + const isAttachedLicenseTerms = + await this.licenseRegistryReadOnlyClient.hasIpAttachedLicenseTerms({ + ipId: parentId, + licenseTemplate: derivativeData.licenseTemplate, + licenseTermsId: derivativeData.licenseTermsIds[i], + }); + if (!isAttachedLicenseTerms) { + throw new Error( + `License terms id ${derivativeData.licenseTermsIds[i]} must be attached to the parent ipId ${derivativeData.parentIpIds[i]} before registering derivative.`, + ); + } + const { royaltyPercent } = await this.licenseRegistryReadOnlyClient.getRoyaltyPercent({ + ipId: parentId, + licenseTemplate: derivativeData.licenseTemplate, + licenseTermsId: derivativeData.licenseTermsIds[i], + }); + if (derivativeData.maxRevenueShare !== 0 && royaltyPercent > derivativeData.maxRevenueShare) { + throw new Error( + `The royalty percent for the parent IP with id ${parentId} is greater than the maximum revenue share ${derivativeData.maxRevenueShare}.`, + ); + } + } + } } diff --git a/packages/core-sdk/src/types/resources/ipAsset.ts b/packages/core-sdk/src/types/resources/ipAsset.ts index 015bb15d..0dfd51cd 100644 --- a/packages/core-sdk/src/types/resources/ipAsset.ts +++ b/packages/core-sdk/src/types/resources/ipAsset.ts @@ -5,6 +5,23 @@ import { RegisterPILTermsRequest } from "./license"; import { EncodedTxData } from "../../abi/generated"; import { IpMetadataAndTxOption } from "../common"; +export type DerivativeData = { + parentIpIds: Address[]; + licenseTermsIds: bigint[] | string[] | number[]; + maxMintingFee: bigint | string | number; + maxRts: number | string; + maxRevenueShare: number | string; + licenseTemplate?: Address; +}; +export type InternalDerivativeData = { + parentIpIds: readonly Address[]; + licenseTermsIds: readonly bigint[]; + royaltyContext: Hex; + maxMintingFee: bigint; + maxRts: number; + maxRevenueShare: number; + licenseTemplate: Address; +}; export type RegisterIpResponse = { txHash?: Hex; encodedTxData?: EncodedTxData; @@ -31,15 +48,9 @@ export type RegisterDerivativeWithLicenseTokensResponse = { }; export type RegisterDerivativeRequest = { - childIpId: Address; - parentIpIds: Address[]; - licenseTermsIds: string[] | bigint[] | number[]; - maxMintingFee: bigint | string | number; - maxRts: number | string; - maxRevenueShare: number | string; - licenseTemplate?: Address; txOptions?: TxOptions; -}; + childIpId: Address; +} & DerivativeData; export type RegisterDerivativeResponse = { txHash?: Hex; @@ -311,14 +322,7 @@ export type RegisterDerivativeAndAttachLicenseTermsAndDistributeRoyaltyTokensReq nftContract: Address; tokenId: bigint | string | number; deadline?: string | number | bigint; - derivData: { - parentIpIds: Address[]; - licenseTemplate?: Address; - licenseTermsIds: bigint[]; - maxMintingFee: number | string | bigint; - maxRts: number | string; - maxRevenueShare: number | string; - }; + derivData: DerivativeData; royaltyShares: RoyaltyShare[]; txOptions?: Omit; } & IPMetadataInfo; @@ -348,14 +352,7 @@ export type MintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokensResponse }; export type MintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokensRequest = { spgNftContract: Address; - derivData: { - parentIpIds: Address[]; - licenseTermsIds: string[] | bigint[] | number[]; - licenseTemplate?: Address; - maxMintingFee: number | string | bigint; - maxRts: number | string; - maxRevenueShare: number | string; - }; + derivData: DerivativeData; royaltyShares: RoyaltyShare[]; allowDuplicates: boolean; recipient?: Address; diff --git a/packages/core-sdk/src/utils/licenseTermsHelper.ts b/packages/core-sdk/src/utils/licenseTermsHelper.ts index 963e2907..5925fef4 100644 --- a/packages/core-sdk/src/utils/licenseTermsHelper.ts +++ b/packages/core-sdk/src/utils/licenseTermsHelper.ts @@ -3,6 +3,7 @@ import { Address, PublicClient, zeroAddress } from "viem"; import { PIL_TYPE, LicenseTerms, RegisterPILTermsRequest } from "../types/resources/license"; import { getAddress } from "./utils"; import { RoyaltyModuleReadOnlyClient } from "../abi/generated"; +import { MAX_ROYALTY_TOKEN } from "../constants/common"; export function getLicenseTermByType( type: PIL_TYPE, @@ -167,5 +168,5 @@ export const getRevenueShare = (revShare: number | string) => { if (revShareNumber < 0 || revShareNumber > 100) { throw new Error("CommercialRevShare should be between 0 and 100."); } - return (revShareNumber / 100) * 100000000; + return (revShareNumber / 100) * MAX_ROYALTY_TOKEN; }; diff --git a/packages/core-sdk/test/unit/resources/ipAsset.test.ts b/packages/core-sdk/test/unit/resources/ipAsset.test.ts index aecc417e..62d0bca2 100644 --- a/packages/core-sdk/test/unit/resources/ipAsset.test.ts +++ b/packages/core-sdk/test/unit/resources/ipAsset.test.ts @@ -483,7 +483,9 @@ describe("Test IpAssetClient", () => { .resolves(true) .onCall(1) .resolves(true); - + sinon.stub(ipAssetClient.licenseRegistryReadOnlyClient, "getRoyaltyPercent").resolves({ + royaltyPercent: 100, + }); try { await ipAssetClient.registerDerivative({ childIpId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -495,12 +497,13 @@ describe("Test IpAssetClient", () => { }); } catch (err) { expect((err as Error).message).equal( - "Failed to register derivative: Parent IP IDs and License terms IDs must be provided in pairs.", + "Failed to register derivative: The number of parent IP IDs must match the number of license terms IDs.", ); } }); it("should throw maxMintingFee error when registerDerivative given maxMintingFee is less than 0", async () => { try { + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(true); await ipAssetClient.registerDerivative({ childIpId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", parentIpIds: ["0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4"], @@ -515,7 +518,8 @@ describe("Test IpAssetClient", () => { ); } }); - it("should throw maxRts error when registerDerivative given maxRts is greater than 100000000", async () => { + it(`should throw maxRts error when registerDerivative given maxRts is greater than ${MAX_ROYALTY_TOKEN}`, async () => { + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(true); try { await ipAssetClient.registerDerivative({ childIpId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -541,7 +545,9 @@ describe("Test IpAssetClient", () => { .resolves(true) .onCall(1) .resolves(true); - + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(true); try { await ipAssetClient.registerDerivative({ childIpId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -2643,6 +2649,10 @@ describe("Test IpAssetClient", () => { sinon .stub(ipAssetClient.ipAssetRegistryClient, "ipId") .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); + sinon.stub(ipAssetClient.licenseRegistryReadOnlyClient, "getRoyaltyPercent").resolves({ + royaltyPercent: 100, + }); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(false); try { await ipAssetClient.registerDerivativeIpAndAttachLicenseTermsAndDistributeRoyaltyTokens({ nftContract: spgNftContract, @@ -2664,10 +2674,14 @@ describe("Test IpAssetClient", () => { ); } }); - it("should throw maxRts error when registerDerivativeAndAttachLicenseTermsAndDistributeRoyaltyTokens given maxRts is greater than MAX_ROYALTY_TOKEN", async () => { + it("should throw maxRts error when registerDerivativeAndAttachLicenseTermsAndDistributeRoyaltyTokens given maxRts is greater than 100000000", async () => { sinon .stub(ipAssetClient.ipAssetRegistryClient, "ipId") .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); + sinon.stub(ipAssetClient.licenseRegistryReadOnlyClient, "getRoyaltyPercent").resolves({ + royaltyPercent: 100, + }); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(false); try { await ipAssetClient.registerDerivativeIpAndAttachLicenseTermsAndDistributeRoyaltyTokens({ nftContract: spgNftContract, @@ -2693,6 +2707,10 @@ describe("Test IpAssetClient", () => { sinon .stub(ipAssetClient.ipAssetRegistryClient, "ipId") .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); + sinon.stub(ipAssetClient.licenseRegistryReadOnlyClient, "getRoyaltyPercent").resolves({ + royaltyPercent: 100, + }); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(false); try { await ipAssetClient.registerDerivativeIpAndAttachLicenseTermsAndDistributeRoyaltyTokens({ nftContract: spgNftContract, @@ -2719,6 +2737,9 @@ describe("Test IpAssetClient", () => { sinon .stub(ipAssetClient.ipAssetRegistryClient, "ipId") .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "getRoyaltyPercent") + .resolves({ royaltyPercent: 100 }); try { await ipAssetClient.registerDerivativeIpAndAttachLicenseTermsAndDistributeRoyaltyTokens({ nftContract: spgNftContract, @@ -2739,7 +2760,7 @@ describe("Test IpAssetClient", () => { }); } catch (err) { expect((err as Error).message).equal( - "Failed to register derivative IP and attach license terms and distribute royalty tokens: Parent IP IDs and License terms IDs must be provided in pairs.", + "Failed to register derivative IP and attach license terms and distribute royalty tokens: The number of parent IP IDs must match the number of license terms IDs.", ); } }); @@ -2783,6 +2804,9 @@ describe("Test IpAssetClient", () => { .resolves(false) .onSecondCall() .resolves(true); + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(true); try { await ipAssetClient.registerDerivativeIpAndAttachLicenseTermsAndDistributeRoyaltyTokens({ nftContract: spgNftContract, @@ -2835,6 +2859,9 @@ describe("Test IpAssetClient", () => { sinon .stub(ipAssetClient.royaltyTokenDistributionWorkflowsClient, "distributeRoyaltyTokens") .resolves(txHash); + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(true); sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ { ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -2904,7 +2931,9 @@ describe("Test IpAssetClient", () => { sinon.stub(ipAssetClient.licenseRegistryReadOnlyClient, "getRoyaltyPercent").resolves({ royaltyPercent: 100, }); - + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(true); sinon .stub(ipAssetClient.royaltyTokenDistributionWorkflowsClient, "distributeRoyaltyTokens") .resolves(txHash); @@ -3059,14 +3088,43 @@ describe("Test IpAssetClient", () => { }); describe("Test ipAssetClient.mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens", async () => { - it("should throw commercial terms error when mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens given license terms id is not commercial", async () => { + it("should throw parent ip id error when mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens given parent ip id is empty", async () => { + sinon.stub(ipAssetClient.licenseTemplateClient, "getLicenseTerms").resolves({ + terms: { + ...licenseTerms, + commercialUse: true, + }, + }); try { - sinon.stub(ipAssetClient.licenseTemplateClient, "getLicenseTerms").resolves({ - terms: { - ...licenseTerms, - commercialUse: false, + await ipAssetClient.mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens({ + spgNftContract, + allowDuplicates: false, + royaltyShares: [ + { recipient: "0x73fcb515cee99e4991465ef586cfe2b072ebb512", percentage: 100 }, + ], + derivData: { + parentIpIds: [], + licenseTermsIds: [1n], + maxMintingFee: 100, + maxRts: 100, + maxRevenueShare: 100, }, }); + } catch (err) { + expect((err as Error).message).equal( + "Failed to mint and register IP and make derivative and distribute royalty tokens: The parent IP IDs must be provided.", + ); + } + }); + + it("should throw license terms id error when mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens given license terms id is empty", async () => { + sinon.stub(ipAssetClient.licenseTemplateClient, "getLicenseTerms").resolves({ + terms: { + ...licenseTerms, + commercialUse: true, + }, + }); + try { await ipAssetClient.mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens({ spgNftContract, allowDuplicates: false, @@ -3075,7 +3133,7 @@ describe("Test IpAssetClient", () => { ], derivData: { parentIpIds: ["0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"], - licenseTermsIds: [1n], + licenseTermsIds: [], maxMintingFee: 100, maxRts: 100, maxRevenueShare: 100, @@ -3083,12 +3141,76 @@ describe("Test IpAssetClient", () => { }); } catch (err) { expect((err as Error).message).equal( - "Failed to mint and register IP and make derivative and distribute royalty tokens: The license terms attached to the IP must be a commercial license to distribute royalty tokens.", + "Failed to mint and register IP and make derivative and distribute royalty tokens: The license terms IDs must be provided.", + ); + } + }); + + it("should throw maxRevenueShare error when mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens given maxRevenueShare is greater than 100", async () => { + sinon.stub(ipAssetClient.licenseTemplateClient, "getLicenseTerms").resolves({ + terms: { + ...licenseTerms, + commercialUse: true, + }, + }); + try { + await ipAssetClient.mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens({ + spgNftContract, + allowDuplicates: false, + royaltyShares: [ + { recipient: "0x73fcb515cee99e4991465ef586cfe2b072ebb512", percentage: 100 }, + ], + derivData: { + parentIpIds: ["0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"], + licenseTermsIds: [1n], + maxMintingFee: 100, + maxRts: 100, + maxRevenueShare: 101, + }, + }); + } catch (err) { + expect((err as Error).message).equal( + "Failed to mint and register IP and make derivative and distribute royalty tokens: CommercialRevShare should be between 0 and 100.", + ); + } + }); + it("should throw maxRevenueShare error when mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens given maxRevenueShare is less than 0", async () => { + sinon.stub(ipAssetClient.licenseTemplateClient, "getLicenseTerms").resolves({ + terms: { + ...licenseTerms, + commercialUse: true, + }, + }); + try { + await ipAssetClient.mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens({ + spgNftContract, + allowDuplicates: false, + royaltyShares: [ + { recipient: "0x73fcb515cee99e4991465ef586cfe2b072ebb512", percentage: 100 }, + ], + derivData: { + parentIpIds: ["0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"], + licenseTermsIds: [1n], + maxMintingFee: 100, + maxRts: 100, + maxRevenueShare: -1, + }, + }); + } catch (err) { + expect((err as Error).message).equal( + "Failed to mint and register IP and make derivative and distribute royalty tokens: CommercialRevShare should be between 0 and 100.", ); } }); it("should return txHash when mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens given correct args", async () => { + sinon.stub(ipAssetClient.licenseRegistryReadOnlyClient, "getRoyaltyPercent").resolves({ + royaltyPercent: 100, + }); + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(true); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(true); sinon .stub( ipAssetClient.royaltyTokenDistributionWorkflowsClient, @@ -3120,6 +3242,13 @@ describe("Test IpAssetClient", () => { }); it("should return txHash when mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens given correct args and waitForTransaction of true", async () => { + sinon.stub(ipAssetClient.licenseRegistryReadOnlyClient, "getRoyaltyPercent").resolves({ + royaltyPercent: 100, + }); + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(true); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(true); sinon .stub( ipAssetClient.royaltyTokenDistributionWorkflowsClient,