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

feat: metaplex bubblegum plugin #1532

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from
Open
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 .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ SOLANA_ADMIN_PRIVATE_KEY= # This wallet is used to verify NFTs
SOLANA_ADMIN_PUBLIC_KEY= # This wallet is used to verify NFTs
SOLANA_VERIFY_TOKEN= # Authentication token for calling the verification API

# Metaplex Bubblegum
MPL_BUBBLEGUM_PRIVATE_KEY= # Metaplex Bubblegum private key
MPL_BUBBLEGUM_RPC_URL= # Metaplex Bubblegum RPC URL (Needs support for DAS API)

# Fallback Wallet Configuration (deprecated)
WALLET_PRIVATE_KEY=
WALLET_PUBLIC_KEY=
Expand Down
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@elizaos/plugin-ton": "workspace:*",
"@elizaos/plugin-sui": "workspace:*",
"@elizaos/plugin-tee": "workspace:*",
"@elizaos/plugin-mpl-bubblegum": "workspace:*",
"@elizaos/plugin-multiversx": "workspace:*",
"@elizaos/plugin-near": "workspace:*",
"@elizaos/plugin-zksync-era": "workspace:*",
Expand Down
5 changes: 5 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { nearPlugin } from "@elizaos/plugin-near";
import { nftGenerationPlugin } from "@elizaos/plugin-nft-generation";
import { createNodePlugin } from "@elizaos/plugin-node";
import { solanaPlugin } from "@elizaos/plugin-solana";
import { mplBubblegumPlugin } from "@elizaos/plugin-mpl-bubblegum";
import { suiPlugin } from "@elizaos/plugin-sui";
import { TEEMode, teePlugin } from "@elizaos/plugin-tee";
import { tonPlugin } from "@elizaos/plugin-ton";
Expand Down Expand Up @@ -530,6 +531,10 @@ export async function createAgent(
!getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x"))
? solanaPlugin
: null,
getSecret(character, "MPL_BUBBLEGUM_PRIVATE_KEY") &&
getSecret(character, "MPL_BUBBLEGUM_RPC_URL")
? mplBubblegumPlugin
: null,
(getSecret(character, "NEAR_ADDRESS") ||
getSecret(character, "NEAR_WALLET_PUBLIC_KEY")) &&
getSecret(character, "NEAR_WALLET_SECRET_KEY")
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-mpl-bubblegum/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*

!dist/**
!package.json
!readme.md
!tsup.config.ts
3 changes: 3 additions & 0 deletions packages/plugin-mpl-bubblegum/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import eslintGlobalConfig from "../../eslint.config.mjs";

export default [...eslintGlobalConfig];
27 changes: 27 additions & 0 deletions packages/plugin-mpl-bubblegum/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@elizaos/plugin-mpl-bubblegum",
"version": "0.1.5-alpha.5",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"@elizaos/plugin-tee": "workspace:*",
"@metaplex-foundation/digital-asset-standard-api": "1.0.4",
"@metaplex-foundation/mpl-bubblegum": "4.2.1",
"@metaplex-foundation/umi-bundle-defaults": "0.9.2",
"@metaplex-foundation/umi-web3js-adapters": "0.9.2",
"@metaplex-foundation/umi": "0.9.2",
"@solana/web3.js": "1.95.8"
},
"devDependencies": {
"@types/node": "20.0.0",
"tsup": "8.3.5"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint --fix --cache .",
"test": "vitest run"
}
}
234 changes: 234 additions & 0 deletions packages/plugin-mpl-bubblegum/src/actions/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import {
Action,
ActionExample,
composeContext,
Content,
elizaLogger,
generateObject,
generateObjectDeprecated,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
State,
} from "@elizaos/core";
import { PublicKey } from "@metaplex-foundation/umi";
import { MplBubblegumProvider } from "../providers/bubblegumProvider";
import { DasApiAsset } from "@metaplex-foundation/digital-asset-standard-api";

export interface TransferContent extends Content {
assetId: string;
}

function isFetchContent(
_runtime: IAgentRuntime,
content: any
): content is TransferContent {
console.log("Content for cNFT fetch", content);
return typeof content.assetId === "string";
}

const fetchTemplate = `Respond with a JSON markdown block containing only the extracted values. Ensure that all extracted values, especially public key addresses, are complete and include every character as they appear in the messages. Use null for any values that cannot be determined.

Example response:
\`\`\`json
{
"assetId": "54dQ8cfHsW1YfKYpmdVZhWpb9iSi6Pac82Nf7sg3bVb",
}
\`\`\`

{{recentMessages}}

Given the recent messages, extract the following information for fetching:
- Asset Id

Respond with a JSON markdown block containing only the extracted values.`;

export default {
name: "FETCH_COMPRESSED_NFT",
similes: ["FETCH_CNFT", "SEARCH_COMPRESSED_NFT", "SEARCH_CNFT"],
validate: async (runtime: IAgentRuntime, message: Memory) => {
return true;
},
description: "Fetch details of a compressed NFT (cNFT)",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
) => {
elizaLogger.log("Starting FETCH_COMPRESSED_NFT handler...");

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

const fetchContext = composeContext({
state,
template: fetchTemplate,
});

const content = await generateObjectDeprecated({
runtime,
context: fetchContext,
modelClass: ModelClass.LARGE,
});

if (!isFetchContent(runtime, content)) {
console.error("Invalid content for FETCH_COMPRESSED_NFT action");
if (callback) {
callback({
text: "Unable to process transfer request. Invalid content provided.",
content: { error: "Invalid transfer content" },
});
}
return false;
}
try {
const RPC_URL = runtime.getSetting("MPL_BUBBLEGUM_RPC_URL");

const mplBubblegumProvider = new MplBubblegumProvider(RPC_URL);

const cNFT: DasApiAsset = await mplBubblegumProvider.getAsset(
content.assetId as PublicKey
);

console.log("Fetch successful: ", content.assetId);

if (callback) {
callback({
text: `
Here are the details for ${content.assetId}\n\n
- Name: ${cNFT.content.metadata.name}\n\n
- Description: ${cNFT.content.metadata.description}\n\n
- Creator: ${cNFT.creators.map((creator) => creator.address).join(", ")}\n\n
- Uri ${cNFT.content.json_uri}\n\n
- Royalties: ${cNFT.royalty.percent * 100} %\n\n
- Collection: ${cNFT.grouping[0].group_value} \n\n
`,
content: {
asset: cNFT,
},
});
}

return true;
} catch (error) {
console.error("Error in transfer", error);
if (callback) {
callback({
text: `Error transferring tokens: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
},
examples: [
[
{
user: "{{user1}}",
content: {
text: "Get details for 2q5tF7QjzY2RKtjrizS32kPgYKSP2xjSTe2wxPZXY2JH",
},
},
{
user: "{{user2}}",
content: {
text: "Getting details for 2q5tF7QjzY2RKtjrizS32kPgYKSP2xjSTe2wxPZXY2JH now...",
action: "FETCH_COMPRESSED_NFT",
},
},
{
user: "{{user2}}",
content: {
text: `Here are the details for 2q5tF7QjzY2RKtjrizS32kPgYKSP2xjSTe2wxPZXY2JH\n
- Name: Cool NFT\n - Description: A very cool NFT\n
- Creator: 5QrQzQk5nnTJbBhdnwwwmQr94AQUkMEaZkDbU5HsvKMY\n
- Uri 'https://ipfs.io/ipfs/Qmb5yyc22CBZKh2XwU3oNbSyVkMWLxxgoMaMYLmD7EkAe4'\n
- Creators:\n
'address': '5QrQzQk5nnTJbBhdnwwwmQr94AQUkMEaZkDbU5HsvKMY',\n
'share': 50,\n
'verified': false,\n
'address': '2HAjX3wYpfRfcBNk36mWjzXWFKUiKjB5Lj5H2rEFdAG8',\n
'share': 50,\n
'verified': true \n
- Royalties: 2%
- Collection: AXvaYiSwE7XKdiM4eSWTfagkswmWKVF7KzwW5EpjCDGk\n
`,
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Fetch the details of cNFT 7g1tX9HkjLyZRftoc4vA7KkJ2nM9YXotRkTyUj9Np5DL",
},
},
{
user: "{{user2}}",
content: {
text: "Getting details for 7g1tX9HkjLyZRftoc4vA7KkJ2nM9YXotRkTyUj9Np5DL now...",
action: "FETCH_COMPRESSED_NFT",
},
},
{
user: "{{user2}}",
content: {
text: `Here are the details for 7g1tX9HkjLyZRftoc4vA7KkJ2nM9YXotRkTyUj9Np5DL\n
- Name: Cool NFT\n - Description: A very cool NFT\n
- Creator: 2HAjX3wYpfRfcBNk36mWjzXWFKUiKjB5Lj5H2rEFdAG8\n
- Uri 'https://ipfs.io/ipfs/Qmb5yyc22CBZKh2XwU3oNbSyVkMWLxxgoMaMYLmD7EkAe4'\n
- Creators:\n
'address': '5QrQzQk5nnTJbBhdnwwwmQr94AQUkMEaZkDbU5HsvKMY',\n
'share': 50,\n
'verified': false,\n
'address': '2HAjX3wYpfRfcBNk36mWjzXWFKUiKjB5Lj5H2rEFdAG8',\n
'share': 50,\n
'verified': true \n
- Royalties: 2%
- Collection: AXvaYiSwE7XKdiM4eSWTfagkswmWKVF7KzwW5EpjCDGk\n
`,
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Retrieve information for the compressed NFT 9h3rQ8PkZLYzLQkfDj8oJ9LnFQW7GYmRtMJkXN5QP9DJ",
},
},
{
user: "{{user2}}",
content: {
text: "Getting details for 9h3rQ8PkZLYzLQkfDj8oJ9LnFQW7GYmRtMJkXN5QP9DJ now...",
action: "FETCH_COMPRESSED_NFT",
},
},
{
user: "{{user2}}",
content: {
text: `Here are the details for 9h3rQ8PkZLYzLQkfDj8oJ9LnFQW7GYmRtMJkXN5QP9DJ\n
- Name: Cool NFT\n - Description: A very cool NFT\n
- Creator: 2HAjX3wYpfRfcBNk36mWjzXWFKUiKjB5Lj5H2rEFdAG8\n
- Uri 'https://ipfs.io/ipfs/Qmb5yyc22CBZKh2XwU3oNbSyVkMWLxxgoMaMYLmD7EkAe4'\n
- Creators:\n
'address': '5QrQzQk5nnTJbBhdnwwwmQr94AQUkMEaZkDbU5HsvKMY',\n
'share': 50,\n
'verified': false,\n
'address': '2HAjX3wYpfRfcBNk36mWjzXWFKUiKjB5Lj5H2rEFdAG8',\n
'share': 50,\n
'verified': true \n
- Royalties: 2%
- Collection: AXvaYiSwE7XKdiM4eSWTfagkswmWKVF7KzwW5EpjCDGk\n
`,
},
},
],
] as ActionExample[][],
} as Action;
Loading
Loading