From b5a3b5c897ef978e33f5984414ce2d9cb022ffc0 Mon Sep 17 00:00:00 2001 From: Bonnie57 <146059114+bonnie57@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:48:29 +0800 Subject: [PATCH] Bump up sdk version to 1.2.0-rc.2 (#317) * Feat/make isRegistered public (#311) * make isRegistered public * make isRegistered public * [feat] add codeowners for pr review (#316) * Implement multicall (#314) * Add batchMintAndRegisterIpAssetWithPilTerms method * Add batchRegisterDerivative method * Enhance batchMintAndRegisterIpAssetWithPilTerms including unit tests and annotation * Add batchMintAndRegisterIpAndMakeDerivative method * Add batch register method * Fix issue about Invalid signature * Refactor code * Add unit tests * Fix unit test * Export getSignature method * Add batchRegisterWithIpMetadata method * Enhance code * Refactor multicall about protocol core * Remove integration test in command * Refactor getDeadline method * Bump up sdk to 1.2.0-rc.2 --------- Co-authored-by: Don <100505855+DonFungible@users.noreply.github.com> Co-authored-by: Andy Wu --- .github/CODEOWNERS | 2 +- packages/core-sdk/package.json | 2 +- packages/core-sdk/src/abi/generated.ts | 458 +++++++++++++ packages/core-sdk/src/index.ts | 13 +- packages/core-sdk/src/resources/group.ts | 10 +- packages/core-sdk/src/resources/ipAccount.ts | 7 +- packages/core-sdk/src/resources/ipAsset.ts | 444 ++++++++++-- packages/core-sdk/src/resources/permission.ts | 9 +- .../core-sdk/src/types/resources/ipAccount.ts | 2 +- .../core-sdk/src/types/resources/ipAsset.ts | 64 +- .../src/types/resources/permission.ts | 14 +- packages/core-sdk/src/utils/chain.ts | 3 +- packages/core-sdk/src/utils/sign.ts | 83 ++- .../core-sdk/test/integration/ipAsset.test.ts | 177 ++++- .../test/integration/permission.test.ts | 2 +- .../test/unit/resources/ipAccount.test.ts | 1 - .../test/unit/resources/ipAsset.test.ts | 647 +++++++++++++++--- packages/core-sdk/test/unit/testUtils.ts | 2 + .../core-sdk/test/unit/utils/sign.test.ts | 56 +- packages/wagmi-generator/wagmi.config.ts | 10 + 20 files changed, 1765 insertions(+), 241 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 37c94edc..1d1e7e7e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @DonFungible @edisonz0718 @jacob-tucker @AndyBoWu +* @LeoHChen @DonFungible @edisonz0718 @jacob-tucker @AndyBoWu diff --git a/packages/core-sdk/package.json b/packages/core-sdk/package.json index 67cf9ce8..b874c2ec 100644 --- a/packages/core-sdk/package.json +++ b/packages/core-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@story-protocol/core-sdk", - "version": "1.2.0-rc.1", + "version": "1.2.0-rc.2", "description": "Story Protocol Core SDK", "main": "dist/story-protocol-core-sdk.cjs.js", "module": "dist/story-protocol-core-sdk.esm.js", diff --git a/packages/core-sdk/src/abi/generated.ts b/packages/core-sdk/src/abi/generated.ts index 74f608c8..58827679 100644 --- a/packages/core-sdk/src/abi/generated.ts +++ b/packages/core-sdk/src/abi/generated.ts @@ -7514,6 +7514,265 @@ export const moduleRegistryConfig = { abi: moduleRegistryAbi, } as const; +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Multicall3 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * + */ +export const multicall3Abi = [ + { + type: "function", + inputs: [ + { + name: "calls", + internalType: "struct Multicall3.Call[]", + type: "tuple[]", + components: [ + { name: "target", internalType: "address", type: "address" }, + { name: "callData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + name: "aggregate", + outputs: [ + { name: "blockNumber", internalType: "uint256", type: "uint256" }, + { name: "returnData", internalType: "bytes[]", type: "bytes[]" }, + ], + stateMutability: "payable", + }, + { + type: "function", + inputs: [ + { + name: "calls", + internalType: "struct Multicall3.Call3[]", + type: "tuple[]", + components: [ + { name: "target", internalType: "address", type: "address" }, + { name: "allowFailure", internalType: "bool", type: "bool" }, + { name: "callData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + name: "aggregate3", + outputs: [ + { + name: "returnData", + internalType: "struct Multicall3.Result[]", + type: "tuple[]", + components: [ + { name: "success", internalType: "bool", type: "bool" }, + { name: "returnData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + stateMutability: "payable", + }, + { + type: "function", + inputs: [ + { + name: "calls", + internalType: "struct Multicall3.Call3Value[]", + type: "tuple[]", + components: [ + { name: "target", internalType: "address", type: "address" }, + { name: "allowFailure", internalType: "bool", type: "bool" }, + { name: "value", internalType: "uint256", type: "uint256" }, + { name: "callData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + name: "aggregate3Value", + outputs: [ + { + name: "returnData", + internalType: "struct Multicall3.Result[]", + type: "tuple[]", + components: [ + { name: "success", internalType: "bool", type: "bool" }, + { name: "returnData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + stateMutability: "payable", + }, + { + type: "function", + inputs: [ + { + name: "calls", + internalType: "struct Multicall3.Call[]", + type: "tuple[]", + components: [ + { name: "target", internalType: "address", type: "address" }, + { name: "callData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + name: "blockAndAggregate", + outputs: [ + { name: "blockNumber", internalType: "uint256", type: "uint256" }, + { name: "blockHash", internalType: "bytes32", type: "bytes32" }, + { + name: "returnData", + internalType: "struct Multicall3.Result[]", + type: "tuple[]", + components: [ + { name: "success", internalType: "bool", type: "bool" }, + { name: "returnData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + stateMutability: "payable", + }, + { + type: "function", + inputs: [], + name: "getBasefee", + outputs: [{ name: "basefee", internalType: "uint256", type: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + inputs: [{ name: "blockNumber", internalType: "uint256", type: "uint256" }], + name: "getBlockHash", + outputs: [{ name: "blockHash", internalType: "bytes32", type: "bytes32" }], + stateMutability: "view", + }, + { + type: "function", + inputs: [], + name: "getBlockNumber", + outputs: [{ name: "blockNumber", internalType: "uint256", type: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + inputs: [], + name: "getChainId", + outputs: [{ name: "chainid", internalType: "uint256", type: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + inputs: [], + name: "getCurrentBlockCoinbase", + outputs: [{ name: "coinbase", internalType: "address", type: "address" }], + stateMutability: "view", + }, + { + type: "function", + inputs: [], + name: "getCurrentBlockDifficulty", + outputs: [{ name: "difficulty", internalType: "uint256", type: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + inputs: [], + name: "getCurrentBlockGasLimit", + outputs: [{ name: "gaslimit", internalType: "uint256", type: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + inputs: [], + name: "getCurrentBlockTimestamp", + outputs: [{ name: "timestamp", internalType: "uint256", type: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + inputs: [{ name: "addr", internalType: "address", type: "address" }], + name: "getEthBalance", + outputs: [{ name: "balance", internalType: "uint256", type: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + inputs: [], + name: "getLastBlockHash", + outputs: [{ name: "blockHash", internalType: "bytes32", type: "bytes32" }], + stateMutability: "view", + }, + { + type: "function", + inputs: [ + { name: "requireSuccess", internalType: "bool", type: "bool" }, + { + name: "calls", + internalType: "struct Multicall3.Call[]", + type: "tuple[]", + components: [ + { name: "target", internalType: "address", type: "address" }, + { name: "callData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + name: "tryAggregate", + outputs: [ + { + name: "returnData", + internalType: "struct Multicall3.Result[]", + type: "tuple[]", + components: [ + { name: "success", internalType: "bool", type: "bool" }, + { name: "returnData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + stateMutability: "payable", + }, + { + type: "function", + inputs: [ + { name: "requireSuccess", internalType: "bool", type: "bool" }, + { + name: "calls", + internalType: "struct Multicall3.Call[]", + type: "tuple[]", + components: [ + { name: "target", internalType: "address", type: "address" }, + { name: "callData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + name: "tryBlockAndAggregate", + outputs: [ + { name: "blockNumber", internalType: "uint256", type: "uint256" }, + { name: "blockHash", internalType: "bytes32", type: "bytes32" }, + { + name: "returnData", + internalType: "struct Multicall3.Result[]", + type: "tuple[]", + components: [ + { name: "success", internalType: "bool", type: "bool" }, + { name: "returnData", internalType: "bytes", type: "bytes" }, + ], + }, + ], + stateMutability: "payable", + }, +] as const; + +/** + * + */ +export const multicall3Address = { + 1516: "0xcA11bde05977b3631167028862bE2a173976CA11", +} as const; + +/** + * + */ +export const multicall3Config = { + address: multicall3Address, + abi: multicall3Abi, +} as const; + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // PILicenseTemplate ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -12349,6 +12608,15 @@ export type DerivativeWorkflowsMintAndRegisterIpAndMakeDerivativeWithLicenseToke recipient: Address; }; +/** + * DerivativeWorkflowsMulticallRequest + * + * @param data bytes[] + */ +export type DerivativeWorkflowsMulticallRequest = { + data: readonly Hex[]; +}; + /** * DerivativeWorkflowsRegisterIpAndMakeDerivativeRequest * @@ -12522,6 +12790,42 @@ export class DerivativeWorkflowsClient { }; } + /** + * method multicall for contract DerivativeWorkflows + * + * @param request DerivativeWorkflowsMulticallRequest + * @return Promise + */ + public async multicall( + request: DerivativeWorkflowsMulticallRequest, + ): Promise { + const { request: call } = await this.rpcClient.simulateContract({ + abi: derivativeWorkflowsAbi, + address: this.address, + functionName: "multicall", + account: this.wallet.account, + args: [request.data], + }); + return await this.wallet.writeContract(call as WriteContractParameters); + } + + /** + * method multicall for contract DerivativeWorkflows with only encode + * + * @param request DerivativeWorkflowsMulticallRequest + * @return EncodedTxData + */ + public multicallEncode(request: DerivativeWorkflowsMulticallRequest): EncodedTxData { + return { + to: this.address, + data: encodeFunctionData({ + abi: derivativeWorkflowsAbi, + functionName: "multicall", + args: [request.data], + }), + }; + } + /** * method registerIpAndMakeDerivative for contract DerivativeWorkflows * @@ -16427,6 +16731,15 @@ export type LicenseAttachmentWorkflowsMintAndRegisterIpAndAttachPilTermsRequest }; }; +/** + * LicenseAttachmentWorkflowsMulticallRequest + * + * @param data bytes[] + */ +export type LicenseAttachmentWorkflowsMulticallRequest = { + data: readonly Hex[]; +}; + /** * LicenseAttachmentWorkflowsRegisterIpAndAttachPilTermsRequest * @@ -16564,6 +16877,42 @@ export class LicenseAttachmentWorkflowsClient { }; } + /** + * method multicall for contract LicenseAttachmentWorkflows + * + * @param request LicenseAttachmentWorkflowsMulticallRequest + * @return Promise + */ + public async multicall( + request: LicenseAttachmentWorkflowsMulticallRequest, + ): Promise { + const { request: call } = await this.rpcClient.simulateContract({ + abi: licenseAttachmentWorkflowsAbi, + address: this.address, + functionName: "multicall", + account: this.wallet.account, + args: [request.data], + }); + return await this.wallet.writeContract(call as WriteContractParameters); + } + + /** + * method multicall for contract LicenseAttachmentWorkflows with only encode + * + * @param request LicenseAttachmentWorkflowsMulticallRequest + * @return EncodedTxData + */ + public multicallEncode(request: LicenseAttachmentWorkflowsMulticallRequest): EncodedTxData { + return { + to: this.address, + data: encodeFunctionData({ + abi: licenseAttachmentWorkflowsAbi, + functionName: "multicall", + args: [request.data], + }), + }; + } + /** * method registerIpAndAttachPILTerms for contract LicenseAttachmentWorkflows * @@ -19402,6 +19751,70 @@ export class ModuleRegistryReadOnlyClient { } } +// Contract Multicall3 ============================================================= + +/** + * Multicall3Aggregate3Request + * + * @param calls tuple[] + */ +export type Multicall3Aggregate3Request = { + calls: { + target: Address; + allowFailure: boolean; + callData: Hex; + }[]; +}; + +/** + * contract Multicall3 write method + */ +export class Multicall3Client { + protected readonly wallet: SimpleWalletClient; + protected readonly rpcClient: PublicClient; + public readonly address: Address; + + constructor(rpcClient: PublicClient, wallet: SimpleWalletClient, address?: Address) { + this.address = address || getAddress(multicall3Address, rpcClient.chain?.id); + this.rpcClient = rpcClient; + this.wallet = wallet; + } + + /** + * method aggregate3 for contract Multicall3 + * + * @param request Multicall3Aggregate3Request + * @return Promise + */ + public async aggregate3(request: Multicall3Aggregate3Request): Promise { + const { request: call } = await this.rpcClient.simulateContract({ + abi: multicall3Abi, + address: this.address, + functionName: "aggregate3", + account: this.wallet.account, + args: [request.calls], + }); + return await this.wallet.writeContract(call as WriteContractParameters); + } + + /** + * method aggregate3 for contract Multicall3 with only encode + * + * @param request Multicall3Aggregate3Request + * @return EncodedTxData + */ + public aggregate3Encode(request: Multicall3Aggregate3Request): EncodedTxData { + return { + to: this.address, + data: encodeFunctionData({ + abi: multicall3Abi, + functionName: "aggregate3", + args: [request.calls], + }), + }; + } +} + // Contract PILicenseTemplate ============================================================= /** @@ -20780,6 +21193,15 @@ export type RegistrationWorkflowsMintAndRegisterIpRequest = { }; }; +/** + * RegistrationWorkflowsMulticallRequest + * + * @param data bytes[] + */ +export type RegistrationWorkflowsMulticallRequest = { + data: readonly Hex[]; +}; + /** * RegistrationWorkflowsRegisterIpRequest * @@ -20945,6 +21367,42 @@ export class RegistrationWorkflowsClient extends RegistrationWorkflowsEventClien }; } + /** + * method multicall for contract RegistrationWorkflows + * + * @param request RegistrationWorkflowsMulticallRequest + * @return Promise + */ + public async multicall( + request: RegistrationWorkflowsMulticallRequest, + ): Promise { + const { request: call } = await this.rpcClient.simulateContract({ + abi: registrationWorkflowsAbi, + address: this.address, + functionName: "multicall", + account: this.wallet.account, + args: [request.data], + }); + return await this.wallet.writeContract(call as WriteContractParameters); + } + + /** + * method multicall for contract RegistrationWorkflows with only encode + * + * @param request RegistrationWorkflowsMulticallRequest + * @return EncodedTxData + */ + public multicallEncode(request: RegistrationWorkflowsMulticallRequest): EncodedTxData { + return { + to: this.address, + data: encodeFunctionData({ + abi: registrationWorkflowsAbi, + functionName: "multicall", + args: [request.data], + }), + }; + } + /** * method registerIp for contract RegistrationWorkflows * diff --git a/packages/core-sdk/src/index.ts b/packages/core-sdk/src/index.ts index acd88fd4..bb0ad2f9 100644 --- a/packages/core-sdk/src/index.ts +++ b/packages/core-sdk/src/index.ts @@ -26,6 +26,7 @@ export type { RegisterIpAndAttachPilTermsRequest, RegisterIpAndAttachPilTermsResponse, MintAndRegisterIpAndMakeDerivativeRequest, + MintAndRegisterIpAndMakeDerivativeResponse, GenerateCreatorMetadataParam, IpCreator, GenerateIpMetadataParam, @@ -41,6 +42,14 @@ export type { RegisterPilTermsAndAttachResponse, MintAndRegisterIpAndMakeDerivativeWithLicenseTokensRequest, RegisterIpAndMakeDerivativeWithLicenseTokensRequest, + BatchMintAndRegisterIpAssetWithPilTermsRequest, + BatchMintAndRegisterIpAssetWithPilTermsResponse, + BatchMintAndRegisterIpAndMakeDerivativeRequest, + BatchMintAndRegisterIpAndMakeDerivativeResponse, + BatchRegisterRequest, + BatchRegisterResponse, + BatchRegisterDerivativeRequest, + BatchRegisterDerivativeResponse, } from "./types/resources/ipAsset"; export type { @@ -86,6 +95,8 @@ export type { CreateBatchPermissionSignatureRequest, PermissionSignatureRequest, PermissionSignatureResponse, + SignatureRequest, + SignatureResponse, } from "./types/resources/permission"; export { AccessPermission } from "./types/resources/permission"; export type { @@ -130,5 +141,5 @@ export type { LicensingModulePredictMintingLicenseFeeResponse, } from "./abi/generated"; -export { getPermissionSignature } from "./utils/sign"; +export { getPermissionSignature, getSignature } from "./utils/sign"; export { convertCIDtoHashIPFS, convertHashIPFStoCID } from "./utils/ipfs"; diff --git a/packages/core-sdk/src/resources/group.ts b/packages/core-sdk/src/resources/group.ts index 731778b6..389a5b55 100644 --- a/packages/core-sdk/src/resources/group.ts +++ b/packages/core-sdk/src/resources/group.ts @@ -104,7 +104,7 @@ export class GroupClient { * @param request.licenseTermsId The ID of the registered license terms that will be attached to the new IP. * @param request.recipient [Optional] The address of the recipient of the minted NFT,default value is your wallet address. * @param request.licenseTemplate [Optional] The address of the license template to be attached to the new group IP,default value is Programmable IP License. - * . @param request.deadline [Optional] The deadline for the signature in milliseconds,default value is 1000ms. + * . @param request.deadline [Optional] The deadline for the signature in seconds, default value is 1000s. * @param 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. @@ -127,7 +127,8 @@ export class GroupClient { } const ipAccount = new IpAccountImplClient(this.rpcClient, this.wallet, groupId); const { result: state } = await ipAccount.state(); - const calculatedDeadline = getDeadline(deadline); + const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; + const calculatedDeadline = getDeadline(blockTimestamp, deadline); const sigAddToGroupSignature = await getPermissionSignature({ ipId: groupId, deadline: calculatedDeadline, @@ -197,7 +198,7 @@ export class GroupClient { * @param request.groupId The ID of the group IP to add the newly registered IP. * @param request.licenseTermsId The ID of the registered license terms that will be attached to the new IP. * @param request.licenseTemplate [Optional] The address of the license template to be attached to the new group IP,default value is Programmable IP License. - * . @param request.deadline [Optional] The deadline for the signature in milliseconds,default is 1000ms. + * . @param request.deadline [Optional] The deadline for the signature in seconds, default is 1000s. * @param 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. @@ -224,7 +225,8 @@ export class GroupClient { } const ipAccount = new IpAccountImplClient(this.rpcClient, this.wallet, request.groupId); const { result: state } = await ipAccount.state(); - const calculatedDeadline = getDeadline(request.deadline); + const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; + const calculatedDeadline = getDeadline(blockTimestamp, request.deadline); const object: GroupingWorkflowsRegisterIpAndAttachLicenseAndAddToGroupRequest = { nftContract: getAddress(request.nftContract, "request.nftContract"), groupId: request.groupId, diff --git a/packages/core-sdk/src/resources/ipAccount.ts b/packages/core-sdk/src/resources/ipAccount.ts index 9987780d..0f3910bf 100644 --- a/packages/core-sdk/src/resources/ipAccount.ts +++ b/packages/core-sdk/src/resources/ipAccount.ts @@ -67,12 +67,12 @@ export class IPAccountClient { * @param request - The request object containing necessary data to execute IP Account a transaction. * @param request.ipId The Ip Id to get ip account. * @param request.to The recipient of the transaction. - * @param request.value The amount of Ether to send. * @param request.data The data to send along with the transaction. * @param request.signer The signer of the transaction. * @param request.deadline The deadline of the transaction signature. * @param request.signature The signature of the transaction, EIP-712 encoded. - * @param request.txOptions - [Optional] transaction. This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property. + * @param request.value [Optional] The amount of Ether to send. + * @param request.txOptions [Optional] This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property. * @returns Tx hash for the transaction. */ public async executeWithSig( @@ -87,13 +87,12 @@ export class IPAccountClient { const req = { to: getAddress(request.to, "request.to"), - value: BigInt(0), + value: BigInt(request.value || 0), data: request.data, signer: getAddress(request.signer, "request.signer"), deadline: BigInt(request.deadline), signature: request.signature, }; - if (request.txOptions?.encodedTxDataOnly) { return { encodedTxData: ipAccountClient.executeWithSigEncode(req) }; } else { diff --git a/packages/core-sdk/src/resources/ipAsset.ts b/packages/core-sdk/src/resources/ipAsset.ts index 86005227..3ea0d75f 100644 --- a/packages/core-sdk/src/resources/ipAsset.ts +++ b/packages/core-sdk/src/resources/ipAsset.ts @@ -17,6 +17,14 @@ import { chain, getAddress } from "../utils/utils"; import { SupportedChainIds } from "../types/config"; import { handleError } from "../utils/errors"; import { + BatchMintAndRegisterIpAndMakeDerivativeRequest, + BatchMintAndRegisterIpAndMakeDerivativeResponse, + BatchMintAndRegisterIpAssetWithPilTermsRequest, + BatchMintAndRegisterIpAssetWithPilTermsResponse, + BatchRegisterDerivativeRequest, + BatchRegisterDerivativeResponse, + BatchRegisterRequest, + BatchRegisterResponse, CreateIpAssetWithPilTermsRequest, CreateIpAssetWithPilTermsResponse, GenerateCreatorMetadataParam, @@ -39,6 +47,7 @@ import { RegisterPilTermsAndAttachRequest, RegisterPilTermsAndAttachResponse, RegisterRequest, + MintAndRegisterIpAndMakeDerivativeResponse, } from "../types/resources/ipAsset"; import { AccessControllerClient, @@ -57,6 +66,7 @@ import { LicenseRegistryReadOnlyClient, LicenseTokenReadOnlyClient, LicensingModuleClient, + Multicall3Client, PiLicenseTemplateClient, RegistrationWorkflowsClient, RegistrationWorkflowsMintAndRegisterIpRequest, @@ -64,10 +74,11 @@ import { SimpleWalletClient, accessControllerAbi, ipAccountImplAbi, + licensingModuleAbi, royaltyPolicyLapAddress, } from "../abi/generated"; import { getLicenseTermByType, validateLicenseTerms } from "../utils/licenseTermsHelper"; -import { getDeadline, getPermissionSignature } from "../utils/sign"; +import { getDeadline, getPermissionSignature, getSignature } from "../utils/sign"; import { AccessPermission, SetPermissionsRequest } from "../types/resources/permission"; export class IPAssetClient { @@ -81,10 +92,12 @@ export class IPAssetClient { public registrationWorkflowsClient: RegistrationWorkflowsClient; public licenseAttachmentWorkflowsClient: LicenseAttachmentWorkflowsClient; public derivativeWorkflowsClient: DerivativeWorkflowsClient; + public multicall3Client: Multicall3Client; private readonly rpcClient: PublicClient; private readonly wallet: SimpleWalletClient; private readonly chainId: SupportedChainIds; + private defaultLicenseTermsId!: bigint; constructor(rpcClient: PublicClient, wallet: SimpleWalletClient, chainId: SupportedChainIds) { this.licensingModuleClient = new LicensingModuleClient(rpcClient, wallet); @@ -97,9 +110,11 @@ export class IPAssetClient { this.registrationWorkflowsClient = new RegistrationWorkflowsClient(rpcClient, wallet); this.licenseAttachmentWorkflowsClient = new LicenseAttachmentWorkflowsClient(rpcClient, wallet); this.derivativeWorkflowsClient = new DerivativeWorkflowsClient(rpcClient, wallet); + this.multicall3Client = new Multicall3Client(rpcClient, wallet); this.rpcClient = rpcClient; this.wallet = wallet; this.chainId = chainId; + void this.getDefaultLicenseTerms(); } /** @@ -219,7 +234,7 @@ export class IPAssetClient { * @param request.ipMetadata.ipMetadataHash [Optional] The hash of the metadata for the IP. * @param request.ipMetadata.nftMetadataURI [Optional] The URI of the metadata for the NFT. * @param request.ipMetadata.nftMetadataHash [Optional] The hash of the metadata for the IP NFT. - * @param request.deadline [Optional] The deadline for the signature in milliseconds, default is 1000ms. + * @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. * @returns A Promise that resolves to a transaction hash, and if encodedTxDataOnly is true, includes encoded transaction data, and if waitForTransaction is true, includes IP ID, token ID. * @emits IPRegistered (ipId, chainId, tokenContract, tokenId, resolverAddr, metadataProviderAddress, metadata) @@ -247,6 +262,34 @@ export class IPAssetClient { signature: zeroHash, }, }; + if (request.ipMetadata) { + const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; + const calculatedDeadline = getDeadline(blockTimestamp, request.deadline); + const signature = await getPermissionSignature({ + ipId: ipIdAddress, + deadline: calculatedDeadline, + state: toHex(0, { size: 32 }), + wallet: this.wallet as WalletClient, + chainId: chain[this.chainId], + permissions: [ + { + ipId: ipIdAddress, + signer: getAddress( + this.registrationWorkflowsClient.address, + "registrationWorkflowsClient", + ), + to: getAddress(this.coreMetadataModuleClient.address, "coreMetadataModuleAddress"), + permission: AccessPermission.ALLOW, + func: "function setAll(address,string,bytes32,bytes32)", + }, + ], + }); + object.sigMetadata = { + signer: getAddress(this.wallet.account!.address, "wallet.account.address"), + deadline: calculatedDeadline, + signature, + }; + } if (request.txOptions?.encodedTxDataOnly) { if (request.ipMetadata) { return { encodedTxData: this.registrationWorkflowsClient.registerIpEncode(object) }; @@ -262,37 +305,12 @@ export class IPAssetClient { } else { let txHash: Hex; if (request.ipMetadata) { - const calculatedDeadline = getDeadline(request.deadline); - const signature = await getPermissionSignature({ - ipId: ipIdAddress, - deadline: calculatedDeadline, - state: toHex(0, { size: 32 }), - wallet: this.wallet as WalletClient, - chainId: chain[this.chainId], - permissions: [ - { - ipId: ipIdAddress, - signer: getAddress( - this.registrationWorkflowsClient.address, - "registrationWorkflowsClient", - ), - to: getAddress(this.coreMetadataModuleClient.address, "coreMetadataModuleAddress"), - permission: AccessPermission.ALLOW, - func: "function setAll(address,string,bytes32,bytes32)", - }, - ], - }); - object.sigMetadata = { - signer: getAddress(this.wallet.account!.address, "wallet.account.address"), - deadline: calculatedDeadline, - signature, - }; txHash = await this.registrationWorkflowsClient.registerIp(object); } else { txHash = await this.ipAssetRegistryClient.register({ tokenContract: object.nftContract, tokenId: object.tokenId, - chainid: BigInt(chain[this.chainId]), + chainid: BigInt(this.chainId), }); } if (request.txOptions?.waitForTransaction) { @@ -300,8 +318,8 @@ export class IPAssetClient { ...request.txOptions, hash: txHash, }); - const targetLogs = this.ipAssetRegistryClient.parseTxIpRegisteredEvent(txReceipt); - return { txHash: txHash, ipId: targetLogs[0].ipId, tokenId: targetLogs[0].tokenId }; + const targetLogs = this.getIpIdAndTokenIdFromEvent(txReceipt)[0]; + return { txHash: txHash, ipId: targetLogs.ipId, tokenId: targetLogs.tokenId }; } else { return { txHash: txHash }; } @@ -311,6 +329,65 @@ export class IPAssetClient { } } + /** + * Batch registers an NFT as IP, creating a corresponding IP record. + * @param request - The request object that contains all data needed to batch register IP. + * @param {Array} request.args The array of objects containing the data needed to register IP. + * @param request.args.nftContract The address of the NFT. + * @param request.args.tokenId The token identifier of the NFT. + * @param request.args.ipMetadata - [Optional] The desired metadata for the newly minted NFT and newly registered IP. + * @param request.args.ipMetadata.ipMetadataURI [Optional] The URI of the metadata for the IP. + * @param request.args.ipMetadata.ipMetadataHash [Optional] The hash of the metadata for the IP. + * @param request.args.ipMetadata.nftMetadataURI [Optional] The URI of the metadata for the NFT. + * @param request.args.ipMetadata.nftMetadataHash [Optional] The hash of the metadata for the IP NFT. + * @param request.txOptions [Optional] This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property. + * @returns A Promise that resolves to a transaction hash, if waitForTransaction is true, includes IP ID, Token ID. + * @emits IPRegistered (ipId, chainId, tokenContract, tokenId, resolverAddr, metadataProviderAddress, metadata) + */ + public async batchRegister(request: BatchRegisterRequest): Promise { + try { + const contracts = []; + let encodedTxData: Hex; + for (const arg of request.args) { + try { + const result = await this.register({ + ...arg, + txOptions: { + encodedTxDataOnly: true, + }, + }); + encodedTxData = result.encodedTxData!.data; + } catch (error) { + throw new Error((error as Error).message.replace("Failed to register IP:", "").trim()); + } + const isSpg = !!arg.ipMetadata; + contracts.push({ + target: isSpg + ? this.registrationWorkflowsClient.address + : this.ipAssetRegistryClient.address, + allowFailure: false, + callData: encodedTxData, + }); + } + const txHash = await this.multicall3Client.aggregate3({ calls: contracts }); + if (request.txOptions?.waitForTransaction) { + const txReceipt = await this.rpcClient.waitForTransactionReceipt({ + ...request.txOptions, + hash: txHash, + }); + const targetLogs = this.getIpIdAndTokenIdFromEvent(txReceipt); + const results = targetLogs.map((log) => ({ + ipId: log.ipId, + tokenId: log.tokenId, + })); + return { txHash: txHash, results }; + } else { + return { txHash: txHash }; + } + } catch (error) { + handleError(error, "Failed to batch register IP"); + } + } /** * Registers a derivative directly with parent IP's license terms, without needing license tokens, * and attaches the license terms of the parent IPs to the derivative IP. @@ -322,7 +399,7 @@ export class IPAssetClient { * @param request.parentIpIds The parent IP IDs. * @param request.licenseTermsIds The IDs of the license terms that the parent IP supports. * @param request.txOptions - [Optional] transaction. This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property. - * @returns A Promise that resolves to an object containing the transaction hash. + * @returns A Promise that resolves to a transaction hash, and if encodedTxDataOnly is true, includes encoded transaction data. */ public async registerDerivative( request: RegisterDerivativeRequest, @@ -386,6 +463,98 @@ export class IPAssetClient { } } + /** + * Batch registers a derivative directly with parent IP's license terms. + * @param request - The request object that contains all data needed to batch register derivative IP. + * @param {Array} request.args The array of objects containing the data needed to register derivative IP. + * @param request.args.childIpId The derivative IP ID. + * @param request.args.parentIpIds The parent IP IDs. + * @param request.args.licenseTermsIds The IDs of the license terms that the parent IP supports. + * @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. + * @returns A Promise that resolves to a transaction hash. + */ + public async batchRegisterDerivative( + request: BatchRegisterDerivativeRequest, + ): Promise { + try { + const contracts = []; + const licenseModuleAddress = getAddress( + this.licensingModuleClient.address, + "licensingModuleAddress", + ); + for (const arg of request.args) { + try { + await this.registerDerivative({ + ...arg, + txOptions: { + encodedTxDataOnly: true, + }, + }); + } catch (error) { + throw new Error( + (error as Error).message.replace("Failed to register derivative:", "").trim(), + ); + } + const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; + const calculatedDeadline = getDeadline(blockTimestamp, request.deadline); + const ipAccount = new IpAccountImplClient( + this.rpcClient, + this.wallet, + getAddress(arg.childIpId, "arg.childIpId"), + ); + const data = encodeFunctionData({ + abi: licensingModuleAbi, + functionName: "registerDerivative", + args: [ + arg.childIpId, + arg.parentIpIds, + arg.licenseTermsIds.map((id) => BigInt(id)), + arg.licenseTemplate || this.licenseTemplateClient.address, + zeroAddress, + ], + }); + const { result: state } = await ipAccount.state(); + const signature = await getSignature({ + state, + to: licenseModuleAddress, + encodeData: data, + wallet: this.wallet, + verifyingContract: arg.childIpId, + deadline: calculatedDeadline, + chainId: chain[this.chainId], + }); + contracts.push({ + target: arg.childIpId, + allowFailure: false, + callData: encodeFunctionData({ + abi: ipAccountImplAbi, + functionName: "executeWithSig", + args: [ + licenseModuleAddress, + BigInt(0), + data, + this.wallet.account!.address, + calculatedDeadline, + signature, + ], + }), + }); + } + const txHash = await this.multicall3Client.aggregate3({ calls: contracts }); + if (request.txOptions?.waitForTransaction) { + await this.rpcClient.waitForTransactionReceipt({ + ...request.txOptions, + hash: txHash, + }); + return { txHash }; + } else { + return { txHash }; + } + } catch (error) { + handleError(error, "Failed to batch register derivative"); + } + } /** * Registers a derivative with license tokens. * the derivative IP is registered with license tokens minted from the parent IP's license terms. @@ -495,8 +664,8 @@ export class IPAssetClient { ...request.txOptions, hash: txHash, }); - const iPRegisteredLog = this.ipAssetRegistryClient.parseTxIpRegisteredEvent(txReceipt)[0]; - const licenseTermsId = await this.getLicenseTermsId(txReceipt); + const iPRegisteredLog = this.getIpIdAndTokenIdFromEvent(txReceipt)[0]; + const licenseTermsId = this.getLicenseTermsId(txReceipt); return { txHash: txHash, ipId: iPRegisteredLog.ipId, @@ -510,6 +679,71 @@ export class IPAssetClient { handleError(error, "Failed to mint and register IP and attach PIL terms"); } } + /** + * Batch mint an NFT from a collection and register it as an IP. + * @param request - The request object that contains all data needed to batch mint and register ip. + * @param {Array} request.args The array of mint and register IP requests. + * @param request.args.spgNftContract The address of the NFT collection. + * @param request.args.pilType The type of the PIL. + * @param request.args.ipMetadata - [Optional] The desired metadata for the newly minted NFT and newly registered IP. + * @param request.args.ipMetadata.ipMetadataURI [Optional] The URI of the metadata for the IP. + * @param request.args.ipMetadata.ipMetadataHash [Optional] The hash of the metadata for the IP. + * @param request.args.ipMetadata.nftMetadataURI [Optional] The URI of the metadata for the NFT. + * @param request.args.ipMetadata.nftMetadataHash [Optional] The hash of the metadata for the IP NFT. + * @param request.args.royaltyPolicyAddress [Optional] The address of the royalty policy contract, default value is LAP. + * @param request.args.recipient [Optional] The address of the recipient of the minted NFT,default value is your wallet address. + * @param request.args.mintingFee [Optional] The fee to be paid when minting a license. + * @param request.args.commercialRevShare [Optional] Percentage of revenue that must be shared with the licensor. + * @param request.args.currency [Optional] The ERC20 token to be used to pay the minting fee. the token must be registered in story protocol. + * @param request.txOptions [Optional] This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property, without encodedTxData option. + * @returns A Promise that resolves to a transaction hash, if waitForTransaction is true, includes IP ID, Token ID, License Terms Id. + * @emits IPRegistered (ipId, chainId, tokenContract, tokenId, name, uri, registrationDate) + * @emits LicenseTermsAttached (caller, ipId, licenseTemplate, licenseTermsId) + */ + public async batchMintAndRegisterIpAssetWithPilTerms( + request: BatchMintAndRegisterIpAssetWithPilTermsRequest, + ): Promise { + try { + const calldata: Hex[] = []; + for (const arg of request.args) { + const result = await this.mintAndRegisterIpAssetWithPilTerms({ + ...arg, + txOptions: { + encodedTxDataOnly: true, + }, + }); + calldata.push(result.encodedTxData!.data); + } + const txHash = await this.licenseAttachmentWorkflowsClient.multicall({ data: calldata }); + if (request.txOptions?.waitForTransaction) { + const txReceipt = await this.rpcClient.waitForTransactionReceipt({ + ...request.txOptions, + hash: txHash, + }); + const licenseTermsEvent = + this.licensingModuleClient.parseTxLicenseTermsAttachedEvent(txReceipt); + let results = this.getIpIdAndTokenIdFromEvent(txReceipt); + results = results.map((result) => { + const licenseTerms = licenseTermsEvent.find((event) => event.ipId === result.ipId); + return { + ...result, + licenseTermsId: + licenseTerms?.licenseTermsId === undefined + ? this.defaultLicenseTermsId + : licenseTerms.licenseTermsId, + }; + }); + return { + txHash: txHash, + results, + }; + } + return { txHash }; + } catch (error) { + handleError(error, "Failed to batch mint and register IP and attach PIL terms"); + } + } + /** * Register a given NFT as an IP and attach Programmable IP License Terms.R. * @param request - The request object that contains all data needed to mint and register ip. @@ -522,12 +756,12 @@ export class IPAssetClient { * @param request.ipMetadata.nftMetadataURI [Optional] The URI of the metadata for the NFT. * @param request.ipMetadata.nftMetadataHash [Optional] The hash of the metadata for the IP NFT. * @param request.royaltyPolicyAddress [Optional] The address of the royalty policy contract, default value is LAP. - * @param request.deadline [Optional] The deadline for the signature in milliseconds, default is 1000ms. + * @param request.deadline [Optional] The deadline for the signature in seconds, default is 1000s. * @param request.mintingFee [Optional] The fee to be paid when minting a license. * @param request.commercialRevShare [Optional] Percentage of revenue that must be shared with the licensor. * @param request.currency [Optional] The ERC20 token to be used to pay the minting fee. the token must be registered in story protocol. * @param request.txOptions - [Optional] transaction. This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property. - * @returns A Promise that resolves to an object containing the transaction hash and optional IP ID, License Terms Id if waitForTxn is set to true. + * @returns A Promise that resolves to an object containing the transaction hash and optional IP ID,Token ID, License Terms Id if waitForTxn is set to true. * @emits LicenseTermsAttached (caller, ipId, licenseTemplate, licenseTermsId) */ public async registerIpAndAttachPilTerms( @@ -552,7 +786,8 @@ export class IPAssetClient { royaltyPolicyLapAddress[chain[this.chainId]], commercialRevShare: request.commercialRevShare, }); - const calculatedDeadline = getDeadline(request.deadline); + const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; + const calculatedDeadline = getDeadline(blockTimestamp, request.deadline); const sigAttachSignature = await getPermissionSignature({ ipId: ipIdAddress, @@ -638,9 +873,14 @@ export class IPAssetClient { ...request.txOptions, hash: txHash, }); - const ipRegisterEvent = this.ipAssetRegistryClient.parseTxIpRegisteredEvent(txReceipt); - const licenseTermsId = await this.getLicenseTermsId(txReceipt); - return { txHash, licenseTermsId: licenseTermsId, ipId: ipRegisterEvent[0].ipId }; + const ipRegisterEvent = this.getIpIdAndTokenIdFromEvent(txReceipt)[0]; + const licenseTermsId = this.getLicenseTermsId(txReceipt); + return { + txHash, + licenseTermsId: licenseTermsId, + ipId: ipRegisterEvent.ipId, + tokenId: ipRegisterEvent.tokenId, + }; } return { txHash }; } @@ -662,9 +902,9 @@ export class IPAssetClient { * @param request.ipMetadata.ipMetadataHash [Optional] The hash of the metadata for the IP. * @param request.ipMetadata.nftMetadataURI [Optional] The URI of the metadata for the NFT. * @param request.ipMetadata.nftMetadataHash [Optional] The hash of the metadata for the IP NFT. - * @param request.deadline [Optional] The deadline for the signature in milliseconds,default is 1000ms. + * @param request.deadline [Optional] The deadline for the signature in seconds, default is 1000s. * @param request.txOptions - [Optional] transaction. This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property. - * @returns A Promise that resolves to an object containing the transaction hash and optional IP ID if waitForTxn is set to true. + * @returns A Promise that resolves to an object containing the transaction hash and optional IP ID, token ID if waitForTxn is set to true. * @emits IPRegistered (ipId, chainId, tokenContract, tokenId, name, uri, registrationDate) */ public async registerDerivativeIp( @@ -699,8 +939,8 @@ export class IPAssetClient { ); } } - const calculatedDeadline = getDeadline(request.deadline); - + const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; + const calculatedDeadline = getDeadline(blockTimestamp, request.deadline); const sigRegisterSignature = await getPermissionSignature({ ipId: ipIdAddress, deadline: calculatedDeadline, @@ -786,8 +1026,8 @@ export class IPAssetClient { ...request.txOptions, hash: txHash, }); - const log = this.ipAssetRegistryClient.parseTxIpRegisteredEvent(receipt)[0]; - return { txHash, ipId: log.ipId }; + const log = this.getIpIdAndTokenIdFromEvent(receipt)[0]; + return { txHash, ipId: log.ipId, tokenId: log.tokenId }; } return { txHash }; } @@ -801,14 +1041,14 @@ export class IPAssetClient { * @param request - The request object that contains all data needed to mint and register ip and make derivative. * @param request.spgNftContract The address of the NFT collection. * @param request.derivData The derivative data to be used for registerDerivative. - * @param request.derivData.parentIpIds The IDs of the parent IPs to link the registered derivative IP. - * @param request.derivData.licenseTermsIds The IDs of the license terms to be used for the linking. - * @param request.derivData.licenseTemplate [Optional] The address of the license template to be used for the linking. + * @param request.derivData.parentIpIds The IDs of the parent IPs to link the registered derivative IP. + * @param request.derivData.licenseTermsIds The IDs of the license terms to be used for the linking. + * @param request.derivData.licenseTemplate [Optional] The address of the license template to be used for the linking. * @param 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. - * @param request.ipMetadata.nftMetadataURI [Optional] The URI of the metadata for the NFT. - * @param request.ipMetadata.nftMetadataHash [Optional] The hash of the metadata for the IP NFT. + * @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. + * @param request.ipMetadata.nftMetadataURI [Optional] The URI of the metadata for the NFT. + * @param request.ipMetadata.nftMetadataHash [Optional] The hash of the metadata for the IP NFT. * @param request.recipient [Optional] The address of the recipient of the minted NFT,default value is your wallet address. * @param request.txOptions - [Optional] transaction. This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property. * @returns A Promise that resolves to a transaction hash, and if encodedTxDataOnly is true, includes encoded transaction data, and if waitForTransaction is true, includes child ip id and token id. @@ -816,7 +1056,7 @@ export class IPAssetClient { */ public async mintAndRegisterIpAndMakeDerivative( request: MintAndRegisterIpAndMakeDerivativeRequest, - ): Promise { + ): Promise { try { if (request.derivData.parentIpIds.length !== request.derivData.licenseTermsIds.length) { throw new Error("Parent IP IDs and License terms IDs must be provided in pairs."); @@ -872,7 +1112,7 @@ export class IPAssetClient { ...request.txOptions, hash: txHash, }); - const log = this.ipAssetRegistryClient.parseTxIpRegisteredEvent(receipt)[0]; + const log = this.getIpIdAndTokenIdFromEvent(receipt)[0]; return { txHash, childIpId: log.ipId, tokenId: log.tokenId }; } return { txHash }; @@ -881,6 +1121,61 @@ export class IPAssetClient { handleError(error, "Failed to mint and register IP and make derivative"); } } + /** + * Batch mint an NFT from a collection and register it as a derivative IP without license tokens. + * @param request - The request object that contains all data needed to batch mint and register ip and make derivative. + * @param {Array} request.args The array of mint and register IP requests. + * @param request.args.spgNftContract The address of the NFT collection. + * @param request.args.derivData The derivative data to be used for registerDerivative. + * @param request.args.derivData.parentIpIds The IDs of the parent IPs to link the registered derivative IP. + * @param request.args.derivData.licenseTermsIds The IDs of the license terms to be used for the linking. + * @param request.args.derivData.licenseTemplate [Optional] The address of the license template to be used for the linking. + * @param request.args.ipMetadata - [Optional] The desired metadata for the newly minted NFT and newly registered IP. + * @param request.args.ipMetadata.ipMetadataURI [Optional] The URI of the metadata for the IP. + * @param request.args.ipMetadata.ipMetadataHash [Optional] The hash of the metadata for the IP. + * @param request.args.ipMetadata.nftMetadataURI [Optional] The URI of the metadata for the NFT. + * @param request.args.ipMetadata.nftMetadataHash [Optional] The hash of the metadata for the IP NFT. + * @param request.arg.recipient [Optional] The address of the recipient of the minted NFT,default value is your wallet address. + * @param request.txOptions - [Optional] transaction. This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property, without encodedTxData option. + * @returns A Promise that resolves to a transaction hash, if waitForTransaction is true, includes child ip id and token id. + * @emits IPRegistered (ipId, chainId, tokenContract, tokenId, name, uri, registrationDate) + */ + public async batchMintAndRegisterIpAndMakeDerivative( + request: BatchMintAndRegisterIpAndMakeDerivativeRequest, + ): Promise { + try { + const calldata: Hex[] = []; + for (const arg of request.args) { + try { + const result = await this.mintAndRegisterIpAndMakeDerivative({ + ...arg, + txOptions: { encodedTxDataOnly: true }, + }); + calldata.push(result.encodedTxData!.data); + } catch (error) { + throw new Error( + (error as Error).message + .replace("Failed to mint and register IP and make derivative: ", "") + .trim(), + ); + } + } + const txHash = await this.derivativeWorkflowsClient.multicall({ data: calldata }); + if (request.txOptions?.waitForTransaction) { + const txReceipt = await this.rpcClient.waitForTransactionReceipt({ + ...request.txOptions, + hash: txHash, + }); + return { + txHash, + results: this.getIpIdAndTokenIdFromEvent(txReceipt), + }; + } + return { txHash }; + } catch (error) { + handleError(error, "Failed to batch mint and register IP and make derivative"); + } + } /** * Mint an NFT from a SPGNFT collection and register it with metadata as an IP. * @param request - The request object that contains all data needed to attach license terms. @@ -918,7 +1213,7 @@ export class IPAssetClient { ...request.txOptions, hash: txHash, }); - const ipRegisterEvent = this.ipAssetRegistryClient.parseTxIpRegisteredEvent(txReceipt); + const ipRegisterEvent = this.getIpIdAndTokenIdFromEvent(txReceipt); return { txHash, ipId: ipRegisterEvent[0].ipId, tokenId: ipRegisterEvent[0].tokenId }; } return { txHash }; @@ -949,7 +1244,7 @@ export class IPAssetClient { * @param request.terms.derivativeRevCeiling The maximum revenue that can be generated from the derivative use of the work. * @param request.terms.currency The ERC20 token to be used to pay the minting fee. the token must be registered in story protocol. * @param request.terms.uri The URI of the license terms, which can be used to fetch the offchain license terms. - * @param request.deadline [Optional] The deadline for the signature in milliseconds,default is 1000ms. + * @param request.deadline [Optional] The deadline for the signature in milliseconds, default is 1000s. * @param request.txOptions [Optional] This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property. * @returns A Promise that resolves to a transaction hash, and if encodedTxDataOnly is true, includes encoded transaction data, and if waitForTransaction is true, includes license terms id. * @emits LicenseTermsAttached (caller, ipId, licenseTemplate, licenseTermsId) @@ -967,7 +1262,8 @@ export class IPAssetClient { const licenseRes = await this.licenseTemplateClient.getLicenseTermsId({ terms: licenseTerms, }); - const calculatedDeadline = getDeadline(request.deadline); + const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; + const calculatedDeadline = getDeadline(blockTimestamp, request.deadline); const ipAccount = new IpAccountImplClient(this.rpcClient, this.wallet, ipId); const { result: state } = await ipAccount.state(); const sigAttachSignature = await getPermissionSignature({ @@ -1073,7 +1369,7 @@ export class IPAssetClient { ...request.txOptions, hash: txHash, }); - const log = this.ipAssetRegistryClient.parseTxIpRegisteredEvent(receipt)[0]; + const log = this.getIpIdAndTokenIdFromEvent(receipt)[0]; return { txHash, ipId: log.ipId, tokenId: log.tokenId }; } return { txHash }; @@ -1088,12 +1384,12 @@ export class IPAssetClient { * @param request.nftContract The address of the NFT collection. * @param request.licenseTokenIds The IDs of the license tokens to be burned for linking the IP to parent IPs. * @param request.tokenId The ID of the NFT. - * @param request.deadline [Optional] The deadline for the signature in milliseconds, default is 1000ms. * @param 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. * @param request.ipMetadata.nftMetadataURI [Optional] The URI of the metadata for the NFT. * @param request.ipMetadata.nftMetadataHash [Optional] The hash of the metadata for the IP NFT. + * @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. * @returns A Promise that resolves to a transaction hash, and if encodedTxDataOnly is true, includes encoded transaction data, or if waitForTransaction is true, includes IP ID. */ @@ -1108,7 +1404,8 @@ export class IPAssetClient { throw new Error(`The NFT with id ${tokenId} is already registered as IP.`); } const licenseTokenIds = await this.validateLicenseTokenIds(request.licenseTokenIds); - const calculatedDeadline = getDeadline(request.deadline); + const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; + const calculatedDeadline = getDeadline(blockTimestamp, request.deadline); const sigMetadataSignature = await getPermissionSignature({ ipId: ipIdAddress, deadline: calculatedDeadline, @@ -1184,7 +1481,7 @@ export class IPAssetClient { ...request.txOptions, hash: txHash, }); - const log = this.ipAssetRegistryClient.parseTxIpRegisteredEvent(receipt)[0]; + const log = this.getIpIdAndTokenIdFromEvent(receipt)[0]; return { txHash, ipId: log.ipId }; } return { txHash }; @@ -1206,7 +1503,7 @@ export class IPAssetClient { return ipId; } - private async isRegistered(ipId: Hex): Promise { + public async isRegistered(ipId: Hex): Promise { return await this.ipAssetRegistryClient.isRegistered({ id: getAddress(ipId, "ipId") }); } @@ -1241,17 +1538,13 @@ export class IPAssetClient { return sigAttachState; } - private async getLicenseTermsId(txReceipt: TransactionReceipt): Promise { + private getLicenseTermsId(txReceipt: TransactionReceipt): bigint { const licensingModuleLicenseTermsAttachedEvent = this.licensingModuleClient.parseTxLicenseTermsAttachedEvent(txReceipt); - let licenseTermsId = + const licenseTermsId = licensingModuleLicenseTermsAttachedEvent.length >= 1 && licensingModuleLicenseTermsAttachedEvent[0].licenseTermsId; - if (licenseTermsId === false) { - const defaultLicenseTerms = await this.licenseRegistryReadOnlyClient.getDefaultLicenseTerms(); - licenseTermsId = defaultLicenseTerms.licenseTermsId; - } - return licenseTermsId; + return licenseTermsId === false ? this.defaultLicenseTermsId : licenseTermsId; } private async validateLicenseTokenIds( @@ -1271,4 +1564,17 @@ export class IPAssetClient { } return newLicenseTokenIds; } + + private getIpIdAndTokenIdFromEvent(txReceipt: TransactionReceipt) { + const IPRegisteredLog = this.ipAssetRegistryClient.parseTxIpRegisteredEvent(txReceipt); + return IPRegisteredLog.map((log) => { + return { ipId: log.ipId, tokenId: log.tokenId }; + }); + } + + private async getDefaultLicenseTerms() { + this.defaultLicenseTermsId = ( + await this.licenseRegistryReadOnlyClient.getDefaultLicenseTerms() + ).licenseTermsId; + } } diff --git a/packages/core-sdk/src/resources/permission.ts b/packages/core-sdk/src/resources/permission.ts index 5f01e81f..353db0c6 100644 --- a/packages/core-sdk/src/resources/permission.ts +++ b/packages/core-sdk/src/resources/permission.ts @@ -97,7 +97,7 @@ export class PermissionClient { * @param request.to The address that can be called by the `signer` (currently only modules can be `to`) * @param request.permission The new permission level. * @param request.func [Optional] The function selector string of `to` that can be called by the `signer` on behalf of the `ipAccount`. Be default, it allows all functions. - * @param request.deadline [Optional] The deadline for the signature in milliseconds, default is 1000ms. + * @param request.deadline [Optional] The deadline for the signature in seconds, default is 1000s. * @param request.txOptions - [Optional] transaction. This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property. * @returns A Promise that resolves to an object containing the transaction hash. * @emits PermissionSet (ipAccountOwner, ipAccount, signer, to, func, permission) @@ -121,7 +121,8 @@ export class PermissionClient { ], }); const { result: state } = await ipAccountClient.state(); - const calculatedDeadline = getDeadline(deadline); + const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; + const calculatedDeadline = getDeadline(blockTimestamp, deadline); const signature = await getPermissionSignature({ ipId, @@ -262,6 +263,7 @@ export class PermissionClient { * @param request.permissions[].to The address that can be called by the `signer` (currently only modules can be `to`). * @param request.permissions[].permission The new permission level. * @param request.permissions[].func [Optional] The function selector string of `to` that can be called by the `signer` on behalf of the `ipAccount`. Be default, it allows all functions. + * @param request.deadline [Optional] The deadline for the signature in seconds, default is 1000s. * @param request.txOptions - [Optional] transaction. This extends `WaitForTransactionReceiptParameters` from the Viem library, excluding the `hash` property. * @returns A Promise that resolves to an object containing the transaction hash. * @emits PermissionSet (ipAccountOwner, ipAccount, signer, to, func, permission) @@ -289,7 +291,8 @@ export class PermissionClient { ], }); const { result: state } = await ipAccountClient.state(); - const calculatedDeadline = getDeadline(deadline); + const blockTimestamp = (await this.rpcClient.getBlock()).timestamp; + const calculatedDeadline = getDeadline(blockTimestamp, deadline); const signature = await getPermissionSignature({ ipId, deadline: calculatedDeadline, diff --git a/packages/core-sdk/src/types/resources/ipAccount.ts b/packages/core-sdk/src/types/resources/ipAccount.ts index 2ef9280b..745ea2f0 100644 --- a/packages/core-sdk/src/types/resources/ipAccount.ts +++ b/packages/core-sdk/src/types/resources/ipAccount.ts @@ -19,11 +19,11 @@ export type IPAccountExecuteResponse = { export type IPAccountExecuteWithSigRequest = { ipId: Address; to: Address; - value: number; data: Address; signer: Address; deadline: number | bigint | string; signature: Address; + value?: number | bigint | string; txOptions?: TxOptions; }; diff --git a/packages/core-sdk/src/types/resources/ipAsset.ts b/packages/core-sdk/src/types/resources/ipAsset.ts index bf9adce0..9f1206f4 100644 --- a/packages/core-sdk/src/types/resources/ipAsset.ts +++ b/packages/core-sdk/src/types/resources/ipAsset.ts @@ -1,4 +1,4 @@ -import { Address } from "viem"; +import { Address, Hex } from "viem"; import { TxOptions } from "../options"; import { PIL_TYPE, RegisterPILTermsRequest } from "./license"; @@ -6,7 +6,7 @@ import { EncodedTxData } from "../../abi/generated"; import { IpMetadataAndTxOption } from "../common"; export type RegisterIpResponse = { - txHash?: string; + txHash?: Hex; encodedTxData?: EncodedTxData; ipId?: Address; tokenId?: bigint; @@ -25,7 +25,7 @@ export type RegisterDerivativeWithLicenseTokensRequest = { }; export type RegisterDerivativeWithLicenseTokensResponse = { - txHash?: string; + txHash?: Hex; encodedTxData?: EncodedTxData; }; @@ -38,10 +38,8 @@ export type RegisterDerivativeRequest = { }; export type RegisterDerivativeResponse = { - txHash?: string; + txHash?: Hex; encodedTxData?: EncodedTxData; - childIpId?: Address; - tokenId?: bigint; }; export type CreateIpAssetWithPilTermsRequest = { @@ -55,7 +53,7 @@ export type CreateIpAssetWithPilTermsRequest = { } & IpMetadataAndTxOption; export type CreateIpAssetWithPilTermsResponse = { - txHash?: string; + txHash?: Hex; encodedTxData?: EncodedTxData; ipId?: Address; tokenId?: bigint; @@ -74,9 +72,10 @@ export type RegisterIpAndMakeDerivativeRequest = { } & IpMetadataAndTxOption; export type RegisterIpAndMakeDerivativeResponse = { - txHash?: string; + txHash?: Hex; encodedTxData?: EncodedTxData; ipId?: Address; + tokenId?: bigint; }; export type RegisterIpAndAttachPilTermsRequest = { @@ -91,10 +90,11 @@ export type RegisterIpAndAttachPilTermsRequest = { } & IpMetadataAndTxOption; export type RegisterIpAndAttachPilTermsResponse = { - txHash?: string; + txHash?: Hex; encodedTxData?: EncodedTxData; ipId?: Address; licenseTermsId?: bigint; + tokenId?: bigint; }; export type MintAndRegisterIpAndMakeDerivativeRequest = { @@ -107,6 +107,12 @@ export type MintAndRegisterIpAndMakeDerivativeRequest = { recipient?: Address; } & IpMetadataAndTxOption; +export type MintAndRegisterIpAndMakeDerivativeResponse = { + txHash?: Hex; + encodedTxData?: EncodedTxData; + childIpId?: Address; + tokenId?: bigint; +}; export type IpRelationship = { parentIpId: Address; type: string; @@ -203,7 +209,7 @@ export type RegisterPilTermsAndAttachRequest = { }; export type RegisterPilTermsAndAttachResponse = { - txHash?: string; + txHash?: Hex; encodedTxData?: EncodedTxData; licenseTermsId?: bigint; }; @@ -220,3 +226,41 @@ export type RegisterIpAndMakeDerivativeWithLicenseTokensRequest = { licenseTokenIds: string[] | bigint[] | number[]; deadline?: string | number | bigint; } & IpMetadataAndTxOption; + +export type BatchMintAndRegisterIpAssetWithPilTermsRequest = { + args: Omit[]; + txOptions?: Omit; +}; + +export type BatchMintAndRegisterIpAssetWithPilTermsResponse = { + txHash: Hex; + results?: Omit[]; +}; + +export type BatchRegisterDerivativeRequest = { + args: RegisterDerivativeRequest[]; + deadline?: string | number | bigint; + txOptions?: TxOptions; +}; + +export type BatchRegisterDerivativeResponse = { + txHash: Hex; +}; +export type BatchMintAndRegisterIpAndMakeDerivativeRequest = { + args: Omit[]; + txOptions?: Omit; +}; +export type BatchMintAndRegisterIpAndMakeDerivativeResponse = { + txHash: string; + results?: { ipId: Address; tokenId: bigint }[]; +}; + +export type BatchRegisterRequest = { + args: Omit[]; + txOptions?: TxOptions; +}; + +export type BatchRegisterResponse = { + txHash: Hex; + results?: { ipId: Address; tokenId: bigint }[]; +}; diff --git a/packages/core-sdk/src/types/resources/permission.ts b/packages/core-sdk/src/types/resources/permission.ts index 474dab78..c7df1fe2 100644 --- a/packages/core-sdk/src/types/resources/permission.ts +++ b/packages/core-sdk/src/types/resources/permission.ts @@ -1,7 +1,7 @@ import { Address, Hex, WalletClient } from "viem"; import { TxOptions } from "../options"; -import { EncodedTxData } from "../../abi/generated"; +import { EncodedTxData, SimpleWalletClient } from "../../abi/generated"; export type SetPermissionsRequest = { ipId: Address; @@ -62,3 +62,15 @@ export type PermissionSignatureRequest = { }; export type PermissionSignatureResponse = Hex; + +export type SignatureRequest = { + state: Hex; + to: Address; + encodeData: Hex; + wallet: SimpleWalletClient; + verifyingContract: Address; + deadline: bigint | number | string; + chainId: number | bigint | string; +}; + +export type SignatureResponse = Hex; diff --git a/packages/core-sdk/src/utils/chain.ts b/packages/core-sdk/src/utils/chain.ts index ded81e3c..79f719d9 100644 --- a/packages/core-sdk/src/utils/chain.ts +++ b/packages/core-sdk/src/utils/chain.ts @@ -16,9 +16,8 @@ export const odyssey = defineChain({ }, }, contracts: { - //TODO: need to confirm the addresses multicall3: { - address: "0xcA11bde05977b3631167028862bE2a173976CA11", + address: "0xca11bde05977b3631167028862be2a173976ca11", blockCreated: 5882, }, }, diff --git a/packages/core-sdk/src/utils/sign.ts b/packages/core-sdk/src/utils/sign.ts index 9662b042..b7d7870e 100644 --- a/packages/core-sdk/src/utils/sign.ts +++ b/packages/core-sdk/src/utils/sign.ts @@ -1,4 +1,10 @@ -import { encodeAbiParameters, encodeFunctionData, keccak256, toFunctionSelector } from "viem"; +import { + WalletClient, + encodeAbiParameters, + encodeFunctionData, + keccak256, + toFunctionSelector, +} from "viem"; import { accessControllerAbi, accessControllerAddress, ipAccountImplAbi } from "../abi/generated"; import { getAddress } from "./utils"; @@ -6,6 +12,8 @@ import { defaultFunctionSelector } from "../constants/common"; import { PermissionSignatureRequest, PermissionSignatureResponse, + SignatureRequest, + SignatureResponse, } from "../types/resources/permission"; /** @@ -24,12 +32,6 @@ export const getPermissionSignature = async ( param: PermissionSignatureRequest, ): Promise => { const { ipId, deadline, state, wallet, chainId, permissions, permissionFunc } = param; - if (!wallet.signTypedData) { - throw new Error("The wallet client does not support signTypedData, please try again."); - } - if (!wallet.account) { - throw new Error("The wallet client does not have an account, please try again."); - } const permissionFunction = permissionFunc ? permissionFunc : "setPermission"; const accessAddress = accessControllerAddress[Number(chainId) as keyof typeof accessControllerAddress]; @@ -55,6 +57,51 @@ export const getPermissionSignature = async ( })), ], }); + return await getSignature({ + state, + to: accessAddress, + encodeData: data, + wallet, + verifyingContract: ipId, + deadline, + chainId, + }); +}; + +export const getDeadline = (unixTimestamp: bigint, deadline?: bigint | number | string): bigint => { + if (deadline && (isNaN(Number(deadline)) || BigInt(deadline) < 0n)) { + throw new Error("Invalid deadline value."); + } + return deadline ? unixTimestamp + BigInt(deadline) : unixTimestamp + 1000n; +}; + +/** + * Get the signature. + * @param param - The parameter object containing necessary data to get the signature. + * @param param.state - The IP Account's state. + * @param param.to - The recipient address. + * @param param.encodeData - The encoded data. + * @param param.wallet - The wallet client. + * @param param.verifyingContract - The verifying contract. + * @param param.deadline - The deadline. + * @param param.chainId - The chain ID. + * @returns A Promise that resolves to the signature. + */ +export const getSignature = async ({ + state, + to, + encodeData, + wallet, + verifyingContract, + deadline, + chainId, +}: SignatureRequest): Promise => { + if (!(wallet as WalletClient).signTypedData) { + throw new Error("The wallet client does not support signTypedData, please try again."); + } + if (!wallet.account) { + throw new Error("The wallet client does not have an account, please try again."); + } const nonce = keccak256( encodeAbiParameters( [ @@ -66,19 +113,18 @@ export const getPermissionSignature = async ( encodeFunctionData({ abi: ipAccountImplAbi, functionName: "execute", - args: [accessAddress, 0n, data], + args: [to, 0n, encodeData], }), ], ), ); - - return await wallet.signTypedData({ + return await (wallet as WalletClient).signTypedData({ account: wallet.account, domain: { name: "Story Protocol IP Account", version: "1", chainId: Number(chainId), - verifyingContract: getAddress(ipId, "ipId"), + verifyingContract, }, types: { Execute: [ @@ -91,22 +137,11 @@ export const getPermissionSignature = async ( }, primaryType: "Execute", message: { - to: getAddress( - accessControllerAddress[Number(chainId) as keyof typeof accessControllerAddress], - "accessControllerAddress", - ), + to, value: BigInt(0), - data, + data: encodeData, nonce, deadline: BigInt(deadline), }, }); }; - -export const getDeadline = (deadline?: bigint | number | string): bigint => { - if (deadline && (isNaN(Number(deadline)) || BigInt(deadline) < 0n)) { - throw new Error("Invalid deadline value."); - } - const timestamp = BigInt(Date.now()); - return deadline ? timestamp + BigInt(deadline) : timestamp + 1000n; -}; diff --git a/packages/core-sdk/test/integration/ipAsset.test.ts b/packages/core-sdk/test/integration/ipAsset.test.ts index 5694f5a6..d46a310e 100644 --- a/packages/core-sdk/test/integration/ipAsset.test.ts +++ b/packages/core-sdk/test/integration/ipAsset.test.ts @@ -11,7 +11,7 @@ import { approveForLicenseToken, } from "./utils/util"; import { MockERC20 } from "./utils/mockERC20"; -import { derivativeWorkflowsAddress, spgnftImplAddress } from "../../src/abi/generated"; +import { derivativeWorkflowsAddress } from "../../src/abi/generated"; chai.use(chaiAsPromised); const expect = chai.expect; @@ -19,6 +19,7 @@ const expect = chai.expect; describe("IP Asset Functions ", () => { let client: StoryClient; let noCommercialLicenseTermsId: bigint; + let parentIpId: Hex; before(async () => { client = getStoryClient(); const res = await client.license.registerNonComSocialRemixingPIL({ @@ -30,7 +31,6 @@ describe("IP Asset Functions ", () => { }); describe("Create IP Asset", async () => { - let parentIpId: Hex; let childIpId: Hex; it("should not throw error when register a IP Asset", async () => { const tokenId = await getTokenId(); @@ -106,6 +106,17 @@ describe("IP Asset Functions ", () => { }); expect(response.txHash).to.be.a("string").not.empty; }); + + it("should return true if IP asset is registered", async () => { + const isRegistered = await client.ipAsset.isRegistered(parentIpId); + expect(isRegistered).to.be.true; + }); + + it("should return false if IP asset is not registered", async () => { + const unregisteredIpId = "0x1234567890123456789012345678901234567890"; + const isRegistered = await client.ipAsset.isRegistered(unregisteredIpId); + expect(isRegistered).to.be.false; + }); }); describe("NFT Client (SPG)", () => { @@ -400,4 +411,166 @@ describe("IP Asset Functions ", () => { expect(result.ipId).to.be.a("string").and.not.empty; }); }); + + describe("Multicall", () => { + let nftContract: Hex; + beforeEach(async () => { + const txData = await client.nftClient.createNFTCollection({ + name: "test-collection", + symbol: "TEST", + maxSupply: 100, + isPublicMinting: true, + mintOpen: true, + contractURI: "test-uri", + mintFeeRecipient: process.env.TEST_WALLET_ADDRESS! as Address, + txOptions: { + waitForTransaction: true, + }, + }); + nftContract = txData.spgNftContract!; + }); + it("should not throw error when call batch register derivative", async () => { + const childTokenId = await getTokenId(); + const childIpId = ( + await client.ipAsset.register({ + nftContract: mockERC721, + tokenId: childTokenId!, + txOptions: { + waitForTransaction: true, + }, + }) + ).ipId!; + const childTokenId2 = await getTokenId(); + const childIpId2 = ( + await client.ipAsset.register({ + nftContract: mockERC721, + tokenId: childTokenId2!, + txOptions: { + waitForTransaction: true, + }, + }) + ).ipId!; + const result = await client.ipAsset.batchRegisterDerivative({ + args: [ + { + childIpId: childIpId, + parentIpIds: [parentIpId], + licenseTermsIds: [noCommercialLicenseTermsId], + }, + { + childIpId: childIpId2, + parentIpIds: [parentIpId], + licenseTermsIds: [noCommercialLicenseTermsId], + }, + ], + txOptions: { + waitForTransaction: true, + }, + }); + expect(result.txHash).to.be.a("string").and.not.empty; + }); + it("should not throw error when call batch mint and register ip asset with pil terms", async () => { + const result = await client.ipAsset.batchMintAndRegisterIpAssetWithPilTerms({ + args: [ + { + spgNftContract: nftContract, + pilType: PIL_TYPE.COMMERCIAL_REMIX, + commercialRevShare: 10, + mintingFee: "100", + currency: MockERC20.address, + }, + { + spgNftContract: nftContract, + pilType: PIL_TYPE.COMMERCIAL_REMIX, + commercialRevShare: 10, + mintingFee: "100", + currency: MockERC20.address, + }, + ], + txOptions: { + waitForTransaction: true, + }, + }); + expect(result.txHash).to.be.a("string").and.not.empty; + expect(result.results).to.be.an("array").and.not.empty; + }); + + it("should not throw error when call batch mint and register ip asset and make derivative", async () => { + const tokenId2 = await getTokenId(); + const parentIpId2 = ( + await client.ipAsset.register({ + nftContract: mockERC721, + tokenId: tokenId2!, + txOptions: { + waitForTransaction: true, + }, + }) + ).ipId!; + const result = await client.ipAsset.batchMintAndRegisterIpAndMakeDerivative({ + args: [ + { + spgNftContract: nftContract, + derivData: { + parentIpIds: [parentIpId!], + licenseTermsIds: [noCommercialLicenseTermsId!], + }, + }, + { + spgNftContract: nftContract, + derivData: { + parentIpIds: [parentIpId2!], + licenseTermsIds: [noCommercialLicenseTermsId!], + }, + }, + ], + txOptions: { + waitForTransaction: true, + }, + }); + expect(result.txHash).to.be.a("string").and.not.empty; + expect(result.results).to.be.an("array").and.not.empty; + }); + + it("should not throw error when call batch register giving parameters without ipMetadata", async () => { + const tokenId = await getTokenId(); + const tokenId2 = await getTokenId(); + const spgTokenId1 = await mintBySpg(nftContract, "test-metadata"); + const spgTokenId2 = await mintBySpg(nftContract, "test-metadata"); + const result = await client.ipAsset.batchRegister({ + args: [ + { + nftContract: mockERC721, + tokenId: tokenId!, + }, + { + nftContract: mockERC721, + tokenId: tokenId2!, + }, + { + nftContract, + tokenId: spgTokenId1!, + ipMetadata: { + ipMetadataURI: "test-uri2", + ipMetadataHash: toHex("test-metadata-hash2", { size: 32 }), + nftMetadataHash: toHex("test-nft-metadata-hash2", { size: 32 }), + }, + }, + { + nftContract, + tokenId: spgTokenId2!, + ipMetadata: { + ipMetadataURI: "test-uri", + ipMetadataHash: toHex("test-metadata-hash", { size: 32 }), + nftMetadataHash: toHex("test-nft-metadata-hash", { size: 32 }), + }, + }, + ], + txOptions: { + waitForTransaction: true, + }, + }); + expect(result.results).to.be.an("array").and.not.empty; + expect(result.txHash).to.be.a("string").and.not.empty; + }); + }); }); diff --git a/packages/core-sdk/test/integration/permission.test.ts b/packages/core-sdk/test/integration/permission.test.ts index 4de48f98..8b103e93 100644 --- a/packages/core-sdk/test/integration/permission.test.ts +++ b/packages/core-sdk/test/integration/permission.test.ts @@ -56,7 +56,7 @@ describe("Permission Functions", () => { it("should not throw error when create set permission signature", async () => { const response = await client.permission.createSetPermissionSignature({ - ipId, + ipId: "0xE54028E60070223a9b77097D9385933340D10691", signer: process.env.TEST_WALLET_ADDRESS as Address, to: coreMetadataModule, func: "function setAll(address,string,bytes32,bytes32)", diff --git a/packages/core-sdk/test/unit/resources/ipAccount.test.ts b/packages/core-sdk/test/unit/resources/ipAccount.test.ts index bdcb28ce..d6a9e220 100644 --- a/packages/core-sdk/test/unit/resources/ipAccount.test.ts +++ b/packages/core-sdk/test/unit/resources/ipAccount.test.ts @@ -115,7 +115,6 @@ describe("Test IPAccountClient", () => { const result = await ipAccountClient.executeWithSig({ ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", to: zeroAddress, - value: 2, data: "0x11111111111111111111111111111", signer: zeroAddress, deadline: 20, diff --git a/packages/core-sdk/test/unit/resources/ipAsset.test.ts b/packages/core-sdk/test/unit/resources/ipAsset.test.ts index 46b57b28..b20877e0 100644 --- a/packages/core-sdk/test/unit/resources/ipAsset.test.ts +++ b/packages/core-sdk/test/unit/resources/ipAsset.test.ts @@ -20,9 +20,13 @@ import { import chaiAsPromised from "chai-as-promised"; import { RegisterIpAndAttachPilTermsRequest } from "../../../src/types/resources/ipAsset"; import { MockERC20 } from "../../integration/utils/mockERC20"; +import { + LicenseRegistryReadOnlyClient, + LicensingModuleLicenseTermsAttachedEvent, +} from "../../../src/abi/generated"; const { RoyaltyModuleReadOnlyClient } = require("../../../src/abi/generated"); const { IpAccountImplClient } = require("../../../src/abi/generated"); - +const txHash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; chai.use(chaiAsPromised); const expect = chai.expect; @@ -37,7 +41,12 @@ describe("Test IpAssetClient", () => { walletMock = createMock(); const accountMock = createMock(); walletMock.account = accountMock; - ipAssetClient = new IPAssetClient(rpcMock, walletMock, "odyssey"); + sinon.stub(LicenseRegistryReadOnlyClient.prototype, "getDefaultLicenseTerms").resolves({ + licenseTemplate: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + licenseTermsId: 5n, + }); + + ipAssetClient = new IPAssetClient(rpcMock, walletMock, "1516"); walletMock.signTypedData = sinon .stub() .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); @@ -53,6 +62,8 @@ describe("Test IpAssetClient", () => { "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"; (ipAssetClient.licenseAttachmentWorkflowsClient as any).address = "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"; + (ipAssetClient.licenseTemplateClient as any).address = + "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"; }); afterEach(() => { @@ -240,18 +251,14 @@ describe("Test IpAssetClient", () => { .stub(ipAssetClient.ipAssetRegistryClient, "ipId") .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(false); - sinon - .stub(ipAssetClient.ipAssetRegistryClient, "register") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "register").resolves(txHash); const res = await ipAssetClient.register({ nftContract: spgNftContract, tokenId: "3", }); - expect(res.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(res.txHash).equal(txHash); }); it("should return ipId and txHash when register a IP and given waitForTransaction of true and tokenId is not registered ", async () => { @@ -259,9 +266,7 @@ describe("Test IpAssetClient", () => { .stub(ipAssetClient.ipAssetRegistryClient, "ipId") .resolves("0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4"); sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(false); - sinon - .stub(ipAssetClient.ipAssetRegistryClient, "register") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "register").resolves(txHash); sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ { ipId: "0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4", @@ -282,9 +287,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(response.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(response.txHash).equal(txHash); expect(response.ipId).equals("0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4"); }); @@ -293,9 +296,7 @@ describe("Test IpAssetClient", () => { .stub(ipAssetClient.ipAssetRegistryClient, "ipId") .resolves("0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4"); sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(false); - sinon - .stub(ipAssetClient.registrationWorkflowsClient, "registerIp") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + sinon.stub(ipAssetClient.registrationWorkflowsClient, "registerIp").resolves(txHash); sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ { ipId: "0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4", @@ -320,9 +321,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(response.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(response.txHash).equal(txHash); expect(response.ipId).equals("0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4"); }); @@ -504,9 +503,7 @@ describe("Test IpAssetClient", () => { sinon .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") .resolves(true); - sinon - .stub(ipAssetClient.licensingModuleClient, "registerDerivative") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + sinon.stub(ipAssetClient.licensingModuleClient, "registerDerivative").resolves(txHash); const res = await ipAssetClient.registerDerivative({ childIpId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -514,9 +511,7 @@ describe("Test IpAssetClient", () => { licenseTermsIds: ["1"], }); - expect(res.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(res.txHash).equal(txHash); }); it("should return txHash when registerDerivative given correct childIpId, parentIpId, licenseTermsIds and waitForTransaction of true ", async () => { @@ -529,9 +524,7 @@ describe("Test IpAssetClient", () => { sinon .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") .resolves(true); - sinon - .stub(ipAssetClient.licensingModuleClient, "registerDerivative") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + sinon.stub(ipAssetClient.licensingModuleClient, "registerDerivative").resolves(txHash); const res = await ipAssetClient.registerDerivative({ childIpId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -543,9 +536,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(res.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(res.txHash).equal(txHash); }); it("should return encoded tx data when registerDerivative given correct childIpId, parentIpId, licenseTermsIds and encodedTxDataOnly of true ", async () => { @@ -625,16 +616,14 @@ describe("Test IpAssetClient", () => { .resolves("0x73fcb515cee99e4991465ef586cfe2b072ebb512"); sinon .stub(ipAssetClient.licensingModuleClient, "registerDerivativeWithLicenseTokens") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + .resolves(txHash); const res = await ipAssetClient.registerDerivativeWithLicenseTokens({ childIpId: "0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4", licenseTokenIds: ["1"], }); - expect(res.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(res.txHash).equal(txHash); }); it("should return txHash when registerDerivativeWithLicenseTokens given correct args and waitForTransaction of true", async () => { @@ -649,7 +638,7 @@ describe("Test IpAssetClient", () => { .resolves("0x73fcb515cee99e4991465ef586cfe2b072ebb512"); sinon .stub(ipAssetClient.licensingModuleClient, "registerDerivativeWithLicenseTokens") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + .resolves(txHash); const res = await ipAssetClient.registerDerivativeWithLicenseTokens({ childIpId: "0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4", @@ -659,9 +648,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(res.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(res.txHash).equal(txHash); }); it("should return encoded tx data when registerDerivativeWithLicenseTokens given correct args and encodedTxDataOnly of true", async () => { @@ -721,10 +708,9 @@ describe("Test IpAssetClient", () => { }); it("should return txHash when createIpAssetWithPilTerms given correct args", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; sinon .stub(ipAssetClient.licenseAttachmentWorkflowsClient, "mintAndRegisterIpAndAttachPilTerms") - .resolves(hash); + .resolves(txHash); const result = await ipAssetClient.mintAndRegisterIpAssetWithPilTerms({ spgNftContract, pilType: PIL_TYPE.COMMERCIAL_USE, @@ -738,14 +724,13 @@ describe("Test IpAssetClient", () => { }, }); - expect(result.txHash).to.equal(hash); + expect(result.txHash).to.equal(txHash); }); it("should return ipId, tokenId, licenseTermsId,txHash when createIpAssetWithPilTerms given correct args and waitForTransaction of true", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; sinon .stub(ipAssetClient.licenseAttachmentWorkflowsClient, "mintAndRegisterIpAndAttachPilTerms") - .resolves(hash); + .resolves(txHash); sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ { ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -778,17 +763,16 @@ describe("Test IpAssetClient", () => { }, }); - expect(result.txHash).to.equal(hash); + expect(result.txHash).to.equal(txHash); expect(result.ipId).to.equal("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); expect(result.licenseTermsId).to.equal(0n); expect(result.tokenId).to.equal(1n); }); it("should return encoded tx data when createIpAssetWithPilTerms given correct args and encodedTxDataOnly is true", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; - sinon - .stub(ipAssetClient.licenseAttachmentWorkflowsClient, "mintAndRegisterIpAndAttachPilTerms") - .resolves(hash); + // sinon + // .stub(ipAssetClient.licenseAttachmentWorkflowsClient, "mintAndRegisterIpAndAttachPilTerms") + // .resolves(txHash); const result = await ipAssetClient.mintAndRegisterIpAssetWithPilTerms({ spgNftContract: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", pilType: 0, @@ -888,7 +872,7 @@ describe("Test IpAssetClient", () => { .resolves(true); sinon .stub(ipAssetClient.derivativeWorkflowsClient, "registerIpAndMakeDerivative") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + .resolves(txHash); const res = await ipAssetClient.registerDerivativeIp({ nftContract: spgNftContract, @@ -903,9 +887,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(res.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(res.txHash).equal(txHash); }); it("should return txHash when registerDerivativeIp given correct args", async () => { sinon @@ -917,7 +899,7 @@ describe("Test IpAssetClient", () => { .resolves(true); sinon .stub(ipAssetClient.derivativeWorkflowsClient, "registerIpAndMakeDerivative") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + .resolves(txHash); const res = await ipAssetClient.registerDerivativeIp({ nftContract: spgNftContract, @@ -933,9 +915,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(res.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(res.txHash).equal(txHash); }); it("should return txHash and ipId when registerDerivativeIp given correct args and waitForTransaction of true", async () => { @@ -948,7 +928,7 @@ describe("Test IpAssetClient", () => { .resolves(true); sinon .stub(ipAssetClient.derivativeWorkflowsClient, "registerIpAndMakeDerivative") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + .resolves(txHash); sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ { ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -977,9 +957,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(res.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(res.txHash).equal(txHash); expect(res.ipId).equal("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); }); @@ -1095,10 +1073,9 @@ describe("Test IpAssetClient", () => { }); }); it("should return hash when registerIpAndAttachPilTerms given correct args", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; sinon .stub(ipAssetClient.licenseAttachmentWorkflowsClient, "registerIpAndAttachPilTerms") - .resolves(hash); + .resolves(txHash); sinon .stub(ipAssetClient.ipAssetRegistryClient, "ipId") .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); @@ -1116,11 +1093,10 @@ describe("Test IpAssetClient", () => { royaltyPolicyAddress: zeroAddress, }); - expect(result.txHash).to.equal(hash); + expect(result.txHash).to.equal(txHash); }); it("should return txHash and ipId when registerIpAndAttachPilTerms given correct args and waitForTransaction of true", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; sinon .stub(ipAssetClient.ipAssetRegistryClient, "ipId") .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); @@ -1128,7 +1104,7 @@ describe("Test IpAssetClient", () => { sinon .stub(ipAssetClient.licenseAttachmentWorkflowsClient, "registerIpAndAttachPilTerms") - .resolves(hash); + .resolves(txHash); sinon .stub(ipAssetClient.licensingModuleClient, "parseTxLicenseTermsAttachedEvent") .returns([]); @@ -1143,10 +1119,6 @@ describe("Test IpAssetClient", () => { registrationDate: 0n, }, ]); - sinon.stub(ipAssetClient.licenseRegistryReadOnlyClient, "getDefaultLicenseTerms").resolves({ - licenseTemplate: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", - licenseTermsId: 5n, - }); const result = await ipAssetClient.registerIpAndAttachPilTerms({ nftContract: spgNftContract, tokenId: "3", @@ -1161,13 +1133,12 @@ describe("Test IpAssetClient", () => { }, }); - expect(result.txHash).to.equal(hash); + expect(result.txHash).to.equal(txHash); expect(result.ipId).to.equal("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); expect(result.licenseTermsId).to.equal(5n); }); it("should return encoded tx data when registerIpAndAttachPilTerms given correct args and encodedTxDataOnly of true", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; sinon .stub(ipAssetClient.ipAssetRegistryClient, "ipId") .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); @@ -1175,7 +1146,7 @@ describe("Test IpAssetClient", () => { sinon .stub(ipAssetClient.licenseAttachmentWorkflowsClient, "registerIpAndAttachPilTerms") - .resolves(hash); + .resolves(txHash); sinon.stub(ipAssetClient.licensingModuleClient, "parseTxLicenseTermsAttachedEvent").returns([ { ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -1258,7 +1229,7 @@ describe("Test IpAssetClient", () => { .resolves(true); sinon .stub(ipAssetClient.derivativeWorkflowsClient, "mintAndRegisterIpAndMakeDerivative") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + .resolves(txHash); const res = await ipAssetClient.mintAndRegisterIpAndMakeDerivative({ spgNftContract, @@ -1272,9 +1243,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(res.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(res.txHash).equal(txHash); }); it("should return txHash and ipId when call mintAndRegisterIpAndMakeDerivative given correct args and waitForTransaction of true", async () => { sinon @@ -1286,7 +1255,7 @@ describe("Test IpAssetClient", () => { .resolves(true); sinon .stub(ipAssetClient.derivativeWorkflowsClient, "mintAndRegisterIpAndMakeDerivative") - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + .resolves(txHash); sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ { ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -1315,9 +1284,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(res.txHash).equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(res.txHash).equal(txHash); expect(res.childIpId).equal("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); }); @@ -1398,8 +1365,7 @@ describe("Test IpAssetClient", () => { }); it("should return txHash when mintAndRegisterIp given correct args", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; - sinon.stub(ipAssetClient.registrationWorkflowsClient, "mintAndRegisterIp").resolves(hash); + sinon.stub(ipAssetClient.registrationWorkflowsClient, "mintAndRegisterIp").resolves(txHash); const result = await ipAssetClient.mintAndRegisterIp({ spgNftContract, @@ -1409,12 +1375,11 @@ describe("Test IpAssetClient", () => { }, }); - expect(result.txHash).to.equal(hash); + expect(result.txHash).to.equal(txHash); }); it("should return ipId,txHash when mintAndRegisterIp given correct args and waitForTransaction of true", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; - sinon.stub(ipAssetClient.registrationWorkflowsClient, "mintAndRegisterIp").resolves(hash); + sinon.stub(ipAssetClient.registrationWorkflowsClient, "mintAndRegisterIp").resolves(txHash); sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ { ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -1438,13 +1403,12 @@ describe("Test IpAssetClient", () => { }, }); - expect(result.txHash).to.equal(hash); + expect(result.txHash).to.equal(txHash); expect(result.ipId).to.equal("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); }); it("should return encoded tx data when mintAndRegisterIp given correct args and encodedTxDataOnly of true", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; - sinon.stub(ipAssetClient.registrationWorkflowsClient, "mintAndRegisterIp").resolves(hash); + sinon.stub(ipAssetClient.registrationWorkflowsClient, "mintAndRegisterIp").resolves(txHash); const result = await ipAssetClient.mintAndRegisterIp({ spgNftContract, @@ -1536,10 +1500,9 @@ describe("Test IpAssetClient", () => { }); it("should return txHash when registerPilTermsAndAttach given correct args", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; sinon .stub(ipAssetClient.licenseAttachmentWorkflowsClient, "registerPilTermsAndAttach") - .resolves(hash); + .resolves(txHash); sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(true); sinon .stub(ipAssetClient.licenseTemplateClient, "getLicenseTermsId") @@ -1549,14 +1512,13 @@ describe("Test IpAssetClient", () => { terms: licenseTerms, }); - expect(result.txHash).to.equal(hash); + expect(result.txHash).to.equal(txHash); }); it("should return txHash and licenseTermsId when registerPilTermsAndAttach given correct args and waitForTransaction of true", async () => { - const hash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; sinon .stub(ipAssetClient.licenseAttachmentWorkflowsClient, "registerPilTermsAndAttach") - .resolves(hash); + .resolves(txHash); sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(true); sinon .stub(ipAssetClient.licenseTemplateClient, "getLicenseTermsId") @@ -1578,7 +1540,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(result.txHash).to.equal(hash); + expect(result.txHash).to.equal(txHash); expect(result.licenseTermsId).to.equal(0n); }); }); @@ -1648,7 +1610,7 @@ describe("Test IpAssetClient", () => { ipAssetClient.derivativeWorkflowsClient, "mintAndRegisterIpAndMakeDerivativeWithLicenseTokens", ) - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + .resolves(txHash); sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ { ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -1669,9 +1631,7 @@ describe("Test IpAssetClient", () => { }, }); - expect(result.txHash).to.equal( - "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997", - ); + expect(result.txHash).to.equal(txHash); expect(result.ipId).to.equal("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); expect(result.tokenId).to.equal(1n); }); @@ -1753,7 +1713,7 @@ describe("Test IpAssetClient", () => { ipAssetClient.derivativeWorkflowsClient, "registerIpAndMakeDerivativeWithLicenseTokens", ) - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + .resolves(txHash); const result = await ipAssetClient.registerIpAndMakeDerivativeWithLicenseTokens({ nftContract: spgNftContract, tokenId: "3", @@ -1763,7 +1723,7 @@ describe("Test IpAssetClient", () => { ipMetadataHash: toHex(0, { size: 32 }), }, }); - expect(result.txHash).to.be.a("string").and.not.empty; + expect(result.txHash).to.equal(txHash); }); it("should return txHash and ipId when registerIpAndMakeDerivativeWithLicenseTokens given correct args and waitForTransaction of true", async () => { @@ -1776,7 +1736,7 @@ describe("Test IpAssetClient", () => { ipAssetClient.derivativeWorkflowsClient, "registerIpAndMakeDerivativeWithLicenseTokens", ) - .resolves("0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"); + .resolves(txHash); sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ { ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", @@ -1796,7 +1756,7 @@ describe("Test IpAssetClient", () => { waitForTransaction: true, }, }); - expect(result.txHash).to.be.a("string").and.not.empty; + expect(result.txHash).to.equal(txHash); expect(result.ipId).to.equal("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); }); @@ -1828,4 +1788,483 @@ describe("Test IpAssetClient", () => { expect(result.encodedTxData!.data).to.be.a("string").and.not.empty; }); }); + + describe("Test ipAssetClient.batchMintAndRegisterIpAssetWithPilTerms", async () => { + it("should throw spgNftContract error when batchMintAndRegisterIpAssetWithPilTerms given spgNftContract is wrong address", async () => { + try { + await ipAssetClient.batchMintAndRegisterIpAssetWithPilTerms({ + args: [ + { + spgNftContract: "0x", + ipMetadata: { + ipMetadataURI: "", + ipMetadataHash: toHex(0, { size: 32 }), + }, + pilType: 0, + mintingFee: 1, + currency: zeroAddress, + }, + ], + }); + } catch (err) { + expect((err as Error).message).equal( + "Failed to batch mint and register IP and attach PIL terms: Failed to mint and register IP and attach PIL terms: request.spgNftContract address is invalid: 0x, Address must be a hex value of 20 bytes (40 hex characters) and match its checksum counterpart.", + ); + } + }); + + it("should return txHash when batchMintAndRegisterIpAssetWithPilTerms given correct args", async () => { + sinon.stub(ipAssetClient.licenseAttachmentWorkflowsClient, "multicall").resolves(txHash); + + const result = await ipAssetClient.batchMintAndRegisterIpAssetWithPilTerms({ + args: [ + { + spgNftContract, + ipMetadata: { + ipMetadataURI: "", + ipMetadataHash: toHex(0, { size: 32 }), + }, + pilType: 0, + mintingFee: 1, + currency: zeroAddress, + }, + { + spgNftContract, + ipMetadata: { + ipMetadataURI: "", + ipMetadataHash: toHex(0, { size: 32 }), + }, + pilType: 0, + mintingFee: 1, + currency: zeroAddress, + }, + ], + }); + + expect(result.txHash).to.equal(txHash); + }); + + it("should return txHash and ipId when batchMintAndRegisterIpAssetWithPilTerms given correct args and waitForTransaction of true", async () => { + sinon.stub(ipAssetClient.licenseAttachmentWorkflowsClient, "multicall").resolves(txHash); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ + { + ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + chainId: 0n, + tokenContract: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + tokenId: 1n, + name: "", + uri: "", + registrationDate: 0n, + }, + { + ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD94c6627c", + chainId: 0n, + tokenContract: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + tokenId: 2n, + name: "", + uri: "", + registrationDate: 0n, + }, + { + ipId: "0x1daAE3197Bc469Cb97B9171a460a12dD94c6627c", + chainId: 0n, + tokenContract: "0x1daAE3197Bc469Cb97B917a460a12dD95c6627c", + tokenId: 3n, + name: "", + uri: "", + registrationDate: 0n, + }, + ]); + sinon.stub(ipAssetClient.licensingModuleClient, "parseTxLicenseTermsAttachedEvent").returns([ + { + ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + caller: "0x73fcb515cee99e4991465ef586cfe2b072ebb512", + licenseTemplate: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + licenseTermsId: 0n, + }, + { + ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD94c6627c", + caller: "0x73fcb515cee99e4991465ef586cfe2b072ebb512", + licenseTemplate: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + licenseTermsId: 4n, + }, + { + ipId: "0x1daAE3197Bc469Cb97B9171a460a12dD94c6627c", + caller: "0x73fcb515cee99e4991465ef586cfe2b072ebb512", + licenseTemplate: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + } as unknown as LicensingModuleLicenseTermsAttachedEvent, + ]); + const result = await ipAssetClient.batchMintAndRegisterIpAssetWithPilTerms({ + args: [ + { + spgNftContract, + ipMetadata: { + ipMetadataURI: "", + ipMetadataHash: toHex(0, { size: 32 }), + }, + pilType: 0, + mintingFee: 1, + currency: zeroAddress, + }, + { + spgNftContract, + ipMetadata: { + ipMetadataURI: "", + ipMetadataHash: toHex(0, { size: 32 }), + }, + pilType: 0, + mintingFee: 1, + currency: zeroAddress, + }, + ], + txOptions: { + waitForTransaction: true, + }, + }); + + expect(result.txHash).to.equal(txHash); + expect(result.results).to.deep.equal([ + { ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", licenseTermsId: 0n, tokenId: 1n }, + { ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD94c6627c", licenseTermsId: 4n, tokenId: 2n }, + { ipId: "0x1daAE3197Bc469Cb97B9171a460a12dD94c6627c", licenseTermsId: 5n, tokenId: 3n }, + ]); + }); + }); + + describe("Test ipAssetClient.batchMintAndRegisterIpAndMakeDerivative", async () => { + it("should throw ipId and licenseTerms error when batchMintAndRegisterIpAndMakeDerivative given ipId and licenseTerms is not match", async () => { + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(false); + try { + await ipAssetClient.batchMintAndRegisterIpAndMakeDerivative({ + args: [ + { + spgNftContract, + recipient: "0x73fcb515cee99e4991465ef586cfe2b072ebb512", + derivData: { + parentIpIds: ["0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4"], + licenseTermsIds: ["1"], + }, + ipMetadata: { + ipMetadataURI: "https://", + nftMetadataHash: toHex("nftMetadata", { size: 32 }), + }, + }, + ], + }); + } catch (err) { + expect((err as Error).message).equal( + "Failed to batch mint and register IP and make derivative: License terms id 1 must be attached to the parent ipId 0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4 before registering derivative.", + ); + } + }); + + it("should return txHash when batchMintAndRegisterIpAndMakeDerivative given correct args", async () => { + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(true); + sinon + .stub(ipAssetClient.derivativeWorkflowsClient, "mintAndRegisterIpAndMakeDerivative") + .resolves(txHash); + sinon + .stub(ipAssetClient.ipAssetRegistryClient, "ipId") + .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); + sinon.stub(ipAssetClient.derivativeWorkflowsClient, "multicall").resolves(txHash); + + const result = await ipAssetClient.batchMintAndRegisterIpAndMakeDerivative({ + args: [ + { + spgNftContract, + recipient: "0x73fcb515cee99e4991465ef586cfe2b072ebb512", + derivData: { + parentIpIds: ["0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4"], + licenseTermsIds: ["1"], + }, + ipMetadata: { + ipMetadataURI: "https://", + nftMetadataHash: toHex("nftMetadata", { size: 32 }), + }, + }, + ], + }); + + expect(result.txHash).to.equal(txHash); + }); + + it("should return txHash and ipId when batchMintAndRegisterIpAndMakeDerivative given correct args and waitForTransaction of true", async () => { + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(true); + sinon + .stub(ipAssetClient.derivativeWorkflowsClient, "mintAndRegisterIpAndMakeDerivative") + .resolves(txHash); + sinon.stub(ipAssetClient.derivativeWorkflowsClient, "multicall").resolves(txHash); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ + { + ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + chainId: 0n, + tokenContract: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + tokenId: 1n, + name: "", + uri: "", + registrationDate: 0n, + }, + { + ipId: "0x11aAE3197Bc469Cb97B9171a460a12dD95c6627c", + chainId: 0n, + tokenContract: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + tokenId: 2n, + name: "", + uri: "", + registrationDate: 0n, + }, + ]); + + const result = await ipAssetClient.batchMintAndRegisterIpAndMakeDerivative({ + args: [ + { + spgNftContract, + recipient: "0x73fcb515cee99e4991465ef586cfe2b072ebb512", + derivData: { + parentIpIds: ["0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4"], + licenseTermsIds: ["1"], + }, + ipMetadata: { + ipMetadataURI: "https://", + nftMetadataHash: toHex("nftMetadata", { size: 32 }), + }, + }, + { + spgNftContract, + recipient: "0x73fcb515cee99e4991465ef586cfe2b072ebb512", + derivData: { + parentIpIds: ["0xd142822Dc1674154EaF4DDF38bbF7EF8f0D8ECe4"], + licenseTermsIds: ["1"], + }, + ipMetadata: { + ipMetadataURI: "https://", + nftMetadataHash: toHex("nftMetadata", { size: 32 }), + }, + }, + ], + txOptions: { + waitForTransaction: true, + }, + }); + + expect(result.txHash).to.equal(txHash); + expect(result.results).to.deep.equal([ + { + ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + tokenId: 1n, + }, + { + ipId: "0x11aAE3197Bc469Cb97B9171a460a12dD95c6627c", + tokenId: 2n, + }, + ]); + }); + }); + + describe("Test ipAssetClient.batchRegister", async () => { + it("should throw error when call batchRegister given args have wrong nftContract", async () => { + try { + await ipAssetClient.batchRegister({ + args: [ + { + nftContract: "0x", + tokenId: "1", + }, + ], + }); + } catch (err) { + expect((err as Error).message).equal( + "Failed to batch register IP: nftContract address is invalid: 0x, Address must be a hex value of 20 bytes (40 hex characters) and match its checksum counterpart.", + ); + } + }); + + it("should return txhash when call batchRegister given correct args", async () => { + sinon + .stub(ipAssetClient.ipAssetRegistryClient, "ipId") + .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(false); + sinon.stub(ipAssetClient.registrationWorkflowsClient, "registerIpEncode").returns({ + data: "0x", + to: "0x", + }); + sinon.stub(ipAssetClient.multicall3Client, "aggregate3").resolves(txHash); + const result = await ipAssetClient.batchRegister({ + args: [ + { + nftContract: spgNftContract, + tokenId: "1", + }, + ], + }); + + expect(result.txHash).to.equal(txHash); + }); + + it("should return txhash and ipId when call batchRegister given correct args and waitForTransaction of true", async () => { + sinon + .stub(ipAssetClient.ipAssetRegistryClient, "ipId") + .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(false); + sinon.stub(ipAssetClient.registrationWorkflowsClient, "registerIpEncode").returns({ + data: "0x", + to: "0x", + }); + sinon.stub(ipAssetClient.multicall3Client, "aggregate3").resolves(txHash); + sinon.stub(ipAssetClient.ipAssetRegistryClient, "parseTxIpRegisteredEvent").returns([ + { + ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + chainId: 0n, + tokenContract: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + tokenId: 1n, + name: "", + uri: "", + registrationDate: 0n, + }, + { + ipId: "0x1daAE3197Bc469Cb87B917aa460a12dD95c6627c", + chainId: 0n, + tokenContract: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + tokenId: 2n, + name: "", + uri: "", + registrationDate: 0n, + }, + ]); + const result = await ipAssetClient.batchRegister({ + args: [ + { + nftContract: spgNftContract, + tokenId: "1", + }, + { + nftContract: spgNftContract, + tokenId: "2", + ipMetadata: { + ipMetadataURI: "", + ipMetadataHash: toHex(0, { size: 32 }), + nftMetadataHash: toHex("nftMetadata", { size: 32 }), + nftMetadataURI: "", + }, + }, + ], + txOptions: { + waitForTransaction: true, + }, + }); + + expect(result.txHash).to.equal(txHash); + expect(result.results).to.deep.equal([ + { + ipId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + tokenId: 1n, + }, + { + ipId: "0x1daAE3197Bc469Cb87B917aa460a12dD95c6627c", + tokenId: 2n, + }, + ]); + }); + }); + describe("Test ipAssetClient.batchRegisterDerivative", async () => { + it("should throw childIpId error when call batchRegisterDerivative given childIpId is wrong address", async () => { + try { + await ipAssetClient.batchRegisterDerivative({ + args: [ + { + childIpId: "0x", + parentIpIds: ["0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"], + licenseTermsIds: ["1"], + }, + ], + }); + } catch (err) { + expect((err as Error).message).equal( + "Failed to batch register derivative: ipId address is invalid: 0x, Address must be a hex value of 20 bytes (40 hex characters) and match its checksum counterpart.", + ); + } + }); + + it("should return results when call batchRegisterDerivative given correct args", async () => { + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(true); + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(true); + sinon.stub(ipAssetClient.licensingModuleClient, "registerDerivativeEncode").returns({ + data: "0x", + to: "0x", + }); + sinon + .stub(IpAccountImplClient.prototype, "state") + .resolves({ result: "0x2e778894d11b5308e4153f094e190496c1e0609652c19f8b87e5176484b9a56e" }); + sinon.stub(ipAssetClient.multicall3Client, "aggregate3").resolves(txHash); + const result = await ipAssetClient.batchRegisterDerivative({ + args: [ + { + childIpId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + parentIpIds: ["0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"], + licenseTermsIds: ["1"], + }, + ], + }); + + expect(result.txHash).to.equal(txHash); + }); + + it("should return results when call batchRegisterDerivative given correct args and waitForTransaction of true", async () => { + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(true); + sinon + .stub(ipAssetClient.licenseRegistryReadOnlyClient, "hasIpAttachedLicenseTerms") + .resolves(true); + sinon.stub(ipAssetClient.licensingModuleClient, "registerDerivativeEncode").returns({ + data: "0x", + to: "0x", + }); + sinon + .stub(IpAccountImplClient.prototype, "state") + .resolves({ result: "0x2e778894d11b5308e4153f094e190496c1e0609652c19f8b87e5176484b9a56e" }); + sinon.stub(ipAssetClient.multicall3Client, "aggregate3").resolves(txHash); + const result = await ipAssetClient.batchRegisterDerivative({ + args: [ + { + childIpId: "0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c", + parentIpIds: ["0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"], + licenseTermsIds: ["1"], + }, + ], + txOptions: { + waitForTransaction: true, + }, + }); + + expect(result.txHash).to.equal(txHash); + }); + }); + + describe("Test ipAssetClient.isRegistered", async () => { + beforeEach(() => { + sinon + .stub(ipAssetClient.ipAssetRegistryClient, "ipId") + .resolves("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c"); + }); + it("should return true if IP asset is registered", async () => { + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(true); + + expect(await ipAssetClient.isRegistered("0x1daAE3197Bc469Cb97B917aa460a12dD95c6627c")).to.be + .true; + }); + + it("should return false if IP asset is not registered", async () => { + sinon.stub(ipAssetClient.ipAssetRegistryClient, "isRegistered").resolves(false); + + expect(await ipAssetClient.isRegistered("0x2BCAE3197Bc469Cb97B917aa460a12dD95c6538D")).to.be + .false; + }); + }); }); diff --git a/packages/core-sdk/test/unit/testUtils.ts b/packages/core-sdk/test/unit/testUtils.ts index 11b9dd82..1159d084 100644 --- a/packages/core-sdk/test/unit/testUtils.ts +++ b/packages/core-sdk/test/unit/testUtils.ts @@ -4,5 +4,7 @@ export function createMock(obj = {}): T { const mockObj: any = obj; mockObj.waitForTransactionReceipt = sinon.stub().resolves({}); mockObj.address = "0x73fcb515cee99e4991465ef586cfe2b072ebb512"; + mockObj.multicall = sinon.stub().returns([{ error: "", status: "success" }]); + mockObj.getBlock = sinon.stub().resolves({ timestamp: 1629820800n }); return mockObj; } diff --git a/packages/core-sdk/test/unit/utils/sign.test.ts b/packages/core-sdk/test/unit/utils/sign.test.ts index 0dd526db..055e3fef 100644 --- a/packages/core-sdk/test/unit/utils/sign.test.ts +++ b/packages/core-sdk/test/unit/utils/sign.test.ts @@ -14,7 +14,7 @@ describe("Sign", () => { ipId: zeroAddress, state: "0x2e778894d11b5308e4153f094e190496c1e0609652c19f8b87e5176484b9a56e", deadline: 1000n, - permissions: [], + permissions: [{ ipId: zeroAddress, signer: zeroAddress, to: zeroAddress, permission: 0 }], wallet: {} as WalletClient, chainId: BigInt(odyssey), }); @@ -31,7 +31,7 @@ describe("Sign", () => { ipId: zeroAddress, state: "0x2e778894d11b5308e4153f094e190496c1e0609652c19f8b87e5176484b9a56e", deadline: 1000n, - permissions: [], + permissions: [{ ipId: zeroAddress, signer: zeroAddress, to: zeroAddress, permission: 0 }], wallet: { signTypedData: () => Promise.resolve("") } as unknown as WalletClient, chainId: BigInt(odyssey), }); @@ -52,7 +52,42 @@ describe("Sign", () => { ipId: zeroAddress, state: "0x2e778894d11b5308e4153f094e190496c1e0609652c19f8b87e5176484b9a56e", deadline: 1000n, - permissions: [{ ipId: zeroAddress, signer: zeroAddress, to: zeroAddress, permission: 0 }], + permissions: [ + { + ipId: zeroAddress, + signer: zeroAddress, + to: zeroAddress, + permission: 0, + func: "function setAll(address,string,bytes32,bytes32)", + }, + ], + wallet: walletClient, + chainId: BigInt(odyssey), + }); + expect(result).is.a("string").and.not.empty; + }); + + it("should return signature when call getPermissionSignature given account support signTypedData and multiple permissions", async () => { + const walletClient = createWalletClient({ + chain: chainStringToViemChain("odyssey"), + transport: http(), + account: privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as Hex), + }); + const result = await getPermissionSignature({ + ipId: zeroAddress, + state: "0x2e778894d11b5308e4153f094e190496c1e0609652c19f8b87e5176484b9a56e", + deadline: 1000n, + permissionFunc: "setBatchPermissions", + permissions: [ + { ipId: zeroAddress, signer: zeroAddress, to: zeroAddress, permission: 0 }, + { + ipId: zeroAddress, + signer: zeroAddress, + to: zeroAddress, + permission: 0, + func: "function setAll(address,string,bytes32,bytes32)", + }, + ], wallet: walletClient, chainId: BigInt(odyssey), }); @@ -60,12 +95,9 @@ describe("Sign", () => { }); }); describe("Get Deadline", () => { - before(() => { - sinon.stub(Date, "now").returns(1000); - }); it("should throw invalid deadline value when call getDeadline given deadline is not number", () => { try { - getDeadline("invalid"); + getDeadline(12n, "invalid"); } catch (e) { expect((e as Error).message).to.equal("Invalid deadline value."); } @@ -73,20 +105,20 @@ describe("Sign", () => { it("should throw invalid deadline value when call getDeadline given deadline is less than 0", () => { try { - getDeadline(-1); + getDeadline(12n, -1); } catch (e) { expect((e as Error).message).to.equal("Invalid deadline value."); } }); it("should return 2000 when call getDeadline", () => { - const result = getDeadline(); - expect(result).to.equal(2000n); + const result = getDeadline(12n); + expect(result).to.equal(1012n); }); it("should return timestamp plus deadline when call getDeadline given deadline", () => { - const result = getDeadline(2000); - expect(result).to.equal(3000n); + const result = getDeadline(12n, 3000); + expect(result).to.equal(3012n); }); }); }); diff --git a/packages/wagmi-generator/wagmi.config.ts b/packages/wagmi-generator/wagmi.config.ts index 3fd74424..c92fc4b0 100644 --- a/packages/wagmi-generator/wagmi.config.ts +++ b/packages/wagmi-generator/wagmi.config.ts @@ -153,6 +153,12 @@ export default defineConfig(async () => { [odysseyChainId]: "0x12A8b0DcC6e3bB0915638361D9D49942Da07F455", }, }, + { + name: "Multicall3", + address: { + [odysseyChainId]: "0xca11bde05977b3631167028862be2a173976ca11", + }, + }, ]; return { out: "../core-sdk/src/abi/generated.ts", @@ -242,17 +248,20 @@ export default defineConfig(async () => { "mintAndRegisterIpAndMakeDerivative", "registerIpAndMakeDerivativeWithLicenseTokens", "mintAndRegisterIpAndMakeDerivativeWithLicenseTokens", + "multicall", ], RegistrationWorkflows: [ "createCollection", "mintAndRegisterIp", "registerIp", "CollectionCreated", + "multicall", ], LicenseAttachmentWorkflows: [ "registerPILTermsAndAttach", "registerIpAndAttachPILTerms", "mintAndRegisterIpAndAttachPILTerms", + "multicall", ], RoyaltyWorkflows: [ "transferToVaultAndSnapshotAndClaimByTokenBatch", @@ -260,6 +269,7 @@ export default defineConfig(async () => { "snapshotAndClaimByTokenBatch", "snapshotAndClaimBySnapshotBatch", ], + Multicall3: ["aggregate3"], }, }), ],