Skip to content

Commit

Permalink
Modify other modules
Browse files Browse the repository at this point in the history
  • Loading branch information
bonnie57 committed Jan 7, 2025
1 parent 341f314 commit 4e63b6e
Show file tree
Hide file tree
Showing 13 changed files with 347 additions and 761 deletions.
2 changes: 1 addition & 1 deletion packages/core-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"build": "pnpm run fix && preconstruct build",
"test": "pnpm run test:unit",
"test:unit": "TS_NODE_PROJECT='./tsconfig.test.json' c8 --all --src ./src mocha -r ts-node/register './test/unit/**/*.test.ts'",
"test:integration": "TS_NODE_PROJECT='./tsconfig.test.json' mocha -r ts-node/register './test/integration/**/ipAsset.test.ts' --timeout 240000",
"test:integration": "TS_NODE_PROJECT='./tsconfig.test.json' mocha -r ts-node/register './test/integration/**/*.test.ts' --timeout 240000",
"fix": "pnpm run format:fix && pnpm run lint:fix",
"format": "prettier --check .",
"format:fix": "prettier --write .",
Expand Down
39 changes: 24 additions & 15 deletions packages/core-sdk/src/resources/group.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { PublicClient, WalletClient, toHex, zeroHash } from "viem";

import {
coreMetadataModuleAbi,
CoreMetadataModuleClient,
groupingModuleAbi,
GroupingModuleClient,
GroupingModuleEventClient,
GroupingModuleRegisterGroupRequest,
Expand All @@ -13,6 +15,7 @@ import {
IpAssetRegistryClient,
LicenseRegistryReadOnlyClient,
LicenseTokenReadOnlyClient,
licensingModuleAbi,
LicensingModuleClient,
PiLicenseTemplateClient,
SimpleWalletClient,
Expand All @@ -34,6 +37,7 @@ import {
RegisterIpAndAttachLicenseAndAddToGroupRequest,
RegisterIpAndAttachLicenseAndAddToGroupResponse,
} from "../types/resources/group";
import { getFunctionSignature } from "../utils/getFunctionSignature";

export class GroupClient {
public groupingWorkflowsClient: GroupingWorkflowsClient;
Expand Down Expand Up @@ -97,7 +101,6 @@ export class GroupClient {
handleError(error, "Failed to register group");
}
}
//TODO: Need to add the new method
/** @deprecated This method is deprecated and will be removed in a future version */
/** Mint an NFT from a SPGNFT collection, register it with metadata as an IP, attach license terms to the registered IP, and add it to a group IP.
* @param request - The request object containing necessary data to mint and register Ip and attach license and add to group.
Expand Down Expand Up @@ -195,7 +198,6 @@ export class GroupClient {
handleError(error, "Failed to mint and register IP and attach license and add to group");
}
}
//TODO: Need to add the new method
/** @deprecated This method is deprecated and will be removed in a future version */
/** Register an NFT as IP with metadata, attach license terms to the registered IP, and add it to a group IP.
* @param request - The request object containing necessary data to register ip and attach license and add to group.
Expand All @@ -205,11 +207,11 @@ export class GroupClient {
* @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 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.
* @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 {Object} request.ipMetadata - [Optional] The desired metadata for the newly minted NFT and newly registered IP.
* @param request.ipMetadata.ipMetadataURI [Optional] The URI of the metadata for the IP.
* @param request.ipMetadata.ipMetadataHash [Optional] The hash of the metadata for the IP.
* @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.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)
Expand All @@ -233,6 +235,7 @@ export class GroupClient {
const { result: state } = await ipAccount.state();
const blockTimestamp = (await this.rpcClient.getBlock()).timestamp;
const calculatedDeadline = getDeadline(blockTimestamp, request.deadline);

const { signature: sigAddToGroupSignature } = await getPermissionSignature({
ipId: getAddress(request.groupId, "request.groupId"),
deadline: calculatedDeadline,
Expand All @@ -242,10 +245,10 @@ export class GroupClient {
permissions: [
{
ipId: getAddress(request.groupId, "request.groupId"),
signer: getAddress(this.groupingWorkflowsClient.address, "groupingWorkflowsClient"),
to: getAddress(this.groupingModuleClient.address, "groupingModuleClient"),
signer: this.groupingWorkflowsClient.address,
to: this.groupingModuleClient.address,
permission: AccessPermission.ALLOW,
func: "function addIp(address,address[])",
func: getFunctionSignature(groupingModuleAbi, "addIp"),
},
],
});
Expand All @@ -259,17 +262,24 @@ export class GroupClient {
permissions: [
{
ipId: ipIdAddress,
signer: getAddress(this.groupingWorkflowsClient.address, "groupingWorkflowsClient"),
signer: this.groupingWorkflowsClient.address,
to: getAddress(this.coreMetadataModuleClient.address, "coreMetadataModuleAddress"),
permission: AccessPermission.ALLOW,
func: "function setAll(address,string,bytes32,bytes32)",
func: getFunctionSignature(coreMetadataModuleAbi, "setAll"),
},
{
ipId: ipIdAddress,
signer: getAddress(this.groupingWorkflowsClient.address, "groupingWorkflowsClient"),
signer: this.groupingWorkflowsClient.address,
to: getAddress(this.licensingModuleClient.address, "licensingModuleAddress"),
permission: AccessPermission.ALLOW,
func: getFunctionSignature(licensingModuleAbi, "attachLicenseTerms"),
},
{
ipId: ipIdAddress,
signer: this.groupingWorkflowsClient.address,
to: getAddress(this.licensingModuleClient.address, "licensingModuleAddress"),
permission: AccessPermission.ALLOW,
func: "function attachLicenseTerms(address,address,uint256)",
func: getFunctionSignature(licensingModuleAbi, "setLicensingConfig"),
},
],
});
Expand Down Expand Up @@ -362,7 +372,6 @@ export class GroupClient {
handleError(error, "Failed to register group and attach license");
}
}
//TODO: Need to add the new method
/** @deprecated This method is deprecated and will be removed in a future version */
/** Register a group IP with a group reward pool, attach license terms to the group IP, and add individual IPs to the group IP.
* @param request - The request object containing necessary data to register group and attach license and add ips.
Expand Down
109 changes: 58 additions & 51 deletions packages/core-sdk/src/resources/license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ import {
SetLicensingConfigResponse,
} from "../types/resources/license";
import { handleError } from "../utils/errors";
import { getLicenseTermByType, validateLicenseTerms } from "../utils/licenseTermsHelper";
import {
getLicenseTermByType,
getRevenueShare,
validateLicenseTerms,
} from "../utils/licenseTermsHelper";
import { chain, getAddress } from "../utils/utils";
import { SupportedChainIds } from "../types/config";

Expand Down Expand Up @@ -351,7 +355,7 @@ export class LicenseClient {
* @param request.amount The amount of license tokens to mint.
* @param request.receiver The address of the receiver.
* @param request.maxMintingFee The maximum minting fee that the caller is willing to pay. if set to 0 then no limit.
* @param request.maxRevenueShare The maximum revenue share that the caller is willing to pay. if set to 0 then no limit.
* @param request.maxRevenueShare The maximum revenue share percentage allowed for minting the License Tokens. Must be between 0 and 100,000,000 (where 100,000,000 represents 100%).
* @param request.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 license token IDs.
* @emits LicenseTokensMinted (msg.sender, licensorIpId, licenseTemplate, licenseTermsId, amount, receiver, startLicenseTokenId);
Expand All @@ -360,15 +364,32 @@ export class LicenseClient {
request: MintLicenseTokensRequest,
): Promise<MintLicenseTokensResponse> {
try {
request.licenseTermsId = BigInt(request.licenseTermsId);
const req: LicensingModuleMintLicenseTokensRequest = {
licensorIpId: getAddress(request.licensorIpId, "request.licensorIpId"),
licenseTemplate:
(request.licenseTemplate &&
getAddress(request.licenseTemplate, "request.licenseTemplate")) ||
this.licenseTemplateClient.address,
licenseTermsId: BigInt(request.licenseTermsId),
amount: BigInt(request.amount || 1),
receiver:
(request.receiver && getAddress(request.receiver, "request.receiver")) ||
this.wallet.account!.address,
royaltyContext: zeroAddress,
maxMintingFee: BigInt(request.maxMintingFee),
maxRevenueShare: getRevenueShare(request.maxRevenueShare),
} as const;
if (req.maxMintingFee < 0) {
throw new Error(`maxMintingFee must be greater than 0.`);
}
const isLicenseIpIdRegistered = await this.ipAssetRegistryClient.isRegistered({
id: getAddress(request.licensorIpId, "request.licensorIpId"),
});
if (!isLicenseIpIdRegistered) {
throw new Error(`The licensor IP with id ${request.licensorIpId} is not registered.`);
}
const isExisted = await this.piLicenseTemplateReadOnlyClient.exists({
licenseTermsId: request.licenseTermsId,
licenseTermsId: req.licenseTermsId,
});
if (!isExisted) {
throw new Error(`License terms id ${request.licenseTermsId} do not exist.`);
Expand All @@ -380,26 +401,13 @@ export class LicenseClient {
(request.licenseTemplate &&
getAddress(request.licenseTemplate, "request.licenseTemplate")) ||
this.licenseTemplateClient.address,
licenseTermsId: request.licenseTermsId,
licenseTermsId: req.licenseTermsId,
});
if (!isAttachedLicenseTerms) {
throw new Error(
`License terms id ${request.licenseTermsId} is not attached to the IP with id ${request.licensorIpId}.`,
);
}
const amount = BigInt(request.amount || 1);
const req: LicensingModuleMintLicenseTokensRequest = {
licensorIpId: request.licensorIpId,
licenseTemplate: request.licenseTemplate || this.licenseTemplateClient.address,
licenseTermsId: request.licenseTermsId,
amount,
receiver:
(request.receiver && getAddress(request.receiver, "request.receiver")) ||
this.wallet.account!.address,
royaltyContext: zeroAddress,
maxMintingFee: BigInt(request.maxMintingFee),
maxRevenueShare: Number(request.maxRevenueShare),
};
if (request.txOptions?.encodedTxDataOnly) {
return { encodedTxData: this.licensingModuleClient.mintLicenseTokensEncode(req) };
} else {
Expand All @@ -412,7 +420,7 @@ export class LicenseClient {
const targetLogs = this.licensingModuleClient.parseTxLicenseTokensMintedEvent(txReceipt);
const startLicenseTokenId = targetLogs[0].startLicenseTokenId;
const licenseTokenIds = [];
for (let i = 0; i < amount; i++) {
for (let i = 0; i < req.amount; i++) {
licenseTokenIds.push(startLicenseTokenId + BigInt(i));
}
return { txHash: txHash, licenseTokenIds: licenseTokenIds };
Expand Down Expand Up @@ -513,39 +521,17 @@ export class LicenseClient {
request: SetLicensingConfigRequest,
): Promise<SetLicensingConfigResponse> {
try {
const isLicenseIpIdRegistered = await this.ipAssetRegistryClient.isRegistered({
id: getAddress(request.ipId, "request.ipId"),
});
if (!isLicenseIpIdRegistered) {
throw new Error(`The licensor IP with id ${request.ipId} is not registered.`);
}
const licenseTermsId = BigInt(request.licenseTermsId);
const isExisted = await this.piLicenseTemplateReadOnlyClient.exists({
licenseTermsId,
});
if (!isExisted) {
throw new Error(`License terms id ${request.licenseTermsId} do not exist.`);
}
if (request.licensingConfig.licensingHook !== zeroAddress) {
const isRegistered = await this.moduleRegistryReadOnlyClient.isRegistered({
moduleAddress: request.licensingConfig.licensingHook,
});
if (!isRegistered) {
throw new Error("The licensing hook is not registered.");
}
}
const object: LicensingModuleSetLicensingConfigRequest = {
const req: LicensingModuleSetLicensingConfigRequest = {
ipId: request.ipId,
licenseTemplate: getAddress(request.licenseTemplate, "request.licenseTemplate"),
licenseTermsId,
licenseTermsId: BigInt(request.licenseTermsId),
licensingConfig: {
isSet: request.licensingConfig.isSet,
mintingFee: BigInt(request.licensingConfig.mintingFee),
hookData: request.licensingConfig.hookData,
licensingHook: request.licensingConfig.licensingHook,
disabled: request.licensingConfig.disabled,
//TODO: check if this is correct with Sebastian
commercialRevShare: Number(request.licensingConfig.commercialRevShare),
commercialRevShare: getRevenueShare(request.licensingConfig.commercialRevShare),
expectGroupRewardPool: getAddress(
request.licensingConfig.expectGroupRewardPool,
"request.licensingConfig.expectGroupRewardPool",
Expand All @@ -555,24 +541,45 @@ export class LicenseClient {
),
},
};

if (req.licensingConfig.mintingFee < 0) {
throw new Error("Minting fee must be greater than 0.");
}
if (
request.licenseTemplate === zeroAddress &&
object.licensingConfig.commercialRevShare === 0
request.licensingConfig.commercialRevShare !== 0
) {
//When Set LicenseConfig the license template cannot be Zero address if royalty percentage is not Zero.
throw new Error(
"LicenseTemplate cannot be zero address if commercial revenue share is not zero.",
"license Template cannot be zero address if commercial revenue share is not zero.",
);
}
const isLicenseIpIdRegistered = await this.ipAssetRegistryClient.isRegistered({
id: getAddress(request.ipId, "request.ipId"),
});
if (!isLicenseIpIdRegistered) {
throw new Error(`The licensor IP with id ${request.ipId} is not registered.`);
}
const isExisted = await this.piLicenseTemplateReadOnlyClient.exists({
licenseTermsId: req.licenseTermsId,
});
if (!isExisted) {
throw new Error(`License terms id ${request.licenseTermsId} do not exist.`);
}
if (request.licensingConfig.licensingHook !== zeroAddress) {
const isRegistered = await this.moduleRegistryReadOnlyClient.isRegistered({
moduleAddress: request.licensingConfig.licensingHook,
});
if (!isRegistered) {
throw new Error("The licensing hook is not registered.");
}
}
if (request.licenseTemplate === zeroAddress && request.licenseTermsId !== 0n) {
throw new Error("licenseTemplate is zero address but licenseTermsId is zero.");
throw new Error("license template is zero address but license terms id is zero.");
}

if (request.txOptions?.encodedTxDataOnly) {
return { encodedTxData: this.licensingModuleClient.setLicensingConfigEncode(object) };
return { encodedTxData: this.licensingModuleClient.setLicensingConfigEncode(req) };
} else {
const txHash = await this.licensingModuleClient.setLicensingConfig(object);
const txHash = await this.licensingModuleClient.setLicensingConfig(req);
if (request.txOptions?.waitForTransaction) {
await this.rpcClient.waitForTransactionReceipt({
...request.txOptions,
Expand Down
48 changes: 48 additions & 0 deletions packages/core-sdk/src/utils/getFunctionSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Abi, AbiFunction, AbiParameter } from "viem";

/**
* Gets the function signature from an ABI for a given method name
* @param abi - The contract ABI
* @param methodName - The name of the method to get the signature for
* @param overloadIndex - Optional index for overloaded functions (0-based)
* @returns The function signature in standard format (e.g. "methodName(uint256,address)")
* @throws Error if method not found or if overloadIndex is required but not provided
*/
export function getFunctionSignature(abi: Abi, methodName: string, overloadIndex?: number): string {
const functions = abi.filter(
(x): x is AbiFunction => x.type === "function" && x.name === methodName,
);

if (functions.length === 0) {
throw new Error(`Method ${methodName} not found in ABI.`);
}

if (functions.length > 1 && overloadIndex === undefined) {
throw new Error(
`Method ${methodName} has ${functions.length} overloads. Please specify overloadIndex (0-${
functions.length - 1
}).`,
);
}

const func = functions[overloadIndex || 0];

const getTypeString = (
input: AbiParameter & { components?: readonly AbiParameter[] },
): string => {
if (input.type.startsWith("tuple")) {
const components = input.components
?.map((comp: AbiParameter) =>
getTypeString(comp as AbiParameter & { components?: AbiParameter[] }),
)
.join(",");
return `(${components})`;
}
return input.type;
};

const inputs = func.inputs
.map((input) => getTypeString(input as AbiParameter & { components?: readonly AbiParameter[] }))
.join(",");
return `${methodName}(${inputs})`;
}
Loading

0 comments on commit 4e63b6e

Please sign in to comment.