From 05cd25d54adb92317c15be7b79f2a8651b87416b Mon Sep 17 00:00:00 2001 From: iankm Date: Mon, 23 Dec 2024 21:28:47 -0500 Subject: [PATCH] add thirdweb plugin --- .env.example | 3 + agent/package.json | 119 +++++----- agent/src/index.ts | 2 + packages/plugin-thirdweb/.npmignore | 6 + packages/plugin-thirdweb/README.md | 62 +++++ packages/plugin-thirdweb/eslint.config.mjs | 3 + packages/plugin-thirdweb/package.json | 20 ++ packages/plugin-thirdweb/src/actions/chat.ts | 214 ++++++++++++++++++ packages/plugin-thirdweb/src/actions/index.ts | 1 + packages/plugin-thirdweb/src/index.ts | 12 + packages/plugin-thirdweb/tsconfig.json | 13 ++ packages/plugin-thirdweb/tsup.config.ts | 20 ++ 12 files changed, 416 insertions(+), 59 deletions(-) create mode 100644 packages/plugin-thirdweb/.npmignore create mode 100644 packages/plugin-thirdweb/README.md create mode 100644 packages/plugin-thirdweb/eslint.config.mjs create mode 100644 packages/plugin-thirdweb/package.json create mode 100644 packages/plugin-thirdweb/src/actions/chat.ts create mode 100644 packages/plugin-thirdweb/src/actions/index.ts create mode 100644 packages/plugin-thirdweb/src/index.ts create mode 100644 packages/plugin-thirdweb/tsconfig.json create mode 100644 packages/plugin-thirdweb/tsup.config.ts diff --git a/.env.example b/.env.example index d2d89eda85..476b411c62 100644 --- a/.env.example +++ b/.env.example @@ -215,6 +215,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 3f25e7b553..9866b38274 100644 --- a/agent/package.json +++ b/agent/package.json @@ -1,61 +1,62 @@ { - "name": "@ai16z/agent", - "version": "0.1.6-alpha.4", - "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": { - "@ai16z/adapter-postgres": "workspace:*", - "@ai16z/adapter-sqlite": "workspace:*", - "@ai16z/client-auto": "workspace:*", - "@ai16z/client-direct": "workspace:*", - "@ai16z/client-discord": "workspace:*", - "@ai16z/client-farcaster": "workspace:*", - "@ai16z/client-lens": "workspace:*", - "@ai16z/client-telegram": "workspace:*", - "@ai16z/client-twitter": "workspace:*", - "@ai16z/client-slack": "workspace:*", - "@ai16z/eliza": "workspace:*", - "@ai16z/plugin-0g": "workspace:*", - "@ai16z/plugin-aptos": "workspace:*", - "@ai16z/plugin-bootstrap": "workspace:*", - "@ai16z/plugin-intiface": "workspace:*", - "@ai16z/plugin-coinbase": "workspace:*", - "@ai16z/plugin-conflux": "workspace:*", - "@ai16z/plugin-evm": "workspace:*", - "@ai16z/plugin-flow": "workspace:*", - "@ai16z/plugin-story": "workspace:*", - "@ai16z/plugin-goat": "workspace:*", - "@ai16z/plugin-icp": "workspace:*", - "@ai16z/plugin-image-generation": "workspace:*", - "@ai16z/plugin-nft-generation": "workspace:*", - "@ai16z/plugin-node": "workspace:*", - "@ai16z/plugin-solana": "workspace:*", - "@ai16z/plugin-starknet": "workspace:*", - "@ai16z/plugin-ton": "workspace:*", - "@ai16z/plugin-sui": "workspace:*", - "@ai16z/plugin-tee": "workspace:*", - "@ai16z/plugin-multiversx": "workspace:*", - "@ai16z/plugin-near": "workspace:*", - "@ai16z/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": "@ai16z/agent", + "version": "0.1.6-alpha.4", + "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": { + "@ai16z/adapter-postgres": "workspace:*", + "@ai16z/adapter-sqlite": "workspace:*", + "@ai16z/client-auto": "workspace:*", + "@ai16z/client-direct": "workspace:*", + "@ai16z/client-discord": "workspace:*", + "@ai16z/client-farcaster": "workspace:*", + "@ai16z/client-lens": "workspace:*", + "@ai16z/client-telegram": "workspace:*", + "@ai16z/client-twitter": "workspace:*", + "@ai16z/client-slack": "workspace:*", + "@ai16z/eliza": "workspace:*", + "@ai16z/plugin-0g": "workspace:*", + "@ai16z/plugin-aptos": "workspace:*", + "@ai16z/plugin-bootstrap": "workspace:*", + "@ai16z/plugin-intiface": "workspace:*", + "@ai16z/plugin-coinbase": "workspace:*", + "@ai16z/plugin-conflux": "workspace:*", + "@ai16z/plugin-evm": "workspace:*", + "@ai16z/plugin-flow": "workspace:*", + "@ai16z/plugin-story": "workspace:*", + "@ai16z/plugin-goat": "workspace:*", + "@ai16z/plugin-icp": "workspace:*", + "@ai16z/plugin-image-generation": "workspace:*", + "@ai16z/plugin-nft-generation": "workspace:*", + "@ai16z/plugin-node": "workspace:*", + "@ai16z/plugin-solana": "workspace:*", + "@ai16z/plugin-starknet": "workspace:*", + "@ai16z/plugin-ton": "workspace:*", + "@ai16z/plugin-sui": "workspace:*", + "@ai16z/plugin-tee": "workspace:*", + "@ai16z/plugin-thirdweb": "workspace:*", + "@ai16z/plugin-multiversx": "workspace:*", + "@ai16z/plugin-near": "workspace:*", + "@ai16z/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" + } } diff --git a/agent/src/index.ts b/agent/src/index.ts index 1968a64a6c..213d65f6e4 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -52,6 +52,7 @@ import { solanaPlugin } from "@ai16z/plugin-solana"; import { suiPlugin } from "@ai16z/plugin-sui"; import { TEEMode, teePlugin } from "@ai16z/plugin-tee"; import { tonPlugin } from "@ai16z/plugin-ton"; +import { thirdwebPlugin } from "@ai16z/plugin-thirdweb"; import { zksyncEraPlugin } from "@ai16z/plugin-zksync-era"; import Database from "better-sqlite3"; import fs from "fs"; @@ -548,6 +549,7 @@ export async function createAgent( getSecret(character, "MVX_PRIVATE_KEY") ? multiversxPlugin : null, getSecret(character, "ZKSYNC_PRIVATE_KEY") ? zksyncEraPlugin : 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, ].filter(Boolean), 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..84397837f2 --- /dev/null +++ b/packages/plugin-thirdweb/package.json @@ -0,0 +1,20 @@ +{ + "name": "@ai16z/plugin-thirdweb", + "version": "0.1.6-alpha.4", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@ai16z/eliza": "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..a22a5f5fc7 --- /dev/null +++ b/packages/plugin-thirdweb/src/actions/chat.ts @@ -0,0 +1,214 @@ +import { + elizaLogger, + HandlerCallback, + IAgentRuntime, + Memory, + State, + type Action, +} from "@ai16z/eliza"; + +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..552d1c1941 --- /dev/null +++ b/packages/plugin-thirdweb/src/index.ts @@ -0,0 +1,12 @@ +import { Plugin } from "@ai16z/eliza"; +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 + ], +});