Skip to content

Commit

Permalink
Merge pull request #3 from storyprotocol/plugin-improvements
Browse files Browse the repository at this point in the history
add attachTerms and improve registerIP to use IP metadata spec
  • Loading branch information
jacob-tucker authored Dec 11, 2024
2 parents 76aa199 + dcd9428 commit 5595de8
Show file tree
Hide file tree
Showing 10 changed files with 495 additions and 39 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,5 @@ APTOS_PRIVATE_KEY= # Aptos private key
APTOS_NETWORK= # must be one of mainnet, testnet

# Story
STORY_PRIVATE_KEY= # Story private key
STORY_PRIVATE_KEY= # Story private key
PINATA_JWT= # Pinata JWT for uploading files to IPFS
3 changes: 2 additions & 1 deletion packages/plugin-story/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"@ai16z/plugin-trustdb": "workspace:*",
"@story-protocol/core-sdk": "1.2.0-rc.3",
"tsup": "8.3.5",
"viem": "2.21.54"
"viem": "2.21.54",
"@pinata/sdk": "^2.1.0"
},
"scripts": {
"build": "tsup --format esm --dts",
Expand Down
153 changes: 153 additions & 0 deletions packages/plugin-story/src/actions/attachTerms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {
composeContext,
elizaLogger,
generateObjectDEPRECATED,
HandlerCallback,
ModelClass,
type IAgentRuntime,
type Memory,
type State,
} from "@ai16z/eliza";
import { storyWalletProvider, WalletProvider } from "../providers/wallet";
import { attachTermsTemplate } from "../templates";
import {
AttachLicenseTermsResponse,
LicenseTerms,
RegisterPILResponse,
} from "@story-protocol/core-sdk";
import { AttachTermsParams } from "../types";
import { zeroAddress } from "viem";

export { attachTermsTemplate };

export class AttachTermsAction {
constructor(private walletProvider: WalletProvider) {}

async attachTerms(params: AttachTermsParams): Promise<{
attachTermsResponse: AttachLicenseTermsResponse;
registerPilTermsResponse: RegisterPILResponse;
}> {
const storyClient = this.walletProvider.getStoryClient();

console.log("params", params);

const licenseTerms: LicenseTerms = {
transferable: true,
royaltyPolicy: params.commercialUse
? "0x28b4F70ffE5ba7A26aEF979226f77Eb57fb9Fdb6"
: zeroAddress,
defaultMintingFee: params.mintingFee
? BigInt(params.mintingFee)
: BigInt(0),
expiration: BigInt(0),
commercialUse: params.commercialUse || false,
commercialAttribution: false,
commercializerChecker: zeroAddress,
commercializerCheckerData: zeroAddress,
commercialRevShare: params.commercialUse
? params.commercialRevShare
: 0,
commercialRevCeiling: BigInt(0),
derivativesAllowed: true,
derivativesAttribution: true,
derivativesApproval: false,
derivativesReciprocal: true,
derivativeRevCeiling: BigInt(0),
currency: "0xC0F6E387aC0B324Ec18EAcf22EE7271207dCE3d5",
uri: "",
};

const registerPilTermsResponse =
await storyClient.license.registerPILTerms({
...licenseTerms,
txOptions: { waitForTransaction: true },
});

const attachTermsResponse =
await storyClient.license.attachLicenseTerms({
ipId: params.ipId,
licenseTermsId: registerPilTermsResponse.licenseTermsId,
txOptions: { waitForTransaction: true },
});

return { attachTermsResponse, registerPilTermsResponse };
}
}

export const attachTermsAction = {
name: "ATTACH_TERMS",
description: "Attach license terms to an IP Asset on Story",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
options: any,
callback?: HandlerCallback
): Promise<boolean> => {
elizaLogger.log("Starting ATTACH_TERMS handler...");

// initialize or update state
if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}

const walletInfo = await storyWalletProvider.get(
runtime,
message,
state
);
state.walletInfo = walletInfo;

const attachTermsContext = composeContext({
state,
template: attachTermsTemplate,
});

const content = await generateObjectDEPRECATED({
runtime,
context: attachTermsContext,
modelClass: ModelClass.SMALL,
});

const walletProvider = new WalletProvider(runtime);
const action = new AttachTermsAction(walletProvider);
try {
const response = await action.attachTerms(content);
// if license terms were attached
if (response.attachTermsResponse.success) {
callback?.({
text: `Successfully attached license terms: ${response.registerPilTermsResponse.licenseTermsId}\nTransaction Hash: ${response.attachTermsResponse.txHash}`,
});
return true;
}
// if license terms were already attached
callback?.({
text: `License terms ${response.registerPilTermsResponse.licenseTermsId} were already attached to IP Asset ${content.ipId}`,
});
return true;
} catch (e) {
elizaLogger.error("Error licensing IP:", e.message);
callback?.({ text: `Error licensing IP: ${e.message}` });
return false;
}
},
template: attachTermsTemplate,
validate: async (runtime: IAgentRuntime) => {
const privateKey = runtime.getSetting("STORY_PRIVATE_KEY");
return typeof privateKey === "string" && privateKey.startsWith("0x");
},
examples: [
[
{
user: "user",
content: {
text: "Attach commercial, 10% rev share license terms to IP Asset 0x2265F2b8e47F98b3Bdf7a1937EAc27282954A4Db",
action: "ATTACH_TERMS",
},
},
],
],
similes: ["ATTACH_TERMS", "ATTACH_TERMS_TO_IP"],
};
18 changes: 9 additions & 9 deletions packages/plugin-story/src/actions/licenseIP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
type Memory,
type State,
} from "@ai16z/eliza";
import { WalletProvider } from "../providers/wallet";
import { storyWalletProvider, WalletProvider } from "../providers/wallet";
import { licenseIPTemplate } from "../templates";
import { LicenseIPParams } from "../types";
import { MintLicenseTokensResponse } from "@story-protocol/core-sdk";
Expand Down Expand Up @@ -37,7 +37,7 @@ export class LicenseIPAction {
const response = await storyClient.license.mintLicenseTokens({
licensorIpId: params.licensorIpId,
licenseTermsId: params.licenseTermsId,
amount: params.amount,
amount: params.amount || 1,
txOptions: { waitForTransaction: true },
});

Expand All @@ -64,6 +64,13 @@ export const licenseIPAction = {
state = await runtime.updateRecentMessageState(state);
}

const walletInfo = await storyWalletProvider.get(
runtime,
message,
state
);
state.walletInfo = walletInfo;

const licenseIPContext = composeContext({
state,
template: licenseIPTemplate,
Expand Down Expand Up @@ -96,13 +103,6 @@ export const licenseIPAction = {
},
examples: [
[
{
user: "assistant",
content: {
text: "Ill help you license an IP Asset 0x2265F2b8e47F98b3Bdf7a1937EAc27282954A4Db with license terms 1",
action: "LICENSE_IP",
},
},
{
user: "user",
content: {
Expand Down
69 changes: 54 additions & 15 deletions packages/plugin-story/src/actions/registerIP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,63 @@ import {
type Memory,
type State,
} from "@ai16z/eliza";
import { WalletProvider } from "../providers/wallet";
import { storyWalletProvider, WalletProvider } from "../providers/wallet";
import { registerIPTemplate } from "../templates";
import { RegisterIPParams } from "../types";
import { RegisterIpResponse } from "@story-protocol/core-sdk";
import { PinataProvider } from "../providers/pinata";
import { createHash } from "crypto";

export { registerIPTemplate };

export class RegisterIPAction {
constructor(private walletProvider: WalletProvider) {}
constructor(
private walletProvider: WalletProvider,
private pinataProvider: PinataProvider
) {}

async registerIP(params: RegisterIPParams): Promise<RegisterIpResponse> {
const storyClient = this.walletProvider.getStoryClient();

const response = await storyClient.ipAsset.register({
nftContract: params.contractAddress,
tokenId: params.tokenId,
txOptions: { waitForTransaction: true },
// configure ip metadata
const ipMetadata = storyClient.ipAsset.generateIpMetadata({
title: params.title,
description: params.description,
ipType: params.ipType ? params.ipType : undefined,
});

// configure nft metadata
const nftMetadata = {
name: params.title,
description: params.description,
};

// upload metadata to ipfs
const ipIpfsHash =
await this.pinataProvider.uploadJSONToIPFS(ipMetadata);
const ipHash = createHash("sha256")
.update(JSON.stringify(ipMetadata))
.digest("hex");
const nftIpfsHash =
await this.pinataProvider.uploadJSONToIPFS(nftMetadata);
const nftHash = createHash("sha256")
.update(JSON.stringify(nftMetadata))
.digest("hex");

// register ip
const response =
await storyClient.ipAsset.mintAndRegisterIpAssetWithPilTerms({
spgNftContract: "0xC81B2cbEFD1aA0227bf513729580d3CF40fd61dF",
terms: [],
ipMetadata: {
ipMetadataURI: `https://ipfs.io/ipfs/${ipIpfsHash}`,
ipMetadataHash: `0x${ipHash}`,
nftMetadataURI: `https://ipfs.io/ipfs/${nftIpfsHash}`,
nftMetadataHash: `0x${nftHash}`,
},
txOptions: { waitForTransaction: true },
});

return response;
}
}
Expand All @@ -50,6 +88,13 @@ export const registerIPAction = {
state = await runtime.updateRecentMessageState(state);
}

const walletInfo = await storyWalletProvider.get(
runtime,
message,
state
);
state.walletInfo = walletInfo;

const registerIPContext = composeContext({
state,
template: registerIPTemplate,
Expand All @@ -62,7 +107,8 @@ export const registerIPAction = {
});

const walletProvider = new WalletProvider(runtime);
const action = new RegisterIPAction(walletProvider);
const pinataProvider = new PinataProvider(runtime);
const action = new RegisterIPAction(walletProvider, pinataProvider);
try {
const response = await action.registerIP(content);
callback?.({
Expand All @@ -82,17 +128,10 @@ export const registerIPAction = {
},
examples: [
[
{
user: "assistant",
content: {
text: "Ill help you register an NFT with contract address 0x041B4F29183317Fd352AE57e331154b73F8a1D73 and token id 209 as IP",
action: "REGISTER_IP",
},
},
{
user: "user",
content: {
text: "Register an NFT with contract address 0x041B4F29183317Fd352AE57e331154b73F8a1D73 and token id 209 as IP",
text: "Register my IP titled 'My IP' with the description 'This is my IP'",
action: "REGISTER_IP",
},
},
Expand Down
8 changes: 6 additions & 2 deletions packages/plugin-story/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
export * from "./actions/registerIP";
export * from "./actions/licenseIP";
export * from "./actions/attachTerms";
export * from "./providers/wallet";
export * from "./providers/pinata";
export * from "./types";

import type { Plugin } from "@ai16z/eliza";
import { storyWalletProvider } from "./providers/wallet";
import { storyPinataProvider } from "./providers/pinata";
import { registerIPAction } from "./actions/registerIP";
import { licenseIPAction } from "./actions/licenseIP";
import { attachTermsAction } from "./actions/attachTerms";

export const storyPlugin: Plugin = {
name: "story",
description: "Story integration plugin",
providers: [storyWalletProvider],
providers: [storyWalletProvider, storyPinataProvider],
evaluators: [],
services: [],
actions: [registerIPAction, licenseIPAction],
actions: [registerIPAction, licenseIPAction, attachTermsAction],
};

export default storyPlugin;
Loading

0 comments on commit 5595de8

Please sign in to comment.