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 #1386

Open
wants to merge 11 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 @@ -169,6 +169,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
121 changes: 61 additions & 60 deletions agent/package.json
Original file line number Diff line number Diff line change
@@ -1,62 +1,63 @@
{
"name": "@elizaos/agent",
"version": "0.1.7-alpha.1",
"main": "src/index.ts",
"type": "module",
"scripts": {
"start": "node --loader ts-node/esm src/index.ts",
"dev": "node --loader ts-node/esm src/index.ts",
"check-types": "tsc --noEmit"
},
"nodemonConfig": {
"watch": [
"src",
"../core/dist"
],
"ext": "ts,json",
"exec": "node --enable-source-maps --loader ts-node/esm src/index.ts"
},
"dependencies": {
"@elizaos/adapter-postgres": "workspace:*",
"@elizaos/adapter-redis": "workspace:*",
"@elizaos/adapter-sqlite": "workspace:*",
"@elizaos/client-auto": "workspace:*",
"@elizaos/client-direct": "workspace:*",
"@elizaos/client-discord": "workspace:*",
"@elizaos/client-farcaster": "workspace:*",
"@elizaos/client-lens": "workspace:*",
"@elizaos/client-telegram": "workspace:*",
"@elizaos/client-twitter": "workspace:*",
"@elizaos/client-slack": "workspace:*",
"@elizaos/core": "workspace:*",
"@elizaos/plugin-0g": "workspace:*",
"@elizaos/plugin-aptos": "workspace:*",
"@elizaos/plugin-bootstrap": "workspace:*",
"@elizaos/plugin-intiface": "workspace:*",
"@elizaos/plugin-coinbase": "workspace:*",
"@elizaos/plugin-conflux": "workspace:*",
"@elizaos/plugin-evm": "workspace:*",
"@elizaos/plugin-flow": "workspace:*",
"@elizaos/plugin-story": "workspace:*",
"@elizaos/plugin-goat": "workspace:*",
"@elizaos/plugin-icp": "workspace:*",
"@elizaos/plugin-image-generation": "workspace:*",
"@elizaos/plugin-nft-generation": "workspace:*",
"@elizaos/plugin-node": "workspace:*",
"@elizaos/plugin-solana": "workspace:*",
"@elizaos/plugin-starknet": "workspace:*",
"@elizaos/plugin-ton": "workspace:*",
"@elizaos/plugin-sui": "workspace:*",
"@elizaos/plugin-tee": "workspace:*",
"@elizaos/plugin-multiversx": "workspace:*",
"@elizaos/plugin-near": "workspace:*",
"@elizaos/plugin-zksync-era": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
},
"devDependencies": {
"ts-node": "10.9.2",
"tsup": "8.3.5"
}
"name": "@elizaos/agent",
"version": "0.1.7-alpha.1",
"main": "src/index.ts",
"type": "module",
"scripts": {
"start": "node --loader ts-node/esm src/index.ts",
"dev": "node --loader ts-node/esm src/index.ts",
"check-types": "tsc --noEmit"
},
"nodemonConfig": {
"watch": [
"src",
"../core/dist"
],
"ext": "ts,json",
"exec": "node --enable-source-maps --loader ts-node/esm src/index.ts"
},
"dependencies": {
"@elizaos/adapter-postgres": "workspace:*",
"@elizaos/adapter-redis": "workspace:*",
"@elizaos/adapter-sqlite": "workspace:*",
"@elizaos/client-auto": "workspace:*",
"@elizaos/client-direct": "workspace:*",
"@elizaos/client-discord": "workspace:*",
"@elizaos/client-farcaster": "workspace:*",
"@elizaos/client-lens": "workspace:*",
"@elizaos/client-telegram": "workspace:*",
"@elizaos/client-twitter": "workspace:*",
"@elizaos/client-slack": "workspace:*",
"@elizaos/core": "workspace:*",
"@elizaos/plugin-0g": "workspace:*",
"@elizaos/plugin-aptos": "workspace:*",
"@elizaos/plugin-bootstrap": "workspace:*",
"@elizaos/plugin-intiface": "workspace:*",
"@elizaos/plugin-coinbase": "workspace:*",
"@elizaos/plugin-conflux": "workspace:*",
"@elizaos/plugin-evm": "workspace:*",
"@elizaos/plugin-flow": "workspace:*",
"@elizaos/plugin-story": "workspace:*",
"@elizaos/plugin-goat": "workspace:*",
"@elizaos/plugin-icp": "workspace:*",
"@elizaos/plugin-image-generation": "workspace:*",
"@elizaos/plugin-nft-generation": "workspace:*",
"@elizaos/plugin-node": "workspace:*",
"@elizaos/plugin-solana": "workspace:*",
"@elizaos/plugin-mpl-bubblegum": "workspace:*",
"@elizaos/plugin-starknet": "workspace:*",
"@elizaos/plugin-ton": "workspace:*",
"@elizaos/plugin-sui": "workspace:*",
"@elizaos/plugin-tee": "workspace:*",
"@elizaos/plugin-multiversx": "workspace:*",
"@elizaos/plugin-near": "workspace:*",
"@elizaos/plugin-zksync-era": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
},
"devDependencies": {
"ts-node": "10.9.2",
"tsup": "8.3.5"
}
}
5 changes: 5 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,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 @@ -513,6 +514,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"
}
}
165 changes: 165 additions & 0 deletions packages/plugin-mpl-bubblegum/src/actions/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import {
ActionExample,
composeContext,
Content,
elizaLogger,
generateObject,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
State,
} from "@elizaos/core";
import { validateMplBubblegumConfig } from "../environment";
import { getWalletKey } from "../utils";
import { MplBubblegumProvider } from "../providers/bubblegumProvider";
import { fromWeb3JsKeypair } from "@metaplex-foundation/umi-web3js-adapters";
import { PublicKey } from "@metaplex-foundation/umi";

