diff --git a/packages/plugin-nft-generation/src/actions/mintNFTAction.ts b/packages/plugin-nft-generation/src/actions/mintNFTAction.ts new file mode 100644 index 00000000000..61f4d70984e --- /dev/null +++ b/packages/plugin-nft-generation/src/actions/mintNFTAction.ts @@ -0,0 +1,320 @@ +import { + Action, + composeContext, + elizaLogger, + generateObject, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, +} from "@elizaos/core"; +import { createNFT } from "../handlers/createNFT.ts"; +import { verifyNFT } from "../handlers/verifyNFT.ts"; +import { sleep } from "../index.ts"; +import WalletSolana from "../provider/wallet/walletSolana.ts"; +import { PublicKey } from "@solana/web3.js"; +import { mintNFTTemplate } from "../templates.ts"; +import { MintNFTContent, MintNFTSchema } from "../types.ts"; + +function isMintNFTContent(content: any): content is MintNFTContent { + return typeof content.collectionAddress === "string"; +} + +const mintNFTAction: Action = { + name: "MINT_NFT", + similes: [ + "NFT_MINTING", + "NFT_CREATION", + "CREATE_NFT", + "GENERATE_NFT", + "MINT_TOKEN", + "CREATE_TOKEN", + "MAKE_NFT", + "TOKEN_GENERATION", + ], + description: "Mint NFTs for the collection", + validate: async (runtime: IAgentRuntime, _message: Memory) => { + const awsAccessKeyIdOk = !!runtime.getSetting("AWS_ACCESS_KEY_ID"); + const awsSecretAccessKeyOk = !!runtime.getSetting( + "AWS_SECRET_ACCESS_KEY" + ); + const awsRegionOk = !!runtime.getSetting("AWS_REGION"); + const awsS3BucketOk = !!runtime.getSetting("AWS_S3_BUCKET"); + const solanaAdminPrivateKeyOk = !!runtime.getSetting( + "SOLANA_ADMIN_PRIVATE_KEY" + ); + const solanaAdminPublicKeyOk = !!runtime.getSetting( + "SOLANA_ADMIN_PUBLIC_KEY" + ); + + return ( + awsAccessKeyIdOk || + awsSecretAccessKeyOk || + awsRegionOk || + awsS3BucketOk || + solanaAdminPrivateKeyOk || + solanaAdminPublicKeyOk + ); + }, + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + options: { [key: string]: unknown }, + callback: HandlerCallback + ) => { + try { + elizaLogger.log("Composing state for message:", message); + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose transfer context + const transferContext = composeContext({ + state, + template: mintNFTTemplate, + }); + + const res = await generateObject({ + runtime, + context: transferContext, + modelClass: ModelClass.LARGE, + schema: MintNFTSchema, + }); + const content = res.object; + + elizaLogger.log("Generate Object:", content); + + if (!isMintNFTContent(content)) { + elizaLogger.error("Invalid content for MINT_NFT action."); + if (callback) { + callback({ + text: "Unable to process mint request. Invalid content provided.", + content: { error: "Invalid mint content" }, + }); + } + return false; + } + + + const publicKey = runtime.getSetting("SOLANA_PUBLIC_KEY"); + const privateKey = runtime.getSetting("SOLANA_PRIVATE_KEY"); + + const wallet = new WalletSolana( + new PublicKey(publicKey), + privateKey + ); + + const collectionInfo = await wallet.fetchDigitalAsset( + content.collectionAddress + ); + elizaLogger.log("Collection Info", collectionInfo); + const metadata = collectionInfo.metadata; + if (metadata.collection?.["value"]) { + callback({ + text: `Unable to process mint request. Invalid collection address ${content.collectionAddress}.`, + content: { error: "Invalid collection address." }, + }); + return false; + } + if (metadata) { + const nftRes = await createNFT({ + runtime, + collectionName: metadata.name, + collectionAddress: content.collectionAddress, + collectionAdminPublicKey: metadata.updateAuthority, + collectionFee: metadata.sellerFeeBasisPoints, + tokenId: 1, + }); + + elizaLogger.log("NFT Address:", nftRes); + + if (nftRes) { + callback({ + text: `Congratulations to you! 🎉🎉🎉 \nCollection Address: ${content.collectionAddress}\n NFT Address: ${nftRes.address}\n NFT Link: ${nftRes.link}`, //caption.description, + attachments: [], + }); + await sleep(15000); + await verifyNFT({ + runtime, + collectionAddress: content.collectionAddress, + NFTAddress: nftRes.address, + }); + } else { + callback({ + text: `Mint NFT Error in ${content.collectionAddress}.`, + content: { error: "Mint NFT Error." }, + }); + return false; + } + } else { + callback({ + text: "Unable to process mint request. Invalid collection address.", + content: { error: "Invalid collection address." }, + }); + return false; + } + return []; + } catch (e: any) { + elizaLogger.log(e); + throw e; + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "mint nft for collection: D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS", + }, + }, + { + user: "{{agentName}}", + content: { + text: "I've minted a new NFT in your specified collection.", + action: "MINT_NFT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Could you create an NFT in collection D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS?", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Successfully minted your NFT in the specified collection.", + action: "MINT_NFT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Please mint a new token in D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS collection", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Your NFT has been minted in the collection successfully.", + action: "MINT_NFT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Generate NFT for D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS", + }, + }, + { + user: "{{agentName}}", + content: { + text: "I've generated and minted your NFT in the collection.", + action: "MINT_NFT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "I want to mint an NFT in collection D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Your NFT has been successfully minted in the collection.", + action: "MINT_NFT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Create a new NFT token in D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS collection", + }, + }, + { + user: "{{agentName}}", + content: { + text: "The NFT has been created in your specified collection.", + action: "MINT_NFT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Issue an NFT for collection D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS", + }, + }, + { + user: "{{agentName}}", + content: { + text: "I've issued your NFT in the requested collection.", + action: "MINT_NFT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Make a new NFT in D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Your new NFT has been minted in the collection.", + action: "MINT_NFT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Can you mint an NFT for D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS collection?", + }, + }, + { + user: "{{agentName}}", + content: { + text: "I've completed minting your NFT in the collection.", + action: "MINT_NFT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Add a new NFT to collection D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS", + }, + }, + { + user: "{{agentName}}", + content: { + text: "A new NFT has been added to your collection.", + action: "MINT_NFT", + }, + }, + ], + ], +} as Action; + +export default mintNFTAction; diff --git a/packages/plugin-nft-generation/src/actions/nftCollectionGeneration.ts b/packages/plugin-nft-generation/src/actions/nftCollectionGeneration.ts new file mode 100644 index 00000000000..19e1fc3f880 --- /dev/null +++ b/packages/plugin-nft-generation/src/actions/nftCollectionGeneration.ts @@ -0,0 +1,290 @@ +import { + Action, + elizaLogger, + HandlerCallback, + IAgentRuntime, + Memory, + State, +} from "@elizaos/core"; +import { createCollection } from "../handlers/createCollection.ts"; + +const nftCollectionGeneration: Action = { + name: "GENERATE_COLLECTION", + similes: [ + "COLLECTION_GENERATION", + "COLLECTION_GEN", + "CREATE_COLLECTION", + "MAKE_COLLECTION", + "GENERATE_COLLECTION", + ], + description: "Generate an NFT collection for the message", + validate: async (runtime: IAgentRuntime, _message: Memory) => { + const awsAccessKeyIdOk = !!runtime.getSetting("AWS_ACCESS_KEY_ID"); + const awsSecretAccessKeyOk = !!runtime.getSetting( + "AWS_SECRET_ACCESS_KEY" + ); + const awsRegionOk = !!runtime.getSetting("AWS_REGION"); + const awsS3BucketOk = !!runtime.getSetting("AWS_S3_BUCKET"); + const solanaPrivateKeyOk = !!runtime.getSetting("SOLANA_PRIVATE_KEY"); + const solanaPublicKeyOk = !!runtime.getSetting("SOLANA_PUBLIC_KEY"); + + return ( + awsAccessKeyIdOk || + awsSecretAccessKeyOk || + awsRegionOk || + awsS3BucketOk || + solanaPrivateKeyOk || + solanaPublicKeyOk + ); + }, + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + options: { [key: string]: unknown }, + callback: HandlerCallback + ) => { + try { + elizaLogger.log("Composing state for message:", message); + const collectionAddressRes = await createCollection({ + runtime, + collectionName: runtime.character.name, + }); + elizaLogger.log("Collection Info:", collectionAddressRes); + if (callback) { + callback({ + text: `Congratulations to you! 🎉🎉🎉 \nCollection Link : ${collectionAddressRes.link}\n Address: ${collectionAddressRes.address}`, //caption.description, + attachments: [], + }); + } + return []; + } catch (e: any) { + console.log(e); + throw e; + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { text: "Generate a collection" }, + }, + { + user: "{{agentName}}", + content: { + text: "Here's the collection you requested.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "Generate a collection using {{agentName}}" }, + }, + { + user: "{{agentName}}", + content: { + text: "We've successfully created a collection.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "Create a collection using {{agentName}}" }, + }, + { + user: "{{agentName}}", + content: { + text: "Here's the collection you requested.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "Build a Collection" }, + }, + { + user: "{{agentName}}", + content: { + text: "The collection has been successfully built.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "Assemble a collection with {{agentName}}" }, + }, + { + user: "{{agentName}}", + content: { + text: "The collection has been assembled", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "Make a collection" }, + }, + { + user: "{{agentName}}", + content: { + text: "The collection has been produced successfully.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Could you create a new collection for my photos?", + }, + }, + { + user: "{{agentName}}", + content: { + text: "I've created a new collection for your photos.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "I need a collection for organizing my music", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Your music collection has been generated.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Please set up a collection for my documents", + }, + }, + { + user: "{{agentName}}", + content: { + text: "I've set up a new collection for your documents.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "Start a new collection for me" }, + }, + { + user: "{{agentName}}", + content: { + text: "Your new collection has been created.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "I'd like to make a collection of my recipes", + }, + }, + { + user: "{{agentName}}", + content: { + text: "I've generated a collection for your recipes.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Can you generate a collection for my artwork?", + }, + }, + { + user: "{{agentName}}", + content: { + text: "Your artwork collection has been generated.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "Initialize a new collection please" }, + }, + { + user: "{{agentName}}", + content: { + text: "I've initialized a new collection for you.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "Create a collection for my travel memories" }, + }, + { + user: "{{agentName}}", + content: { + text: "Your travel memories collection has been created.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Would you make a collection for my projects?", + }, + }, + { + user: "{{agentName}}", + content: { + text: "I've made a collection for your projects.", + action: "GENERATE_COLLECTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { text: "Set up a collection for my bookmarks" }, + }, + { + user: "{{agentName}}", + content: { + text: "Your bookmarks collection has been set up.", + action: "GENERATE_COLLECTION", + }, + }, + ], + ], +} as Action; + +export default nftCollectionGeneration; diff --git a/packages/plugin-nft-generation/src/index.ts b/packages/plugin-nft-generation/src/index.ts index 07a147ef0d6..55f3fe0e3e2 100644 --- a/packages/plugin-nft-generation/src/index.ts +++ b/packages/plugin-nft-generation/src/index.ts @@ -1,16 +1,6 @@ -import { - Action, - elizaLogger, - HandlerCallback, - IAgentRuntime, - Memory, - Plugin, - State, -} from "@elizaos/core"; - -import { createCollection } from "./handlers/createCollection.ts"; -import { createNFT } from "./handlers/createNFT.ts"; -import { verifyNFT } from "./handlers/verifyNFT.ts"; +import { Plugin } from "@elizaos/core"; +import nftCollectionGeneration from "./actions/nftCollectionGeneration.ts"; +import mintNFTAction from "./actions/mintNFTAction.ts"; export * from "./provider/wallet/walletSolana.ts"; export * from "./api.ts"; @@ -21,181 +11,10 @@ export async function sleep(ms: number = 3000) { }); } -const nftCollectionGeneration: Action = { - name: "GENERATE_COLLECTION", - similes: [ - "COLLECTION_GENERATION", - "COLLECTION_GEN", - "CREATE_COLLECTION", - "MAKE_COLLECTION", - "GENERATE_COLLECTION", - ], - description: "Generate an NFT collection for the message", - validate: async (runtime: IAgentRuntime, _message: Memory) => { - const AwsAccessKeyIdOk = !!runtime.getSetting("AWS_ACCESS_KEY_ID"); - const AwsSecretAccessKeyOk = !!runtime.getSetting( - "AWS_SECRET_ACCESS_KEY" - ); - const AwsRegionOk = !!runtime.getSetting("AWS_REGION"); - const AwsS3BucketOk = !!runtime.getSetting("AWS_S3_BUCKET"); - - return ( - AwsAccessKeyIdOk || - AwsSecretAccessKeyOk || - AwsRegionOk || - AwsS3BucketOk - ); - }, - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - options: { [key: string]: unknown }, - callback: HandlerCallback - ) => { - try { - elizaLogger.log("Composing state for message:", message); - const userId = runtime.agentId; - elizaLogger.log("User ID:", userId); - - const collectionAddressRes = await createCollection({ - runtime, - collectionName: runtime.character.name, - }); - - const collectionInfo = collectionAddressRes.collectionInfo; - - elizaLogger.log("Collection Address:", collectionAddressRes); - - const nftRes = await createNFT({ - runtime, - collectionName: collectionInfo.name, - collectionAddress: collectionAddressRes.address, - collectionAdminPublicKey: collectionInfo.adminPublicKey, - collectionFee: collectionInfo.fee, - tokenId: 1, - }); - - elizaLogger.log("NFT Address:", nftRes); - - callback({ - text: `Congratulations to you! 🎉🎉🎉 \nCollection : ${collectionAddressRes.link}\n NFT: ${nftRes.link}`, //caption.description, - attachments: [], - }); - await sleep(15000); - await verifyNFT({ - runtime, - collectionAddress: collectionAddressRes.address, - NFTAddress: nftRes.address, - }); - return []; - } catch (e: any) { - console.log(e); - } - - // callback(); - }, - examples: [ - // TODO: We want to generate images in more abstract ways, not just when asked to generate an image - - [ - { - user: "{{user1}}", - content: { text: "Generate a collection" }, - }, - { - user: "{{agentName}}", - content: { - text: "Here's the collection you requested.", - action: "GENERATE_COLLECTION", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { text: "Generate a collection using {{agentName}}" }, - }, - { - user: "{{agentName}}", - content: { - text: "We've successfully created a collection.", - action: "GENERATE_COLLECTION", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { text: "Create a collection using {{agentName}}" }, - }, - { - user: "{{agentName}}", - content: { - text: "Here's the collection you requested.", - action: "GENERATE_COLLECTION", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { text: "Build a Collection" }, - }, - { - user: "{{agentName}}", - content: { - text: "The collection has been successfully built.", - action: "GENERATE_COLLECTION", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { text: "Assemble a collection with {{agentName}}" }, - }, - { - user: "{{agentName}}", - content: { - text: "The collection has been assembled", - action: "GENERATE_COLLECTION", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { text: "Make a collection" }, - }, - { - user: "{{agentName}}", - content: { - text: "The collection has been produced successfully.", - action: "GENERATE_COLLECTION", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { text: "Compile a collection" }, - }, - { - user: "{{agentName}}", - content: { - text: "The collection has been compiled.", - action: "GENERATE_COLLECTION", - }, - }, - ], - ], -} as Action; - export const nftGenerationPlugin: Plugin = { name: "nftCollectionGeneration", description: "Generate NFT Collections", - actions: [nftCollectionGeneration], + actions: [nftCollectionGeneration, mintNFTAction], evaluators: [], providers: [], }; diff --git a/packages/plugin-nft-generation/src/provider/wallet/walletSolana.ts b/packages/plugin-nft-generation/src/provider/wallet/walletSolana.ts index 2bfeb85ca67..74a605c9c2b 100644 --- a/packages/plugin-nft-generation/src/provider/wallet/walletSolana.ts +++ b/packages/plugin-nft-generation/src/provider/wallet/walletSolana.ts @@ -10,6 +10,7 @@ import { createNft, findMetadataPda, mplTokenMetadata, + fetchDigitalAsset, updateV1, verifyCollectionV1, } from "@metaplex-foundation/mpl-token-metadata"; @@ -55,6 +56,9 @@ export class WalletSolana { this.umi = umi; } + async fetchDigitalAsset (address: string) { + return fetchDigitalAsset(this.umi, publicKey(address)) + } async getBalance() { const balance = await this.connection.getBalance(this.walletPublicKey); return { diff --git a/packages/plugin-nft-generation/src/templates.ts b/packages/plugin-nft-generation/src/templates.ts new file mode 100644 index 00000000000..8c6f9169edb --- /dev/null +++ b/packages/plugin-nft-generation/src/templates.ts @@ -0,0 +1,18 @@ + +export const mintNFTTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. + +Example response: +\`\`\`json +{ + "collectionAddress": "D8j4ubQ3MKwmAqiJw83qT7KQNKjhsuoC7zJJdJa5BkvS", +} +\`\`\` + +{{recentMessages}} + +Given the recent messages, extract the following information about the requested mint nft: +- collection contract address + +Respond with a JSON markdown block containing only the extracted values. + +Note: Make sure to extract the collection address from the most recent messages whenever possible.`; diff --git a/packages/plugin-nft-generation/src/types.ts b/packages/plugin-nft-generation/src/types.ts new file mode 100644 index 00000000000..e00fe709d97 --- /dev/null +++ b/packages/plugin-nft-generation/src/types.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; +import { Content } from "@elizaos/core"; + + +export interface MintNFTContent extends Content { + collectionAddress: string; +} + +export const MintNFTSchema = z.object({ + collectionAddress: z.string(), +});