From b5feccdd8636da3f2313937afed952a5bc335231 Mon Sep 17 00:00:00 2001 From: jacob-tucker Date: Tue, 10 Dec 2024 23:16:45 -0500 Subject: [PATCH 1/3] add pinata and improve register action --- .env.example | 3 +- packages/plugin-story/package.json | 3 +- .../plugin-story/src/actions/registerIP.ts | 55 +++++- packages/plugin-story/src/index.ts | 4 +- packages/plugin-story/src/providers/pinata.ts | 42 ++++ packages/plugin-story/src/templates/index.ts | 14 +- packages/plugin-story/src/types/index.ts | 5 +- pnpm-lock.yaml | 180 +++++++++++++++++- 8 files changed, 287 insertions(+), 19 deletions(-) create mode 100644 packages/plugin-story/src/providers/pinata.ts diff --git a/.env.example b/.env.example index eb8af77aea2..5245d24add6 100644 --- a/.env.example +++ b/.env.example @@ -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 \ No newline at end of file +STORY_PRIVATE_KEY= # Story private key +PINATA_JWT= # Pinata JWT for uploading files to IPFS \ No newline at end of file diff --git a/packages/plugin-story/package.json b/packages/plugin-story/package.json index 153581435bd..ba2cf648f39 100644 --- a/packages/plugin-story/package.json +++ b/packages/plugin-story/package.json @@ -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", diff --git a/packages/plugin-story/src/actions/registerIP.ts b/packages/plugin-story/src/actions/registerIP.ts index ad3e75270ce..32ab602537c 100644 --- a/packages/plugin-story/src/actions/registerIP.ts +++ b/packages/plugin-story/src/actions/registerIP.ts @@ -12,21 +12,59 @@ import { 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 { 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: "0xc89775f80BA9D1c7901a490a62483282813aeE06", + 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; } } @@ -62,7 +100,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?.({ @@ -85,14 +124,14 @@ export const registerIPAction = { { user: "assistant", content: { - text: "Ill help you register an NFT with contract address 0x041B4F29183317Fd352AE57e331154b73F8a1D73 and token id 209 as IP", + text: "Ill help you register your IP titled 'My IP' with the description 'This is my 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", }, }, diff --git a/packages/plugin-story/src/index.ts b/packages/plugin-story/src/index.ts index 2a3a3209fb0..0420958d58f 100644 --- a/packages/plugin-story/src/index.ts +++ b/packages/plugin-story/src/index.ts @@ -1,17 +1,19 @@ export * from "./actions/registerIP"; export * from "./actions/licenseIP"; 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"; export const storyPlugin: Plugin = { name: "story", description: "Story integration plugin", - providers: [storyWalletProvider], + providers: [storyWalletProvider, storyPinataProvider], evaluators: [], services: [], actions: [registerIPAction, licenseIPAction], diff --git a/packages/plugin-story/src/providers/pinata.ts b/packages/plugin-story/src/providers/pinata.ts new file mode 100644 index 00000000000..c9a8850de94 --- /dev/null +++ b/packages/plugin-story/src/providers/pinata.ts @@ -0,0 +1,42 @@ +import type { IAgentRuntime, Provider, Memory, State } from "@ai16z/eliza"; +import pinataSDK from "@pinata/sdk"; + +export class PinataProvider { + private pinata; + runtime: IAgentRuntime; + + constructor(runtime: IAgentRuntime) { + const pinataJWT = runtime.getSetting("PINATA_JWT"); + if (!pinataJWT) throw new Error("PINATA_JWT not configured"); + + this.runtime = runtime; + + this.pinata = new pinataSDK({ pinataJWTKey: pinataJWT }); + } + + async uploadJSONToIPFS(jsonMetadata: any): Promise { + const { IpfsHash } = await this.pinata.pinJSONToIPFS(jsonMetadata); + return IpfsHash; + } +} + +export const storyPinataProvider: Provider = { + async get( + runtime: IAgentRuntime, + message: Memory, + state?: State + ): Promise { + // Check if the user has a pinata jwt + if (!runtime.getSetting("PINATA_JWT")) { + return null; + } + + try { + const pinataProvider = new PinataProvider(runtime); + return `Story Pinata Provider`; + } catch (error) { + console.error("Error in Story wallet provider:", error); + return null; + } + }, +}; diff --git a/packages/plugin-story/src/templates/index.ts b/packages/plugin-story/src/templates/index.ts index f3973ca5470..2118ddc573f 100644 --- a/packages/plugin-story/src/templates/index.ts +++ b/packages/plugin-story/src/templates/index.ts @@ -5,15 +5,19 @@ export const registerIPTemplate = `Given the recent messages and wallet informat {{walletInfo}} Extract the following information about the requested IP registration: -- Field "contractAddress": Contract address of the NFT (ERC 721) to register -- Field "tokenId": Token ID of the NFT (ERC 721) to register +- Field "title": The title of your IP +- Field "description": The description of your IP +- Field "ipType": The type of your IP. Type of the IP Asset, can be defined arbitrarily by the +creator. I.e. “character”, “chapter”, “location”, “items”, "music", etc. If a user doesn't provide +an ipType, you can infer it from the title and description. It should be one word. -Respond with a JSON markdown block containing only the extracted values: +Respond with a JSON markdown block containing only the extracted values. A user must explicity provide a title and description. \`\`\`json { - "contractAddress": string | null, - "tokenId": string | null + "title": string, + "description": string, + "ipType": string } \`\`\` `; diff --git a/packages/plugin-story/src/types/index.ts b/packages/plugin-story/src/types/index.ts index b3c75e0e1e6..fe55e898767 100644 --- a/packages/plugin-story/src/types/index.ts +++ b/packages/plugin-story/src/types/index.ts @@ -59,8 +59,9 @@ export interface ChainConfig { // Action parameters export interface RegisterIPParams { - contractAddress: Address; - tokenId: string; + title: string; + description: string; + ipType: string; } export interface LicenseIPParams { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f662c56cd2d..e82d4f7df4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1307,6 +1307,9 @@ importers: '@ai16z/plugin-trustdb': specifier: workspace:* version: link:../plugin-trustdb + '@pinata/sdk': + specifier: ^2.1.0 + version: 2.1.0 '@story-protocol/core-sdk': specifier: 1.2.0-rc.3 version: 1.2.0-rc.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) @@ -4911,6 +4914,10 @@ packages: resolution: {integrity: sha512-slh2ltTrq2cgMztapvTFFRHtO4Ilx3NMhXYh3+ypSO2T2ANWgoqEzEoTT69OctyzMOTgYcyfla7uH8A3cLPaTQ==} engines: {node: '>=18.0.0'} + '@pinata/sdk@2.1.0': + resolution: {integrity: sha512-hkS0tcKtsjf9xhsEBs2Nbey5s+Db7x5rlOH9TaWHBXkJ7IwwOs2xnEDigNaxAHKjYAwcw+m2hzpO5QgOfeF7Zw==} + deprecated: Please install the new IPFS SDK at pinata-web3. More information at https://docs.pinata.cloud/web3/sdk + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -7409,6 +7416,9 @@ packages: peerDependencies: axios: 0.x || 1.x + axios@0.21.4: + resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} + axios@0.27.2: resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} @@ -7945,6 +7955,16 @@ packages: resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} engines: {node: '>=8'} + cids@0.7.5: + resolution: {integrity: sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==} + engines: {node: '>=4.0.0', npm: '>=3.0.0'} + deprecated: This module has been superseded by the multiformats module + + cids@0.8.3: + resolution: {integrity: sha512-yoXTbV3llpm+EBGWKeL9xKtksPE/s6DPoDSY4fn8I8TEW1zehWXPSB0pwAXVDlLaOlrw+sNynj995uD9abmPhA==} + engines: {node: '>=4.0.0', npm: '>=3.0.0'} + deprecated: This module has been superseded by the multiformats module + cipher-base@1.0.6: resolution: {integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==} engines: {node: '>= 0.10'} @@ -7958,6 +7978,9 @@ packages: cjs-module-lexer@1.4.1: resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + class-is@1.1.0: + resolution: {integrity: sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==} + class-transformer@0.5.1: resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} @@ -10601,6 +10624,10 @@ packages: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} + ip-regex@4.3.0: + resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} + engines: {node: '>=8'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -10713,6 +10740,13 @@ packages: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} + is-ip@3.1.0: + resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} + engines: {node: '>=8'} + + is-ipfs@0.6.3: + resolution: {integrity: sha512-HyRot1dvLcxImtDqPxAaY1miO6WsiP/z7Yxpg2qpaLWv5UdhAPtLvHJ4kMLM0w8GSl8AFsVF23PHe1LzuWrUlQ==} + is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} @@ -11644,6 +11678,9 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + mafmt@7.1.0: + resolution: {integrity: sha512-vpeo9S+hepT3k2h5iFxzEHvvR0GPBx9uKaErmnRzYNcaKb03DgOArjEMlgG4a9LcuZZ89a3I8xbeto487n26eA==} + magic-bytes.js@1.10.0: resolution: {integrity: sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==} @@ -12193,13 +12230,41 @@ packages: multi-integer-range@3.0.0: resolution: {integrity: sha512-uQzynjVJ8F7x5wjaK0g4Ybhy2TvO/pk96+YHyS5g1W4GuUEV6HMebZ8HcRwWgKIRCUT2MLbM5uCKwYcAqkS+8Q==} + multiaddr@7.5.0: + resolution: {integrity: sha512-GvhHsIGDULh06jyb6ev+VfREH9evJCFIRnh3jUt9iEZ6XDbyoisZRFEI9bMvK/AiR6y66y6P+eoBw9mBYMhMvw==} + deprecated: This module is deprecated, please upgrade to @multiformats/multiaddr + + multibase@0.6.1: + resolution: {integrity: sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==} + deprecated: This module has been superseded by the multiformats module + + multibase@0.7.0: + resolution: {integrity: sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==} + deprecated: This module has been superseded by the multiformats module + + multibase@1.0.1: + resolution: {integrity: sha512-KcCxpBVY8fdVKu4dJMAahq4F/2Z/9xqEjIiR7PiMe7LRGeorFn2NLmicN6nLBCqQvft6MG2Lc9X5P0IdyvnxEw==} + engines: {node: '>=10.0.0', npm: '>=6.0.0'} + deprecated: This module has been superseded by the multiformats module + multicast-dns@7.2.5: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true + multicodec@1.0.4: + resolution: {integrity: sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==} + deprecated: This module has been superseded by the multiformats module + multiformats@9.9.0: resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} + multihashes@0.4.21: + resolution: {integrity: sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==} + + multihashes@1.0.1: + resolution: {integrity: sha512-S27Tepg4i8atNiFaU5ZOm3+gl3KQlUanLs/jWcBxQHFttgq+5x1OgbQmf2d8axJ/48zYGBd/wT9d723USMFduw==} + engines: {node: '>=10.0.0', npm: '>=6.0.0'} + multimatch@5.0.0: resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} engines: {node: '>=10'} @@ -12924,6 +12989,9 @@ packages: resolution: {integrity: sha512-+vnG6S4dYcYxZd+CZxzXCNKdELYZSKfohrk98yajCo1PtRoDgCTrrwOvK1GT0UoAdVszagDVllQc0U1vaX4NUQ==} engines: {node: '>=6'} + path@0.12.7: + resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -16044,6 +16112,9 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + util@0.10.4: + resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==} + utila@0.4.0: resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} @@ -16113,6 +16184,9 @@ packages: value-equal@1.0.1: resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==} + varint@5.0.2: + resolution: {integrity: sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==} + varuint-bitcoin@2.0.0: resolution: {integrity: sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==} @@ -21950,6 +22024,15 @@ snapshots: - utf-8-validate - zod + '@pinata/sdk@2.1.0': + dependencies: + axios: 0.21.4 + form-data: 2.3.3 + is-ipfs: 0.6.3 + path: 0.12.7 + transitivePeerDependencies: + - debug + '@pkgjs/parseargs@0.11.0': optional: true @@ -25103,6 +25186,12 @@ snapshots: axios: 1.7.8(debug@4.4.0) is-retry-allowed: 2.2.0 + axios@0.21.4: + dependencies: + follow-redirects: 1.15.9(debug@4.4.0) + transitivePeerDependencies: + - debug + axios@0.27.2: dependencies: follow-redirects: 1.15.9(debug@4.4.0) @@ -25793,6 +25882,22 @@ snapshots: ci-info@4.1.0: {} + cids@0.7.5: + dependencies: + buffer: 5.7.1 + class-is: 1.1.0 + multibase: 0.6.1 + multicodec: 1.0.4 + multihashes: 0.4.21 + + cids@0.8.3: + dependencies: + buffer: 5.7.1 + class-is: 1.1.0 + multibase: 1.0.1 + multicodec: 1.0.4 + multihashes: 1.0.1 + cipher-base@1.0.6: dependencies: inherits: 2.0.4 @@ -25817,6 +25922,8 @@ snapshots: cjs-module-lexer@1.4.1: {} + class-is@1.1.0: {} + class-transformer@0.5.1: {} class-variance-authority@0.7.1: @@ -27783,7 +27890,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.4 + debug: 4.4.0(supports-color@5.5.0) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -29049,6 +29156,8 @@ snapshots: jsbn: 1.1.0 sprintf-js: 1.1.3 + ip-regex@4.3.0: {} + ipaddr.js@1.9.1: {} ipaddr.js@2.2.0: {} @@ -29149,6 +29258,19 @@ snapshots: is-interactive@2.0.0: {} + is-ip@3.1.0: + dependencies: + ip-regex: 4.3.0 + + is-ipfs@0.6.3: + dependencies: + bs58: 4.0.1 + cids: 0.7.5 + mafmt: 7.1.0 + multiaddr: 7.5.0 + multibase: 0.6.1 + multihashes: 0.4.21 + is-lambda@1.0.1: {} is-module@1.0.0: {} @@ -30434,6 +30556,10 @@ snapshots: lunr@2.3.9: {} + mafmt@7.1.0: + dependencies: + multiaddr: 7.5.0 + magic-bytes.js@1.10.0: {} magic-string@0.30.15: @@ -31331,13 +31457,54 @@ snapshots: multi-integer-range@3.0.0: {} + multiaddr@7.5.0: + dependencies: + buffer: 5.7.1 + cids: 0.8.3 + class-is: 1.1.0 + is-ip: 3.1.0 + multibase: 0.7.0 + varint: 5.0.2 + + multibase@0.6.1: + dependencies: + base-x: 3.0.10 + buffer: 5.7.1 + + multibase@0.7.0: + dependencies: + base-x: 3.0.10 + buffer: 5.7.1 + + multibase@1.0.1: + dependencies: + base-x: 3.0.10 + buffer: 5.7.1 + multicast-dns@7.2.5: dependencies: dns-packet: 5.6.1 thunky: 1.1.0 + multicodec@1.0.4: + dependencies: + buffer: 5.7.1 + varint: 5.0.2 + multiformats@9.9.0: {} + multihashes@0.4.21: + dependencies: + buffer: 5.7.1 + multibase: 0.7.0 + varint: 5.0.2 + + multihashes@1.0.1: + dependencies: + buffer: 5.7.1 + multibase: 1.0.1 + varint: 5.0.2 + multimatch@5.0.0: dependencies: '@types/minimatch': 3.0.5 @@ -32219,6 +32386,11 @@ snapshots: path2d@0.2.2: optional: true + path@0.12.7: + dependencies: + process: 0.11.10 + util: 0.10.4 + pathe@1.1.2: {} pathval@2.0.0: {} @@ -35730,6 +35902,10 @@ snapshots: util-deprecate@1.0.2: {} + util@0.10.4: + dependencies: + inherits: 2.0.3 + utila@0.4.0: {} utility-types@3.11.0: {} @@ -35775,6 +35951,8 @@ snapshots: value-equal@1.0.1: {} + varint@5.0.2: {} + varuint-bitcoin@2.0.0: dependencies: uint8array-tools: 0.0.8 From 104f20642a4fd37da53fca11f0f9f7ae3f67acc9 Mon Sep 17 00:00:00 2001 From: jacob-tucker Date: Wed, 11 Dec 2024 00:27:27 -0500 Subject: [PATCH 2/3] add attachTerms --- .../plugin-story/src/actions/attachTerms.ts | 153 ++++++++++++++++++ .../plugin-story/src/actions/licenseIP.ts | 2 +- packages/plugin-story/src/index.ts | 4 +- packages/plugin-story/src/templates/index.ts | 34 +++- packages/plugin-story/src/types/index.ts | 7 + 5 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 packages/plugin-story/src/actions/attachTerms.ts diff --git a/packages/plugin-story/src/actions/attachTerms.ts b/packages/plugin-story/src/actions/attachTerms.ts new file mode 100644 index 00000000000..547ac4f2bdd --- /dev/null +++ b/packages/plugin-story/src/actions/attachTerms.ts @@ -0,0 +1,153 @@ +import { + composeContext, + elizaLogger, + generateObjectDEPRECATED, + HandlerCallback, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@ai16z/eliza"; +import { 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 => { + 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 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: "assistant", + content: { + text: "Ill help you attach commercial, 10% rev share license terms to IP Asset 0x2265F2b8e47F98b3Bdf7a1937EAc27282954A4Db", + action: "ATTACH_TERMS", + }, + }, + { + 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"], +}; diff --git a/packages/plugin-story/src/actions/licenseIP.ts b/packages/plugin-story/src/actions/licenseIP.ts index 370171d3f6e..cfa3aaf3b92 100644 --- a/packages/plugin-story/src/actions/licenseIP.ts +++ b/packages/plugin-story/src/actions/licenseIP.ts @@ -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 }, }); diff --git a/packages/plugin-story/src/index.ts b/packages/plugin-story/src/index.ts index 0420958d58f..81d3b0a1432 100644 --- a/packages/plugin-story/src/index.ts +++ b/packages/plugin-story/src/index.ts @@ -1,5 +1,6 @@ export * from "./actions/registerIP"; export * from "./actions/licenseIP"; +export * from "./actions/attachTerms"; export * from "./providers/wallet"; export * from "./providers/pinata"; export * from "./types"; @@ -9,6 +10,7 @@ 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", @@ -16,7 +18,7 @@ export const storyPlugin: Plugin = { providers: [storyWalletProvider, storyPinataProvider], evaluators: [], services: [], - actions: [registerIPAction, licenseIPAction], + actions: [registerIPAction, licenseIPAction, attachTermsAction], }; export default storyPlugin; diff --git a/packages/plugin-story/src/templates/index.ts b/packages/plugin-story/src/templates/index.ts index 2118ddc573f..d38084098a4 100644 --- a/packages/plugin-story/src/templates/index.ts +++ b/packages/plugin-story/src/templates/index.ts @@ -11,7 +11,7 @@ Extract the following information about the requested IP registration: creator. I.e. “character”, “chapter”, “location”, “items”, "music", etc. If a user doesn't provide an ipType, you can infer it from the title and description. It should be one word. -Respond with a JSON markdown block containing only the extracted values. A user must explicity provide a title and description. +Respond with a JSON markdown block containing only the extracted values. A user must explicitly provide a title and description. \`\`\`json { @@ -33,13 +33,39 @@ Extract the following information about the requested IP licensing: - Field "licenseTermsId": The license terms that you want to mint a license for - Field "amount": The amount of licenses to mint -Respond with a JSON markdown block containing only the extracted values: +Respond with a JSON markdown block containing only the extracted values. A user must explicitly provide a licensorIpId and licenseTermsId. \`\`\`json { - "licensorIpId": string | null, - "licenseTermsId": string | null, + "licensorIpId": string, + "licenseTermsId": string, "amount": number | null } \`\`\` `; + +export const attachTermsTemplate = `Given the recent messages and wallet information below: + +{{recentMessages}} + +{{walletInfo}} + +Extract the following information about attaching license terms to an IP Asset: +- Field "ipId": The IP Asset that you want to attach the license terms to +- Field "mintingFee": The fee to mint this license from the IP Asset. +- Field "commercialUse": Whether or not the IP Asset can be used commercially. +- Field "commercialRevShare": The percentage of revenue that the IP Asset owner will receive +from commercial use of the IP Asset. This must be between 0 and 100. If a user specifies +a commercialRevShare, then commercialUse must be set to true. + +Respond with a JSON markdown block containing only the extracted values. A user must provide an ipId. + +\`\`\`json +{ + "ipId": string, + "mintingFee": number | null, + "commercialUse": boolean | null, + "commercialRevShare": number | null +} +\`\`\` +`; diff --git a/packages/plugin-story/src/types/index.ts b/packages/plugin-story/src/types/index.ts index fe55e898767..722985a1cc6 100644 --- a/packages/plugin-story/src/types/index.ts +++ b/packages/plugin-story/src/types/index.ts @@ -70,6 +70,13 @@ export interface LicenseIPParams { amount: number; } +export interface AttachTermsParams { + ipId: Address; + mintingFee: number; + commercialUse: boolean; + commercialRevShare: number; +} + // Plugin configuration export interface EvmPluginConfig { rpcUrl?: { From dcd9428fbc273c83fcf64afd1d8fb7ed5205af15 Mon Sep 17 00:00:00 2001 From: jacob-tucker Date: Wed, 11 Dec 2024 11:07:42 -0500 Subject: [PATCH 3/3] minor changes --- .../plugin-story/src/actions/attachTerms.ts | 16 ++++++++-------- packages/plugin-story/src/actions/licenseIP.ts | 16 ++++++++-------- .../plugin-story/src/actions/registerIP.ts | 18 +++++++++--------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/plugin-story/src/actions/attachTerms.ts b/packages/plugin-story/src/actions/attachTerms.ts index 547ac4f2bdd..41e39c5c647 100644 --- a/packages/plugin-story/src/actions/attachTerms.ts +++ b/packages/plugin-story/src/actions/attachTerms.ts @@ -8,7 +8,7 @@ import { type Memory, type State, } from "@ai16z/eliza"; -import { WalletProvider } from "../providers/wallet"; +import { storyWalletProvider, WalletProvider } from "../providers/wallet"; import { attachTermsTemplate } from "../templates"; import { AttachLicenseTermsResponse, @@ -93,6 +93,13 @@ export const attachTermsAction = { state = await runtime.updateRecentMessageState(state); } + const walletInfo = await storyWalletProvider.get( + runtime, + message, + state + ); + state.walletInfo = walletInfo; + const attachTermsContext = composeContext({ state, template: attachTermsTemplate, @@ -133,13 +140,6 @@ export const attachTermsAction = { }, examples: [ [ - { - user: "assistant", - content: { - text: "Ill help you attach commercial, 10% rev share license terms to IP Asset 0x2265F2b8e47F98b3Bdf7a1937EAc27282954A4Db", - action: "ATTACH_TERMS", - }, - }, { user: "user", content: { diff --git a/packages/plugin-story/src/actions/licenseIP.ts b/packages/plugin-story/src/actions/licenseIP.ts index cfa3aaf3b92..17922a24d60 100644 --- a/packages/plugin-story/src/actions/licenseIP.ts +++ b/packages/plugin-story/src/actions/licenseIP.ts @@ -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"; @@ -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, @@ -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: { diff --git a/packages/plugin-story/src/actions/registerIP.ts b/packages/plugin-story/src/actions/registerIP.ts index 32ab602537c..8907bddf66d 100644 --- a/packages/plugin-story/src/actions/registerIP.ts +++ b/packages/plugin-story/src/actions/registerIP.ts @@ -8,7 +8,7 @@ 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"; @@ -54,7 +54,7 @@ export class RegisterIPAction { // register ip const response = await storyClient.ipAsset.mintAndRegisterIpAssetWithPilTerms({ - spgNftContract: "0xc89775f80BA9D1c7901a490a62483282813aeE06", + spgNftContract: "0xC81B2cbEFD1aA0227bf513729580d3CF40fd61dF", terms: [], ipMetadata: { ipMetadataURI: `https://ipfs.io/ipfs/${ipIpfsHash}`, @@ -88,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, @@ -121,13 +128,6 @@ export const registerIPAction = { }, examples: [ [ - { - user: "assistant", - content: { - text: "Ill help you register your IP titled 'My IP' with the description 'This is my IP'", - action: "REGISTER_IP", - }, - }, { user: "user", content: {