-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
bucurdavid
wants to merge
20
commits into
elizaOS:develop
Choose a base branch
from
bucurdavid:develop
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,674
−136
Open
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
5419436
feat: add support for mpl cNFTs
bucurdavid 698f47f
chore: add config to examples
bucurdavid eab5719
feat: add plugin to agent
bucurdavid ae19d52
chore: update from main
bucurdavid c4f2f85
chore: pnpm lock
bucurdavid c0bdca8
fix: tee import
bucurdavid a6d10a7
Merge branch 'develop' into develop
odilitime d418fba
Merge branch 'develop' into develop
bucurdavid 7314a53
fix: provider initialization in action
bucurdavid 9a6426a
feat: more context in action examples
bucurdavid cc9f5d6
Merge branch 'develop' into develop
bucurdavid 4da4c68
chore: add plugin in agent dependencies
bucurdavid 6a64505
feat: fetch compress nft details
bucurdavid b85f042
fix: add action type
bucurdavid 34ebe42
Merge branch 'develop' into develop
bucurdavid efd5f39
Merge branch 'develop' into develop
bucurdavid 7c402a7
Merge branch 'develop' into develop
bucurdavid d52f4d6
Merge branch 'develop' into develop
bucurdavid ba96c0f
fix: prevent undefined plugin usage in UMI initialization
bucurdavid 3a070e0
refactor: improve callback response
bucurdavid File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
* | ||
|
||
!dist/** | ||
!package.json | ||
!readme.md | ||
!tsup.config.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import eslintGlobalConfig from "../../eslint.config.mjs"; | ||
|
||
export default [...eslintGlobalConfig]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, true); | ||
|
||
const RPC_URL = runtime.getSetting("MPL_BUBBLEGUM_RPC_URL"); | ||
|
||
const agentKeypairAdapter = fromWeb3JsKeypair(agentKeypair); | ||
|
||
const mplBubblegumProvider = new MplBubblegumProvider( | ||
agentKeypairAdapter, | ||
RPC_URL | ||
); | ||
|
||
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[][], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { IAgentRuntime } from "@elizaos/core"; | ||
import { z } from "zod"; | ||
|
||
export const mplBubblegumEnvSchema = z.object({ | ||
MPL_BUBBLEGUM_PRIVATE_KEY: z | ||
.string() | ||
.min(1, "Private key for Bubblegum is required."), | ||
MPL_BUBBLEGUM_RPC_URL: z | ||
.string() | ||
.min(1, "Bubblegum RPC URL with DAS API support is required."), | ||
}); | ||
|
||
export type MplBubblegumConfig = z.infer<typeof mplBubblegumEnvSchema>; | ||
|
||
export async function validateMplBubblegumConfig( | ||
runtime: IAgentRuntime | ||
): Promise<MplBubblegumConfig> { | ||
try { | ||
const config = { | ||
MPL_BUBBLEGUM_PRIVATE_KEY: | ||
runtime.getSetting("MPL_BUBBLEGUM_PRIVATE_KEY") || | ||
process.env.MPL_BUBBLEGUM_PRIVATE_KEY, | ||
MPL_BUBBLEGUM_RPC_URL: | ||
runtime.getSetting("MPL_BUBBLEGUM_RPC_URL") || | ||
process.env.MPL_BUBBLEGUM_RPC_URL, | ||
}; | ||
|
||
return mplBubblegumEnvSchema.parse(config); | ||
} catch (error) { | ||
if (error instanceof z.ZodError) { | ||
const errorMessages = error.errors | ||
.map((err) => `${err.path.join(".")}: ${err.message}`) | ||
.join("\n"); | ||
throw new Error( | ||
`Mpl Bubblegum configuration validation failed:\n${errorMessages}` | ||
); | ||
} | ||
throw error; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Plugin } from "@elizaos/core"; | ||
import transfer from "./actions/transfer"; | ||
|
||
export const mplBubblegumPlugin: Plugin = { | ||
name: "mpl-bubblegum", | ||
description: "Bubblegum Plugin for Eliza", | ||
actions: [transfer], | ||
providers: [], | ||
evaluators: [], | ||
services: [], | ||
clients: [], | ||
}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know nothing about metaplex bubblegum but assuming our system is full of similar plugins are these examples going to be specific enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TL;DR
Bubblegum is Solana's protocol for compressed NFTs, allowing for high-scale, low-cost creation of NFTs by storing "non-essential" data off-chain while maintaining proofs on-chain.
It requires a custom implementation separate from the Solana SPL Token standard.
Yes, I agree it could lead to issues when used alongside similar plugins.