Skip to content

Commit a38269f

Browse files
committed
Merge branch 'main' into bp/add-remove-ip-integration-tests
2 parents 2196c62 + 484af1e commit a38269f

File tree

13 files changed

+1100
-85
lines changed

13 files changed

+1100
-85
lines changed

packages/core-sdk/src/abi/generated.ts

Lines changed: 362 additions & 4 deletions
Large diffs are not rendered by default.

packages/core-sdk/src/resources/group.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
CoreMetadataModuleClient,
66
groupingModuleAbi,
77
GroupingModuleAddIpRequest,
8+
GroupingModuleClaimRewardRequest,
89
GroupingModuleClient,
10+
GroupingModuleCollectRoyaltiesRequest,
911
GroupingModuleEventClient,
1012
GroupingModuleRegisterGroupRequest,
1113
GroupingModuleRemoveIpRequest,
@@ -45,7 +47,12 @@ import {
4547
CollectAndDistributeGroupRoyaltiesRequest,
4648
CollectAndDistributeGroupRoyaltiesResponse,
4749
AddIpRequest,
50+
ClaimRewardRequest,
51+
ClaimRewardResponse,
52+
GetClaimableRewardRequest,
4853
RemoveIpsFromGroupRequest,
54+
CollectRoyaltiesRequest,
55+
CollectRoyaltiesResponse,
4956
} from "../types/resources/group";
5057
import { getFunctionSignature } from "../utils/getFunctionSignature";
5158
import { validateLicenseConfig } from "../utils/validateLicenseConfig";
@@ -503,6 +510,27 @@ export class GroupClient {
503510
handleError(error, "Failed to add IP to group");
504511
}
505512
}
513+
/**
514+
* Returns the available reward for each IP in the group.
515+
*/
516+
public async getClaimableReward({
517+
groupIpId,
518+
currencyToken,
519+
memberIpIds,
520+
}: GetClaimableRewardRequest): Promise<bigint[]> {
521+
try {
522+
const claimableReward = await this.groupingModuleClient.getClaimableReward({
523+
groupId: validateAddress(groupIpId),
524+
ipIds: validateAddresses(memberIpIds),
525+
token: validateAddress(currencyToken),
526+
});
527+
// The result is cast as bigint[] because the `claimableReward` array is of type `readonly bigint[]`.
528+
return claimableReward as bigint[];
529+
} catch (error) {
530+
handleError(error, "Failed to get claimable reward");
531+
}
532+
}
533+
506534
/**
507535
* Removes IPs from group.
508536
* The function must be called by the Group IP owner or an authorized operator.
@@ -527,6 +555,72 @@ export class GroupClient {
527555
handleError(error, "Failed to remove IPs from group");
528556
}
529557
}
558+
559+
/**
560+
* Claims reward.
561+
*
562+
* Emits an on-chain {@link https://github.com/storyprotocol/protocol-core-v1/blob/v1.3.1/contracts/interfaces/modules/grouping/IGroupingModule.sol#L31 | `ClaimedReward`} event.
563+
*/
564+
public async claimReward({
565+
groupIpId,
566+
currencyToken,
567+
memberIpIds,
568+
txOptions,
569+
}: ClaimRewardRequest): Promise<ClaimRewardResponse> {
570+
try {
571+
const claimRewardParam: GroupingModuleClaimRewardRequest = {
572+
groupId: validateAddress(groupIpId),
573+
ipIds: validateAddresses(memberIpIds),
574+
token: validateAddress(currencyToken),
575+
};
576+
const txHash = await this.groupingModuleClient.claimReward(claimRewardParam);
577+
const { receipt } = await waitForTxReceipt({
578+
txHash,
579+
txOptions,
580+
rpcClient: this.rpcClient,
581+
});
582+
if (!receipt) {
583+
return { txHash };
584+
}
585+
const claimedReward = this.groupingModuleEventClient.parseTxClaimedRewardEvent(receipt);
586+
return { txHash, claimedReward };
587+
} catch (error) {
588+
handleError(error, "Failed to claim reward");
589+
}
590+
}
591+
592+
/**
593+
* Collects royalties into the pool, making them claimable by group member IPs.
594+
*
595+
* Emits an on-chain {@link https://github.com/storyprotocol/protocol-core-v1/blob/v1.3.1/contracts/interfaces/modules/grouping/IGroupingModule.sol#L38 | `CollectedRoyaltiesToGroupPool`} event.
596+
*/
597+
public async collectRoyalties({
598+
groupIpId,
599+
currencyToken,
600+
txOptions,
601+
}: CollectRoyaltiesRequest): Promise<CollectRoyaltiesResponse> {
602+
try {
603+
const collectRoyaltiesParam: GroupingModuleCollectRoyaltiesRequest = {
604+
groupId: validateAddress(groupIpId),
605+
token: validateAddress(currencyToken),
606+
};
607+
const txHash = await this.groupingModuleClient.collectRoyalties(collectRoyaltiesParam);
608+
const { receipt } = await waitForTxReceipt({
609+
txHash,
610+
txOptions,
611+
rpcClient: this.rpcClient,
612+
});
613+
if (!receipt) {
614+
return { txHash };
615+
}
616+
const collectedRoyalties =
617+
this.groupingModuleEventClient.parseTxCollectedRoyaltiesToGroupPoolEvent(receipt)[0].amount;
618+
return { txHash, collectedRoyalties };
619+
} catch (error) {
620+
handleError(error, "Failed to collect royalties");
621+
}
622+
}
623+
530624
private getLicenseData(licenseData: LicenseDataInput[] | LicenseDataInput): LicenseData[] {
531625
const isArray = Array.isArray(licenseData);
532626
if ((isArray && licenseData.length === 0) || !licenseData) {

packages/core-sdk/src/resources/ipAsset.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ import { handleMulticall } from "../utils/registrationUtils/registerHelper";
106106
import {
107107
getCalculatedDeadline,
108108
getIpIdAddress,
109+
getPublicMinting,
109110
} from "../utils/registrationUtils/registerValidation";
110111
import {
111112
getRoyaltyShares,
@@ -1530,12 +1531,25 @@ export class IPAssetClient {
15301531
}: CommonRegistrationParams): Promise<CommonRegistrationTxResponse> {
15311532
let totalFees = 0n;
15321533
const wipSpenders: Erc20Spender[] = [];
1534+
let useMulticallWhenPossible = wipOptions?.useMulticallWhenPossible ?? true;
15331535

15341536
// get spg minting fee
15351537
if (spgNftContract) {
15361538
const nftMintFee = await calculateSPGWipMintFee(
15371539
new SpgnftImplReadOnlyClient(this.rpcClient, spgNftContract),
15381540
);
1541+
const publicMinting = await getPublicMinting(spgNftContract, this.rpcClient);
1542+
/**
1543+
* If the SPG NFT contract's public minting is disabled, we need to check if the caller has the `minter role`.
1544+
* When public minting is disabled, we can't use multicall because we need to perform additional role checks
1545+
* that aren't compatible with batched transactions.
1546+
*
1547+
* This is because role-based access control requires the transaction's msg.sender to be verified directly,
1548+
* which is not preserved when using multicall (where the multicall contract becomes the sender).
1549+
*/
1550+
if (!publicMinting) {
1551+
useMulticallWhenPossible = false;
1552+
}
15391553
totalFees += nftMintFee;
15401554
wipSpenders.push({
15411555
address: spgNftContract,
@@ -1568,7 +1582,7 @@ export class IPAssetClient {
15681582

15691583
const { txHash, receipt } = await contractCallWithFees({
15701584
totalFees,
1571-
options: { wipOptions },
1585+
options: { wipOptions: { ...wipOptions, useMulticallWhenPossible } },
15721586
multicall3Address: this.multicall3Client.address,
15731587
rpcClient: this.rpcClient,
15741588
tokenSpenders: wipSpenders,

packages/core-sdk/src/resources/nftClient.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@ import {
44
RegistrationWorkflowsClient,
55
RegistrationWorkflowsCreateCollectionRequest,
66
SimpleWalletClient,
7+
SpgnftImplClient,
78
SpgnftImplReadOnlyClient,
89
} from "../abi/generated";
910
import {
1011
CreateNFTCollectionRequest,
1112
CreateNFTCollectionResponse,
13+
GetTokenURIRequest,
14+
SetTokenURIRequest,
1215
} from "../types/resources/nftClient";
1316
import { handleError } from "../utils/errors";
1417
import { validateAddress } from "../utils/utils";
18+
import { TransactionResponse } from "../types/options";
19+
import { waitForTxReceipt } from "../utils/txOptions";
1520

1621
export class NftClient {
1722
public registrationWorkflowsClient: RegistrationWorkflowsClient;
@@ -105,4 +110,35 @@ export class NftClient {
105110
);
106111
return spgNftClient.mintFee();
107112
}
113+
/**
114+
* Sets the token URI for a specific token id.
115+
*
116+
* @remarks
117+
* Only callable by the owner of the token.
118+
*/
119+
public async setTokenURI({
120+
tokenId,
121+
tokenURI,
122+
spgNftContract,
123+
txOptions,
124+
}: SetTokenURIRequest): Promise<TransactionResponse> {
125+
try {
126+
const spgNftClient = new SpgnftImplClient(this.rpcClient, this.wallet, spgNftContract);
127+
const txHash = await spgNftClient.setTokenUri({
128+
tokenId: BigInt(tokenId),
129+
tokenUri: tokenURI,
130+
});
131+
return waitForTxReceipt({ txHash, txOptions, rpcClient: this.rpcClient });
132+
} catch (error) {
133+
handleError(error, "Failed to set token URI");
134+
}
135+
}
136+
137+
/**
138+
* Returns the token URI for a specific token id.
139+
*/
140+
public async getTokenURI({ tokenId, spgNftContract }: GetTokenURIRequest): Promise<string> {
141+
const spgNftClient = new SpgnftImplReadOnlyClient(this.rpcClient, spgNftContract);
142+
return await spgNftClient.tokenUri({ tokenId: BigInt(tokenId) });
143+
}
108144
}

packages/core-sdk/src/types/resources/group.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Address, Hash, TransactionReceipt } from "viem";
33
import { TxOptions } from "../options";
44
import {
55
EncodedTxData,
6+
GroupingModuleClaimedRewardEvent,
67
GroupingModuleCollectedRoyaltiesToGroupPoolEvent,
78
} from "../../abi/generated";
89
import { IpMetadataAndTxOptions, LicensingConfigInput, LicensingConfig } from "../common";
@@ -154,8 +155,39 @@ export type AddIpRequest = {
154155
txOptions?: Omit<TxOptions, "encodedTxDataOnly">;
155156
};
156157

158+
export type ClaimRewardRequest = {
159+
groupIpId: Address;
160+
/** The address of the currency (revenue) token to claim. */
161+
currencyToken: Address;
162+
/** The IDs of the member IPs to distribute the rewards to. */
163+
memberIpIds: Address[];
164+
txOptions?: Omit<TxOptions, "encodedTxDataOnly">;
165+
};
166+
167+
export type ClaimRewardResponse = {
168+
txHash: Hash;
169+
claimedReward?: GroupingModuleClaimedRewardEvent[];
170+
};
171+
export type GetClaimableRewardRequest = {
172+
groupIpId: Address;
173+
currencyToken: Address;
174+
memberIpIds: Address[];
175+
};
176+
157177
export type RemoveIpsFromGroupRequest = {
158178
groupIpId: Address;
159179
ipIds: Address[];
160180
txOptions?: Omit<TxOptions, "encodedTxDataOnly">;
161181
};
182+
183+
export type CollectRoyaltiesRequest = {
184+
groupIpId: Address;
185+
currencyToken: Address;
186+
txOptions?: Omit<TxOptions, "encodedTxDataOnly">;
187+
};
188+
189+
export type CollectRoyaltiesResponse = {
190+
txHash: Hash;
191+
/** The amount of royalties collected. */
192+
collectedRoyalties?: bigint;
193+
};

packages/core-sdk/src/types/resources/nftClient.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,16 @@ export type CreateNFTCollectionResponse = {
3131
encodedTxData?: EncodedTxData;
3232
spgNftContract?: Address;
3333
};
34+
35+
export type SetTokenURIRequest = {
36+
tokenId: bigint | number;
37+
/** The new metadata URI to associate with the token. */
38+
tokenURI: string;
39+
spgNftContract: Address;
40+
txOptions?: Omit<TxOptions, "encodedTxDataOnly">;
41+
};
42+
43+
export type GetTokenURIRequest = {
44+
tokenId: bigint | number;
45+
spgNftContract: Address;
46+
};

0 commit comments

Comments
 (0)