Skip to content

Commit

Permalink
Add royalty.claimRevenue method and unit tests (#145)
Browse files Browse the repository at this point in the history
* Add unit tests about ipAssetClient.registerDerivativeWithLicenseTokens

* Add license module unit tests

* Add royalty.collectRoyaltyTokens unit test

* Add royalty.claimRevenue method

* Bump up the version to 1.0.0-rc.4
  • Loading branch information
bonnie57 authored Apr 18, 2024
1 parent d647cbb commit e1c9f2f
Show file tree
Hide file tree
Showing 11 changed files with 866 additions and 93 deletions.
2 changes: 1 addition & 1 deletion packages/core-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@story-protocol/core-sdk",
"version": "1.0.0-rc.3",
"version": "1.0.0-rc.4",
"description": "Story Protocol Core SDK",
"main": "dist/story-protocol-core-sdk.cjs.js",
"module": "dist/story-protocol-core-sdk.esm.js",
Expand Down
54 changes: 54 additions & 0 deletions packages/core-sdk/src/abi/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5745,6 +5745,19 @@ export class IpAssetRegistryClient extends IpAssetRegistryReadOnlyClient {

// Contract IpRoyaltyVaultImpl =============================================================

/**
* IpRoyaltyVaultImplRevenueTokenClaimedEvent
*
* @param claimer address
* @param token address
* @param amount uint256
*/
export type IpRoyaltyVaultImplRevenueTokenClaimedEvent = {
claimer: Address;
token: Address;
amount: bigint;
};

/**
* IpRoyaltyVaultImplRoyaltyTokensCollectedEvent
*
Expand Down Expand Up @@ -5829,6 +5842,47 @@ export class IpRoyaltyVaultImplEventClient {
this.rpcClient = rpcClient;
}

/**
* event RevenueTokenClaimed for contract IpRoyaltyVaultImpl
*/
public watchRevenueTokenClaimedEvent(
onLogs: (txHash: Hex, ev: Partial<IpRoyaltyVaultImplRevenueTokenClaimedEvent>) => void,
): WatchContractEventReturnType {
return this.rpcClient.watchContractEvent({
abi: ipRoyaltyVaultImplAbi,
address: this.address,
eventName: "RevenueTokenClaimed",
onLogs: (evs) => {
evs.forEach((it) => onLogs(it.transactionHash, it.args));
},
});
}

/**
* parse tx receipt event RevenueTokenClaimed for contract IpRoyaltyVaultImpl
*/
public parseTxRevenueTokenClaimedEvent(
txReceipt: TransactionReceipt,
): Array<IpRoyaltyVaultImplRevenueTokenClaimedEvent> {
const targetLogs: Array<IpRoyaltyVaultImplRevenueTokenClaimedEvent> = [];
for (const log of txReceipt.logs) {
try {
const event = decodeEventLog({
abi: ipRoyaltyVaultImplAbi,
eventName: "RevenueTokenClaimed",
data: log.data,
topics: log.topics,
});
if (event.eventName === "RevenueTokenClaimed") {
targetLogs.push(event.args);
}
} catch (e) {
/* empty */
}
}
return targetLogs;
}

/**
* event RoyaltyTokensCollected for contract IpRoyaltyVaultImpl
*/
Expand Down
6 changes: 3 additions & 3 deletions packages/core-sdk/src/resources/ipAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,14 @@ export class IPAssetClient {
try {
const isChildIpIdRegistered = await this.isRegistered(request.childIpId);
if (!isChildIpIdRegistered) {
throw new Error(`The child IP with id ${request.childIpId} is not registered`);
throw new Error(`The child IP with id ${request.childIpId} is not registered.`);
}
for (const licenseTokenId of request.licenseTokenIds) {
const tokenOwnerAddress = await this.licenseTokenReadOnlyClient.ownerOf({
tokenId: BigInt(licenseTokenId),
});
if (!tokenOwnerAddress) {
throw new Error(`License token id ${licenseTokenId} must be owned by the caller`);
throw new Error(`License token id ${licenseTokenId} must be owned by the caller.`);
}
}
const txHash = await this.licensingModuleClient.registerDerivativeWithLicenseTokens({
Expand All @@ -172,7 +172,7 @@ export class IPAssetClient {
return { txHash: txHash };
}
} catch (error) {
handleError(error, "Failed to register derivative with license tokens.");
handleError(error, "Failed to register derivative with license tokens");
}
}

Expand Down
70 changes: 37 additions & 33 deletions packages/core-sdk/src/resources/license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ export class LicenseClient {
}
/**
* Convenient function to register a PIL non commercial social remix license to the registry
* @param request The request object that contains all data needed to register a PIL non commercial social remix license.
* @param request [Optional] The request object that contains all data needed to register a PIL non commercial social remix license.
* @param request.txOptions [Optional] The transaction options.
* @returns A Promise that resolves to an object containing the optional transaction hash and optional license terms Id.
* @emits LicenseTermsRegistered (licenseTermsId, licenseTemplate, licenseTerms);
*/
public async registerNonComSocialRemixingPIL(
request: RegisterNonComSocialRemixingPILRequest,
request?: RegisterNonComSocialRemixingPILRequest,
): Promise<RegisterPILResponse> {
try {
const licenseTerms: LicenseTerms = {
Expand All @@ -79,7 +79,7 @@ export class LicenseClient {
return { licenseTermsId: licenseTermsId.toString() };
}
const txHash = await this.licenseTemplateClient.registerLicenseTerms({ terms: licenseTerms });
if (request.txOptions?.waitForTransaction) {
if (request?.txOptions?.waitForTransaction) {
const txReceipt = await this.rpcClient.waitForTransactionReceipt({ hash: txHash });
const targetLogs = this.licenseTemplateClient.parseTxLicenseTermsRegisteredEvent(txReceipt);
return { txHash: txHash, licenseTermsId: targetLogs[0].licenseTermsId.toString() };
Expand Down Expand Up @@ -198,37 +198,41 @@ export class LicenseClient {
* @returns A Promise that resolves to an object containing the transaction hash.
*/
public async attachLicenseTerms(request: AttachLicenseTermsRequest) {
const isRegistered = await this.ipAssetRegistryClient.isRegistered({ id: request.ipId });
if (!isRegistered) {
throw new Error(`The IP with id ${request.ipId} is not registered`);
}
const isExisted = await this.piLicenseTemplateReadOnlyClient.exists({
licenseTermsId: BigInt(request.licenseTermsId),
});
if (!isExisted) {
throw new Error(`License terms id ${request.licenseTermsId} do not exist`);
}
const isAttachedLicenseTerms =
await this.licenseRegistryReadOnlyClient.hasIpAttachedLicenseTerms({
try {
const isRegistered = await this.ipAssetRegistryClient.isRegistered({ id: request.ipId });
if (!isRegistered) {
throw new Error(`The IP with id ${request.ipId} is not registered.`);
}
const isExisted = await this.piLicenseTemplateReadOnlyClient.exists({
licenseTermsId: BigInt(request.licenseTermsId),
});
if (!isExisted) {
throw new Error(`License terms id ${request.licenseTermsId} do not exist.`);
}
const isAttachedLicenseTerms =
await this.licenseRegistryReadOnlyClient.hasIpAttachedLicenseTerms({
ipId: request.ipId,
licenseTemplate: request.licenseTemplate || this.licenseTemplateClient.address,
licenseTermsId: BigInt(request.licenseTermsId),
});
if (isAttachedLicenseTerms) {
throw new Error(
`License terms id ${request.licenseTermsId} is already attached to the IP with id ${request.ipId}.`,
);
}
const txHash = await this.licensingModuleClient.attachLicenseTerms({
ipId: request.ipId,
licenseTemplate: request.licenseTemplate || this.licenseTemplateClient.address,
licenseTermsId: BigInt(request.licenseTermsId),
});
if (isAttachedLicenseTerms) {
throw new Error(
`License terms id ${request.licenseTermsId} is already attached to the IP with id ${request.ipId}`,
);
}
const txHash = await this.licensingModuleClient.attachLicenseTerms({
ipId: request.ipId,
licenseTemplate: request.licenseTemplate || this.licenseTemplateClient.address,
licenseTermsId: BigInt(request.licenseTermsId),
});
if (request.txOptions?.waitForTransaction) {
await this.rpcClient.waitForTransactionReceipt({ hash: txHash });
return { txHash: txHash };
} else {
return { txHash: txHash };
if (request.txOptions?.waitForTransaction) {
await this.rpcClient.waitForTransactionReceipt({ hash: txHash });
return { txHash: txHash };
} else {
return { txHash: txHash };
}
} catch (error) {
handleError(error, "Failed to attach license terms");
}
}

Expand Down Expand Up @@ -262,13 +266,13 @@ export class LicenseClient {
id: request.licensorIpId,
});
if (!isLicenseIpIdRegistered) {
throw new Error(`The licensor IP with id ${request.licensorIpId} is not registered`);
throw new Error(`The licensor IP with id ${request.licensorIpId} is not registered.`);
}
const isExisted = await this.piLicenseTemplateReadOnlyClient.exists({
licenseTermsId: BigInt(request.licenseTermsId),
});
if (!isExisted) {
throw new Error(`License terms id ${request.licenseTermsId} do not exist`);
throw new Error(`License terms id ${request.licenseTermsId} do not exist.`);
}
const isAttachedLicenseTerms =
await this.licenseRegistryReadOnlyClient.hasIpAttachedLicenseTerms({
Expand All @@ -278,7 +282,7 @@ export class LicenseClient {
});
if (!isAttachedLicenseTerms) {
throw new Error(
`License terms id ${request.licenseTermsId} is not attached to the IP with id ${request.licensorIpId}`,
`License terms id ${request.licenseTermsId} is not attached to the IP with id ${request.licensorIpId}.`,
);
}
const txHash = await this.licensingModuleClient.mintLicenseTokens({
Expand Down
42 changes: 39 additions & 3 deletions packages/core-sdk/src/resources/royalty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
RoyaltyVaultAddress,
SnapshotRequest,
SnapshotResponse,
claimRevenueRequest,
claimRevenueResponse,
} from "../types/resources/royalty";
import {
IpAssetRegistryClient,
Expand Down Expand Up @@ -52,7 +54,7 @@ export class RoyaltyClient {
id: request.parentIpId,
});
if (!isParentIpIdRegistered) {
throw new Error(`The parent IP with id ${request.parentIpId} is not registered`);
throw new Error(`The parent IP with id ${request.parentIpId} is not registered.`);
}
const proxyAddress = await this.getRoyaltyVaultProxyAddress(request.royaltyVaultIpId);
const ipRoyaltyVault = new IpRoyaltyVaultImplClient(
Expand Down Expand Up @@ -150,6 +152,40 @@ export class RoyaltyClient {
handleError(error, "Failed to calculate claimable revenue");
}
}

/**
* Allows token holders to claim by a list of snapshot ids based on the token balance at certain snapshot
* @param request - The request object that contains all data needed to claim revenue.
* @param request.snapshotIds The list of snapshot ids.
* @param request.royaltyVaultIpId The id of the royalty vault.
* @param request.token The revenue token to claim.
* @param request.txOptions [Optional] The transaction options.
* @returns A Promise that resolves to an object containing the transaction hash and optional claimableToken if waitForTxn is set to true.
* @emits RevenueTokenClaimed (claimer, token, amount).
*/
public async claimRevenue(request: claimRevenueRequest): Promise<claimRevenueResponse> {
try {
const proxyAddress = await this.getRoyaltyVaultProxyAddress(request.royaltyVaultIpId);
const ipRoyaltyVault = new IpRoyaltyVaultImplClient(
this.rpcClient,
this.wallet,
proxyAddress,
);
const txHash = await ipRoyaltyVault.claimRevenueBySnapshotBatch({
snapshotIds: request.snapshotIds.map((id) => BigInt(id)),
token: request.token,
});
if (request.txOptions?.waitForTransaction) {
const txReceipt = await this.rpcClient.waitForTransactionReceipt({ hash: txHash });
const targetLogs = ipRoyaltyVault.parseTxRevenueTokenClaimedEvent(txReceipt);
return { txHash, claimableToken: targetLogs[0].amount };
} else {
return { txHash };
}
} catch (error) {
handleError(error, "Failed to claim revenue");
}
}
/**
* Snapshots the claimable revenue and royalty token amounts.
* @param request - The request object that contains all data needed to snapshot.
Expand Down Expand Up @@ -184,13 +220,13 @@ export class RoyaltyClient {
id: royaltyVaultIpId,
});
if (!isRoyaltyVaultIpIdRegistered) {
throw new Error(`The royalty vault IP with id ${royaltyVaultIpId} is not registered`);
throw new Error(`The royalty vault IP with id ${royaltyVaultIpId} is not registered.`);
}
const data = await this.royaltyPolicyLapClient.getRoyaltyData({
ipId: royaltyVaultIpId,
});
if (!data[1] || data[1] === "0x") {
throw new Error(`The royalty vault IP with id ${royaltyVaultIpId} address is not set`);
throw new Error(`The royalty vault IP with id ${royaltyVaultIpId} address is not set.`);
}
return data[1];
}
Expand Down
12 changes: 12 additions & 0 deletions packages/core-sdk/src/types/resources/royalty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ export type SnapshotRequest = {
txOptions?: TxOptions;
};

export type claimRevenueRequest = {
snapshotIds: string[];
token: Hex;
royaltyVaultIpId: Hex;
txOptions?: TxOptions;
};

export type claimRevenueResponse = {
txHash: string;
claimableToken?: bigint;
};

export type SnapshotResponse = {
txHash: string;
snapshotId?: bigint;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { MockERC721, MockERC20, getTokenId } from "./util";

chai.use(chaiAsPromised);
const expect = chai.expect;
let startTokenId = 180;
let startTokenId = 198;
let snapshotId: bigint;
describe.skip("Test royalty Functions", () => {
let client: StoryClient;
Expand Down Expand Up @@ -43,7 +43,6 @@ describe.skip("Test royalty Functions", () => {
let ipId1: Hex;
let ipId2: Hex;
const getIpId = async (): Promise<Hex> => {
console.log("startTokenId", startTokenId);
const tokenId = await getTokenId(startTokenId++);
const response = await client.ipAsset.register({
tokenContract: MockERC721,
Expand All @@ -63,7 +62,6 @@ describe.skip("Test royalty Functions", () => {
waitForTransaction: true,
},
});
console.log("licenseTermsId", response.licenseTermsId);
return response.licenseTermsId!;
};

Expand All @@ -80,7 +78,6 @@ describe.skip("Test royalty Functions", () => {
before(async () => {
ipId1 = await getIpId();
ipId2 = await getIpId();
console.log("ipId1", ipId1, "ipId2", ipId2);
const licenseTermsId = await getCommercialPolicyId();
await attachLicenseTerms(ipId1, licenseTermsId);
await client.ipAsset.registerDerivative({
Expand Down Expand Up @@ -181,7 +178,6 @@ describe.skip("Test royalty Functions", () => {
expect(response.txHash).to.be.a("string").not.empty;
expect(response.snapshotId).to.be.a("bigint");
snapshotId = response.snapshotId!;
console.log("snapshot", snapshotId);
});

it("should not throw error when collect royalty tokens", async () => {
Expand All @@ -208,5 +204,18 @@ describe.skip("Test royalty Functions", () => {
});
expect(response).to.be.a("bigint");
});

it("should not throw error when claim revenue", async () => {
const response = await client.royalty.claimRevenue({
royaltyVaultIpId: ipId2,
snapshotIds: [snapshotId.toString()],
token: "0xA36F2A4A02f5C215d1b3630f71A4Ff55B5492AAE",
txOptions: {
waitForTransaction: true,
},
});

expect(response.claimableToken).to.be.a("bigint");
});
});
});
Loading

0 comments on commit e1c9f2f

Please sign in to comment.