diff --git a/.env.example b/.env.example index a7e7b0b0a3..50dcd50d5e 100644 --- a/.env.example +++ b/.env.example @@ -268,6 +268,9 @@ CHARITY_ADDRESS_ETH=0x750EF1D7a0b4Ab1c97B7A623D7917CcEb5ea779C CHARITY_ADDRESS_ARB=0x1234567890123456789012345678901234567890 CHARITY_ADDRESS_POL=0x1234567890123456789012345678901234567890 +# thirdweb +THIRDWEB_SECRET_KEY= # Create key on thirdweb developer dashboard: https://thirdweb.com/ + # Conflux Configuration CONFLUX_CORE_PRIVATE_KEY= CONFLUX_CORE_SPACE_RPC_URL= diff --git a/agent/package.json b/agent/package.json index 1d23b40061..e0c9257f4f 100644 --- a/agent/package.json +++ b/agent/package.json @@ -69,6 +69,7 @@ "@elizaos/plugin-fuel": "workspace:*", "@elizaos/plugin-avalanche": "workspace:*", "@elizaos/plugin-web-search": "workspace:*", + "@elizaos/plugin-thirdweb": "workspace:*", "@elizaos/plugin-genlayer": "workspace:*", "@elizaos/plugin-open-weather": "workspace:*", "@elizaos/plugin-obsidian": "workspace:*", diff --git a/agent/src/index.ts b/agent/src/index.ts index 630bb396f1..abf11aa917 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -70,14 +70,14 @@ import { TEEMode, teePlugin } from "@elizaos/plugin-tee"; import { teeMarlinPlugin } from "@elizaos/plugin-tee-marlin"; import { tonPlugin } from "@elizaos/plugin-ton"; import { webSearchPlugin } from "@elizaos/plugin-web-search"; +import { echoChamberPlugin } from "@elizaos/plugin-echochambers"; +import { thirdwebPlugin } from "@elizaos/plugin-thirdweb"; import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era"; import { availPlugin } from "@elizaos/plugin-avail"; import { openWeatherPlugin } from "@elizaos/plugin-open-weather"; - import { artheraPlugin } from "@elizaos/plugin-arthera"; import { stargazePlugin } from "@elizaos/plugin-stargaze"; import { obsidianPlugin } from "@elizaos/plugin-obsidian"; - import Database from "better-sqlite3"; import fs from "fs"; import net from "net"; @@ -644,6 +644,7 @@ export async function createAgent( : null, getSecret(character, "TEE_MARLIN") ? teeMarlinPlugin : null, getSecret(character, "TON_PRIVATE_KEY") ? tonPlugin : null, + getSecret(character, "THIRDWEB_SECRET_KEY") ? thirdwebPlugin : null, getSecret(character, "SUI_PRIVATE_KEY") ? suiPlugin : null, getSecret(character, "STORY_PRIVATE_KEY") ? storyPlugin : null, getSecret(character, "FUEL_PRIVATE_KEY") ? fuelPlugin : null, diff --git a/packages/plugin-thirdweb/.npmignore b/packages/plugin-thirdweb/.npmignore new file mode 100644 index 0000000000..078562ecea --- /dev/null +++ b/packages/plugin-thirdweb/.npmignore @@ -0,0 +1,6 @@ +* + +!dist/** +!package.json +!readme.md +!tsup.config.ts \ No newline at end of file diff --git a/packages/plugin-thirdweb/README.md b/packages/plugin-thirdweb/README.md new file mode 100644 index 0000000000..f4d220a0f8 --- /dev/null +++ b/packages/plugin-thirdweb/README.md @@ -0,0 +1,62 @@ +# `ai16z/plugin-thirdweb` + +This plugin provides access to thirdweb's Nebula AI interface: [https://portal.thirdweb.com/nebula](https://portal.thirdweb.com/nebula). + +## Configuration + +### Default Setup + +By default, \*_thirdweb plugin_ is enabled. To use it, simply add your secret key to the `.env` file: + +```env +THIRDWEB_SECRET_KEY=your-thirdweb-secret-key-here +``` + +--- + +## Actions + +### Chat + +Interact with the thirdweb Nebula natural language interface to perform any of the following: + +- Analyze any smart contract's functionality and features +- Explain contract interfaces and supported standards +- Read contract data and state +- Help you understand function behaviors and parameters +- Decode complex contract interactions +- Retrieve detailed contract metadata and source code analysis +- Provide real-time network status and gas prices +- Explain block and transaction details +- Help you understand blockchain network specifications +- Offer insights about different blockchain networks +- Track transaction status and history +- Access detailed chain metadata including RPC endpoints +- Look up token information across different networks +- Track token prices and market data +- Explain token standards and implementations +- Help you understand token bridges and cross-chain aspects +- Monitor trading pairs and liquidity +- Fetch token metadata and current exchange rates +- Retrieve detailed transaction information using transaction hashes +- Provide wallet balance and transaction history + +**Example usage:** + +```env +What is the ETH balance for 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 +``` + +```env +What is the total NFT supply for 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D? +``` + +```env +Does 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 hold USDC on Base? +``` + +```env +What is the address of USDC on Ethereum? +``` + +--- diff --git a/packages/plugin-thirdweb/eslint.config.mjs b/packages/plugin-thirdweb/eslint.config.mjs new file mode 100644 index 0000000000..92fe5bbebe --- /dev/null +++ b/packages/plugin-thirdweb/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [...eslintGlobalConfig]; diff --git a/packages/plugin-thirdweb/package.json b/packages/plugin-thirdweb/package.json new file mode 100644 index 0000000000..125d7fd14e --- /dev/null +++ b/packages/plugin-thirdweb/package.json @@ -0,0 +1,20 @@ +{ + "name": "@elizaos/plugin-thirdweb", + "version": "0.1.7-alpha.2", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@elizaos/core": "workspace:*", + "thirdweb": "^5.80.0", + "tsup": "8.3.5" + }, + "scripts": { + "build": "tsup --format esm --dts", + "dev": "tsup --format esm --dts --watch", + "lint": "eslint --fix --cache ." + }, + "peerDependencies": { + "whatwg-url": "7.1.0" + } +} diff --git a/packages/plugin-thirdweb/src/actions/chat.ts b/packages/plugin-thirdweb/src/actions/chat.ts new file mode 100644 index 0000000000..16d558a4e1 --- /dev/null +++ b/packages/plugin-thirdweb/src/actions/chat.ts @@ -0,0 +1,214 @@ +import { + elizaLogger, + HandlerCallback, + IAgentRuntime, + Memory, + State, + type Action, +} from "@elizaos/core"; + +const BASE_URL = "https://nebula-api.thirdweb.com"; + +// If chat is a stream, wait for stream to complete before returning response +async function handleStreamResponse( + response: Response +): Promise { + elizaLogger.log("Starting stream response handling"); + const reader = response.body?.getReader(); + if (!reader) { + elizaLogger.error("No readable stream available"); + throw new Error("No readable stream available"); + } + + return new ReadableStream({ + async start(controller) { + try { + while (true) { + const { done, value } = await reader.read(); + if (done) { + elizaLogger.log("Stream reading completed"); + break; + } + + const events = new TextDecoder() + .decode(value) + .split("\n\n"); + elizaLogger.debug( + `Processing ${events.length} stream events` + ); + for (const event of events) { + if (!event.trim()) continue; + controller.enqueue(event); + } + } + } finally { + reader.releaseLock(); + controller.close(); + elizaLogger.log("Stream controller closed"); + } + }, + }); +} + +// Process & return a response to the current message with thirdweb Nebula +export const blockchainChatAction: Action = { + name: "BLOCKCHAIN_CHAT", + similes: [ + "QUERY_BLOCKCHAIN", + "CHECK_BLOCKCHAIN", + "BLOCKCHAIN_SEARCH", + "CRYPTO_LOOKUP", + "WEB3_SEARCH", + "BLOCKCHAIN_HISTORY_EXPLORER", + "UNIVERSAL_BLOCKCHAIN_TRANSALTOR", + "BLOCKCHAIN_DATA_PROVIDER", + "HISTORICAL_BLOCKCHAIN_DATA", + "TRACK_BLOCKCHAIN_TRANSACTIONS", + "BLOCKCHAIN_INTERPRETER", + "BLOCKCHAIN_TRANSACTION_DETAILS", + ], + validate: async ( + runtime: IAgentRuntime, + _message: Memory + ): Promise => { + const secretKey = + runtime.getSetting("THIRDWEB_SECRET_KEY") ?? + process.env.THIRDWEB_SECRET_KEY; + return Boolean(secretKey); + }, + description: + "Query blockchain data and execute transactions through natural language interaction with the Nebula API", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + _state: State, + _options: any, + callback: HandlerCallback + ): Promise => { + try { + elizaLogger.log("Starting blockchain chat handler"); + const secretKey = + runtime.getSetting("THIRDWEB_SECRET_KEY") ?? + process.env.THIRDWEB_SECRET_KEY; + + if (!secretKey) { + elizaLogger.error("THIRDWEB_SECRET_KEY not configured"); + throw new Error("THIRDWEB_SECRET_KEY is not configured"); + } + + const request = { + message: message.content.text, + stream: false, + }; + + elizaLogger.log("NEBULA CHAT REQUEST: ", request); + + elizaLogger.debug("Sending request to Nebula API"); + const response = await fetch(`${BASE_URL}/chat`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-secret-key": secretKey, + }, + body: JSON.stringify(request), + }); + elizaLogger.debug("Received response from Nebula API"); + + if (!request.stream) { + const text = await response.text(); + elizaLogger.debug("Raw response text:", text); + + try { + const cleanedText = text.trim().split("\n").pop() || text; + const parsed = JSON.parse(cleanedText); + elizaLogger.log("Successfully parsed response:", parsed); + + console.log(parsed.message); + + await callback({ text: parsed.message }); + + return parsed; + } catch (parseError) { + elizaLogger.error("Parse error details:", parseError); + elizaLogger.error( + "Failed to parse JSON response. Raw text:", + text + ); + return { text: text }; + } + } + + elizaLogger.log("Handling streaming response"); + return handleStreamResponse(response); + } catch (error) { + elizaLogger.error("Blockchain chat failed:", error); + throw new Error(`Blockchain chat failed: ${error.message}`); + } + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "What's the ETH balance of vitalik.eth?", + action: "BLOCKCHAIN_CHAT", + }, + }, + { + user: "{{user2}}", + content: { + text: "The current ETH balance of vitalik.eth (0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045) is 1,123.45 ETH", + action: "BLOCKCHAIN_CHAT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "send 0.1 ETH to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e", + action: "BLOCKCHAIN_CHAT", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll help you send 0.1 ETH. Please review and sign the transaction.", + action: "BLOCKCHAIN_CHAT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Show me the floor price of BAYC", + action: "BLOCKCHAIN_CHAT", + }, + }, + { + user: "{{user2}}", + content: { + text: "The current floor price for BAYC is 32.5 ETH with 3 sales in the last 24h", + action: "BLOCKCHAIN_CHAT", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Show me my recent transactions", + action: "BLOCKCHAIN_CHAT", + }, + }, + { + user: "{{user2}}", + content: { + text: "Here are your recent transactions: 1. Sent 1.5 ETH 2. Swapped tokens on Uniswap 3. Received 0.5 ETH", + action: "BLOCKCHAIN_CHAT", + }, + }, + ], + ], +} as Action; diff --git a/packages/plugin-thirdweb/src/actions/index.ts b/packages/plugin-thirdweb/src/actions/index.ts new file mode 100644 index 0000000000..5ea161c6e4 --- /dev/null +++ b/packages/plugin-thirdweb/src/actions/index.ts @@ -0,0 +1 @@ +export * from "./chat.ts"; diff --git a/packages/plugin-thirdweb/src/index.ts b/packages/plugin-thirdweb/src/index.ts new file mode 100644 index 0000000000..49bbe528ab --- /dev/null +++ b/packages/plugin-thirdweb/src/index.ts @@ -0,0 +1,12 @@ +import { Plugin } from "@elizaos/core"; +import { blockchainChatAction } from "./actions/chat"; +export * as actions from "./actions/index.ts"; + +export const thirdwebPlugin: Plugin = { + name: "PROVIDE_BLOCKCHAIN_DATA", + description: + "Search the blockchain with thirdweb Nebula for information about wallet addresses, token prices, token owners, transactions and their details.", + actions: [blockchainChatAction], + evaluators: [], + providers: [], +}; diff --git a/packages/plugin-thirdweb/tsconfig.json b/packages/plugin-thirdweb/tsconfig.json new file mode 100644 index 0000000000..834c4dce26 --- /dev/null +++ b/packages/plugin-thirdweb/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../core/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "types": [ + "node" + ] + }, + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/plugin-thirdweb/tsup.config.ts b/packages/plugin-thirdweb/tsup.config.ts new file mode 100644 index 0000000000..e42bf4efea --- /dev/null +++ b/packages/plugin-thirdweb/tsup.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "dist", + sourcemap: true, + clean: true, + format: ["esm"], // Ensure you're targeting CommonJS + external: [ + "dotenv", // Externalize dotenv to prevent bundling + "fs", // Externalize fs to use Node.js built-in module + "path", // Externalize other built-ins if necessary + "@reflink/reflink", + "@node-llama-cpp", + "https", + "http", + "agentkeepalive", + // Add other modules you want to externalize + ], +});