Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Beta add License and Policy #54

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/core-sdk/src/abi/taggingModule.abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ if (typeof process !== "undefined") {
dotenv.config();
}

if (typeof process !== "undefined") {
dotenv.config();
}

export const TaggingModuleRaw = TaggingModuleABI;
export const TaggingModuleReadable = formatAbi(TaggingModuleRaw);

Expand Down
34 changes: 30 additions & 4 deletions packages/core-sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import { IPAssetClient } from "./resources/ipAsset";
import { IPAssetReadOnlyClient } from "./resources/ipAssetReadOnly";
import { PermissionClient } from "./resources/permission";
import { PermissionReadOnlyClient } from "./resources/permissionReadOnly";
import { LicenseReadOnlyClient } from "./resources/licenseReadOnly";
import { PolicyReadOnlyClient } from "./resources/policyReadOnly";
import { LicenseClient } from "./resources/license";
import { PolicyClient } from "./resources/policy";
import { DisputeClient } from "./resources/dispute";
import { DisputeReadOnlyClient } from "./resources/disputeReadOnly";
import { chainStringToViemChain } from "./utils/utils";
Expand All @@ -32,8 +36,10 @@ export class StoryClient {
private readonly rpcClient: PublicClient;
private readonly wallet?: WalletClient;

private _ipAccount: IPAssetClient | IPAssetReadOnlyClient | null = null;
private _ipAsset: IPAssetClient | IPAssetReadOnlyClient | null = null;
private _permission: PermissionClient | PermissionReadOnlyClient | null = null;
private _license: LicenseClient | LicenseReadOnlyClient | null = null;
private _policy: PolicyClient | PolicyReadOnlyClient | null = null;
private _transaction: TransactionClient | TransactionReadOnlyClient | null = null;
private _platform: PlatformClient | null = null;
private _module: ModuleReadOnlyClient | null = null;
Expand Down Expand Up @@ -95,13 +101,13 @@ export class StoryClient {
}

public get ipAsset(): IPAssetClient | IPAssetReadOnlyClient {
if (this._ipAccount === null) {
this._ipAccount = this.isReadOnly
if (this._ipAsset === null) {
this._ipAsset = this.isReadOnly
? new IPAssetReadOnlyClient(this.httpClient, this.rpcClient)
: new IPAssetClient(this.httpClient, this.rpcClient, this.wallet!);
}

return this._ipAccount;
return this._ipAsset;
}

public get permission(): PermissionClient | PermissionReadOnlyClient {
Expand All @@ -114,6 +120,26 @@ export class StoryClient {
return this._permission;
}

public get license(): LicenseClient | LicenseReadOnlyClient {
if (this._license === null) {
this._license = this.isReadOnly
? new LicenseReadOnlyClient(this.httpClient, this.rpcClient)
: new LicenseClient(this.httpClient, this.rpcClient, this.wallet!);
}

return this._license;
}

public get policy(): PolicyClient | PolicyReadOnlyClient {
if (this._policy === null) {
this._policy = this.isReadOnly
? new PolicyReadOnlyClient(this.httpClient, this.rpcClient)
: new PolicyClient(this.httpClient, this.rpcClient, this.wallet!);
}

return this._policy;
}

/**
* Getter for the transaction client. The client is lazily created when
* this method is called.
Expand Down
4 changes: 4 additions & 0 deletions packages/core-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export { IPAssetClient } from "./resources/ipAsset";
export { IPAssetReadOnlyClient } from "./resources/ipAssetReadOnly";
export { PermissionClient } from "./resources/permission";
export { PermissionReadOnlyClient } from "./resources/permissionReadOnly";
export { LicenseClient } from "./resources/license";
export { LicenseReadOnlyClient } from "./resources/licenseReadOnly";
export { PolicyClient } from "./resources/policy";
export { PolicyReadOnlyClient } from "./resources/policyReadOnly";

export type { StoryConfig, StoryReadOnlyConfig } from "./types/config";
export type { Client, ReadOnlyClient } from "./types/client";
Expand Down
145 changes: 145 additions & 0 deletions packages/core-sdk/src/resources/license.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { AxiosInstance } from "axios";
import { PublicClient, WalletClient, encodeFunctionData, getAddress } from "viem";

import { handleError } from "../utils/errors";
import { parseToBigInt, waitTxAndFilterLog } from "../utils/utils";
import { LicenseReadOnlyClient } from "./licenseReadOnly";
import { IPAccountImplMerged } from "../abi/ipAccountImpl.abi";
import { LicenseRegistryConfig, LicenseRegistryRaw } from "../abi/licenseRegistry.abi";
import {
linkIpToParentRequest,
linkIpToParentResponse,
mintLicenseRequest,
mintLicenseResponse,
// transferRequest,
// transferResponse,
} from "../types/resources/license";

export class LicenseClient extends LicenseReadOnlyClient {
private readonly wallet: WalletClient;

constructor(httpClient: AxiosInstance, rpcClient: PublicClient, wallet: WalletClient) {
super(httpClient, rpcClient);
this.wallet = wallet;
}

public async mintLicense(request: mintLicenseRequest): Promise<mintLicenseResponse> {
try {
const IPAccountConfig = {
abi: IPAccountImplMerged,
// TODO: find out which ipId to use to call the execute fn
address: getAddress(request.licensorIps[0]),
};

const licenseRegistry = getAddress(
process.env.LICENSE_REGISTRY || process.env.NEXT_PUBLIC_LICENSE_REGISTRY || "",
);

const { request: call } = await this.rpcClient.simulateContract({
...IPAccountConfig,
functionName: "execute",
args: [
licenseRegistry,
0,
encodeFunctionData({
abi: LicenseRegistryRaw,
functionName: "mintLicense",
args: [
{ policyId: parseToBigInt(request.policyId), licensorIpIds: request.licensorIps },
parseToBigInt(request.mintAmount),
getAddress(request.receiverAddress),
],
}),
],
account: this.wallet.account,
});
const txHash = await this.wallet.writeContract(call);
if (request.txOptions?.waitForTransaction) {
const targetLog = await waitTxAndFilterLog(this.rpcClient, txHash, {
...LicenseRegistryConfig,
eventName: "LicenseMinted",
});
return { txHash: txHash, licenseId: targetLog?.args?.licenseId.toString() };
} else {
return { txHash: txHash };
}
} catch (error) {
handleError(error, "Failed to mint license");
}
}

public async linkIpToParent(request: linkIpToParentRequest): Promise<linkIpToParentResponse> {
try {
const IPAccountConfig = {
abi: IPAccountImplMerged,
address: getAddress(request.childIpId),
};

const licenseRegistry = getAddress(
process.env.LICENSE_REGISTRY || process.env.NEXT_PUBLIC_LICENSE_REGISTRY || "",
);

const { request: call } = await this.rpcClient.simulateContract({
...IPAccountConfig,
functionName: "execute",
args: [
licenseRegistry,
0,
encodeFunctionData({
abi: LicenseRegistryRaw,
functionName: "linkIpToParent",
args: [
parseToBigInt(request.licenseId),
getAddress(request.childIpId),
getAddress(request.holderAddress),
],
}),
],
account: this.wallet.account,
});
const txHash = await this.wallet.writeContract(call);
// if (request.txOptions?.waitForTransaction) {
// const targetLog = await waitTxAndFilterLog(this.rpcClient, txHash, {
// ...LicenseRegistryConfig,
// eventName: "LicenseMinted",
// });
// return { txHash: txHash, licenseId: targetLog?.args.account.toString() };
// } else {
return { txHash: txHash };
// }
} catch (error) {
handleError(error, "Failed to mint license");
}
}

// public async transfer(request: transferRequest): Promise<transferResponse> {
// try {
// const { request: call } = await this.rpcClient.simulateContract({
// ...LicenseRegistryConfig,
// functionName: "transfer",
// args: [
// // TODO: format args
// request.operator,
// request.fromAddress,
// request.toAddress,
// request.id,
// request.value,
// ],
// account: this.wallet.account,
// });

// const txHash = await this.wallet.writeContract(call);
// // if (request.txOptions?.waitForTransaction) {
// // const targetLog = await waitTxAndFilterLog(this.rpcClient, txHash, {
// // ...LicenseRegistryConfig,
// // eventName: "LicenseBurnt",
// // });
// // return { txHash: txHash, ipAccountId: targetLog?.args.account.toString() };
// // } else {
// return { txHash: txHash };
// // }
// } catch (error) {
// handleError(error, "Failed to register root IP");
// }
// }
}
56 changes: 56 additions & 0 deletions packages/core-sdk/src/resources/licenseReadOnly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { AxiosInstance } from "axios";
import { PublicClient } from "viem";

import {
GetLicenseRequest,
GetLicenseResponse,
ListLicensesRequest,
ListLicensesResponse,
} from "../types/resources/license";
import { handleError } from "../utils/errors";
import { isIntegerString } from "../utils/utils";

/**
* LicenseReadOnlyClient allows you to view and search Licenses on Story Protocol.
*/
export class LicenseReadOnlyClient {
protected readonly httpClient: AxiosInstance;
protected readonly rpcClient: PublicClient;

constructor(httpClient: AxiosInstance, rpcClient: PublicClient) {
this.httpClient = httpClient;
this.rpcClient = rpcClient;
}

/**
* Get a License based on the specified License ID.
*
* @param request - the request object for getting an License.
* @returns the response object the contains the fetched License.
*/
public async get(request: GetLicenseRequest): Promise<GetLicenseResponse> {
try {
if (!isIntegerString(request.licenseId)) {
throw new Error(`Invalid license id. Must be an integer. But get: ${request.licenseId}`);
}
const response = await this.httpClient.get(`/licenses/${request.licenseId}`);
return response.data as GetLicenseResponse;
} catch (error: unknown) {
handleError(error, "Failed to get License");
}
}

/**
* List Licenses
*
* @returns the response object that contains results from listing query.
*/
public async list(request?: ListLicensesRequest): Promise<ListLicensesResponse> {
try {
const response = await this.httpClient.post(`/licenses`, request || {});
return response.data as ListLicensesResponse;
} catch (error) {
handleError(error, "Failed to list Licenses.");
}
}
}
106 changes: 106 additions & 0 deletions packages/core-sdk/src/resources/policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { AxiosInstance } from "axios";
import { PublicClient, WalletClient, encodeFunctionData, getAddress } from "viem";

import { handleError } from "../utils/errors";
import { LicenseRegistryConfig, LicenseRegistryRaw } from "../abi/licenseRegistry.abi";
import { parseToBigInt, waitTxAndFilterLog } from "../utils/utils";
import { PolicyReadOnlyClient } from "./policyReadOnly";
import {
addPolicyRequest,
addPolicyResponse,
addPolicyToIpRequest,
addPolicyToIpResponse,
} from "../types/resources/policy";
import { IPAccountImplMerged } from "../abi/ipAccountImpl.abi";

export class PolicyClient extends PolicyReadOnlyClient {
private readonly wallet: WalletClient;

constructor(httpClient: AxiosInstance, rpcClient: PublicClient, wallet: WalletClient) {
super(httpClient, rpcClient);
this.wallet = wallet;
}

/**
* Create a policy on Story Protocol based on the specified params.
*
* @param request - the request object that contains all data needed to register a policy.
* @returns the response object that contains results from the policy creation.
*/
public async createPolicy(request: addPolicyRequest): Promise<addPolicyResponse> {
try {
const { request: call } = await this.rpcClient.simulateContract({
...LicenseRegistryConfig,
functionName: "addPolicy",
args: [
{
frameworkId: parseToBigInt(request.frameworkId),
mintingParamValues: request.mintingParamValues.map((add) => getAddress(add)),
activationParamValues: request.activationParamValues.map((add) => getAddress(add)),
needsActivation: request.needsActivation,
linkParentParamValues: request.linkParentParamValues.map((add) => getAddress(add)),
},
],
});

const txHash = await this.wallet.writeContract(call);

// if (request.txOptions?.waitForTransaction) {
// const targetLog = await waitTxAndFilterLog(this.rpcClient, txHash, {
// ...LicenseRegistryConfig,
// // TODO: need PolicyCreated ABI
// eventName: "PolicyCreated",
// });
// return { txHash: txHash, policyId: targetLog?.args.account.toString() };
// } else {
return { txHash: txHash };
// }
} catch (error) {
handleError(error, "Failed to register derivative IP");
}
}

// TODO: move to License resource
public async addPolicyToIp(request: addPolicyToIpRequest): Promise<addPolicyToIpResponse> {
try {
const IPAccountConfig = {
abi: IPAccountImplMerged,
address: getAddress(request.ipId),
};
const licenseRegistry = getAddress(
process.env.LICENSE_REGISTRY || process.env.NEXT_PUBLIC_LICENSE_REGISTRY || "",
);
const { request: call } = await this.rpcClient.simulateContract({
...IPAccountConfig,
functionName: "execute",
args: [
licenseRegistry,
0,
encodeFunctionData({
abi: LicenseRegistryRaw,
functionName: "addPolicyToIp",
args: [
getAddress(request.ipId), // 0x Address
parseToBigInt(request.policyId),
],
}),
],
account: this.wallet.account,
});

const txHash = await this.wallet.writeContract(call);
// TODO: the emit event doesn't return anything
if (request.txOptions?.waitForTransaction) {
await waitTxAndFilterLog(this.rpcClient, txHash, {
...LicenseRegistryConfig,
eventName: "PolicyAddedToIpId",
});
return { txHash: txHash };
} else {
return { txHash: txHash };
}
} catch (error) {
handleError(error, "Failed to add policy to IP");
}
}
}
Loading
Loading