export interface TransferContent extends Content {
assetId: string;
newLeafOwner: string;
}

function isTransferContent(
_runtime: IAgentRuntime,
content: any
): content is TransferContent {
console.log("Content for transfer", content);
return (
typeof content.assetId === "string" &&
typeof content.newLeafOwner === "string"
);
}

const transferTemplate = `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",
"newLeafOwner": "G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY"
}
\`\`\`

{{recentMessages}}

Given the recent messages, extract the following information about the requested token transfer:
- Asset Id
- New Leaf Owner (new owner public key)

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

export default {
name: "SEND_COMPRESSED_NFT",
similes: ["TRANSFER_COMPRESSED_NFT", "SEND_CNFT", "TRANSFER_CNFT"],
validate: async (runtime: IAgentRuntime, message: Memory) => {
console.log("Validating config for user:", message.userId);
await validateMplBubblegumConfig(runtime);
return true;
},
description:
"Transfer compressed NFT (cNFT) from the agent wallet to another address",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
) => {
elizaLogger.log("Starting SEND_COMPRESSED_NFT handler...");

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

const transferContext = composeContext({
state,
template: transferTemplate,
});

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

if (!isTransferContent(runtime, content)) {
console.error("Invalid content for SEND_COMPRESSED_NFT action.");
if (callback) {
callback({
text: "Unable to process transfer request. Invalid content provided.",
content: { error: "Invalid transfer content" },
});
}
return false;
}

try {
const { keypair: agentKeypair } = await getWalletKey(runtime);

const RPC_URL = runtime.getSetting("MPL_BUBBLEGUM_RPC_URL");

const agentKeypairAdapter = fromWeb3JsKeypair(agentKeypair);

const mplBubblegumProvider = new MplBubblegumProvider(
RPC_URL,
agentKeypairAdapter
);

const { signature, result } = await mplBubblegumProvider.transfer(
content.assetId as PublicKey,
content.newLeafOwner as PublicKey
);

console.log("Transfer successful", signature);

if (callback) {
callback({
text: `Successfully transferred ${content.assetId} to ${content.newLeafOwner}\nTransaction: ${signature}`,
content: {
success: true,
signature,
recipient: content.newLeafOwner,
},
});
}

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: "Send BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa",
},
},
{
user: "{{user2}}",
content: {
text: "I'll send BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump now...",
action: "SEND_COMPRESSED_NFT",
},
},
{
user: "{{user2}}",
content: {
text: "Successfully sent BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\nTransaction: 5KtPn3DXXzHkb7VAVHZGwXJQqww39ASnrf7YkyJoF2qAGEpBEEGvRHLnnTG8ZVwKqNHMqSckWVGnsQAgfH5pbxEb",
},
},
],
] as ActionExample[][],
};
Loading
Loading