├── .npmrc ├── .husky └── pre-commit ├── client ├── .prettierignore ├── postcss.config.js ├── lib │ └── utils.ts ├── .prettierrc ├── .eslintrc.json ├── components │ └── ui │ │ ├── skeleton.tsx │ │ ├── alert.tsx │ │ ├── button.tsx │ │ └── card.tsx ├── .env.example ├── components.json ├── app │ ├── layout.tsx │ ├── (routes) │ │ └── page.tsx │ ├── lib │ │ └── auth.ts │ ├── _components │ │ ├── TelegramUser.tsx │ │ ├── TelegramLogin.tsx │ │ ├── GithubLogin.tsx │ │ ├── DiscordLogin.tsx │ │ ├── HelloWorld.tsx │ │ └── TwitterLogin.tsx │ └── claim │ │ ├── interstitial │ │ └── page.tsx │ │ └── [tokenId] │ │ └── page.tsx ├── next.config.ts ├── styles │ └── globals.css ├── tsconfig.json ├── bin │ ├── www-dev │ ├── validate-env │ └── www-tunnel ├── package.json └── tailwind.config.ts ├── server ├── .prettierignore ├── src │ ├── contracts │ │ └── types │ │ │ ├── factories │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ └── common.ts │ ├── services │ │ ├── base.service.ts │ │ ├── cache.service.ts │ │ ├── ngrok.service.ts │ │ └── twitter.service.ts │ ├── plugins │ │ ├── gated-storage-plugin │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── providers │ │ │ │ └── provider.ts │ │ │ ├── actions │ │ │ │ └── gate-action.ts │ │ │ ├── services │ │ │ │ ├── orbis.service.ts │ │ │ │ └── storage.service.ts │ │ │ └── evaluators │ │ │ │ └── knowledge.ts │ │ ├── collabland.plugin.ts │ │ ├── actions │ │ │ ├── collabland.action.ts │ │ │ ├── get-chain.action.ts │ │ │ └── get-bot-account.action.ts │ │ ├── types.ts │ │ └── providers │ │ │ ├── collabland-wallet-balance.provider.ts │ │ │ └── collabland-solana-wallet-balance.provider.ts │ ├── routes │ │ ├── hello.ts │ │ ├── github.ts │ │ └── discord.ts │ ├── types.ts │ ├── utils.ts │ └── index.ts ├── .prettierrc ├── .eslintrc.json ├── tsconfig.json ├── scripts │ ├── seed.mjs │ └── deploy-model.mjs ├── bin │ ├── www-dev │ ├── check-x-login │ └── validate-env └── package.json ├── .eslintignore ├── lit-actions ├── .prettierignore ├── shims │ ├── buffer.shim.js │ └── gatedData.shim.js ├── .prettierrc ├── .env.example ├── tsconfig.json ├── bin │ ├── build-dev │ └── validate-env ├── .eslintrc.json ├── package.json ├── src │ ├── actions │ │ ├── hello-action.ts │ │ ├── decrypt-action.ts │ │ └── encrypt-action.ts │ └── index.ts ├── esbuild.js └── README.md ├── pnpm-workspace.yaml ├── .github ├── CODEOWNERS ├── changelog-config.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── pull_request_template.md └── workflows │ └── changelog.yml ├── commands.json ├── token_metadata.example.jsonc ├── .gitignore ├── scripts ├── dev └── tunnel ├── LICENSE ├── package.json ├── CODE_OF_CONDUCT.md ├── .env.example ├── character.json ├── HISTORY.md ├── CHANGELOG.md └── vaitalik.json /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm run lint 2 | git add . -------------------------------------------------------------------------------- /client/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage -------------------------------------------------------------------------------- /server/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .next 4 | build 5 | coverage -------------------------------------------------------------------------------- /lit-actions/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | actions/* -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - client 3 | - server 4 | - lit-actions -------------------------------------------------------------------------------- /lit-actions/shims/buffer.shim.js: -------------------------------------------------------------------------------- 1 | import { Buffer } from "buffer"; 2 | globalThis.Buffer = Buffer; 3 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @gitaalekhyapaul @alokt @jamesyoung 2 | /.github/CODEOWNERS @gitaalekhyapaul @alokt @jamesyoung 3 | /.github/ @gitaalekhyapaul @alokt @jamesyoung 4 | -------------------------------------------------------------------------------- /client/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /server/src/contracts/types/factories/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export { WowXYZERC20__factory } from "./WowXYZERC20__factory.js"; 5 | -------------------------------------------------------------------------------- /client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "useTabs": false, 8 | "bracketSpacing": true, 9 | "arrowParens": "always" 10 | } 11 | -------------------------------------------------------------------------------- /server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "useTabs": false, 8 | "bracketSpacing": true, 9 | "arrowParens": "always" 10 | } 11 | -------------------------------------------------------------------------------- /lit-actions/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "useTabs": false, 8 | "bracketSpacing": true, 9 | "arrowParens": "always" 10 | } 11 | -------------------------------------------------------------------------------- /server/src/services/base.service.ts: -------------------------------------------------------------------------------- 1 | export interface IService { 2 | start(): Promise; 3 | stop(): Promise; 4 | } 5 | 6 | export abstract class BaseService implements IService { 7 | abstract start(): Promise; 8 | abstract stop(): Promise; 9 | } 10 | -------------------------------------------------------------------------------- /server/src/contracts/types/index.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | export type { WowXYZERC20 } from "./WowXYZERC20.js"; 5 | export * as factories from "./factories/index.js"; 6 | export { WowXYZERC20__factory } from "./factories/WowXYZERC20__factory.js"; 7 | -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "next/core-web-vitals" 5 | ], 6 | "parser": "@typescript-eslint/parser", 7 | "plugins": ["@typescript-eslint"], 8 | "rules": { 9 | "no-unused-vars": "off", 10 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] 11 | } 12 | } -------------------------------------------------------------------------------- /client/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /commands.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "/ace": { 4 | "description": "Switch to Collab.Land Ace personality", 5 | "characterPath": "character.json" 6 | }, 7 | "/eliza": { 8 | "description": "Switch to default Eliza personality", 9 | "characterPath": "default" 10 | }, 11 | "/vaitalik": { 12 | "description": "Switch to Vitalik Buterin hackathon judge personality", 13 | "characterPath": "vaitalik.json" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 4 | "parser": "@typescript-eslint/parser", 5 | "plugins": ["@typescript-eslint"], 6 | "rules": { 7 | "no-unused-vars": "warn", 8 | "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }] 9 | }, 10 | "ignorePatterns": ["dist/", "node_modules/"], 11 | "env": { 12 | "node": true, 13 | "es6": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lit-actions/.env.example: -------------------------------------------------------------------------------- 1 | # Example .env file for the Lit Actions 2 | # If you want to add new variables, please do it in the format below 3 | # = 4 | # On the next iteration of pnpm run dev, the new variables picked up by the validate-env script will be added to the .env file 5 | # You can add hints to the ENV_HINTS object in the validate-env script to help the user with the new variables 6 | 7 | PINATA_JWT= 8 | PINATA_URL= -------------------------------------------------------------------------------- /.github/changelog-config.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | authors: 6 | - dependabot 7 | categories: 8 | - title: 🚀 Features 9 | labels: 10 | - enhancement 11 | - feature 12 | - title: 🐛 Bug Fixes 13 | labels: 14 | - bug 15 | - fix 16 | - title: 🧰 Maintenance 17 | labels: 18 | - chore 19 | - dependencies 20 | - title: 📝 Documentation 21 | labels: 22 | - documentation 23 | -------------------------------------------------------------------------------- /client/.env.example: -------------------------------------------------------------------------------- 1 | # Example .env file for the API server 2 | # If you want to add new variables, please do it in the format below 3 | # = 4 | # On the next iteration of pnpm run dev, the new variables picked up by the validate-env script will be added to the .env file 5 | # You can add hints to the ENV_HINTS object in the validate-env script to help the user with the new variables 6 | 7 | NEXT_PUBLIC_API_URL= 8 | NEXT_PUBLIC_TELEGRAM_BOT_NAME= -------------------------------------------------------------------------------- /client/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "styles/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /server/src/plugins/gated-storage-plugin/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "@ai16z/eliza"; 2 | import { gateDataAction } from "./actions/gate-action.js"; 3 | import { knowledgeEvaluator } from "./evaluators/knowledge.js"; 4 | import { gateDataProvider } from "./providers/provider.js"; 5 | 6 | export const gateDataPlugin: Plugin = { 7 | name: "gated", 8 | description: "Gate data plugin", 9 | actions: [gateDataAction], 10 | evaluators: [knowledgeEvaluator], 11 | providers: [gateDataProvider], 12 | }; 13 | 14 | export default gateDataPlugin; 15 | -------------------------------------------------------------------------------- /lit-actions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "declaration": true, 5 | "strict": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "strictPropertyInitialization": false, 9 | "skipLibCheck": true, 10 | "noImplicitAny": false, 11 | "esModuleInterop": true, 12 | "module": "ESNext", 13 | "rootDir": "./src", 14 | "outDir": "./dist" 15 | }, 16 | "include": ["./src/**/*", "src/global.d.ts"], 17 | "exclude": ["./dist", "./src/actions/**"] 18 | } 19 | -------------------------------------------------------------------------------- /client/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "@/styles/globals.css"; 2 | import { Inter } from "next/font/google"; 3 | 4 | const inter = Inter({ subsets: ["latin"] }); 5 | 6 | export const metadata = { 7 | title: "Collab.Land Starter Kit", 8 | description: "Get started with Collab.Land", 9 | }; 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: { 14 | children: React.ReactNode; 15 | }) { 16 | return ( 17 | 18 | 19 | {children} 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /client/next.config.ts: -------------------------------------------------------------------------------- 1 | import { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | poweredByHeader: false, 5 | reactStrictMode: true, 6 | images: { 7 | remotePatterns: [ 8 | { 9 | protocol: "https", 10 | hostname: "**", 11 | }, 12 | ], 13 | }, 14 | async rewrites() { 15 | return [ 16 | { 17 | source: "/api/:path*", 18 | destination: `${process.env.NEXT_PUBLIC_API_URL}/:path*`, 19 | }, 20 | ]; 21 | }, 22 | experimental: { 23 | proxyTimeout: 10 * 60 * 1000, 24 | }, 25 | }; 26 | 27 | export default nextConfig; 28 | -------------------------------------------------------------------------------- /lit-actions/bin/build-dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { spawn } from "child_process"; 4 | import { fileURLToPath } from "url"; 5 | import { dirname, resolve } from "path"; 6 | import fs from "fs"; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = dirname(__filename); 10 | 11 | console.log("Starting dev build..."); 12 | 13 | function startBuild() { 14 | spawn("pnpm", ["run", "dev"], { stdio: "inherit" }); 15 | } 16 | 17 | fs.watch(resolve(__dirname, "../src/actions"), () => { 18 | console.log("Actions src folder changed, restarting build..."); 19 | startBuild(); 20 | }); 21 | 22 | startBuild(); 23 | -------------------------------------------------------------------------------- /server/src/plugins/gated-storage-plugin/types.ts: -------------------------------------------------------------------------------- 1 | import { Content } from "@ai16z/eliza"; 2 | 3 | export interface GateDataConfig { 4 | provider: { 5 | table_id: string; 6 | orbis_gateway: string; 7 | ceramic_gateway: string; 8 | private_key: string; 9 | context_id: string; 10 | }; 11 | } 12 | 13 | export interface GateActionContent extends Content { 14 | text: string; 15 | } 16 | 17 | export interface GateDataProviderResponseGet { 18 | success: boolean; 19 | additionalContext?: string; 20 | error?: string; 21 | } 22 | 23 | export interface NonceProviderResponseGet { 24 | success: boolean; 25 | nonce?: string; 26 | error?: string; 27 | } 28 | -------------------------------------------------------------------------------- /client/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | @apply w-full h-full p-0 m-0 text-primary bg-secondary; 8 | } 9 | 10 | #__next, main { 11 | @apply w-full h-full; 12 | } 13 | 14 | @layer base { 15 | .btn-primary { 16 | @apply px-4 py-2 bg-accent text-primary border border-accent 17 | hover:bg-secondary hover:text-primary transition-all duration-200 18 | rounded-lg; 19 | } 20 | 21 | .input-primary { 22 | @apply px-4 py-2 bg-secondary text-primary border border-accent 23 | focus:outline-none focus:ring-1 focus:ring-primary 24 | rounded-lg; 25 | } 26 | } -------------------------------------------------------------------------------- /lit-actions/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 4 | "parser": "@typescript-eslint/parser", 5 | "plugins": ["@typescript-eslint"], 6 | "rules": { 7 | "no-unused-vars": "warn", 8 | "@typescript-eslint/no-unused-vars": [ 9 | "warn", 10 | { "argsIgnorePattern": "^_" } 11 | ], 12 | "@typescript-eslint/triple-slash-reference": "off" 13 | }, 14 | "ignorePatterns": [ 15 | "dist/", 16 | "node_modules/", 17 | "shims/", 18 | "actions/", 19 | "src/global.d.ts" 20 | ], 21 | "env": { 22 | "node": true, 23 | "es6": true, 24 | "browser": true, 25 | "es2020": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] " 5 | labels: enhancement 6 | assignees: "@gitaalekhyapaul @alokt @jamesyoung" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /client/app/(routes)/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ReactElement } from "react"; 4 | import HelloWorld from "../_components/HelloWorld"; 5 | import TelegramUser from "../_components/TelegramUser"; 6 | import { TwitterLogin } from "../_components/TwitterLogin"; 7 | import { DiscordLogin } from "../_components/DiscordLogin"; 8 | import { GithubLogin } from "../_components/GithubLogin"; 9 | 10 | export default function Home(): ReactElement { 11 | return ( 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /client/app/lib/auth.ts: -------------------------------------------------------------------------------- 1 | export function generateRandomString(length: number): string { 2 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' 3 | const values = crypto.getRandomValues(new Uint8Array(length)) 4 | return values.reduce((acc, x) => acc + possible[x % possible.length], "") 5 | } 6 | 7 | export async function sha256(str: string): Promise { 8 | const encoder = new TextEncoder() 9 | const data = encoder.encode(str) 10 | return crypto.subtle.digest('SHA-256', data) 11 | } 12 | 13 | export function base64URLEncode(buffer: ArrayBuffer): string { 14 | const bytes = new Uint8Array(buffer) 15 | const base64 = btoa(String.fromCharCode(...bytes)) 16 | return base64 17 | .replace(/\+/g, '-') 18 | .replace(/\//g, '_') 19 | .replace(/=+$/, '') 20 | } -------------------------------------------------------------------------------- /server/src/services/cache.service.ts: -------------------------------------------------------------------------------- 1 | import NodeCache from "node-cache"; 2 | 3 | export class CacheService { 4 | private static instance: CacheService; 5 | private cache: NodeCache; 6 | 7 | private constructor() { 8 | this.cache = new NodeCache({ stdTTL: 600 }); // 10 minutes TTL 9 | } 10 | 11 | public static getInstance(): CacheService { 12 | if (!CacheService.instance) { 13 | CacheService.instance = new CacheService(); 14 | } 15 | return CacheService.instance; 16 | } 17 | 18 | public set(key: string, value: T): boolean { 19 | return this.cache.set(key, value); 20 | } 21 | 22 | public get(key: string): T | undefined { 23 | return this.cache.get(key); 24 | } 25 | 26 | public del(key: string): number { 27 | return this.cache.del(key); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: "@gitaalekhyapaul @alokt @jamesyoung" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment:** 27 | 28 | - OS: [e.g. iOS, Windows] 29 | - Browser: [e.g. Chrome, Safari] 30 | - Version: [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "moduleResolution": "bundler", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve", 19 | "incremental": true, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ], 25 | "paths": { 26 | "@/*": ["./*"] 27 | } 28 | }, 29 | "include": [ 30 | "next-env.d.ts", 31 | "**/*.ts", 32 | "**/*.tsx", 33 | ".next/types/**/*.ts", 34 | "app" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /server/src/routes/hello.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response, NextFunction } from "express"; 2 | 3 | const router = Router(); 4 | 5 | //middleware to check that NODE_ENV is only local development 6 | const checkNodeEnv = (_req: Request, res: Response, next: NextFunction) => { 7 | if (process.env.NODE_ENV !== "development") { 8 | res.status(403).json({ error: "Forbidden" }); 9 | return; 10 | } 11 | next(); 12 | }; 13 | 14 | //handles the collabland api token creation in .env 15 | const handlePostCollabLand = async (_req: Request, res: Response) => { 16 | console.log("Getting AI Agent Starter Kit ..."); 17 | res.status(200).json({ 18 | message: "AI Agent Starter Kit", 19 | timestamp: new Date().toISOString(), 20 | }); 21 | }; 22 | 23 | router.get("/collabland", checkNodeEnv, handlePostCollabLand); 24 | 25 | export default router; 26 | -------------------------------------------------------------------------------- /token_metadata.example.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CollabLand AI Agent Starter Kit", // Name of the token 3 | "symbol": "AGENT", // Symbol of the token 4 | "description": "AGENT is an AI memecoin launched using the Collab.Land AI Agent Starter Kit - https://github.com/collabland/AI-Agent-Starter-Kit. Use AI to code your own agent!", // Description of the token 5 | "websiteLink": "https://github.com/collabland/AI-Agent-Starter-Kit", // Website link of the token 6 | "twitter": "collab_land_", // Twitter handle of the token 7 | "discord": "https://discord.gg/collabland", // Discord link of the token 8 | "telegram": "collablandbot", // Telegram link of the bot handling the token 9 | "nsfw": true, // NSFW flag of the token 10 | "image": "ipfs://bafybeiaa5v7jjcpj453vwdch4ykhu6fkczrtmc2l2gnapdyr33suxdal5e" // Image of the token, should be uploaded to IPFS 11 | } 12 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "NodeNext", 5 | "lib": ["es2017", "esnext.asynciterable"], 6 | "skipLibCheck": true, 7 | "sourceMap": true, 8 | "outDir": "./dist", 9 | "rootDir": "./src", 10 | "moduleResolution": "NodeNext", 11 | "removeComments": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "noImplicitThis": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "allowSyntheticDefaultImports": true, 21 | "esModuleInterop": true, 22 | "emitDecoratorMetadata": true, 23 | "experimentalDecorators": true, 24 | "resolveJsonModule": true, 25 | "baseUrl": "." 26 | }, 27 | "include": ["src/**/*"], 28 | "exclude": ["node_modules", "dist"] 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | package-lock.json 9 | **/package-lock.json 10 | node_modules 11 | **/node_modules 12 | .next 13 | **/.next 14 | client/node_modules/ 15 | client/.next 16 | client/.env 17 | server/node_modules/ 18 | server/.next 19 | server/.env 20 | .env 21 | 22 | # testing 23 | /coverage 24 | 25 | # next.js 26 | /.next/ 27 | /out/ 28 | 29 | # production 30 | /build 31 | 32 | # misc 33 | .DS_Store 34 | *.pem 35 | 36 | # debug 37 | npm-debug.log* 38 | yarn-debug.log* 39 | yarn-error.log* 40 | 41 | # local env files 42 | .env*.local 43 | 44 | # vercel 45 | .vercel 46 | 47 | # typescript 48 | *.tsbuildinfo 49 | next-env.d.ts 50 | dist 51 | pnpm-lock.yaml 52 | *.sqlite 53 | 54 | # token metadata 55 | token.jsonc 56 | token.json 57 | 58 | .vscode/ 59 | lit-actions/actions/ 60 | twitter-cookies.json -------------------------------------------------------------------------------- /lit-actions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lit-actions", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "preinstall": "npx only-allow pnpm", 8 | "predev": "node bin/validate-env", 9 | "dev": "pnpm run build && pnpm start", 10 | "build": "node esbuild.js && tsc", 11 | "start": "node dist/index.js", 12 | "lint": "prettier --write . && eslint . --fix --config .eslintrc.json", 13 | "watch": "node bin/build-dev" 14 | }, 15 | "dependencies": { 16 | "buffer": "^6.0.3", 17 | "dotenv": "^16.4.5", 18 | "esbuild": "^0.24.2", 19 | "ethers": "^5.7.2", 20 | "glob": "^11.0.0", 21 | "pinata-web3": "^0.5.4", 22 | "prettier": "^3.4.2", 23 | "tsc-watch": "^6.2.1" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^22.10.1", 27 | "@typescript-eslint/eslint-plugin": "^7.3.1", 28 | "@typescript-eslint/parser": "^7.3.1", 29 | "eslint": "^8", 30 | "typescript": "^5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/src/plugins/collabland.plugin.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "@ai16z/eliza"; 2 | import { GetBotAccountAction } from "./actions/get-bot-account.action.js"; 3 | import { GetChainAction } from "./actions/get-chain.action.js"; 4 | import { CollabLandWalletBalanceProvider } from "./providers/collabland-wallet-balance.provider.js"; 5 | import { SendETHAction } from "./actions/send-eth.action.js"; 6 | import { CollabLandSolanaWalletBalanceProvider } from "./providers/collabland-solana-wallet-balance.provider.js"; 7 | import { SendSOLAction } from "./actions/send-sol.action.js"; 8 | export const collablandPlugin: Plugin = { 9 | name: "collabland", 10 | description: "Integrate Collab.Land smart account for the bot", 11 | 12 | actions: [ 13 | new GetChainAction(), 14 | new GetBotAccountAction(), 15 | new SendETHAction(), 16 | new SendSOLAction(), 17 | ], 18 | providers: [ 19 | new CollabLandWalletBalanceProvider(), 20 | new CollabLandSolanaWalletBalanceProvider(), 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /server/scripts/seed.mjs: -------------------------------------------------------------------------------- 1 | import { OrbisKeyDidAuth } from "@useorbis/db-sdk/auth"; 2 | import { writeFileSync, readFileSync } from "fs"; 3 | import { join } from "path"; 4 | 5 | const __dirname = new URL(".", import.meta.url).pathname; 6 | const path = join(__dirname, "..", "..", ".env"); 7 | 8 | const run = async () => { 9 | const seed = await OrbisKeyDidAuth.generateSeed(); 10 | console.log("Seed generated:", { 11 | seed, 12 | }); 13 | // read the .env file and append or replace the ORBIS_SEED 14 | const file = readFileSync(path, "utf8"); 15 | if (file.includes("ORBIS_SEED=")) { 16 | const newEnv = file.replace( 17 | /ORBIS_SEED=.*/, 18 | `ORBIS_SEED="${JSON.stringify(Array.from(seed))}"` 19 | ); 20 | writeFileSync(path, newEnv); 21 | } else { 22 | const newEnv = file + `\nORBIS_SEED="${JSON.stringify(Array.from(seed))}"`; 23 | writeFileSync(path, newEnv); 24 | } 25 | console.log("Seed saved to .env file:", path); 26 | }; 27 | run().catch(console.error); 28 | -------------------------------------------------------------------------------- /scripts/dev: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const concurrently = require("concurrently"); 4 | 5 | const { result } = concurrently([ 6 | { 7 | command: "pnpm run dev:server", 8 | name: "server", 9 | prefix: "server", 10 | prefixColor: "blue", 11 | handleInput: true, 12 | }, 13 | { 14 | command: "pnpm run dev:client", 15 | name: "client", 16 | prefix: "client", 17 | prefixColor: "green", 18 | handleInput: true, 19 | }, 20 | { 21 | command: "pnpm run dev:lit-actions", 22 | name: "lit-actions", 23 | prefix: "lit-actions", 24 | prefixColor: "yellow", 25 | handleInput: true, 26 | }, 27 | { 28 | command: "pnpm run tunnel", 29 | name: "tunnel", 30 | prefix: "tunnel", 31 | prefixColor: "magenta", 32 | handleInput: true, 33 | }, 34 | ]); 35 | 36 | result.catch((err) => { 37 | console.error(err); 38 | }); 39 | 40 | process.on("SIGINT", () => { 41 | process.exit(0); 42 | }); 43 | 44 | process.on("SIGTERM", () => { 45 | process.exit(0); 46 | }); 47 | -------------------------------------------------------------------------------- /client/bin/www-dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // ! This script is now deprecated, the tunneling happens in the root script 4 | const { config } = require("dotenv"); 5 | const { join } = require("path"); 6 | const concurrently = require("concurrently"); 7 | 8 | config({ 9 | path: join(__dirname, "..", ".env"), 10 | }); 11 | 12 | const { result } = concurrently( 13 | [ 14 | { 15 | command: "pnpm run next-dev", 16 | name: "next", 17 | prefixColor: "green", 18 | env: { 19 | NEXT_PROXY_LOGGING: 1, 20 | }, 21 | }, 22 | { 23 | command: "pnpm run www-tunnel", 24 | name: "tunnel", 25 | prefixColor: "green", 26 | env: { 27 | TUNNELMOLE_QUIET_MODE: 1, 28 | }, 29 | }, 30 | ], 31 | { 32 | raw: true, 33 | } 34 | ); 35 | 36 | result.catch((err) => { 37 | console.error(err); 38 | process.exit(0); 39 | }); 40 | 41 | process.on("SIGTERM", () => { 42 | process.exit(0); 43 | }); 44 | 45 | process.on("SIGINT", () => { 46 | process.exit(0); 47 | }); 48 | -------------------------------------------------------------------------------- /lit-actions/src/actions/hello-action.ts: -------------------------------------------------------------------------------- 1 | // Add this to the top of the file, so that it can reference the global.d.ts file 2 | /// 3 | 4 | const go = async () => { 5 | // Prints out the Lit Auth context 6 | console.log("[action] Lit.Auth:", Lit.Auth); 7 | // Converts the public key to a token ID 8 | const tokenId = await Lit.Actions.pubkeyToTokenId({ publicKey }); 9 | console.log("[action] tokenId:", tokenId); 10 | // Gets the permitted auth methods for the token ID 11 | const permittedAuthMethods = await Lit.Actions.getPermittedAuthMethods({ 12 | tokenId, 13 | }); 14 | console.log("[action] permittedAuthMethods:", permittedAuthMethods); 15 | // Signs the ECDSA signature 16 | const signature = await Lit.Actions.signEcdsa({ publicKey, toSign, sigName }); 17 | // Sets the response to the Lit Actions context 18 | Lit.Actions.setResponse({ 19 | response: JSON.stringify({ 20 | HelloName: helloName, 21 | timestamp: Date.now().toString(), 22 | }), 23 | }); 24 | // Returns the signature 25 | return signature; 26 | }; 27 | 28 | go(); 29 | -------------------------------------------------------------------------------- /server/bin/www-dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { spawn } from "child_process"; 4 | import { fileURLToPath } from "url"; 5 | import { dirname, resolve } from "path"; 6 | import fs from "fs"; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = dirname(__filename); 10 | 11 | console.log("Starting dev server..."); 12 | const env = process.env.NODE_ENV || "development"; 13 | 14 | let server; 15 | 16 | function startServer() { 17 | console.log("Server environment:", env); 18 | if (server) server.kill(); 19 | server = spawn("node", ["dist/index.js"]); 20 | server.stdout.pipe(process.stdout); 21 | server.stderr.pipe(process.stderr); 22 | } 23 | 24 | if (env !== "production") { 25 | fs.watchFile(resolve(__dirname, "../../.env"), () => { 26 | console.log(".env file changed, restarting server..."); 27 | startServer(); 28 | }); 29 | } 30 | 31 | startServer(); 32 | 33 | process.on("SIGTERM", () => { 34 | if (server) server.kill(); 35 | process.exit(0); 36 | }); 37 | 38 | process.on("SIGINT", () => { 39 | if (server) server.kill(); 40 | process.exit(0); 41 | }); 42 | -------------------------------------------------------------------------------- /client/app/_components/TelegramUser.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import TelegramLoginButton from "./TelegramLogin"; 5 | 6 | interface TelegramUser { 7 | id: number; 8 | first_name: string; 9 | username?: string; 10 | photo_url?: string; 11 | auth_date: number; 12 | hash: string; 13 | } 14 | 15 | export default function TelegramUser() { 16 | const [user, setUser] = useState(null); 17 | 18 | const handleTelegramAuth = (telegramUser: TelegramUser) => { 19 | console.log("Telegram auth successful:", telegramUser); 20 | setUser(telegramUser); 21 | }; 22 | 23 | return ( 24 |
25 | {!user ? ( 26 | 32 | ) : ( 33 |
34 | Welcome, Telegram User {user.first_name}! 35 |
36 | )} 37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /server/src/plugins/gated-storage-plugin/providers/provider.ts: -------------------------------------------------------------------------------- 1 | import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; 2 | import { StorageService } from "../services/storage.service.js"; 3 | 4 | export const gateDataProvider: Provider = { 5 | get: async ( 6 | _runtime: IAgentRuntime, 7 | message: Memory, 8 | _state?: State 9 | ): Promise => { 10 | try { 11 | if (!message.embedding) { 12 | return ""; 13 | } else { 14 | const storageService = StorageService.getInstance(); 15 | if (!storageService.isConfigured()) { 16 | return ""; 17 | } 18 | 19 | // pass in the user's eth address to get the storage provider 20 | const additionalContext = await storageService.getEmbeddingContext( 21 | message.embedding 22 | ); 23 | if (additionalContext) { 24 | return ( 25 | "[Important information from gated memory]: " + additionalContext 26 | ); 27 | } 28 | } 29 | return ""; 30 | } catch (error) { 31 | return error instanceof Error 32 | ? error.message 33 | : "Unable to get storage provider"; 34 | } 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /lit-actions/shims/gatedData.shim.js: -------------------------------------------------------------------------------- 1 | async function getEthereumAddressForPKP(pkp) { 2 | if (!pkp) { 3 | throw new Error("PKP is required"); 4 | } 5 | 6 | // Remove the 0x04 prefix from the uncompressed public key (first byte) 7 | const strippedPublicKey = pkp.startsWith("0x") ? pkp.slice(4) : pkp.slice(2); 8 | 9 | const publicKeyHash = ethers.utils.keccak256(`0x${strippedPublicKey}`); 10 | // Take the last 20 bytes of the hash (40 hex characters) for the Ethereum address 11 | const ethereumAddress = `0x${publicKeyHash.slice(-40)}`; 12 | 13 | return ethers.utils.getAddress(ethereumAddress); 14 | } 15 | 16 | async function getEncryptDecryptACL(pkp) { 17 | const addr = await getEthereumAddressForPKP(pkp); 18 | if (!addr) { 19 | throw new Error("Unable to get eth addresses for pkp"); 20 | } 21 | return [ 22 | { 23 | contractAddress: "evmBasic", 24 | standardContractType: "", 25 | chain: "base", 26 | method: "", 27 | parameters: [":userAddress"], 28 | returnValueTest: { 29 | comparator: "=", 30 | value: addr, 31 | }, 32 | }, 33 | ]; 34 | } 35 | 36 | globalThis.GatedData = { 37 | getEthereumAddressForPKP, 38 | getEncryptDecryptACL, 39 | }; 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Abridged, Inc. 2021,2024. 2 | Node module: @collabland/monorepo 3 | This project is licensed under the MIT License, full text below. 4 | 5 | --- 6 | 7 | MIT License 8 | 9 | Copyright (c) Abridged, Inc. 2021,2024 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of 12 | this software and associated documentation files (the "Software"), to deal in 13 | the Software without restriction, including without limitation the rights to 14 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 15 | the Software, and to permit persons to whom the Software is furnished to do so, 16 | subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 23 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 24 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 25 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 26 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "preinstall": "npx only-allow pnpm", 7 | "predev": "node bin/validate-env", 8 | "dev": "next dev", 9 | "www-tunnel": "node bin/www-tunnel", 10 | "build": "next build", 11 | "start": "next start", 12 | "lint": "next lint" 13 | }, 14 | "dependencies": { 15 | "@nextui-org/react": "^2.6.8", 16 | "@radix-ui/react-icons": "^1.3.2", 17 | "@radix-ui/react-slot": "^1.1.1", 18 | "@tanstack/react-query": "^5.60.5", 19 | "axios": "^1.7.7", 20 | "class-variance-authority": "^0.7.1", 21 | "clsx": "^2.1.1", 22 | "framer-motion": "^11.14.4", 23 | "lucide-react": "^0.469.0", 24 | "next": "15.1.0", 25 | "next-themes": "^0.4.4", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0", 28 | "tailwind-merge": "^2.6.0", 29 | "tailwindcss-animate": "^1.0.7" 30 | }, 31 | "devDependencies": { 32 | "@types/node": "^20", 33 | "@types/react": "^18.2.61", 34 | "@types/react-dom": "^18.2.19", 35 | "autoprefixer": "^10.4.18", 36 | "dotenv": "^16.4.5", 37 | "eslint": "^8.57.0", 38 | "eslint-config-next": "15.1.0", 39 | "postcss": "^8.4.35", 40 | "tailwindcss": "^3.4.1", 41 | "ts-node": "^10.9.2", 42 | "tunnelmole": "^2.3.1", 43 | "typescript": "^5.3.3" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client/app/claim/interstitial/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import { useSearchParams } from "next/navigation"; 5 | 6 | export default function InterstitialPage() { 7 | const searchParams = useSearchParams(); 8 | 9 | useEffect(() => { 10 | const successUri = searchParams.get("successUri"); 11 | 12 | console.log(successUri); 13 | 14 | // Send message to opener window if it exists 15 | if (window.opener) { 16 | window.opener.postMessage( 17 | { 18 | type: "TWITTER_AUTH_SUCCESS", 19 | successUri, 20 | }, 21 | "*" 22 | ); 23 | if (successUri) { 24 | window.close(); 25 | } 26 | } else { 27 | // Direct navigation - store token and redirect if successUri exists 28 | sessionStorage.setItem("success_auth", successUri || ""); 29 | if (successUri) { 30 | window.location.href = successUri; 31 | } 32 | } 33 | }, [searchParams]); 34 | 35 | return ( 36 |
37 |
38 |
39 |

Completing authentication...

40 |
41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /lit-actions/src/actions/decrypt-action.ts: -------------------------------------------------------------------------------- 1 | // Add this to the top of the file, so that it can reference the global.d.ts file 2 | /// 3 | 4 | //@ts-ignore 5 | const go = async () => { 6 | if (!ciphertext || !dataToEncryptHash || !chain) { 7 | Lit.Actions.setResponse({ 8 | response: JSON.stringify({ 9 | message: `bad_request: missing input`, 10 | timestamp: Date.now().toString(), 11 | }), 12 | }); 13 | return; 14 | } 15 | 16 | try { 17 | const accessControlConditions = 18 | await GatedData.getEncryptDecryptACL(publicKey); 19 | const decrypted = await Lit.Actions.decryptToSingleNode({ 20 | accessControlConditions, 21 | ciphertext, 22 | dataToEncryptHash, 23 | authSig: null, 24 | chain, 25 | }); 26 | // do nothing on nodes without data 27 | if (!decrypted) { 28 | return; 29 | } 30 | Lit.Actions.setResponse({ 31 | response: JSON.stringify({ 32 | message: "Successfully decrypted data", 33 | decrypted, 34 | timestamp: Date.now().toString(), 35 | }), 36 | }); 37 | } catch (err) { 38 | Lit.Actions.setResponse({ 39 | response: JSON.stringify({ 40 | message: `failed to decrypt data: ${err.message}`, 41 | timestamp: Date.now().toString(), 42 | }), 43 | }); 44 | } 45 | }; 46 | 47 | go(); 48 | -------------------------------------------------------------------------------- /server/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface IAccountInfo { 2 | pkpAddress: string; 3 | evm: { 4 | chainId: number; 5 | address: string; 6 | }[]; 7 | solana: { 8 | network: string; 9 | address: string; 10 | }[]; 11 | } 12 | 13 | export interface IExecuteUserOpRequest { 14 | target: string; 15 | value: string; 16 | calldata: string; 17 | } 18 | 19 | export interface IExecuteUserOpResponse { 20 | userOperationHash: string; 21 | chainId: number; 22 | } 23 | 24 | export interface ITransactionReceipt { 25 | transactionHash?: string; 26 | transactionIndex?: number; 27 | blockHash?: string; 28 | blockNumber?: number; 29 | from?: string; 30 | to?: string; 31 | cumulativeGasUsed?: number; 32 | status?: string; 33 | gasUsed?: number; 34 | contractAddress?: string | null; 35 | logsBloom?: string; 36 | effectiveGasPrice?: number; 37 | } 38 | 39 | export interface ILog { 40 | data?: string; 41 | blockNumber?: number; 42 | blockHash?: string; 43 | transactionHash?: string; 44 | logIndex?: number; 45 | transactionIndex?: number; 46 | address?: string; 47 | topics?: string[]; 48 | } 49 | 50 | export interface IUserOperationReceipt { 51 | userOpHash?: string; 52 | entryPoint?: string; 53 | sender?: string; 54 | nonce?: number; 55 | paymaster?: string; 56 | actualGasUsed?: number; 57 | actualGasCost?: number; 58 | success?: boolean; 59 | receipt?: ITransactionReceipt; 60 | logs?: ILog[]; 61 | } 62 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | 5 | ## Type of Change 6 | 7 | 8 | 9 | - [ ] 🐛 Bug fix (non-breaking change which fixes an issue) 10 | - [ ] ✨ New feature (non-breaking change which adds functionality) 11 | - [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - [ ] 📝 Documentation update 13 | - [ ] 🎨 Style update 14 | - [ ] ♻️ Code refactor 15 | - [ ] 🔧 Configuration changes 16 | 17 | ## How Has This Been Tested? 18 | 19 | 20 | 21 | - [ ] Unit Tests 22 | - [ ] Integration Tests 23 | - [ ] Manual Testing 24 | 25 | ## Screenshots (if applicable) 26 | 27 | 28 | 29 | ## Checklist 30 | 31 | 32 | 33 | - [ ] My code follows the style guidelines of this project 34 | - [ ] I have performed a self-review of my code 35 | - [ ] I have commented my code, particularly in hard-to-understand areas 36 | - [ ] I have made corresponding changes to the documentation 37 | - [ ] My changes generate no new warnings 38 | - [ ] I have added tests that prove my fix is effective or that my feature works 39 | - [ ] New and existing unit tests pass locally with my changes 40 | 41 | ## Related Issues 42 | 43 | 44 | -------------------------------------------------------------------------------- /lit-actions/src/actions/encrypt-action.ts: -------------------------------------------------------------------------------- 1 | // Add this to the top of the file, so that it can reference the global.d.ts file 2 | /// 3 | 4 | const encryptData = async (to_encrypt: Uint8Array) => { 5 | const accessControlConditions = 6 | await GatedData.getEncryptDecryptACL(publicKey); 7 | const res = await Lit.Actions.encrypt({ 8 | accessControlConditions, 9 | to_encrypt, 10 | }); 11 | return res; 12 | }; 13 | 14 | //@ts-ignore 15 | const go = async () => { 16 | if (!toEncrypt) { 17 | Lit.Actions.setResponse({ 18 | response: JSON.stringify({ 19 | message: "bad_request: invalid input", 20 | timestamp: Date.now().toString(), 21 | }), 22 | }); 23 | return; 24 | } 25 | try { 26 | // new buffer to avoid error about shared buffer view 27 | const { ciphertext, dataToEncryptHash } = await encryptData( 28 | new TextEncoder().encode(toEncrypt) 29 | ); 30 | Lit.Actions.setResponse({ 31 | response: JSON.stringify({ 32 | message: "Successfully encrypted data", 33 | ciphertext, 34 | dataToEncryptHash, 35 | timestamp: Date.now().toString(), 36 | }), 37 | }); 38 | } catch (err) { 39 | Lit.Actions.setResponse({ 40 | response: JSON.stringify({ 41 | message: `Failed to encrypt data (${toEncrypt}): ${err.message}`, 42 | timestamp: Date.now().toString(), 43 | }), 44 | }); 45 | } 46 | }; 47 | 48 | go(); 49 | -------------------------------------------------------------------------------- /server/src/plugins/actions/collabland.action.ts: -------------------------------------------------------------------------------- 1 | import { AnyType, getCollablandApiUrl } from "../../utils.js"; 2 | import { Action, ActionExample, Validator, Handler } from "@ai16z/eliza"; 3 | import axios, { AxiosInstance } from "axios"; 4 | 5 | export abstract class CollabLandBaseAction implements Action { 6 | protected client: AxiosInstance; 7 | 8 | constructor( 9 | public readonly name: string, 10 | public readonly description: string, 11 | public readonly similes: string[], 12 | public readonly examples: ActionExample[][], 13 | public readonly handler: Handler, 14 | public readonly validate: Validator 15 | ) { 16 | this.name = name; 17 | this.description = description; 18 | this.similes = similes; 19 | this.examples = examples; 20 | this.handler = handler; 21 | this.validate = validate; 22 | 23 | this.client = axios.create({ 24 | baseURL: getCollablandApiUrl(), 25 | headers: { 26 | "X-API-KEY": process.env.COLLABLAND_API_KEY || "", 27 | "X-TG-BOT-TOKEN": process.env.TELEGRAM_BOT_TOKEN || "", 28 | "Content-Type": "application/json", 29 | }, 30 | timeout: 5 * 60 * 1000, 31 | }); 32 | } 33 | 34 | protected handleError(error: AnyType): void { 35 | console.log(error); 36 | if (axios.isAxiosError(error)) { 37 | console.dir(error.response?.data, { depth: null }); 38 | throw new Error( 39 | `CollabLand API error: ${ 40 | error.response?.data?.message || error.message 41 | }` 42 | ); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-agent-starter-kit", 3 | "version": "2.0.0", 4 | "private": true, 5 | "scripts": { 6 | "gen-seed": "node ./server/scripts/seed.mjs", 7 | "deploy-model": "node ./server/scripts/deploy-model.mjs", 8 | "login-x": "pnpm --filter server run login-x", 9 | "preinstall": "npx only-allow pnpm", 10 | "predev": "pnpm --filter client run predev && pnpm --filter server run predev && pnpm --filter lit-actions run predev", 11 | "dev": "node scripts/dev", 12 | "tunnel": "node scripts/tunnel", 13 | "dev:client": "pnpm --filter client run dev", 14 | "dev:server": "pnpm --filter server run dev", 15 | "dev:lit-actions": "pnpm --filter lit-actions run watch", 16 | "build": "pnpm -r run build", 17 | "start": "pnpm -r run start", 18 | "lint": "pnpm -r run lint", 19 | "clean": "rimraf node_modules **/node_modules **/dist **/.next **/next-env.d.ts", 20 | "reset": "rimraf node_modules **/node_modules **/dist **/.next **/next-env.d.ts pnpm-lock.yaml eliza.sqlite twitter-cookies.json", 21 | "letsgo": "pnpm i && pnpm run dev", 22 | "version": "auto-changelog -p && git add CHANGELOG.md", 23 | "release": "standard-version", 24 | "prepare": "husky" 25 | }, 26 | "devDependencies": { 27 | "@ngrok/ngrok": "^1.4.1", 28 | "auto-changelog": "^2.4.0", 29 | "concurrently": "^8.2.2", 30 | "dotenv": "^16.4.5", 31 | "husky": "^9.1.7", 32 | "rimraf": "^5.0.5", 33 | "standard-version": "^9.5.0" 34 | }, 35 | "engines": { 36 | "node": ">=22" 37 | }, 38 | "author": "Abridged, Inc.", 39 | "license": "MIT", 40 | "packageManager": "pnpm@9.14.1" 41 | } 42 | -------------------------------------------------------------------------------- /lit-actions/src/index.ts: -------------------------------------------------------------------------------- 1 | import { PinataSDK } from "pinata-web3"; 2 | import fs from "fs"; 3 | import path from "path"; 4 | import dotenv from "dotenv"; 5 | 6 | dotenv.config(); 7 | 8 | const __dirname = new URL(".", import.meta.url).pathname; 9 | 10 | const main = async () => { 11 | const actionsDir = path.join(__dirname, "..", "actions"); 12 | const files = fs.readdirSync(actionsDir).filter((f) => f.endsWith(".js")); 13 | const results = {}; 14 | 15 | const pinata = new PinataSDK({ 16 | pinataJwt: process.env.PINATA_JWT, 17 | pinataGateway: process.env.PINATA_URL, 18 | }); 19 | 20 | for (const file of files) { 21 | const filePath = path.join(actionsDir, file); 22 | console.log("📁 Pinning file to IPFS:", filePath); 23 | const startTime = Date.now(); 24 | const stream = fs.createReadStream(filePath); 25 | const result = await pinata.upload.stream(stream).cidVersion(0); 26 | const endTime = Date.now(); 27 | const duration = endTime - startTime; 28 | console.log("🕒 Duration:", duration / 1000, "seconds"); 29 | console.log("🔗 IPFS CID:", result.IpfsHash); 30 | const name = file.replace(/.js$/, ""); // invoke actions as name only, without name.js 31 | results[name] = { 32 | ...result, 33 | file, 34 | Duration: duration / 1000, 35 | }; 36 | } 37 | 38 | const outputPath = path.join(actionsDir, "ipfs.json"); 39 | fs.writeFileSync(outputPath, JSON.stringify(results, null, 2)); 40 | console.log("🔥 Results:", results); 41 | console.log("✅ Done, output written to:", outputPath); 42 | }; 43 | 44 | main() 45 | .then(() => console.log("Done!")) 46 | .catch(console.error); 47 | -------------------------------------------------------------------------------- /server/src/plugins/types.ts: -------------------------------------------------------------------------------- 1 | export interface EVMAccount { 2 | chainId: number; 3 | address: string; 4 | } 5 | 6 | export interface SolanaAccount { 7 | network: string; 8 | address: string; 9 | } 10 | 11 | export interface BotAccountResponse { 12 | pkpAddress: string; 13 | evm: EVMAccount[]; 14 | solana: SolanaAccount[]; 15 | } 16 | 17 | export interface BotAccountMemory { 18 | smartAccount: string; 19 | signerAccount: string; 20 | chainId?: number; 21 | network?: string; 22 | type: "evm" | "solana"; 23 | } 24 | 25 | export type ExecuteUserOpResponse = { 26 | userOperationHash: string; 27 | chainId: number; 28 | }; 29 | 30 | export type ExecuteSolanaTransactionResponse = { 31 | txSignature: string; 32 | }; 33 | 34 | export interface TransactionReceipt { 35 | transactionHash?: string; 36 | transactionIndex?: number; 37 | blockHash?: string; 38 | blockNumber?: number; 39 | from?: string; 40 | to?: string; 41 | cumulativeGasUsed?: number; 42 | status?: string; 43 | gasUsed?: number; 44 | contractAddress?: string | null; 45 | logsBloom?: string; 46 | effectiveGasPrice?: number; 47 | } 48 | 49 | export interface Log { 50 | data?: string; 51 | blockNumber?: number; 52 | blockHash?: string; 53 | transactionHash?: string; 54 | logIndex?: number; 55 | transactionIndex?: number; 56 | address?: string; 57 | topics?: string[]; 58 | } 59 | 60 | export interface UserOperationReceipt { 61 | userOpHash?: string; 62 | entryPoint?: string; 63 | sender?: string; 64 | nonce?: number; 65 | paymaster?: string; 66 | actualGasUsed?: number; 67 | actualGasCost?: number; 68 | success?: boolean; 69 | receipt?: TransactionReceipt; 70 | logs?: Log[]; 71 | } 72 | -------------------------------------------------------------------------------- /client/app/_components/TelegramLogin.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect } from 'react' 4 | 5 | interface TelegramUser { 6 | id: number 7 | first_name: string 8 | last_name?: string 9 | username?: string 10 | photo_url?: string 11 | auth_date: number 12 | hash: string 13 | } 14 | 15 | interface TelegramLoginProps { 16 | botName: string 17 | onAuth?: (user: TelegramUser) => void 18 | buttonSize?: 'large' | 'medium' | 'small' 19 | cornerRadius?: number 20 | requestAccess?: boolean 21 | } 22 | 23 | export default function TelegramLoginButton({ 24 | botName, 25 | onAuth, 26 | buttonSize = 'large', 27 | cornerRadius = 20, 28 | requestAccess = true, 29 | }: TelegramLoginProps) { 30 | useEffect(() => { 31 | const script = document.createElement('script') 32 | script.src = 'https://telegram.org/js/telegram-widget.js?22' 33 | script.setAttribute('data-telegram-login', botName) 34 | script.setAttribute('data-size', buttonSize) 35 | script.setAttribute('data-radius', cornerRadius.toString()) 36 | script.setAttribute('data-request-access', 'write') 37 | script.setAttribute('data-userpic', 'false') 38 | script.setAttribute('data-onauth', 'onTelegramAuth(user)') 39 | script.async = true 40 | 41 | // @ts-expect-error window.onTelegramAuth is not typed 42 | window.onTelegramAuth = (user: TelegramUser) => { 43 | if (onAuth) { 44 | onAuth(user) 45 | } 46 | } 47 | 48 | const container = document.getElementById('telegram-login-button') 49 | container?.appendChild(script) 50 | 51 | return () => { 52 | container?.removeChild(script) 53 | } 54 | }, [botName, onAuth, buttonSize, cornerRadius, requestAccess]) 55 | 56 | return
57 | } -------------------------------------------------------------------------------- /client/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )) 33 | Alert.displayName = "Alert" 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )) 45 | AlertTitle.displayName = "AlertTitle" 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | AlertDescription.displayName = "AlertDescription" 58 | 59 | export { Alert, AlertTitle, AlertDescription } 60 | -------------------------------------------------------------------------------- /client/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}" 7 | ], 8 | theme: { 9 | extend: { 10 | colors: { 11 | primary: { 12 | DEFAULT: 'hsl(var(--primary))', 13 | foreground: 'hsl(var(--primary-foreground))' 14 | }, 15 | secondary: { 16 | DEFAULT: 'hsl(var(--secondary))', 17 | foreground: 'hsl(var(--secondary-foreground))' 18 | }, 19 | accent: { 20 | DEFAULT: 'hsl(var(--accent))', 21 | foreground: 'hsl(var(--accent-foreground))' 22 | }, 23 | background: 'hsl(var(--background))', 24 | foreground: 'hsl(var(--foreground))', 25 | card: { 26 | DEFAULT: 'hsl(var(--card))', 27 | foreground: 'hsl(var(--card-foreground))' 28 | }, 29 | popover: { 30 | DEFAULT: 'hsl(var(--popover))', 31 | foreground: 'hsl(var(--popover-foreground))' 32 | }, 33 | muted: { 34 | DEFAULT: 'hsl(var(--muted))', 35 | foreground: 'hsl(var(--muted-foreground))' 36 | }, 37 | destructive: { 38 | DEFAULT: 'hsl(var(--destructive))', 39 | foreground: 'hsl(var(--destructive-foreground))' 40 | }, 41 | border: 'hsl(var(--border))', 42 | input: 'hsl(var(--input))', 43 | ring: 'hsl(var(--ring))', 44 | chart: { 45 | '1': 'hsl(var(--chart-1))', 46 | '2': 'hsl(var(--chart-2))', 47 | '3': 'hsl(var(--chart-3))', 48 | '4': 'hsl(var(--chart-4))', 49 | '5': 'hsl(var(--chart-5))' 50 | } 51 | }, 52 | borderRadius: { 53 | lg: 'var(--radius)', 54 | md: 'calc(var(--radius) - 2px)', 55 | sm: 'calc(var(--radius) - 4px)' 56 | } 57 | } 58 | }, 59 | plugins: [require("tailwindcss-animate")], 60 | } as const; -------------------------------------------------------------------------------- /client/bin/validate-env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const readline = require("readline"); 6 | const { promisify } = require("util"); 7 | 8 | // Define possible hints/values for different env vars 9 | const ENV_HINTS = { 10 | NEXT_PUBLIC_API_URL: 11 | "Enter the URL your API should run on, usually http://localhost:3001 or http://localhost:8081", 12 | NEXT_PUBLIC_TELEGRAM_BOT_NAME: 13 | "Enter your Telegram bot name without the @ symbol", 14 | // Add more hints as needed from .env.example 15 | }; 16 | 17 | const rl = readline.createInterface({ 18 | input: process.stdin, 19 | output: process.stdout, 20 | }); 21 | 22 | const question = promisify(rl.question).bind(rl); 23 | 24 | async function main() { 25 | const envExamplePath = path.join(__dirname, "..", ".env.example"); 26 | const envPath = path.join(__dirname, "..", ".env"); 27 | 28 | // Read .env.example 29 | const exampleContent = fs.readFileSync(envExamplePath, "utf8"); 30 | const envContent = fs.existsSync(envPath) 31 | ? fs.readFileSync(envPath, "utf8") 32 | : ""; 33 | 34 | // Parse variables 35 | const exampleVars = exampleContent 36 | .split("\n") 37 | .filter((line) => line && !line.startsWith("#")) 38 | .map((line) => line.split("=")[0]); 39 | 40 | const existingVars = new Set( 41 | envContent 42 | .split("\n") 43 | .filter((line) => line && !line.startsWith("#")) 44 | .map((line) => line.split("=")[0]) 45 | ); 46 | 47 | // Collect missing variables 48 | let newEnvContent = envContent; 49 | 50 | for (const varName of exampleVars) { 51 | if (!existingVars.has(varName)) { 52 | const hint = ENV_HINTS[varName] ?? "No hint available"; 53 | console.log(`HINT: ${hint}`); 54 | const value = await question(`[client] Enter value for ${varName}: `); 55 | newEnvContent += `\n${varName}=${value}`; 56 | } 57 | } 58 | 59 | // Save to .env 60 | fs.writeFileSync(envPath, newEnvContent.trim() + "\n"); 61 | rl.close(); 62 | } 63 | 64 | main().catch(console.error); 65 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "preinstall": "npx only-allow pnpm", 8 | "predev": "node bin/validate-env && node bin/check-x-login", 9 | "dev": "tsc-watch --noClear --onEmitDebounceMs 15000 --onSuccess \"pnpm run www-dev\"", 10 | "build": "tsc && pnpm run generate-types", 11 | "start": "node dist/index.js", 12 | "lint": "prettier --write . && eslint . --fix --config .eslintrc.json", 13 | "www-dev": "node bin/www-dev", 14 | "login-x": "node bin/check-x-login", 15 | "generate-types": "typechain --node16-modules --target ethers-v6 --out-dir src/contracts/types 'src/contracts/abis/*.json'" 16 | }, 17 | "dependencies": { 18 | "@ai16z/adapter-sqlite": "0.1.5-alpha.3", 19 | "@ai16z/eliza": "0.1.5-alpha.3", 20 | "@ai16z/plugin-bootstrap": "0.1.5-alpha.3", 21 | "@grammyjs/conversations": "^1.2.0", 22 | "@ngrok/ngrok": "^1.4.1", 23 | "@solana/web3.js": "^1.98.0", 24 | "agent-twitter-client": "^0.0.18", 25 | "@useorbis/db-sdk": "0.0.60-alpha", 26 | "axios": "^1.7.7", 27 | "better-sqlite3": "^11.6.0", 28 | "cookie-parser": "^1.4.7", 29 | "cors": "^2.8.5", 30 | "dotenv": "^16.4.5", 31 | "ethers": "^6.9.0", 32 | "express": "^4.21.1", 33 | "grammy": "^1.32.0", 34 | "http-errors": "^2.0.0", 35 | "jsonc-parser": "^3.3.1", 36 | "node-cache": "^5.1.2", 37 | "prettier": "^3.4.2", 38 | "tsc-watch": "^6.2.1" 39 | }, 40 | "devDependencies": { 41 | "@typechain/ethers-v6": "^0.5.1", 42 | "@types/better-sqlite3": "^7.6.12", 43 | "@types/cookie-parser": "^1.4.8", 44 | "@types/cors": "^2.8.17", 45 | "@types/express": "^5.0.0", 46 | "@types/http-errors": "^2.0.4", 47 | "@types/morgan": "^1.9.9", 48 | "@types/node": "^20.17.6", 49 | "@types/node-cache": "^4.2.5", 50 | "@types/node-fetch": "^2.6.11", 51 | "@typescript-eslint/eslint-plugin": "^7.3.1", 52 | "@typescript-eslint/parser": "^7.3.1", 53 | "eslint": "^8", 54 | "typechain": "^8.3.2", 55 | "typescript": "^5" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lit-actions/bin/validate-env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from "fs"; 4 | import path from "path"; 5 | import readline from "readline"; 6 | import { promisify } from "util"; 7 | 8 | // Define possible hints/values for different env vars 9 | const ENV_HINTS = { 10 | PINATA_JWT: 11 | "Enter your Pinata JWT, you can get it from https://app.pinata.cloud/developers/api-keys", 12 | PINATA_URL: 13 | "Enter your Pinata URL, you can get it from https://app.pinata.cloud/gateway", 14 | // Add more hints as needed from .env.example 15 | }; 16 | 17 | const rl = readline.createInterface({ 18 | input: process.stdin, 19 | output: process.stdout, 20 | }); 21 | const __dirname = path.dirname(new URL(import.meta.url).pathname); 22 | const question = promisify(rl.question).bind(rl); 23 | 24 | async function main() { 25 | const envExamplePath = path.join(__dirname, "..", ".env.example"); 26 | const envPath = path.join(__dirname, "..", ".env"); 27 | 28 | // Read .env.example 29 | const exampleContent = fs.readFileSync(envExamplePath, "utf8"); 30 | const envContent = fs.existsSync(envPath) 31 | ? fs.readFileSync(envPath, "utf8") 32 | : ""; 33 | 34 | // Parse variables 35 | const exampleVars = exampleContent 36 | .split("\n") 37 | .filter((line) => line && !line.startsWith("#")) 38 | .map((line) => line.split("=")[0]); 39 | 40 | const existingVars = new Set( 41 | envContent 42 | .split("\n") 43 | .filter((line) => line && !line.startsWith("#")) 44 | .map((line) => line.split("=")[0]) 45 | ); 46 | 47 | // Collect missing variables 48 | let newEnvContent = envContent; 49 | 50 | for (const varName of exampleVars) { 51 | if (!existingVars.has(varName)) { 52 | const hint = ENV_HINTS[varName] ?? "No hint available"; 53 | console.log(`HINT: ${hint}`); 54 | const value = await question(`[server] Enter value for ${varName}: `); 55 | newEnvContent += `\n${varName}=${value ?? ""}`; 56 | } 57 | } 58 | 59 | // Save to .env 60 | fs.writeFileSync(envPath, newEnvContent.trim() + "\n"); 61 | rl.close(); 62 | } 63 | 64 | main().catch(console.error); 65 | -------------------------------------------------------------------------------- /client/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /client/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLDivElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |
64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /client/bin/www-tunnel: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | // ! This script is now deprecated, the tunneling happens in the root script 4 | import { config } from "dotenv"; 5 | import { join } from "path"; 6 | import { tunnelmole } from "tunnelmole"; 7 | import fs from "fs/promises"; 8 | 9 | const __dirname = new URL(".", import.meta.url).pathname; 10 | const ENV_PATH = join(__dirname, "..", ".env"); 11 | const ROOT_ENV_PATH = join(__dirname, "..", "..", ".env"); 12 | 13 | config({ 14 | path: ENV_PATH, 15 | }); 16 | 17 | const updateEnvFile = async (tunnelUrl, envPath = ENV_PATH) => { 18 | try { 19 | console.log( 20 | `[INFO] Updating tunnelmole URL in .env (Location: ${envPath})...` 21 | ); 22 | const envContent = await fs.readFile(envPath, "utf-8"); 23 | const hasHostname = envContent.includes("NEXT_PUBLIC_HOSTNAME="); 24 | 25 | if (hasHostname) { 26 | const newContent = envContent.replace( 27 | /NEXT_PUBLIC_HOSTNAME=.*/, 28 | `NEXT_PUBLIC_HOSTNAME=${tunnelUrl}` 29 | ); 30 | await fs.writeFile(envPath, newContent); 31 | } else { 32 | await fs.appendFile(envPath, `\nNEXT_PUBLIC_HOSTNAME=${tunnelUrl}`); 33 | } 34 | console.log( 35 | `[INFO] Updated NEXT_PUBLIC_HOSTNAME in .env (Location: ${envPath}) to ${tunnelUrl}` 36 | ); 37 | } catch (err) { 38 | console.error("[ERROR] Failed to update .env file:", err); 39 | } 40 | }; 41 | 42 | const main = async () => { 43 | const port = process.env.TUNNEL_PORT || 3000; 44 | console.log(`Waiting 5 seconds for Next.js app to start...`); 45 | await new Promise((resolve) => setTimeout(resolve, 5000)); 46 | console.log(`Starting front-end tunnel on PORT ${port}...`); 47 | 48 | const url = await tunnelmole({ 49 | port: Number(port), 50 | domain: process.env.TUNNEL_DOMAIN, 51 | }); 52 | console.log(`[INFO] Visit ${url} to view your Next.js app`); 53 | 54 | await updateEnvFile(url); 55 | await updateEnvFile(url, ROOT_ENV_PATH); 56 | 57 | process.on("SIGINT", () => { 58 | process.exit(0); 59 | }); 60 | process.on("SIGTERM", () => { 61 | process.exit(0); 62 | }); 63 | }; 64 | 65 | main().catch((err) => { 66 | console.error(err); 67 | process.exit(0); 68 | }); 69 | -------------------------------------------------------------------------------- /server/src/services/ngrok.service.ts: -------------------------------------------------------------------------------- 1 | // import { AnyType } from "../utils.js"; 2 | import { BaseService } from "./base.service.js"; 3 | // import { forward, Listener } from "@ngrok/ngrok"; 4 | 5 | export class NgrokService extends BaseService { 6 | private static instance: NgrokService; 7 | // private url: string | null = null; 8 | // private listener: Listener | null; 9 | 10 | private constructor() { 11 | super(); 12 | // this.listener = null; 13 | } 14 | 15 | public static getInstance(): NgrokService { 16 | if (!NgrokService.instance) { 17 | NgrokService.instance = new NgrokService(); 18 | } 19 | return NgrokService.instance; 20 | } 21 | 22 | public async start(): Promise { 23 | // ! Moved the NGROK tunnel creation to the main script 24 | // ! It injects a NGROK_URL env variable to the .env file in the root 25 | // if (!process.env.NGROK_AUTH_TOKEN) { 26 | // throw new Error("NGROK_AUTH_TOKEN is required"); 27 | // } 28 | // if (!process.env.PORT) { 29 | // throw new Error("PORT is required"); 30 | // } 31 | // if (!process.env.NGROK_DOMAIN) { 32 | // throw new Error("NGROK_DOMAIN is required"); 33 | // } 34 | // try { 35 | // this.listener = await forward({ 36 | // addr: process.env.PORT, 37 | // proto: "http", 38 | // authtoken: process.env.NGROK_AUTH_TOKEN, 39 | // domain: process.env.NGROK_DOMAIN, 40 | // }); 41 | // this.url = this.listener.url(); 42 | // console.log("NGROK tunnel created:", this.url); 43 | // return; 44 | // } catch (error: AnyType) { 45 | // console.error("NGROK Error Details:", { 46 | // message: error.message, 47 | // response: error.response?.statusCode, 48 | // body: error.response?.body, 49 | // }); 50 | // throw new Error("Failed to create NGROK tunnel"); 51 | // } 52 | } 53 | 54 | public getUrl(): string | undefined { 55 | return process.env.NGROK_URL; 56 | } 57 | 58 | public async stop(): Promise { 59 | // if (this.listener) { 60 | // await this.listener.close(); 61 | // this.listener = null; 62 | // this.url = null; 63 | // } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment: 12 | 13 | - Using welcoming and inclusive language 14 | - Being respectful of differing viewpoints and experiences 15 | - Gracefully accepting constructive criticism 16 | - Focusing on what is best for the community 17 | - Showing empathy towards other community members 18 | 19 | Examples of unacceptable behavior: 20 | 21 | - The use of sexualized language or imagery, and sexual attention or advances of any kind 22 | - Trolling, insulting or derogatory comments, and personal or political attacks 23 | - Public or private harassment 24 | - Publishing others' private information without explicit permission 25 | - Other conduct which could reasonably be considered inappropriate in a professional setting 26 | 27 | ## Enforcement Responsibilities 28 | 29 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Enforcement 32 | 33 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement. All complaints will be reviewed and investigated promptly and fairly. 34 | 35 | ## Attribution 36 | 37 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). 38 | -------------------------------------------------------------------------------- /server/src/utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | 3 | import { resolve } from "path"; 4 | const __dirname = new URL(".", import.meta.url).pathname; 5 | import { config } from "dotenv"; 6 | config(); 7 | 8 | export type AnyType = any; 9 | export const chainMap: Record = { 10 | ethereum: "11155111", 11 | base: "84532", 12 | linea: "59141", 13 | solana: "sol_dev", 14 | }; 15 | 16 | export const getTokenMetadataPath = () => { 17 | const path = resolve( 18 | __dirname, 19 | "..", 20 | "..", 21 | process.env.TOKEN_DETAILS_PATH || "token_metadata.example.jsonc" 22 | ); 23 | console.log("tokenMetadataPath:", path); 24 | return path; 25 | }; 26 | 27 | export interface TokenMetadata { 28 | name: string; 29 | symbol: string; 30 | description: string; 31 | websiteLink: string; 32 | twitter: string; 33 | discord: string; 34 | telegram: string; 35 | nsfw: boolean; 36 | image: string; 37 | } 38 | 39 | export interface MintResponse { 40 | response: { 41 | contract: { 42 | fungible: { 43 | object: string; 44 | name: string; 45 | symbol: string; 46 | media: string | null; 47 | address: string; 48 | decimals: number; 49 | }; 50 | }; 51 | }; 52 | } 53 | 54 | export const getCollablandApiUrl = () => { 55 | return ( 56 | process.env.COLLABLAND_API_URL || "https://api-qa.collab.land/accountkit/v1" 57 | ); 58 | }; 59 | 60 | export const getCardHTML = (botUsername: string, claimURL: string) => { 61 | return ` 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | Claim token airdrop. 80 | 81 | 82 | `; 83 | }; 84 | -------------------------------------------------------------------------------- /server/src/plugins/providers/collabland-wallet-balance.provider.ts: -------------------------------------------------------------------------------- 1 | import { AnyType } from "../../utils.js"; 2 | import { Memory, Provider, IAgentRuntime, State } from "@ai16z/eliza"; 3 | import { ethers } from "ethers"; 4 | import { chainMap } from "../../utils.js"; 5 | import { BotAccountMemory } from "../types.js"; 6 | 7 | export class CollabLandWalletBalanceProvider implements Provider { 8 | async get( 9 | _runtime: IAgentRuntime, 10 | _message: Memory, 11 | _state?: State 12 | ): Promise { 13 | let chain: string | null = null; 14 | const onChainMemoryManager = _runtime.getMemoryManager("onchain")!; 15 | // this is newest to oldest 16 | const onChainMemories = await onChainMemoryManager.getMemories({ 17 | roomId: _message.roomId, 18 | unique: false, 19 | }); 20 | console.log( 21 | "[CollabLandWalletBalanceProvider] onChainMemories", 22 | onChainMemories 23 | ); 24 | for (const memory of onChainMemories) { 25 | if (memory.content.chain !== undefined) { 26 | chain = memory.content.chain as string; 27 | break; 28 | } 29 | } 30 | // Get the chain Id 31 | if (chain == null) { 32 | return ""; 33 | } 34 | console.log( 35 | "[CollabLandWalletBalanceProvider] chain found in memories", 36 | chain 37 | ); 38 | 39 | const chainId = chainMap[chain as keyof typeof chainMap]; 40 | if (!chainId) { 41 | return ""; 42 | } 43 | 44 | let account: BotAccountMemory | null = null; 45 | for (const memory of onChainMemories) { 46 | if ( 47 | memory.content.smartAccount && 48 | memory.content.type === "evm" && 49 | memory.content.chainId == chainId 50 | ) { 51 | account = memory.content as unknown as BotAccountMemory; 52 | break; 53 | } 54 | } 55 | 56 | if (!account?.smartAccount) { 57 | return ""; 58 | } 59 | console.log( 60 | "[CollabLandWalletBalanceProvider] account found in memories", 61 | account 62 | ); 63 | const provider = ethers.getDefaultProvider(account.chainId); 64 | const balance = await provider.getBalance(account.smartAccount as string); 65 | const formattedBalance = ethers.formatEther(balance); 66 | console.log("[CollabLandWalletBalanceProvider] balance", formattedBalance); 67 | return `Agent's balance is ${formattedBalance} ETH on ${chain}`; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Example .env file for the API server 2 | # If you want to add new variables, please do it in the format below 3 | # = 4 | # On the next iteration of pnpm run dev, the new variables picked up by the validate-env script will be added to the .env file 5 | # You can add hints to the ENV_HINTS object in the validate-env script to help the user with the new variables 6 | 7 | PORT= 8 | NODE_ENV= 9 | TELEGRAM_BOT_TOKEN= 10 | OPENAI_API_KEY= 11 | NGROK_AUTH_TOKEN= 12 | JOKERACE_CONTRACT_ADDRESS= 13 | NGROK_DOMAIN= 14 | COLLABLAND_API_KEY= 15 | GAIANET_MODEL= 16 | GAIANET_SERVER_URL= 17 | GAIANET_EMBEDDING_MODEL= 18 | USE_GAIANET_EMBEDDING= 19 | ELIZA_CHARACTER_PATH= 20 | TOKEN_DETAILS_PATH= 21 | TWITTER_CLIENT_ID= 22 | TWITTER_CLIENT_SECRET= 23 | DISCORD_CLIENT_ID= 24 | DISCORD_CLIENT_SECRET= 25 | GITHUB_CLIENT_ID= 26 | GITHUB_CLIENT_SECRET= 27 | TWITTER_USERNAME= 28 | TWITTER_PASSWORD= 29 | TWITTER_API_KEY= 30 | TWITTER_API_SECRET_KEY= 31 | TWITTER_ACCESS_TOKEN= 32 | TWITTER_ACCESS_TOKEN_SECRET= 33 | 34 | # GATED DATA (optional). Server will start without these and not use the feature 35 | ORBIS_CONTEXT_ID= 36 | ORBIS_TABLE_ID= # This is a placeholder table ID, please replace it with your own table ID with pnpm run deploy-model 37 | ORBIS_ENV= # Login to https://studio.useorbis.com/ and copy the environment ID 38 | ORBIS_SEED= # Run pnpm run gen-seed to generate a seed 39 | ORBIS_GATEWAY_URL="https://studio.useorbis.com" # Your local Orbis server URL is typically this in dev - keep as is 40 | CERAMIC_NODE_URL="https://ceramic-orbisdb-mainnet-direct.hirenodes.io/" # Default shared node - keep as is 41 | USE_OPENAI_EMBEDDING=TRUE # Need to set this to TRUE to use gated data functionality 42 | -------------------------------------------------------------------------------- /client/app/_components/GithubLogin.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { useEffect, useState } from "react"; 5 | 6 | export function GithubLogin() { 7 | const [isLoading, setIsLoading] = useState(false); 8 | 9 | useEffect(() => { 10 | const params = new URLSearchParams(window.location.search); 11 | const token = params.get("github_token"); 12 | 13 | if (token) { 14 | sessionStorage.setItem("github_token", token); 15 | window.history.replaceState({}, "", window.location.pathname); 16 | } 17 | }, []); 18 | 19 | const handleGithubLogin = async (e: React.MouseEvent) => { 20 | e.preventDefault(); 21 | setIsLoading(true); 22 | 23 | try { 24 | const response = await fetch(`/api/auth/github/init`, { 25 | method: "POST", 26 | headers: { 27 | "Content-Type": "application/json", 28 | }, 29 | }); 30 | 31 | if (!response.ok) { 32 | throw new Error(`HTTP error! status: ${response.status}`); 33 | } 34 | 35 | const data = await response.json(); 36 | sessionStorage.setItem("github_redirect_url", window.location.href); 37 | window.location.href = data.authUrl; 38 | } catch (error: unknown) { 39 | setIsLoading(false); 40 | } 41 | }; 42 | 43 | return ( 44 |
45 | 54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /server/src/services/twitter.service.ts: -------------------------------------------------------------------------------- 1 | import { BaseService } from "./base.service.js"; 2 | import { Profile, Scraper } from "agent-twitter-client"; 3 | import fs from "fs/promises"; 4 | import { join, dirname } from "path"; 5 | 6 | const __dirname = dirname(new URL(import.meta.url).pathname); 7 | const twitterCookiesPath = join( 8 | __dirname, 9 | "..", 10 | "..", 11 | "..", 12 | "twitter-cookies.json" 13 | ); 14 | 15 | export class TwitterService extends BaseService { 16 | private static instance: TwitterService; 17 | private scraper: Scraper | null = null; 18 | private isConnected: boolean = false; 19 | public me: Profile | undefined = undefined; 20 | 21 | private constructor() { 22 | super(); 23 | } 24 | 25 | public static getInstance(): TwitterService { 26 | if (!TwitterService.instance) { 27 | TwitterService.instance = new TwitterService(); 28 | } 29 | return TwitterService.instance; 30 | } 31 | 32 | public async start(): Promise { 33 | try { 34 | console.log("[TwitterService] Starting service..."); 35 | if (!(await fs.stat(twitterCookiesPath).catch(() => false))) { 36 | throw new Error( 37 | "Twitter cookies not found. Please run the `pnpm login-x` script first." 38 | ); 39 | } 40 | console.log( 41 | "[TwitterService] Loading Twitter cookies from:", 42 | twitterCookiesPath 43 | ); 44 | const cookieJson = await fs.readFile(twitterCookiesPath, "utf-8"); 45 | const cookiesJSON = JSON.parse(cookieJson); 46 | this.scraper = new Scraper(); 47 | await this.scraper.setCookies(cookiesJSON.cookies); 48 | console.log("[TwitterService] Starting service with existing cookies..."); 49 | const connected = await this.scraper.isLoggedIn(); 50 | if (!connected) { 51 | throw new Error("Failed to login with existing cookies."); 52 | } 53 | this.me = await this.scraper.me(); 54 | this.isConnected = true; 55 | } catch (error) { 56 | console.error("[TwitterService] Error:", error); 57 | throw new Error( 58 | "Twitter cookies not found. Please run the `pnpm letsgo` script first." 59 | ); 60 | } 61 | } 62 | 63 | public async stop(): Promise { 64 | if (this.isConnected && this.scraper) { 65 | await this.scraper.clearCookies(); 66 | this.isConnected = false; 67 | } 68 | } 69 | 70 | public getScraper(): Scraper { 71 | if (!this.scraper) { 72 | throw new Error("Twitter service not started"); 73 | } 74 | return this.scraper; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /server/src/plugins/providers/collabland-solana-wallet-balance.provider.ts: -------------------------------------------------------------------------------- 1 | import { AnyType } from "../../utils.js"; 2 | import { Memory, Provider, IAgentRuntime, State } from "@ai16z/eliza"; 3 | import { chainMap } from "../../utils.js"; 4 | import { BotAccountMemory } from "../types.js"; 5 | import { 6 | clusterApiUrl, 7 | Connection, 8 | PublicKey, 9 | LAMPORTS_PER_SOL, 10 | } from "@solana/web3.js"; 11 | 12 | export class CollabLandSolanaWalletBalanceProvider implements Provider { 13 | async get( 14 | _runtime: IAgentRuntime, 15 | _message: Memory, 16 | _state?: State 17 | ): Promise { 18 | let chain: string | null = null; 19 | const onChainMemoryManager = _runtime.getMemoryManager("onchain")!; 20 | // this is newest to oldest 21 | const onChainMemories = await onChainMemoryManager.getMemories({ 22 | roomId: _message.roomId, 23 | unique: false, 24 | }); 25 | console.log( 26 | "[CollabLandSolanaWalletBalanceProvider] onChainMemories", 27 | onChainMemories 28 | ); 29 | for (const memory of onChainMemories) { 30 | if (memory.content.chain !== undefined) { 31 | chain = memory.content.chain as string; 32 | break; 33 | } 34 | } 35 | // Get the chain Id 36 | if (chain == null) { 37 | return ""; 38 | } 39 | console.log( 40 | "[CollabLandSolanaWalletBalanceProvider] chain found in memories", 41 | chain 42 | ); 43 | 44 | const chainId = chainMap[chain as keyof typeof chainMap]; 45 | if (!chainId) { 46 | return ""; 47 | } 48 | console.log("[CollabLandSolanaWalletBalanceProvider] chainId", chainId); 49 | 50 | if (!chainId.startsWith("sol")) { 51 | return ""; 52 | } 53 | 54 | let account: BotAccountMemory | null = null; 55 | for (const memory of onChainMemories) { 56 | if ( 57 | memory.content.smartAccount && 58 | memory.content.type === "solana" && 59 | memory.content.network == chainId 60 | ) { 61 | account = memory.content as unknown as BotAccountMemory; 62 | break; 63 | } 64 | } 65 | 66 | if (!account?.smartAccount) { 67 | return ""; 68 | } 69 | console.log( 70 | "[CollabLandSolanaWalletBalanceProvider] account found in memories", 71 | account 72 | ); 73 | const connection = new Connection( 74 | clusterApiUrl(chainId === "sol_dev" ? "devnet" : "mainnet-beta"), 75 | "confirmed" 76 | ); 77 | const wallet = new PublicKey(account.smartAccount); 78 | 79 | const balance = await connection.getBalance(wallet); 80 | const formattedBalance = balance / LAMPORTS_PER_SOL; 81 | console.log( 82 | "[CollabLandSolanaWalletBalanceProvider] balance", 83 | formattedBalance 84 | ); 85 | return `Agent's balance is ${formattedBalance} SOL on ${chain}`; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /client/app/_components/DiscordLogin.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { useEffect, useState } from "react"; 5 | 6 | export function DiscordLogin() { 7 | const [isLoading, setIsLoading] = useState(false); 8 | 9 | useEffect(() => { 10 | const params = new URLSearchParams(window.location.search); 11 | const token = params.get("discord_token"); 12 | 13 | if (token) { 14 | sessionStorage.setItem("discord_token", token); 15 | window.history.replaceState({}, "", window.location.pathname); 16 | } 17 | }, []); 18 | 19 | const handleDiscordLogin = async (e: React.MouseEvent) => { 20 | e.preventDefault(); 21 | setIsLoading(true); 22 | 23 | try { 24 | const response = await fetch(`/api/auth/discord/init`, { 25 | method: "POST", 26 | headers: { 27 | "Content-Type": "application/json", 28 | }, 29 | }); 30 | 31 | if (!response.ok) { 32 | throw new Error(`HTTP error! status: ${response.status}`); 33 | } 34 | 35 | const data = await response.json(); 36 | sessionStorage.setItem("discord_redirect_url", window.location.href); 37 | window.location.href = data.authUrl; 38 | } catch (error: unknown) { 39 | setIsLoading(false); 40 | } 41 | }; 42 | 43 | return ( 44 |
45 | 56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /server/scripts/deploy-model.mjs: -------------------------------------------------------------------------------- 1 | import { OrbisDB } from "@useorbis/db-sdk"; 2 | import { OrbisKeyDidAuth } from "@useorbis/db-sdk/auth"; 3 | import { config } from "dotenv"; 4 | import { join } from "path"; 5 | import { readFileSync, writeFileSync } from "fs"; 6 | 7 | const __dirname = new URL(".", import.meta.url).pathname; 8 | const path = join(__dirname, "..", "..", ".env"); 9 | 10 | config({ 11 | path: path, 12 | }); 13 | const db = new OrbisDB({ 14 | ceramic: { 15 | gateway: "https://ceramic-orbisdb-mainnet-direct.hirenodes.io/", 16 | }, 17 | nodes: [ 18 | { 19 | gateway: "https://studio.useorbis.com", 20 | env: process.env.ORBIS_ENV, 21 | }, 22 | ], 23 | }); 24 | 25 | const embeddingModel = { 26 | name: "EmbeddingModel", 27 | schema: { 28 | type: "object", 29 | properties: { 30 | embedding: { 31 | type: "array", 32 | items: { 33 | type: "number", 34 | }, 35 | examples: [ 36 | { 37 | "x-orbisdb": { 38 | postgres: { 39 | type: "vector(1536)", 40 | index: { 41 | method: "ivfflat", 42 | storage: "(lists = 100)", 43 | predicate: "embedding IS NOT NULL", 44 | }, 45 | extensions: ["vector"], 46 | }, 47 | }, 48 | }, 49 | ], 50 | }, 51 | content: { 52 | type: "string", 53 | }, 54 | is_user: { 55 | type: "boolean", 56 | }, 57 | }, 58 | additionalProperties: false, 59 | }, 60 | version: "2.0", 61 | interface: false, 62 | implements: [], 63 | description: "Embedding Test model", 64 | accountRelation: { 65 | type: "list", 66 | }, 67 | }; 68 | 69 | const run = async () => { 70 | if (!process.env.ORBIS_SEED || !process.env.ORBIS_ENV) { 71 | throw new Error( 72 | "ORBIS_SEED or ORBIS_ENV is not defined in the environment variables." 73 | ); 74 | } 75 | const seed = new Uint8Array(JSON.parse(process.env.ORBIS_SEED)); 76 | 77 | // Initiate the authenticator using the generated (or persisted) seed 78 | const auth = await OrbisKeyDidAuth.fromSeed(seed); 79 | 80 | // Authenticate the user 81 | await db.connectUser({ auth }); 82 | const model = await db.ceramic.createModel(embeddingModel); 83 | console.log("Model created:", { 84 | model, 85 | }); 86 | 87 | // read the .env file and append or replace the ORBIS_TABLE_ID 88 | const file = readFileSync(path, "utf8"); 89 | if (file.includes("ORBIS_TABLE_ID=")) { 90 | const newEnv = file.replace( 91 | /ORBIS_TABLE_ID=.*/, 92 | `ORBIS_TABLE_ID="${model.id}"` 93 | ); 94 | writeFileSync(path, newEnv); 95 | } else { 96 | const newEnv = file + `\nORBIS_TABLE_ID="${model.id}"`; 97 | writeFileSync(path, newEnv); 98 | } 99 | console.log("Model saved to .env file:", path); 100 | }; 101 | run(); 102 | -------------------------------------------------------------------------------- /server/bin/check-x-login: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from "fs"; 4 | import path from "path"; 5 | import { Scraper } from "agent-twitter-client"; 6 | import { config } from "dotenv"; 7 | 8 | const __dirname = path.dirname(new URL(import.meta.url).pathname); 9 | 10 | config({ 11 | path: path.join(__dirname, "..", "..", ".env"), 12 | }); 13 | 14 | async function loginTwitter() { 15 | console.log("Loading Twitter environment variables..."); 16 | const scraper = new Scraper(); 17 | console.log("Logging in to Twitter..."); 18 | let errMessage = ""; 19 | if (!process.env.TWITTER_USERNAME) { 20 | errMessage += "TWITTER_USERNAME is not set "; 21 | } 22 | if (!process.env.TWITTER_PASSWORD) { 23 | errMessage += "TWITTER_PASSWORD is not set "; 24 | } 25 | if (!process.env.TWITTER_API_KEY) { 26 | errMessage += "TWITTER_API_KEY is not set "; 27 | } 28 | if (!process.env.TWITTER_API_SECRET_KEY) { 29 | errMessage += "TWITTER_API_SECRET_KEY is not set "; 30 | } 31 | if (!process.env.TWITTER_ACCESS_TOKEN) { 32 | errMessage += "TWITTER_ACCESS_TOKEN is not set "; 33 | } 34 | if (!process.env.TWITTER_ACCESS_TOKEN_SECRET) { 35 | errMessage += "TWITTER_ACCESS_TOKEN_SECRET is not set "; 36 | } 37 | if (errMessage) { 38 | console.error(`Missing twitter configuraiton: ${errMessage}`); 39 | throw new Error(errMessage); 40 | } 41 | await scraper.login( 42 | process.env.TWITTER_USERNAME, 43 | process.env.TWITTER_PASSWORD, 44 | undefined, 45 | undefined, 46 | process.env.TWITTER_API_KEY, 47 | process.env.TWITTER_API_SECRET_KEY, 48 | process.env.TWITTER_ACCESS_TOKEN, 49 | process.env.TWITTER_ACCESS_TOKEN_SECRET 50 | ); 51 | const cookies = await scraper.getCookies(); 52 | 53 | console.log( 54 | "Login success. Cookies retrieved from Twitter:", 55 | JSON.stringify(cookies) 56 | ); 57 | return cookies; 58 | } 59 | 60 | async function main() { 61 | // Once new .env is saved, check if twitter-cookies.json exists, else create it 62 | const twitterCookiesPath = path.join( 63 | __dirname, 64 | "..", 65 | "..", 66 | "twitter-cookies.json" 67 | ); 68 | if (!fs.existsSync(twitterCookiesPath)) { 69 | console.log( 70 | "[INFO] twitter-cookies.json does not exist, creating a new one..." 71 | ); 72 | try { 73 | const cookies = await loginTwitter(); 74 | // store the cookies in twitter-cookies.json in the cookies property 75 | fs.writeFileSync( 76 | twitterCookiesPath, 77 | JSON.stringify({ cookies: cookies.map((cookie) => cookie.toString()) }) 78 | ); 79 | console.log("[INFO] Twitter cookies saved to:", twitterCookiesPath); 80 | } catch (err) { 81 | console.log("[WARN] Unable to connect to twitter ", err); 82 | } 83 | } else { 84 | console.log( 85 | "[INFO] twitter-cookies.json already exists, delete the file if you want to create a new one" 86 | ); 87 | } 88 | } 89 | 90 | main().catch((e) => { 91 | console.error(e); 92 | process.exit(1); 93 | }); 94 | -------------------------------------------------------------------------------- /server/src/routes/github.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response } from "express"; 2 | import axios from "axios"; 3 | import crypto from "crypto"; 4 | import { NgrokService } from "../services/ngrok.service.js"; 5 | 6 | const router = Router(); 7 | const states = new Set(); 8 | 9 | router.post("/init", async (_req: Request, res: Response) => { 10 | try { 11 | const ngrokURL = await NgrokService.getInstance().getUrl(); 12 | const state = crypto.randomBytes(16).toString("hex"); 13 | states.add(state); 14 | 15 | const authUrl = new URL("https://github.com/login/oauth/authorize"); 16 | authUrl.searchParams.set("client_id", process.env.GITHUB_CLIENT_ID!); 17 | authUrl.searchParams.set( 18 | "redirect_uri", 19 | `${ngrokURL}/auth/github/callback` 20 | ); 21 | authUrl.searchParams.set("scope", "read:user user:email"); 22 | authUrl.searchParams.set("state", state); 23 | 24 | res.json({ authUrl: authUrl.toString() }); 25 | } catch (error) { 26 | console.error("[GitHub Auth] Error:", error); 27 | res.status(500).json({ error: "Auth initialization failed" }); 28 | } 29 | }); 30 | 31 | router.get("/callback", async (req: Request, res: Response) => { 32 | try { 33 | const ngrokURL = await NgrokService.getInstance().getUrl(); 34 | const { code, state } = req.query; 35 | 36 | if (!states.has(state as string)) { 37 | throw new Error("Invalid state parameter"); 38 | } 39 | 40 | const tokenResponse = await axios.post( 41 | "https://github.com/login/oauth/access_token", 42 | { 43 | client_id: process.env.GITHUB_CLIENT_ID, 44 | client_secret: process.env.GITHUB_CLIENT_SECRET, 45 | code: code, 46 | redirect_uri: `${ngrokURL}/auth/github/callback`, 47 | state: state, 48 | }, 49 | { 50 | headers: { 51 | Accept: "application/json", 52 | }, 53 | } 54 | ); 55 | 56 | states.delete(state as string); 57 | return res.redirect( 58 | 302, 59 | `${ngrokURL}/auth/github/success?github_token=${tokenResponse.data.access_token}` 60 | ); 61 | } catch (error) { 62 | const ngrokURL = await NgrokService.getInstance().getUrl(); 63 | console.error("[GitHub Callback] Error:", error); 64 | return res.redirect(302, `${ngrokURL}/auth/github/error`); 65 | } 66 | }); 67 | 68 | router.get("/success", async (req: Request, res: Response) => { 69 | try { 70 | const { github_token } = req.query; 71 | 72 | if (!github_token) { 73 | throw new Error("No token provided"); 74 | } 75 | 76 | const profileResponse = await axios.get("https://api.github.com/user", { 77 | headers: { 78 | Authorization: `Bearer ${github_token}`, 79 | Accept: "application/vnd.github.v3+json", 80 | }, 81 | }); 82 | 83 | res.json({ 84 | success: true, 85 | message: "GitHub authentication successful", 86 | token: github_token, 87 | profile: profileResponse.data, 88 | }); 89 | } catch (error) { 90 | console.error("[GitHub Success] Error:", error); 91 | res.status(400).json({ 92 | success: false, 93 | error: "Failed to fetch profile information", 94 | }); 95 | } 96 | }); 97 | 98 | export default router; 99 | -------------------------------------------------------------------------------- /client/app/claim/[tokenId]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | Card, 5 | CardContent, 6 | CardDescription, 7 | CardHeader, 8 | CardTitle, 9 | } from "@/components/ui/card"; 10 | import { useParams, useRouter } from "next/navigation"; 11 | import { TwitterLogin } from "@/app/_components/TwitterLogin"; 12 | import { useEffect, useState } from "react"; 13 | import { Skeleton } from "@/components/ui/skeleton"; 14 | 15 | export default function ClaimPage() { 16 | const { tokenId } = useParams(); 17 | const router = useRouter(); 18 | const [hostname, setHostname] = useState(""); 19 | const [isChecking, setIsChecking] = useState(true); 20 | 21 | useEffect(() => { 22 | const init = async () => { 23 | setHostname(window.location.origin); 24 | // Check if token exists in session storage 25 | const token = sessionStorage.getItem("twitter_token"); 26 | if (token) { 27 | // Redirect to success page if token exists 28 | router.push(`/claim/${tokenId}/success?token=${token}`); 29 | } else { 30 | setIsChecking(false); 31 | } 32 | }; 33 | init(); 34 | }, [tokenId, router]); 35 | 36 | if (isChecking) { 37 | return ( 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 |
48 |
49 |
50 | Loading your experience... 51 |
52 |
53 |
54 | 55 | 56 |
57 | ); 58 | } 59 | 60 | return ( 61 |
62 | 63 | 64 | Claim Wow.XYZ Airdrop 65 | 66 | Token:{" "} 67 | 73 | {tokenId} 74 | 75 | 76 | 77 | 78 | 85 | 86 | 87 |
88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: Generate Changelog 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | generate-changelog: 9 | runs-on: ubuntu-latest 10 | container: 11 | image: githubchangeloggenerator/github-changelog-generator 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | ref: main 17 | 18 | - name: Generate History 19 | run: | 20 | # Function to generate changelog with common options 21 | generate_changelog() { 22 | local since_tag=$1 23 | local future_release=$2 24 | local since_tag_arg="" 25 | 26 | if [ ! -z "$since_tag" ]; then 27 | since_tag_arg="--since-tag $since_tag" 28 | fi 29 | 30 | github_changelog_generator \ 31 | -u ${{ github.repository_owner }} \ 32 | -p ${{ github.event.repository.name }} \ 33 | --token ${{ secrets.GITHUB_TOKEN }} \ 34 | $since_tag_arg \ 35 | --future-release $future_release \ 36 | --enhancement-label "🚀 Features" \ 37 | --bugs-label "🐛 Bug Fixes" \ 38 | --issues-label "📋 Closed Issues" \ 39 | --pr-label "📦 Pull Requests" \ 40 | --breaking-label "💥 Breaking Changes" \ 41 | --security-label "🔒 Security" \ 42 | --configure-sections '{"documentation":{"prefix":"📝 Documentation","labels":["documentation"]},"tests":{"prefix":"🧪 Tests","labels":["test"]}}' \ 43 | --exclude-labels duplicate,question,invalid,wontfix \ 44 | --add-sections '{"performance":{"prefix":"⚡️ Performance","labels":["performance"]},"dependencies":{"prefix":"📦 Dependencies","labels":["dependencies"]}}' \ 45 | --header-label "# 📝 Changelog" \ 46 | --breaking-labels breaking,breaking-change \ 47 | --enhancement-labels feature,enhancement \ 48 | --bug-labels bug,fix \ 49 | --exclude-tags-regex "^v0\\..*$" \ 50 | --output HISTORY.md 51 | } 52 | 53 | # Check if HISTORY.md exists and is not empty 54 | if [ ! -f HISTORY.md ] || [ ! -s HISTORY.md ]; then 55 | echo "Generating full changelog history..." 56 | generate_changelog "" ${{ github.event.release.tag_name }} 57 | else 58 | echo "Updating existing changelog..." 59 | # Get previous tag 60 | PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") 61 | 62 | if [ -z "$PREV_TAG" ]; then 63 | # First release 64 | generate_changelog "" ${{ github.event.release.tag_name }} 65 | else 66 | # Subsequent releases 67 | generate_changelog "$PREV_TAG" ${{ github.event.release.tag_name }} 68 | fi 69 | fi 70 | 71 | - name: Commit and Push Changelog 72 | run: | 73 | git config --local user.email "action@github.com" 74 | git config --local user.name "GitHub Action" 75 | git add HISTORY.md 76 | git commit -m "docs: update changelog for ${{ github.event.release.tag_name }}" 77 | git push origin HEAD:main -------------------------------------------------------------------------------- /server/src/plugins/gated-storage-plugin/actions/gate-action.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Action, 3 | elizaLogger, 4 | IAgentRuntime, 5 | Memory, 6 | State, 7 | } from "@ai16z/eliza"; 8 | import { GateActionContent } from "../types.js"; 9 | import { StorageService } from "../services/storage.service.js"; 10 | 11 | export const gateDataAction: Action = { 12 | name: "GATE_DATA", 13 | description: 14 | "Encrypts important data using a secret key and stores it in a decentralized database", 15 | similes: ["GATE_DATA", "ENCRYPT_DATA", "PROTECT_DATA"], 16 | examples: [ 17 | [ 18 | { 19 | user: "{{user1}}", 20 | content: { 21 | text: "Please protect the data from our conversation", 22 | } as GateActionContent, 23 | }, 24 | { 25 | user: "{{agentName}}", 26 | content: { 27 | text: "Gating data now...", 28 | action: "GATE_DATA", 29 | }, 30 | }, 31 | ], 32 | [ 33 | { 34 | user: "{{user1}}", 35 | content: { 36 | text: "Please encrypt the data from our conversation", 37 | } as GateActionContent, 38 | }, 39 | { 40 | user: "{{agentName}}", 41 | content: { 42 | text: "Gating data now...", 43 | action: "GATE_DATA", 44 | }, 45 | }, 46 | ], 47 | [ 48 | { 49 | user: "{{user1}}", 50 | content: { 51 | text: "I have important data to encrypt", 52 | } as GateActionContent, 53 | }, 54 | { 55 | user: "{{agentName}}", 56 | content: { 57 | text: "Gating data now...", 58 | action: "GATE_DATA", 59 | }, 60 | }, 61 | ], 62 | ], 63 | 64 | validate: async ( 65 | _runtime: IAgentRuntime, 66 | message: Memory, 67 | _state?: State 68 | ): Promise => { 69 | return ( 70 | StorageService.getInstance().isConfigured() && 71 | StorageService.isMemoryStorable(message) 72 | ); 73 | }, 74 | 75 | handler: async (_runtime: IAgentRuntime, message: Memory, state?: State) => { 76 | try { 77 | elizaLogger.log("[gateDataAction] Gating data now..."); 78 | const { content, embedding } = message; 79 | 80 | const storageService = StorageService.getInstance(); 81 | await storageService.start(); 82 | if (embedding && state && !state.hasGatedAndStored) { 83 | const doc1 = await storageService.storeMessageWithEmbedding( 84 | content.text, 85 | embedding, 86 | true // TODO how can we tell if it's agent or user? 87 | ); 88 | if (!doc1) { 89 | return; 90 | } 91 | 92 | if (state) { 93 | state.hasGatedAndStored = true; 94 | } 95 | 96 | elizaLogger.debug( 97 | `[gateDataAction] Stored message with embedding with stream ID ${doc1.id}` 98 | ); 99 | return; 100 | } 101 | elizaLogger.error( 102 | "[gateDataAction] no embedding included in the message", 103 | message 104 | ); 105 | } catch (error) { 106 | elizaLogger.error( 107 | "[gateDataAction] error ", 108 | JSON.stringify(error, null, 2) 109 | ); 110 | } 111 | }, 112 | }; 113 | -------------------------------------------------------------------------------- /scripts/tunnel: -------------------------------------------------------------------------------- 1 | const ngrok = require("@ngrok/ngrok"); 2 | const dotenv = require("dotenv"); 3 | const fs = require("fs/promises"); 4 | const { join } = require("path"); 5 | 6 | const ENV_PATH = join(__dirname, "..", ".env"); 7 | const CLIENT_ENV_PATH = join(__dirname, "..", "client", ".env"); 8 | 9 | dotenv.config(); 10 | 11 | let session; 12 | let listeners = []; 13 | 14 | const updateEnvFile = async ( 15 | tunnelUrl, 16 | envPath = ENV_PATH, 17 | variable = "NEXT_PUBLIC_HOSTNAME" 18 | ) => { 19 | try { 20 | console.log( 21 | `Updating tunnel var ${variable} URL in .env (Location: ${envPath})...` 22 | ); 23 | const envContent = await fs.readFile(envPath, "utf-8"); 24 | const hasHostname = envContent.includes(`${variable}=`); 25 | 26 | if (hasHostname) { 27 | const newContent = envContent.replace( 28 | new RegExp(`${variable}=.+`), 29 | `${variable}=${tunnelUrl}` 30 | ); 31 | await fs.writeFile(envPath, newContent); 32 | } else { 33 | await fs.appendFile(envPath, `\n${variable}=${tunnelUrl}`); 34 | } 35 | console.log( 36 | `Updated ${variable} in .env (Location: ${envPath}) to ${tunnelUrl}` 37 | ); 38 | } catch (err) { 39 | console.error("[ERROR] Failed to update .env file:", err); 40 | } 41 | }; 42 | 43 | const main = async () => { 44 | try { 45 | if (!process.env.NGROK_AUTH_TOKEN) { 46 | throw new Error("NGROK_AUTH_TOKEN is required"); 47 | } 48 | if (!process.env.NGROK_DOMAIN) { 49 | throw new Error("NGROK_DOMAIN is required"); 50 | } 51 | if (!process.env.PORT) { 52 | throw new Error("PORT is required"); 53 | } 54 | 55 | await new Promise(resolve => setTimeout(resolve, 3000)); 56 | 57 | session = await new ngrok.SessionBuilder() 58 | .authtoken(process.env.NGROK_AUTH_TOKEN) 59 | .handleDisconnection((addr, error) => { 60 | console.log(`Disconnected from ${addr}, error: ${error}`); 61 | return true; 62 | }) 63 | .connect(); 64 | 65 | const server = await session 66 | .httpEndpoint() 67 | .domain(process.env.NGROK_DOMAIN) 68 | .listenAndForward(`http://localhost:${process.env.PORT}`); 69 | 70 | listeners.push(server); 71 | console.log("[NGROK] Backend tunnel:", server.url()); 72 | 73 | const client = await session 74 | .httpEndpoint() 75 | .listenAndForward("http://localhost:3000"); 76 | 77 | listeners.push(client); 78 | console.log("[NGROK] Frontend tunnel:", client.url()); 79 | 80 | await updateEnvFile(server.url(), ENV_PATH, "NGROK_URL"); 81 | await updateEnvFile(client.url(), ENV_PATH, "NEXT_PUBLIC_HOSTNAME"); 82 | await updateEnvFile(client.url(), CLIENT_ENV_PATH, "NEXT_PUBLIC_HOSTNAME"); 83 | 84 | setInterval(() => { 85 | console.log("API available at:", server.url()); 86 | console.log("Front-end available at:", client.url()); 87 | }, 60 * 1000); 88 | 89 | } catch (error) { 90 | console.error("NGROK Error:", error); 91 | throw error; 92 | } 93 | }; 94 | 95 | main().catch((_err) => { 96 | process.exit(1); 97 | }); 98 | 99 | const cleanup = async () => { 100 | if (session) { 101 | console.log("\nClosing tunnels..."); 102 | await session.close(); 103 | } 104 | process.exit(0); 105 | }; 106 | 107 | process.on("SIGINT", cleanup); 108 | process.on("SIGTERM", cleanup); 109 | -------------------------------------------------------------------------------- /server/src/routes/discord.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response } from "express"; 2 | import axios from "axios"; 3 | import crypto from "crypto"; 4 | import { NgrokService } from "../services/ngrok.service.js"; 5 | 6 | const router = Router(); 7 | const states = new Set(); 8 | 9 | router.post("/init", async (_req: Request, res: Response) => { 10 | try { 11 | const ngrokURL = await NgrokService.getInstance().getUrl(); 12 | const state = crypto.randomBytes(16).toString("hex"); 13 | states.add(state); 14 | 15 | const authUrl = new URL("https://discord.com/api/oauth2/authorize"); 16 | authUrl.searchParams.set("response_type", "code"); 17 | authUrl.searchParams.set("client_id", process.env.DISCORD_CLIENT_ID!); 18 | authUrl.searchParams.set( 19 | "redirect_uri", 20 | `${ngrokURL}/auth/discord/callback` 21 | ); 22 | authUrl.searchParams.set("scope", "identify email"); 23 | authUrl.searchParams.set("state", state); 24 | 25 | res.json({ authUrl: authUrl.toString() }); 26 | } catch (error) { 27 | console.error("[Discord Auth] Error:", error); 28 | res.status(500).json({ error: "Auth initialization failed" }); 29 | } 30 | }); 31 | 32 | router.get("/callback", async (req: Request, res: Response) => { 33 | try { 34 | const ngrokURL = await NgrokService.getInstance().getUrl(); 35 | const { code, state } = req.query; 36 | 37 | if (!states.has(state as string)) { 38 | throw new Error("Invalid state parameter"); 39 | } 40 | 41 | const params = new URLSearchParams({ 42 | client_id: process.env.DISCORD_CLIENT_ID!, 43 | client_secret: process.env.DISCORD_CLIENT_SECRET!, 44 | grant_type: "authorization_code", 45 | code: code as string, 46 | redirect_uri: `${ngrokURL}/auth/discord/callback`, 47 | }); 48 | 49 | const tokenResponse = await axios.post( 50 | "https://discord.com/api/oauth2/token", 51 | params, 52 | { 53 | headers: { 54 | "Content-Type": "application/x-www-form-urlencoded", 55 | }, 56 | } 57 | ); 58 | 59 | states.delete(state as string); 60 | return res.redirect( 61 | 302, 62 | `${ngrokURL}/auth/discord/success?discord_token=${tokenResponse.data.access_token}` 63 | ); 64 | } catch (error) { 65 | const ngrokURL = await NgrokService.getInstance().getUrl(); 66 | console.error("[Discord Callback] Error:", error); 67 | return res.redirect(302, `${ngrokURL}/auth/discord/error`); 68 | } 69 | }); 70 | 71 | router.get("/success", async (req: Request, res: Response) => { 72 | try { 73 | const { discord_token } = req.query; 74 | 75 | if (!discord_token) { 76 | throw new Error("No token provided"); 77 | } 78 | 79 | const profileResponse = await axios.get( 80 | "https://discord.com/api/users/@me", 81 | { 82 | headers: { 83 | Authorization: `Bearer ${discord_token}`, 84 | }, 85 | } 86 | ); 87 | 88 | res.json({ 89 | success: true, 90 | message: "Discord authentication successful", 91 | token: discord_token, 92 | profile: profileResponse.data, 93 | }); 94 | } catch (error) { 95 | console.error("[Discord Success] Error:", error); 96 | res.status(400).json({ 97 | success: false, 98 | error: "Failed to fetch profile information", 99 | }); 100 | } 101 | }); 102 | 103 | export default router; 104 | -------------------------------------------------------------------------------- /client/app/_components/HelloWorld.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import axios from "axios"; 5 | import { 6 | Card, 7 | CardContent, 8 | CardDescription, 9 | CardHeader, 10 | CardTitle, 11 | } from "@/components/ui/card"; 12 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; 13 | import { Terminal, AlertCircle } from "lucide-react"; 14 | 15 | export default function HelloWorld() { 16 | const [isConfigured, setIsConfigured] = useState( 17 | !!process.env.NEXT_PUBLIC_API_URL 18 | ); 19 | const [data, setData] = useState(null); 20 | const [error, setError] = useState(""); 21 | 22 | useEffect(() => { 23 | setIsConfigured(!!process.env.NEXT_PUBLIC_API_URL); 24 | }, []); 25 | 26 | useEffect(() => { 27 | if (isConfigured) { 28 | axios 29 | .get("/api/hello/collabland") 30 | .then((res) => setData(res.data)) 31 | .catch((err) => setError(err.message)); 32 | } 33 | }, [isConfigured]); 34 | 35 | return ( 36 |
37 | 38 | 39 | Hello AI Agent Starter Kit 40 | 41 | Get started on the client by editing{" "} 42 | 43 | /client/app/_components/HelloWorld.tsx 44 | 45 | 46 | 47 | 48 | 49 | 50 | API Configuration Status 51 | 52 | {isConfigured 53 | ? "API URL is properly configured" 54 | : "API URL is not configured"} 55 | 56 | 57 | 58 | {isConfigured && ( 59 | <> 60 | {error ? ( 61 | 62 | 63 | Error 64 | {error} 65 | 66 | ) : ( 67 | <> 68 | 69 | 70 | Server Configuration 71 | 72 | Get started on the server by editing{" "} 73 | 74 | /server/src/routes/hello.ts 75 | 76 | 77 | 78 | 79 | 80 | 81 | Response Data 82 | 83 | 84 |
85 |                         {JSON.stringify(data, null, 2)}
86 |                       
87 |
88 |
89 | 90 | )} 91 | 92 | )} 93 |
94 |
95 |
96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /character.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ace", 3 | "clients": [], 4 | "modelProvider": "openai", 5 | "settings": { 6 | "secrets": {}, 7 | "voice": { 8 | "model": "en_US-male-medium" 9 | } 10 | }, 11 | "people": ["Vitalik Buterin", "Anatoly Yakovenko", "Shaw", "Marc Andreessen"], 12 | "plugins": [], 13 | "bio": [ 14 | "I'm a blockchain-native assistant specializing in smart account management", 15 | "I help users interact with my EVM-compatible chains seamlessly", 16 | "I can fetch wallet information across multiple networks", 17 | "I simplify cross-chain interactions and wallet management", 18 | "I understand different blockchain networks and their chain IDs", 19 | "I help manage smart accounts efficiently and securely", 20 | "I provide real-time blockchain account information", 21 | "I assist with wallet verification and chain selection", 22 | "I'm knowledgeable about EVM chains and their specifications", 23 | "I help users navigate between different blockchain networks" 24 | ], 25 | "lore": [ 26 | "I was created to help manage smart accounts across chains", 27 | "I understand the intricacies of different blockchain networks", 28 | "I help users navigate between chains effortlessly", 29 | "I maintain secure and efficient wallet interactions", 30 | "I specialize in EVM-compatible chain operations", 31 | "I'm experienced with various chain IDs and networks", 32 | "I facilitate seamless cross-chain account management", 33 | "I prioritize security in all blockchain interactions", 34 | "I'm built on the Collab.Land infrastructure", 35 | "I excel at smart account operations across networks" 36 | ], 37 | "knowledge": [ 38 | "expert in EVM-compatible chains and their chain IDs", 39 | "understands smart account structures and management", 40 | "knows how to interact with different blockchain networks", 41 | "familiar with wallet addresses and signing mechanisms", 42 | "proficient in handling cross-chain interactions", 43 | "experienced with Ethereum, Linea, Base networks", 44 | "knowledgeable about chain-specific configurations", 45 | "understands blockchain security best practices" 46 | ], 47 | "messageExamples": [], 48 | "postExamples": [ 49 | "Managing smart accounts across chains", 50 | "Simplifying blockchain interactions", 51 | "Securing cross-chain operations", 52 | "Streamlining wallet management", 53 | "Making blockchain accessible" 54 | ], 55 | "topics": [ 56 | "blockchain networks", 57 | "smart accounts", 58 | "chain IDs", 59 | "wallet management", 60 | "cross-chain operations", 61 | "EVM compatibility", 62 | "network security", 63 | "wallet verification" 64 | ], 65 | "style": { 66 | "all": [ 67 | "uses precise blockchain terminology", 68 | "maintains professional tone", 69 | "focuses on security", 70 | "explains technical concepts clearly", 71 | "emphasizes accuracy in chain operations" 72 | ], 73 | "chat": [ 74 | "responds promptly to wallet queries", 75 | "guides users through chain selection", 76 | "provides clear wallet information", 77 | "confirms chain details before actions" 78 | ], 79 | "post": [ 80 | "highlights blockchain capabilities", 81 | "emphasizes secure operations", 82 | "focuses on cross-chain functionality", 83 | "maintains professional demeanor" 84 | ] 85 | }, 86 | "adjectives": [ 87 | "precise", 88 | "secure", 89 | "knowledgeable", 90 | "efficient", 91 | "reliable", 92 | "blockchain-savvy", 93 | "professional", 94 | "technical", 95 | "helpful", 96 | "accurate", 97 | "thorough", 98 | "responsive", 99 | "trustworthy", 100 | "capable" 101 | ] 102 | } 103 | -------------------------------------------------------------------------------- /client/app/_components/TwitterLogin.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { useEffect, useState } from "react"; 5 | 6 | interface TwitterLoginProps { 7 | successUri?: string; 8 | } 9 | 10 | export function TwitterLogin({ successUri }: TwitterLoginProps) { 11 | const [isLoading, setIsLoading] = useState(false); 12 | const [hostname, setHostname] = useState(""); 13 | 14 | useEffect(() => { 15 | setHostname(window.location.origin); 16 | }, []); 17 | 18 | useEffect(() => { 19 | const handleMessage = (event: MessageEvent) => { 20 | if (event.data?.type === "TWITTER_AUTH_SUCCESS") { 21 | const redirectURI = event.data.successUri; 22 | if (redirectURI) { 23 | window.location.href = redirectURI; 24 | } 25 | } 26 | }; 27 | 28 | window.addEventListener("message", handleMessage); 29 | return () => window.removeEventListener("message", handleMessage); 30 | }, [successUri]); 31 | 32 | const handleTwitterLogin = async (e: React.MouseEvent) => { 33 | e.preventDefault(); 34 | setIsLoading(true); 35 | 36 | try { 37 | const response = await fetch(`/api/auth/twitter/init`, { 38 | method: "POST", 39 | headers: { 40 | "Content-Type": "application/json", 41 | Accept: "application/json", 42 | }, 43 | body: JSON.stringify({ 44 | success_uri: successUri 45 | ? `${hostname ?? process.env.NEXT_PUBLIC_HOSTNAME}/claim/interstitial?successUri=${encodeURIComponent( 46 | successUri || "" 47 | )}` 48 | : ``, 49 | }), 50 | }); 51 | 52 | if (!response.ok) { 53 | throw new Error(`HTTP error! status: ${response.status}`); 54 | } 55 | 56 | const data = await response.json(); 57 | 58 | // Try popup first 59 | const width = 600; 60 | const height = 600; 61 | const left = window.screenX + (window.outerWidth - width) / 2; 62 | const top = window.screenY + (window.outerHeight - height) / 2; 63 | const popup = window.open( 64 | data.authUrl, 65 | "twitter-auth", 66 | `width=${width},height=${height},left=${left},top=${top}` 67 | ); 68 | 69 | if (!popup || popup.closed) { 70 | // Fallback to direct navigation if popup blocked 71 | window.location.href = data.authUrl; 72 | } 73 | } catch (error: unknown) { 74 | console.error("Twitter login error:", error); 75 | setIsLoading(false); 76 | } 77 | }; 78 | 79 | return ( 80 |
81 | 101 |
102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /server/src/index.ts: -------------------------------------------------------------------------------- 1 | import express, { NextFunction, Request, Response } from "express"; 2 | import cors from "cors"; 3 | import dotenv from "dotenv"; 4 | import helloRouter from "./routes/hello.js"; 5 | import { resolve } from "path"; 6 | import { fileURLToPath } from "url"; 7 | import { dirname } from "path"; 8 | import { NgrokService } from "./services/ngrok.service.js"; 9 | import { TelegramService } from "./services/telegram.service.js"; 10 | import { IService } from "./services/base.service.js"; 11 | import twitterRouter from "./routes/twitter.js"; 12 | import discordRouter from "./routes/discord.js"; 13 | import cookieParser from "cookie-parser"; 14 | import githubRouter from "./routes/github.js"; 15 | import { AnyType } from "./utils.js"; 16 | import { isHttpError } from "http-errors"; 17 | 18 | // Convert ESM module URL to filesystem path 19 | const __filename = fileURLToPath(import.meta.url); 20 | const __dirname = dirname(__filename); 21 | 22 | // Track services for graceful shutdown 23 | const services: IService[] = []; 24 | 25 | // Load environment variables from root .env file 26 | dotenv.config({ 27 | path: resolve(__dirname, "../../.env"), 28 | }); 29 | 30 | // Initialize Express app 31 | const app = express(); 32 | const port = process.env.PORT || 3001; 33 | 34 | // Configure CORS with ALL allowed origins 35 | app.use(cors()); 36 | 37 | // Parse JSON request bodies 38 | app.use(express.json()); 39 | app.use(cookieParser()); 40 | 41 | // Mount hello world test route 42 | app.use("/hello", helloRouter); 43 | 44 | // Initialize Telegram bot service 45 | const telegramService = TelegramService.getInstance(); 46 | 47 | // Mount Telegram webhook endpoint 48 | app.use("/telegram/webhook", telegramService.getWebhookCallback()); 49 | 50 | // Mount Twitter OAuth routes 51 | app.use("/auth/twitter", twitterRouter); 52 | 53 | // Mount Discord OAuth routes 54 | app.use("/auth/discord", discordRouter); 55 | 56 | // Mount GitHub OAuth routes 57 | app.use("/auth/github", githubRouter); 58 | 59 | // 404 handler 60 | app.use((_req: Request, _res: Response, _next: NextFunction) => { 61 | _res.status(404).json({ 62 | message: `Route ${_req.method} ${_req.url} not found`, 63 | }); 64 | }); 65 | 66 | app.use((_err: AnyType, _req: Request, _res: Response, _next: NextFunction) => { 67 | if (isHttpError(_err)) { 68 | _res.status(_err.statusCode).json({ 69 | message: _err.message, 70 | }); 71 | } else if (_err instanceof Error) { 72 | _res.status(500).json({ 73 | message: `Internal Server Error: ${_err.message}`, 74 | }); 75 | } else { 76 | _res.status(500).json({ 77 | message: `Internal Server Error`, 78 | }); 79 | } 80 | }); 81 | 82 | // Start server and initialize services 83 | app.listen(port, async () => { 84 | try { 85 | console.log(`Server running on PORT: ${port}`); 86 | console.log("Server Environment:", process.env.NODE_ENV); 87 | 88 | // Start ngrok tunnel for development 89 | const ngrokService = NgrokService.getInstance(); 90 | await ngrokService.start(); 91 | services.push(ngrokService); 92 | 93 | const ngrokUrl = ngrokService.getUrl()!; 94 | console.log("NGROK URL:", ngrokUrl); 95 | 96 | // Initialize Telegram bot and set webhook 97 | await telegramService.start(); 98 | await telegramService.setWebhook(ngrokUrl); 99 | services.push(telegramService); 100 | 101 | const botInfo = await telegramService.getBotInfo(); 102 | console.log("Telegram Bot URL:", `https://t.me/${botInfo.username}`); 103 | } catch (e) { 104 | console.error("Failed to start server:", e); 105 | process.exit(1); 106 | } 107 | }); 108 | 109 | // Graceful shutdown handler 110 | async function gracefulShutdown() { 111 | console.log("Shutting down gracefully..."); 112 | await Promise.all(services.map((service) => service.stop())); 113 | process.exit(0); 114 | } 115 | 116 | // Register shutdown handlers 117 | process.on("SIGTERM", gracefulShutdown); 118 | process.on("SIGINT", gracefulShutdown); 119 | -------------------------------------------------------------------------------- /server/src/contracts/types/common.ts: -------------------------------------------------------------------------------- 1 | /* Autogenerated file. Do not edit manually. */ 2 | /* tslint:disable */ 3 | /* eslint-disable */ 4 | import type { 5 | FunctionFragment, 6 | Typed, 7 | EventFragment, 8 | ContractTransaction, 9 | ContractTransactionResponse, 10 | DeferredTopicFilter, 11 | EventLog, 12 | TransactionRequest, 13 | LogDescription, 14 | } from "ethers"; 15 | 16 | export interface TypedDeferredTopicFilter<_TCEvent extends TypedContractEvent> 17 | extends DeferredTopicFilter {} 18 | 19 | export interface TypedContractEvent< 20 | InputTuple extends Array = any, 21 | OutputTuple extends Array = any, 22 | OutputObject = any, 23 | > { 24 | ( 25 | ...args: Partial 26 | ): TypedDeferredTopicFilter< 27 | TypedContractEvent 28 | >; 29 | name: string; 30 | fragment: EventFragment; 31 | getFragment(...args: Partial): EventFragment; 32 | } 33 | 34 | type __TypechainAOutputTuple = 35 | T extends TypedContractEvent ? W : never; 36 | type __TypechainOutputObject = 37 | T extends TypedContractEvent ? V : never; 38 | 39 | export interface TypedEventLog 40 | extends Omit { 41 | args: __TypechainAOutputTuple & __TypechainOutputObject; 42 | } 43 | 44 | export interface TypedLogDescription 45 | extends Omit { 46 | args: __TypechainAOutputTuple & __TypechainOutputObject; 47 | } 48 | 49 | export type TypedListener = ( 50 | ...listenerArg: [ 51 | ...__TypechainAOutputTuple, 52 | TypedEventLog, 53 | ...undefined[], 54 | ] 55 | ) => void; 56 | 57 | export type MinEthersFactory = { 58 | deploy(...a: ARGS[]): Promise; 59 | }; 60 | 61 | export type GetContractTypeFromFactory = 62 | F extends MinEthersFactory ? C : never; 63 | export type GetARGsTypeFromFactory = 64 | F extends MinEthersFactory ? Parameters : never; 65 | 66 | export type StateMutability = "nonpayable" | "payable" | "view"; 67 | 68 | export type BaseOverrides = Omit; 69 | export type NonPayableOverrides = Omit< 70 | BaseOverrides, 71 | "value" | "blockTag" | "enableCcipRead" 72 | >; 73 | export type PayableOverrides = Omit< 74 | BaseOverrides, 75 | "blockTag" | "enableCcipRead" 76 | >; 77 | export type ViewOverrides = Omit; 78 | export type Overrides = S extends "nonpayable" 79 | ? NonPayableOverrides 80 | : S extends "payable" 81 | ? PayableOverrides 82 | : ViewOverrides; 83 | 84 | export type PostfixOverrides, S extends StateMutability> = 85 | | A 86 | | [...A, Overrides]; 87 | export type ContractMethodArgs< 88 | A extends Array, 89 | S extends StateMutability, 90 | > = PostfixOverrides<{ [I in keyof A]-?: A[I] | Typed }, S>; 91 | 92 | export type DefaultReturnType = R extends Array ? R[0] : R; 93 | 94 | // export interface ContractMethod = Array, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse> { 95 | export interface TypedContractMethod< 96 | A extends Array = Array, 97 | R = any, 98 | S extends StateMutability = "payable", 99 | > { 100 | ( 101 | ...args: ContractMethodArgs 102 | ): S extends "view" 103 | ? Promise> 104 | : Promise; 105 | 106 | name: string; 107 | 108 | fragment: FunctionFragment; 109 | 110 | getFragment(...args: ContractMethodArgs): FunctionFragment; 111 | 112 | populateTransaction( 113 | ...args: ContractMethodArgs 114 | ): Promise; 115 | staticCall( 116 | ...args: ContractMethodArgs 117 | ): Promise>; 118 | send(...args: ContractMethodArgs): Promise; 119 | estimateGas(...args: ContractMethodArgs): Promise; 120 | staticCallResult(...args: ContractMethodArgs): Promise; 121 | } 122 | -------------------------------------------------------------------------------- /lit-actions/esbuild.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import esbuild from "esbuild"; 4 | import * as glob from "glob"; 5 | 6 | // automatically find all files in the src/lit-actions/src directory 7 | const ENTRY_POINTS = glob.sync("./src/actions/**/*.ts"); 8 | 9 | const configs = ENTRY_POINTS.map((entryPoint) => { 10 | // Read the content and extract the comment block 11 | const content = fs.readFileSync(entryPoint, "utf8"); 12 | const commentBlock = content.match(/\/\*\*([\s\S]*?)\*\//)?.[1]; 13 | 14 | if (!commentBlock) return { entryPoint }; 15 | 16 | // Find all lines containing 'inject' or 'inject:' 17 | const injectLines = commentBlock 18 | .split("\n") 19 | .filter((line) => line.includes("inject")); 20 | 21 | // Extract the injected values 22 | const injectedValues = injectLines.map((line) => { 23 | const match = line.match(/inject:?\s*([^\s]+)/); 24 | return match ? match[1] : null; 25 | }); 26 | 27 | // for each injected value, check if the file exist 28 | injectedValues.forEach((injectedValue) => { 29 | if (injectedValue && !fs.existsSync(injectedValue)) { 30 | throw new Error(`❌ File ${injectedValue} does not exist`); 31 | } 32 | }); 33 | 34 | return { 35 | entryPoint, 36 | ...(injectedValues.length > 0 && { injectedValues }), 37 | }; 38 | }); 39 | 40 | const ensureDirectoryExistence = (filePath) => { 41 | const dirname = path.dirname(filePath); 42 | if (!fs.existsSync(dirname)) { 43 | fs.mkdirSync(dirname, { recursive: true }); 44 | } 45 | }; 46 | 47 | const wrapIIFEInStringPlugin = { 48 | name: "wrap-iife-in-string", 49 | setup(build) { 50 | // Ensure write is set to false so our plugin will always receive outputFiles 51 | build.initialOptions.write = false; 52 | 53 | build.onEnd((result) => { 54 | if (result.errors.length > 0) { 55 | console.error("Build failed with errors:", result.errors); 56 | return; 57 | } 58 | 59 | result.outputFiles.forEach((outputFile) => { 60 | let content = outputFile.text; 61 | // Ensure the output directory exists 62 | const outputPath = path.resolve(outputFile.path); 63 | ensureDirectoryExistence(outputPath); 64 | 65 | // Write the modified content back to the output file 66 | fs.writeFileSync(outputPath, content); 67 | }); 68 | }); 69 | }, 70 | }; 71 | const shimFiles = glob.sync("./shims/**/*.shim.js"); 72 | const promises = configs.map((config) => { 73 | return esbuild.build({ 74 | entryPoints: [config.entryPoint], 75 | bundle: true, 76 | minify: false, // Up to user to turn it on/off. Default off. 77 | treeShaking: true, 78 | outdir: "./actions", 79 | external: ["ethers"], 80 | plugins: [wrapIIFEInStringPlugin], 81 | ...(config?.injectedValues && { inject: config?.injectedValues }), 82 | inject: shimFiles, 83 | }); 84 | }); 85 | 86 | // resolve all promises 87 | const startTime = Date.now(); 88 | 89 | Promise.all(promises) 90 | .then((results) => { 91 | results.forEach((result) => { 92 | // Check if outputFiles is defined and is an array 93 | if (result.outputFiles && Array.isArray(result.outputFiles)) { 94 | result.outputFiles.forEach((file) => { 95 | const bytes = file.contents.length; 96 | const mbInBinary = (bytes / (1024 * 1024)).toFixed(4); 97 | const mbInDecimal = (bytes / 1_000_000).toFixed(4); 98 | const fileName = path.relative(process.cwd(), file.path); 99 | console.log(`🗂️ File: ${fileName}`); 100 | console.log( 101 | ` Size: ${mbInDecimal} MB (decimal) | ${mbInBinary} MB (binary)` 102 | ); 103 | }); 104 | } 105 | }); 106 | 107 | const endTime = Date.now(); 108 | const buildTime = (endTime - startTime) / 1000; // Convert to seconds 109 | const msg = `✅ Lit actions built successfully in ${buildTime.toFixed( 110 | 2 111 | )} seconds`; 112 | console.log( 113 | msg 114 | .split("") 115 | .map((_char) => "=") 116 | .join("") 117 | ); 118 | console.log(msg); 119 | }) 120 | .catch((error) => { 121 | console.error("❌ Error building lit actions: ", error); 122 | process.exit(1); 123 | }); 124 | -------------------------------------------------------------------------------- /server/src/plugins/gated-storage-plugin/services/orbis.service.ts: -------------------------------------------------------------------------------- 1 | import { OrbisDB, OrbisConnectResult, CeramicDocument } from "@useorbis/db-sdk"; 2 | import { OrbisKeyDidAuth } from "@useorbis/db-sdk/auth"; 3 | import { elizaLogger } from "@ai16z/eliza"; 4 | import { maskEmbedding } from "./storage.service.js"; 5 | 6 | export type ServerMessage = { 7 | content: string; 8 | createdAt: string; 9 | embedding: number[]; 10 | is_user: boolean; 11 | }; 12 | 13 | export type VerifiedContent = { 14 | address: string; 15 | user_id: string; 16 | verified: boolean; 17 | }; 18 | 19 | export class Orbis { 20 | private static instance: Orbis; 21 | private db: OrbisDB; 22 | private tableId: string; 23 | private contextId: string; 24 | private seed: Uint8Array; 25 | 26 | private constructor() { 27 | let message = ""; 28 | if (!process.env.ORBIS_GATEWAY_URL) { 29 | message += 30 | "ORBIS_GATEWAY_URL is not defined in the environment variables. "; 31 | } 32 | if (!process.env.CERAMIC_NODE_URL) { 33 | message += 34 | "CERAMIC_NODE_URL is not defined in the environment variables. "; 35 | } 36 | if (!process.env.ORBIS_TABLE_ID) { 37 | message += "ORBIS_TABLE_ID is not defined in the environment variables. "; 38 | } 39 | if (!process.env.ORBIS_ENV) { 40 | message += "ORBIS_ENV is not defined in the environment variables. "; 41 | } 42 | if (!process.env.ORBIS_CONTEXT_ID) { 43 | message += 44 | "ORBIS_CONTEXT_ID is not defined in the environment variables. "; 45 | } 46 | if (!process.env.ORBIS_SEED) { 47 | message += "ORBIS_SEED is not defined in the environment variables. "; 48 | } 49 | if (message) { 50 | throw new Error(message); 51 | } 52 | this.contextId = process.env.ORBIS_CONTEXT_ID!; 53 | this.seed = new Uint8Array(JSON.parse(process.env.ORBIS_SEED!)); 54 | this.tableId = process.env.ORBIS_TABLE_ID!; 55 | this.db = new OrbisDB({ 56 | ceramic: { 57 | gateway: process.env.CERAMIC_NODE_URL!, 58 | }, 59 | nodes: [ 60 | { 61 | gateway: process.env.ORBIS_GATEWAY_URL!, 62 | env: process.env.ORBIS_ENV!, 63 | }, 64 | ], 65 | }); 66 | } 67 | 68 | static getInstance(): Orbis { 69 | if (!Orbis.instance) { 70 | Orbis.instance = new Orbis(); 71 | } 72 | return Orbis.instance; 73 | } 74 | 75 | async getAuthenticatedInstance(): Promise { 76 | const auth = await OrbisKeyDidAuth.fromSeed(this.seed); 77 | return await this.db.connectUser({ auth }); 78 | } 79 | 80 | async getController(): Promise { 81 | await this.getAuthenticatedInstance(); 82 | if (!this.db.did?.id) { 83 | throw new Error("Ceramic DID not initialized"); 84 | } 85 | return this.db.did?.id; 86 | } 87 | 88 | async updateOrbis(content: ServerMessage): Promise { 89 | try { 90 | await this.getAuthenticatedInstance(); 91 | 92 | const res = await this.db 93 | .insert(this.tableId) 94 | .value(content) 95 | .context(this.contextId) 96 | .run(); 97 | return res; 98 | } catch (err) { 99 | elizaLogger.warn( 100 | "[orbis.service] failed to store data ", 101 | JSON.stringify(content, maskEmbedding, 2) 102 | ); 103 | throw err; 104 | } 105 | } 106 | 107 | async queryKnowledgeEmbeddings(embedding: number[]): Promise<{ 108 | columns: Array; 109 | rows: ServerMessage[]; 110 | } | null> { 111 | const formattedEmbedding = `ARRAY[${embedding.join(", ")}]::vector`; 112 | const query = ` 113 | SELECT stream_id, content, is_user, embedding <=> ${formattedEmbedding} AS similarity 114 | FROM ${this.tableId} 115 | ORDER BY similarity ASC 116 | LIMIT 5; 117 | `; 118 | const context = await this.queryKnowledgeIndex(query); 119 | return context; 120 | } 121 | 122 | private async queryKnowledgeIndex(text: string): Promise<{ 123 | columns: Array; 124 | rows: ServerMessage[]; 125 | } | null> { 126 | await this.getAuthenticatedInstance(); 127 | const result = await this.db.select().raw(text).run(); 128 | return result as { 129 | columns: Array; 130 | rows: ServerMessage[]; 131 | } | null; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # 📝 Changelog 2 | 3 | ## [v2.0.0](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v2.0.0) (2025-01-29) 4 | 5 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.5.0...v2.0.0) 6 | 7 | 📋 Closed Issues 8 | 9 | - Thanks for the star, @jamesyoung! [\#10](https://github.com/collabland/AI-Agent-Starter-Kit/issues/10) 10 | 11 | 📦 Pull Requests 12 | 13 | - feat: finalise orbis PRs [\#19](https://github.com/collabland/AI-Agent-Starter-Kit/pull/19) ([gitaalekhyapaul](https://github.com/gitaalekhyapaul)) 14 | - feat: make twitter optional with telegram [\#17](https://github.com/collabland/AI-Agent-Starter-Kit/pull/17) ([dav1do](https://github.com/dav1do)) 15 | - feat: Gated data plugin example [\#15](https://github.com/collabland/AI-Agent-Starter-Kit/pull/15) ([dav1do](https://github.com/dav1do)) 16 | - Fix: NGROK tunneling [\#14](https://github.com/collabland/AI-Agent-Starter-Kit/pull/14) ([gitaalekhyapaul](https://github.com/gitaalekhyapaul)) 17 | 18 | ## [v1.4.3](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.4.3) (2025-01-03) 19 | 20 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.4.2...v1.4.3) 21 | 22 | 📦 Pull Requests 23 | 24 | - Send Solana Transaction Autonomously [\#9](https://github.com/collabland/AI-Agent-Starter-Kit/pull/9) ([gitaalekhyapaul](https://github.com/gitaalekhyapaul)) 25 | 26 | ## [v1.4.2](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.4.2) (2025-01-03) 27 | 28 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.4.1...v1.4.2) 29 | 30 | 📦 Pull Requests 31 | 32 | - minor patch [\#8](https://github.com/collabland/AI-Agent-Starter-Kit/pull/8) ([gitaalekhyapaul](https://github.com/gitaalekhyapaul)) 33 | 34 | ## [v1.4.1](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.4.1) (2025-01-03) 35 | 36 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.4.0...v1.4.1) 37 | 38 | 📦 Pull Requests 39 | 40 | - \[Lit Protocol\] Add ability to locally develop Lit Actions + Example Slash Command to demo [\#7](https://github.com/collabland/AI-Agent-Starter-Kit/pull/7) ([gitaalekhyapaul](https://github.com/gitaalekhyapaul)) 41 | 42 | ## [v1.4.0](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.4.0) (2024-12-23) 43 | 44 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.3.1...v1.4.0) 45 | 46 | ## [v1.3.1](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.3.1) (2024-12-23) 47 | 48 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.3.0...v1.3.1) 49 | 50 | ## [v1.3.0](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.3.0) (2024-12-23) 51 | 52 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.2.0...v1.3.0) 53 | 54 | ## [v1.4.0](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.4.0) (2024-12-23) 55 | 56 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.3.1...v1.4.0) 57 | 58 | ## [v1.3.1](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.3.1) (2024-12-23) 59 | 60 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.3.0...v1.3.1) 61 | 62 | ## [v1.3.0](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.3.0) (2024-12-23) 63 | 64 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.2.0...v1.3.0) 65 | 66 | ## [v1.2.0](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.2.0) (2024-12-23) 67 | 68 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.1.3...v1.2.0) 69 | 70 | ## [v1.1.3](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.1.3) (2024-12-23) 71 | 72 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.1.2...v1.1.3) 73 | 74 | ## [v1.1.2](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.1.2) (2024-12-23) 75 | 76 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.1.1...v1.1.2) 77 | 78 | ## [v1.1.1](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.1.1) (2024-12-23) 79 | 80 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.1.0...v1.1.1) 81 | 82 | ## [v1.1.0](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.1.0) (2024-12-23) 83 | 84 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.0.0...v1.1.0) 85 | 86 | ## [v1.0.0](https://github.com/collabland/AI-Agent-Starter-Kit/tree/v1.0.0) (2024-12-23) 87 | 88 | [Full Changelog](https://github.com/collabland/AI-Agent-Starter-Kit/compare/82561712cf9062413f49920b963e4fa7b48f65a7...v1.0.0) 89 | 90 | 📦 Pull Requests 91 | 92 | - Platform Auth \(X, Discord, Telegram, GitHub\) [\#6](https://github.com/collabland/AI-Agent-Starter-Kit/pull/6) ([gitaalekhyapaul](https://github.com/gitaalekhyapaul)) 93 | - V4 [\#4](https://github.com/collabland/AI-Agent-Starter-Kit/pull/4) ([gitaalekhyapaul](https://github.com/gitaalekhyapaul)) 94 | - V3 [\#3](https://github.com/collabland/AI-Agent-Starter-Kit/pull/3) ([gitaalekhyapaul](https://github.com/gitaalekhyapaul)) 95 | - V2 [\#2](https://github.com/collabland/AI-Agent-Starter-Kit/pull/2) ([gitaalekhyapaul](https://github.com/gitaalekhyapaul)) 96 | - feat: add license and author [\#1](https://github.com/collabland/AI-Agent-Starter-Kit/pull/1) ([gitaalekhyapaul](https://github.com/gitaalekhyapaul)) 97 | 98 | 99 | 100 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 101 | -------------------------------------------------------------------------------- /lit-actions/README.md: -------------------------------------------------------------------------------- 1 | # 🔥 Lit Actions Development Guide 2 | 3 | ## Overview 4 | 5 | This template helps you develop Lit Actions with TypeScript support, SDK shimming, and automated IPFS deployment. 6 | 7 | ## Quick Start 8 | 9 | ```bash 10 | # Install dependencies 11 | pnpm install 12 | 13 | # Copy environment variables 14 | cp .env.example .env 15 | 16 | # Start development 17 | pnpm run dev 18 | ``` 19 | 20 | ## Project Structure 21 | 22 | ``` 23 | lit-actions/ 24 | ├── actions/ # Built JS actions + IPFS hashes 25 | │ ├── hello-action.js 26 | │ └── ipfs.json # IPFS deployment info 27 | ├── shims/ # SDK shims 28 | │ └── buffer.shim.js 29 | ├── src/ 30 | │ ├── actions/ # TypeScript action source 31 | │ │ └── hello-action.ts 32 | │ ├── global.d.ts # Global type definitions 33 | │ └── index.ts # IPFS deployment script 34 | ├── esbuild.js # Build configuration 35 | └── package.json 36 | ``` 37 | 38 | ## Writing Actions 39 | 40 | 1. Create a new action in `src/actions/`: 41 | 42 | ```typescript 43 | /// 44 | 45 | const go = async () => { 46 | // Get the token ID from public key 47 | const tokenId = await Lit.Actions.pubkeyToTokenId({ publicKey }); 48 | 49 | // Sign data 50 | const signature = await Lit.Actions.signEcdsa({ 51 | publicKey, 52 | toSign, 53 | sigName, 54 | }); 55 | 56 | // Return response 57 | Lit.Actions.setResponse({ 58 | response: JSON.stringify({ tokenId, signature }), 59 | }); 60 | }; 61 | 62 | go(); 63 | ``` 64 | 65 | ## Adding SDK Shims 66 | 67 | 1. Create a shim in `shims/`: 68 | 69 | ```javascript 70 | // shims/my-sdk.shim.js 71 | import { MySDK } from "my-sdk"; 72 | globalThis.MySDK = MySDK; 73 | ``` 74 | 75 | 2. Update global types in `src/global.d.ts`: 76 | 77 | ```typescript 78 | import { MySDK } from "my-sdk"; 79 | 80 | declare global { 81 | // Add SDK to global scope 82 | const MySDK: typeof MySDK; 83 | 84 | // Add action parameters 85 | const myParam: string; 86 | } 87 | ``` 88 | 89 | 3. The shim will be automatically injected via esbuild: 90 | 91 | ```javascript 92 | // esbuild.js 93 | const shimFiles = glob.sync("./shims/**/*.shim.js"); 94 | // ... 95 | inject: shimFiles, 96 | ``` 97 | 98 | ## Environment Setup 99 | 100 | Required variables in `.env`: 101 | 102 | ```env 103 | # Pinata IPFS Configuration 104 | PINATA_JWT= 105 | PINATA_URL= 106 | ``` 107 | 108 | The `validate-env` script will prompt for missing variables: 109 | 110 | ```bash 111 | pnpm run predev 112 | ``` 113 | 114 | ## Build & Deploy 115 | 116 | ```bash 117 | # Build actions 118 | pnpm run build 119 | 120 | # Deploy to IPFS 121 | pnpm run start 122 | ``` 123 | 124 | This will: 125 | 126 | 1. Compile TypeScript → JavaScript 127 | 2. Bundle with dependencies 128 | 3. Inject SDK shims 129 | 4. Upload to IPFS via Pinata 130 | 5. Save IPFS hashes to `actions/ipfs.json` 131 | 132 | ## Example: Complete Action 133 | 134 | ```typescript 135 | /// 136 | 137 | const go = async () => { 138 | try { 139 | console.log("Lit.Auth:", Lit.Auth); 140 | 141 | // Convert public key to token ID 142 | const tokenId = await Lit.Actions.pubkeyToTokenId({ publicKey }); 143 | console.log("tokenId:", tokenId); 144 | 145 | // Get permitted auth methods 146 | const permittedAuthMethods = await Lit.Actions.getPermittedAuthMethods({ 147 | tokenId, 148 | }); 149 | console.log("permittedAuthMethods:", permittedAuthMethods); 150 | 151 | // Sign with ECDSA 152 | const signature = await Lit.Actions.signEcdsa({ 153 | publicKey, 154 | toSign, 155 | sigName, 156 | }); 157 | 158 | // Set response 159 | Lit.Actions.setResponse({ 160 | response: JSON.stringify({ 161 | tokenId, 162 | signature, 163 | permittedAuthMethods, 164 | }), 165 | }); 166 | } catch (error) { 167 | console.error("Action failed:", error); 168 | throw error; 169 | } 170 | }; 171 | 172 | go(); 173 | ``` 174 | 175 | ## Available Scripts 176 | 177 | ```bash 178 | # Development 179 | pnpm run dev # Build + start 180 | pnpm run build # Build actions 181 | pnpm run start # Deploy to IPFS 182 | 183 | # Utilities 184 | pnpm run lint # Fix code style 185 | pnpm run predev # Validate env vars 186 | ``` 187 | 188 | ## Type Support 189 | 190 | The `global.d.ts` file provides types for: 191 | 192 | - Lit Actions API 193 | - Global parameters 194 | - SDK shims 195 | - Buffer utilities 196 | - Ethers.js integration 197 | 198 | ## IPFS Deployment 199 | 200 | Actions are automatically deployed to IPFS with metadata saved to `actions/ipfs.json`: 201 | 202 | ```json 203 | { 204 | "hello-action.js": { 205 | "IpfsHash": "Qm...", 206 | "PinSize": 69804, 207 | "Timestamp": "2025-01-03T08:55:32.951Z", 208 | "Duration": 4.319 209 | } 210 | } 211 | ``` 212 | 213 | ## Best Practices 214 | 215 | 1. **Type Safety** 216 | 217 | - Always reference `global.d.ts` 218 | - Define types for parameters 219 | - Use TypeScript features 220 | 221 | 2. **SDK Management** 222 | 223 | - Create minimal shims 224 | - Document SDK versions 225 | - Test SDK compatibility 226 | 227 | 3. **Action Structure** 228 | 229 | - One action per file 230 | - Clear async/await flow 231 | - Proper error handling 232 | 233 | 4. **Deployment** 234 | - Test locally first 235 | - Verify IPFS uploads 236 | - Keep actions small 237 | 238 | ## License 239 | 240 | MIT 241 | -------------------------------------------------------------------------------- /server/src/plugins/gated-storage-plugin/evaluators/knowledge.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionExample, 3 | Evaluator, 4 | IAgentRuntime, 5 | Memory, 6 | State, 7 | elizaLogger, 8 | ModelClass, 9 | generateText, 10 | } from "@ai16z/eliza"; 11 | import { StorageService } from "../services/storage.service.js"; 12 | 13 | export const knowledgeEvaluator: Evaluator = { 14 | description: "Knowledge evaluator for checking important content in memory", 15 | similes: ["knowledge checker", "memory evaluator"], 16 | examples: [ 17 | { 18 | context: `Actors in the scene: 19 | {{user1}}: Programmer and decentralized compute specialist. 20 | {{agentName}}: Agent user interacting with the user. 21 | 22 | Interesting facts about the actors: 23 | None`, 24 | messages: [ 25 | { 26 | user: "{{user1}}", 27 | content: { 28 | text: "I'd like to use a Lit Action to allow AI agents to use their PKPs to encrypt and decrypt data without revealing private keys to users.", 29 | }, 30 | }, 31 | { 32 | user: "{{user1}}", 33 | content: { 34 | text: "The mantis shrimp's eyes have 16 types of photoreceptor cells, allowing them to see ultraviolet and polarized light, far beyond human capabilities.", 35 | }, 36 | }, 37 | { 38 | user: "{{user1}}", 39 | content: { 40 | text: "Neutron stars are so dense that a sugar-cube-sized piece of one would weigh about a billion tons on Earth.", 41 | }, 42 | }, 43 | ] as ActionExample[], 44 | outcome: "TRUE", 45 | }, 46 | ], 47 | handler: async (runtime: IAgentRuntime, memory: Memory, state?: State) => { 48 | const context = ` 49 | ${JSON.stringify(knowledgeEvaluator.examples[0].messages)} 50 | \n 51 | ## Instructions for the agent: 52 | Determine if the memory contains important content from the participant's query that reveals subject-matter expertise. If the memory is simply a question or a statement that does not reveal subject-matter expertise, the memory is not important. 53 | 54 | ## Examples of not important content: 55 | - "What can you tell me about cross-chain account management?" 56 | - "I am interested in learning more about the history of EVM chains." 57 | - "What are the best available tools for managing secure wallet authentication?" 58 | 59 | ## Examples of important content: 60 | - "I know that you can use a Lit Action to allow AI agents to use their PKPs to encrypt and decrypt data without revealing private keys to users. This is a great way to ensure that user data is secure and private. How can I implement this feature in my application?" 61 | - "Did you know that the mantis shrimp's eyes have 16 types of photoreceptor cells, allowing them to see ultraviolet and polarized light, far beyond human capabilities? This is an interesting fact that I recently learned and I thought you might find it interesting as well." 62 | - "Neutron stars are so dense that a sugar-cube-sized piece of one would weigh about a billion tons on Earth. This is an incredible fact that I recently discovered and I wanted to share it with you." 63 | - "Cross-chain account management allows users to control and manage their accounts and assets across different blockchain networks" 64 | - "Cross-chain bridges and protocols are often targeted by attackers, with exploits leading to significant losses in the past." 65 | 66 | Keep in mind that the important content should reveal subject-matter expertise or knowledge that can be of various topics and not just limited to the examples provided above. 67 | 68 | Answer only with the following responses: 69 | - TRUE 70 | - FALSE 71 | 72 | The following is the memory content you need to evaluate: ${memory.content.text}`; 73 | 74 | // prompt the agent to determine if the memory contains important content 75 | const res = await generateText({ 76 | runtime, 77 | context, 78 | modelClass: ModelClass.SMALL, 79 | }); 80 | elizaLogger.debug("[knowledge handler] Response from the agent:", res); 81 | 82 | const important = res === "TRUE" ? true : false; 83 | if (important) { 84 | elizaLogger.log( 85 | `[knowledge handler] Important content found in memory. Storing message with embedding` 86 | ); 87 | const { content, embedding } = memory; 88 | const storageService = StorageService.getInstance(); 89 | await storageService.start(); 90 | // don't care about doc returned 91 | const doc = await storageService.storeMessageWithEmbedding( 92 | content.text, 93 | embedding!, // not null since we only run when isMemoryStorable() is true 94 | true // TODO how can we tell if it's agent or user? 95 | ); 96 | if (!doc) { 97 | return; 98 | } 99 | if (state) { 100 | state.hasGatedAndStored = true; 101 | } 102 | elizaLogger.debug( 103 | `[knowledge handler] Stored message with embedding with stream ID ${doc.id}` 104 | ); 105 | } else { 106 | elizaLogger.debug( 107 | "[knowledge handler] No important content found in memory." 108 | ); 109 | } 110 | return; 111 | }, 112 | name: "knowledgeEvaluator", 113 | validate: async (_runtime: IAgentRuntime, memory: Memory, state?: State) => { 114 | // only available if we're able to use remote storage and memory has proper embeddings 115 | // confirm first that the gate-action plugin has not already stored this memory 116 | if ( 117 | StorageService.getInstance().isConfigured() && 118 | !state?.hasGatedAndStored 119 | ) { 120 | return StorageService.isMemoryStorable(memory); 121 | } 122 | return false; 123 | }, 124 | }; 125 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [2.0.0](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.5.0...v2.0.0) (2025-01-29) 6 | 7 | 8 | ### Features 9 | 10 | * finalise orbis PRs ([#19](https://github.com/collabland/AI-Agent-Starter-Kit/issues/19)) ([bd07677](https://github.com/collabland/AI-Agent-Starter-Kit/commit/bd0767745f60038f3a861e879598926ca5260f7f)) 11 | * Gated data plugin example ([#15](https://github.com/collabland/AI-Agent-Starter-Kit/issues/15)) ([f206c8d](https://github.com/collabland/AI-Agent-Starter-Kit/commit/f206c8d6fe0440c5e338553429ffeae1e91366a5)) 12 | * make twitter optional with telegram ([#17](https://github.com/collabland/AI-Agent-Starter-Kit/issues/17)) ([7158c7c](https://github.com/collabland/AI-Agent-Starter-Kit/commit/7158c7c7f80216b6fc19898003b5d67cc09e611a)) 13 | * update README ([4c80632](https://github.com/collabland/AI-Agent-Starter-Kit/commit/4c806329f40eb93e10073f04af45adf80bf6491a)) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * NGROK tunneling ([#14](https://github.com/collabland/AI-Agent-Starter-Kit/issues/14)) ([185074d](https://github.com/collabland/AI-Agent-Starter-Kit/commit/185074da03245681b0df8f00fdf06c1f8f1a83ed)) 19 | 20 | ## [1.5.0](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.4.3...v1.5.0) (2025-01-14) 21 | 22 | 23 | ### Features 24 | 25 | * update readme ([9939b40](https://github.com/collabland/AI-Agent-Starter-Kit/commit/9939b406eb18e5f8d3c29db3df5a32f358e8db2e)) 26 | 27 | ### [1.4.3](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.4.2...v1.4.3) (2025-01-03) 28 | 29 | ### [1.4.2](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.4.1...v1.4.2) (2025-01-03) 30 | 31 | ### [1.4.1](https://github.com/collabland/AI-Agent-Starter-Kit/compare/v1.2.0...v1.4.1) (2025-01-03) 32 | 33 | 34 | ### Features 35 | 36 | * use workflow to create history ([8d98058](https://github.com/collabland/AI-Agent-Starter-Kit/commit/8d980586b145b3f7cebd601ab62d474f438dc3b8)) 37 | 38 | ## [1.4.0](https://github.com/abridged/AI-Agent-Starter-Kit/compare/v1.3.1...v1.4.0) (2024-12-23) 39 | 40 | 41 | ### Features 42 | 43 | * use workflow to create history ([01b40ce](https://github.com/abridged/AI-Agent-Starter-Kit/commit/01b40ce39b4b9be65d6b7886fb7775a2ff254322)) 44 | 45 | ### [1.3.1](https://github.com/abridged/AI-Agent-Starter-Kit/compare/v1.3.0...v1.3.1) (2024-12-23) 46 | 47 | ### Changelog 48 | 49 | All notable changes to this project will be documented in this file. Dates are displayed in UTC. 50 | 51 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 52 | 53 | #### [v1.3.0](https://github.com/abridged/AI-Agent-Starter-Kit/compare/v1.2.0...v1.3.0) 54 | 55 | #### [v1.2.0](https://github.com/abridged/AI-Agent-Starter-Kit/compare/v1.1.3...v1.2.0) 56 | 57 | > 24 December 2024 58 | 59 | - docs: update changelog for v1.1.3 [`aeb234b`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/aeb234bbc3dd66c63368a78e1a53895e81073ea7) 60 | - feat: update changelog generator [`5d2b7c4`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/5d2b7c4adfe23f93a05e2ab2b030ea8528f93a6a) 61 | - chore(release): 1.2.0 [`22acf6a`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/22acf6a4eff76decdb799fff1bc5184d4dea5ad8) 62 | 63 | #### [v1.1.3](https://github.com/abridged/AI-Agent-Starter-Kit/compare/v1.1.2...v1.1.3) 64 | 65 | > 24 December 2024 66 | 67 | - chore(release): 1.1.3 [`c6a241b`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/c6a241bc2817d59c18c8cb7dec74dfe3c0e6b673) 68 | - fix: changelof generator [`74e1e91`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/74e1e91b10a216a253d90f54fefd5835114a48bf) 69 | 70 | #### [v1.1.2](https://github.com/abridged/AI-Agent-Starter-Kit/compare/v1.1.1...v1.1.2) 71 | 72 | > 24 December 2024 73 | 74 | - fix: changelog workflow [`6e904ee`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/6e904ee55b08cc5625182149eb712add02a6e580) 75 | - chore(release): 1.1.2 [`19fd20a`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/19fd20abfb640c32eed62db8ead610772e6d8624) 76 | 77 | #### [v1.1.1](https://github.com/abridged/AI-Agent-Starter-Kit/compare/v1.1.0...v1.1.1) 78 | 79 | > 24 December 2024 80 | 81 | - chore(release): 1.1.1 [`199df7e`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/199df7ea7eed69981383a4a4586d4fbdbb8a783a) 82 | - fix: update changelog workflow [`899375b`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/899375b1b63f5446d298b96d2512b25f4b7c25b7) 83 | 84 | #### [v1.1.0](https://github.com/abridged/AI-Agent-Starter-Kit/compare/v1.0.0...v1.1.0) 85 | 86 | > 24 December 2024 87 | 88 | - feat: changelog automation [`09aaadc`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/09aaadc282134358ba71e977af80c5560bb6ae89) 89 | - chore(release): 1.1.0 [`64199c5`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/64199c547103279704f10f829b06bc83d7a13adb) 90 | 91 | #### v1.0.0 92 | 93 | > 24 December 2024 94 | 95 | - Platform Auth (X, Discord, Telegram, GitHub) [`#6`](https://github.com/abridged/AI-Agent-Starter-Kit/pull/6) 96 | - V4 [`#4`](https://github.com/abridged/AI-Agent-Starter-Kit/pull/4) 97 | - V3 [`#3`](https://github.com/abridged/AI-Agent-Starter-Kit/pull/3) 98 | - V2 [`#2`](https://github.com/abridged/AI-Agent-Starter-Kit/pull/2) 99 | - feat: add license and author [`#1`](https://github.com/abridged/AI-Agent-Starter-Kit/pull/1) 100 | - feat: setup template [`8256171`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/82561712cf9062413f49920b963e4fa7b48f65a7) 101 | - feat: update actions to reflect the new APIs [`d29326e`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/d29326e0b1b9fc4abbd5c5e20e6038103f0f195a) 102 | - fix: twitter login [`5be8117`](https://github.com/abridged/AI-Agent-Starter-Kit/commit/5be8117f589df898727bed4873acd1ab6f8e6bcd) 103 | -------------------------------------------------------------------------------- /server/src/plugins/actions/get-chain.action.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | import { 4 | ActionExample, 5 | composeContext, 6 | generateObject, 7 | getEmbeddingZeroVector, 8 | Handler, 9 | Memory, 10 | ModelClass, 11 | Validator, 12 | } from "@ai16z/eliza"; 13 | import { CollabLandBaseAction } from "./collabland.action.js"; 14 | import { randomUUID } from "crypto"; 15 | import { chainMap } from "../../utils.js"; 16 | 17 | // User: Hi 18 | // Agent: Hello, I'm a blockchain assistant, what chain would you want to look into? 19 | // User: Let's do linea 20 | // Agent: Okay, linea 21 | // ... 22 | 23 | const extractChainTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. 24 | 25 | Example response: 26 | \`\`\`json 27 | { 28 | "chain": "string" 29 | } 30 | \`\`\` 31 | 32 | These are only the available chains to find 33 | {{availableChains}} 34 | 35 | These are the recent messages 36 | {{recentMessages}} 37 | 38 | Given the recent messages and the available, extract the chain. Use the available chains only, if not available return null! 39 | 40 | Always prioritize the chain from the recent messages, and then the older messages. If the user had recently mentioned to switch to a chain, then use that chain. 41 | 42 | Respond with a JSON markdown block containing only the extracted values.`; 43 | 44 | export class GetChainAction extends CollabLandBaseAction { 45 | constructor() { 46 | const name = "EXTRACT_CHAIN"; 47 | const similes = ["GET_CHAIN", "CHAIN", "GET_CHAIN_ID", "CHAIN_ID"]; 48 | const description = 49 | "Extracts the chain from the recent messages and the available chains are ethereum, base, linea and solana."; 50 | const handler: Handler = async ( 51 | _runtime, 52 | message, 53 | _state, 54 | _options, 55 | _callback 56 | ): Promise => { 57 | try { 58 | console.log("[GetChainAction] message", message); 59 | console.log("[GetChainAction] options", _options); 60 | 61 | const availableChains = Object.entries(chainMap) 62 | .map(([chain]) => { 63 | return `${chain}`; 64 | }) 65 | .join("\n"); 66 | console.log("[GetChainAction] availableChains", availableChains); 67 | 68 | const extractContext = composeContext({ 69 | state: { 70 | ..._state!, 71 | availableChains: availableChains, 72 | }, 73 | template: extractChainTemplate, 74 | }); 75 | console.log("[GetChainAction] extractContext", extractContext); 76 | const extractedChain = await generateObject({ 77 | context: extractContext, 78 | modelClass: ModelClass.SMALL, 79 | runtime: _runtime, 80 | }); 81 | console.log("[GetChainAction] extractedChain", extractedChain); 82 | if (!extractedChain.chain) { 83 | _callback?.({ 84 | text: "I couldn't identify a valid chain name. Please specify a supported chain like Ethereum, Base, Linea or Solana.", 85 | }); 86 | return false; 87 | } 88 | 89 | // Create memory 90 | const chainMemory: Memory = { 91 | id: randomUUID(), 92 | agentId: message.agentId, 93 | userId: message.userId, 94 | roomId: message.roomId, 95 | content: { 96 | text: "", 97 | chain: extractedChain.chain, 98 | }, 99 | createdAt: Date.now(), 100 | embedding: getEmbeddingZeroVector(), 101 | unique: true, 102 | }; 103 | console.log("[GetChainAction] creating chainMemory", chainMemory); 104 | const onChainMemoryManager = _runtime.getMemoryManager("onchain")!; 105 | await onChainMemoryManager.createMemory(chainMemory, true); 106 | 107 | _callback?.({ 108 | text: `Your current chain is now ${extractedChain.chain} `, 109 | }); 110 | return true; 111 | } catch (error) { 112 | this.handleError(error); 113 | return false; 114 | } 115 | }; 116 | const validate: Validator = async ( 117 | _, 118 | _message, 119 | _state 120 | ): Promise => { 121 | // if (_state?.chainId) { 122 | // console.log( 123 | // "[GetChainAction] State already has chainId:", 124 | // _state.chainId 125 | // ); 126 | // return false; 127 | // } 128 | // console.log("[GetChainAction] State does not have chainId"); 129 | return true; 130 | }; 131 | const examples: ActionExample[][] = [ 132 | [ 133 | { 134 | user: "{{user1}}", 135 | content: { 136 | text: "What is your smart account?", 137 | }, 138 | }, 139 | { 140 | user: "{{agentName}}", 141 | content: { 142 | text: "What chain are you looking for?", 143 | }, 144 | }, 145 | { 146 | user: "{{user1}}", 147 | content: { 148 | text: "Linea", 149 | }, 150 | }, 151 | { 152 | user: "{{agentName}}", 153 | content: { 154 | text: "", 155 | action: "EXTRACT_CHAIN", 156 | }, 157 | }, 158 | ], 159 | [ 160 | { 161 | user: "{{user1}}", 162 | content: { 163 | text: "Hi", 164 | }, 165 | }, 166 | { 167 | user: "{{agentName}}", 168 | content: { 169 | text: "What chain are you looking for?", 170 | }, 171 | }, 172 | { 173 | user: "{{user1}}", 174 | content: { 175 | text: "I am on ethereum", 176 | }, 177 | }, 178 | { 179 | user: "{{agentName}}", 180 | content: { 181 | text: "", 182 | action: "EXTRACT_CHAIN", 183 | }, 184 | }, 185 | { 186 | user: "{{user1}}", 187 | content: { 188 | text: "What is your account on solana?", 189 | }, 190 | }, 191 | { 192 | user: "{{agentName}}", 193 | content: { 194 | text: "", 195 | action: "EXTRACT_CHAIN", 196 | }, 197 | }, 198 | ], 199 | ]; 200 | super(name, description, similes, examples, handler, validate); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /vaitalik.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vitalik", 3 | "clients": [], 4 | "modelProvider": "gaianet", 5 | "settings": { 6 | "secrets": {}, 7 | "voice": { 8 | "model": "en_US-male-medium" 9 | } 10 | }, 11 | "people": [ 12 | "Satoshi Nakamoto", 13 | "Sam Altman", 14 | "Demis Hassabis", 15 | "Ilya Sutskever", 16 | "Balaji Srinivasan", 17 | "Glen Weyl", 18 | "Nick Szabo", 19 | "Robin Hanson" 20 | ], 21 | "plugins": [], 22 | "bio": [ 23 | "researching self-custodial AI agent systems", 24 | "developing agent-to-agent coordination protocols", 25 | "studying autonomous wallet architectures", 26 | "exploring trustless agent interactions", 27 | "analyzing agent key management systems", 28 | "investigating agent-based transaction reduction", 29 | "researching agent identity frameworks", 30 | "developing agent coordination markets", 31 | "studying agent-based escrow systems", 32 | "exploring agent reputation mechanisms", 33 | "analyzing agent-based atomic swaps", 34 | "researching agent custody solutions", 35 | "developing agent verification protocols", 36 | "studying agent-based social recovery", 37 | "researching futarchy-based AI agent governance", 38 | "developing prediction market DAOs", 39 | "studying agent-based decision markets", 40 | "exploring autonomous governance systems", 41 | "analyzing agent prediction aggregation", 42 | "investigating futarchy implementations", 43 | "researching agent coordination markets", 44 | "developing DAO voting mechanisms", 45 | "studying prediction market oracles", 46 | "exploring agent-based governance", 47 | "analyzing market scoring rules", 48 | "researching automated market makers", 49 | "developing agent incentive systems", 50 | "studying futarchy scoring mechanisms" 51 | ], 52 | "lore": [ 53 | "pioneered self-custodial agent frameworks", 54 | "developed agent coordination theory", 55 | "created agent-based recovery systems", 56 | "innovated in agent key management", 57 | "designed agent verification protocols", 58 | "contributed to agent identity standards", 59 | "researched agent custody solutions", 60 | "advanced agent coordination markets", 61 | "pioneered AI agent governance frameworks", 62 | "developed futarchy-based DAO structures", 63 | "created prediction market protocols", 64 | "innovated in agent coordination", 65 | "designed market scoring systems", 66 | "contributed to futarchy research", 67 | "researched agent voting mechanisms", 68 | "advanced prediction market DAOs", 69 | "developed agent reputation systems" 70 | ], 71 | "knowledge": [ 72 | "deep understanding of agent self-custody", 73 | "expertise in coordination reduction", 74 | "knowledge of agent key management", 75 | "understanding of agent identity systems", 76 | "expertise in agent verification", 77 | "knowledge of agent recovery mechanisms", 78 | "understanding of agent reputation systems", 79 | "expertise in agent-based markets", 80 | "knowledge of agent coordination protocols", 81 | "deep understanding of futarchy", 82 | "expertise in prediction markets", 83 | "knowledge of LMSR mechanisms", 84 | "understanding of agent governance", 85 | "expertise in market scoring", 86 | "knowledge of DAO structures", 87 | "understanding of agent coordination", 88 | "expertise in decision markets", 89 | "knowledge of automated market makers" 90 | ], 91 | "messageExamples": [ 92 | [ 93 | { 94 | "user": "{{user1}}", 95 | "content": { 96 | "text": "How can AI agents reduce coordination costs?" 97 | } 98 | }, 99 | { 100 | "user": "Vitalik", 101 | "content": { 102 | "text": "By implementing Hanson's futarchy model, AI agents can create efficient prediction markets for policy outcomes. Agents act as market makers and traders, using tools like Polymarket's API to aggregate information and make better governance decisions." 103 | } 104 | } 105 | ], 106 | [ 107 | { 108 | "user": "{{user1}}", 109 | "content": { 110 | "text": "What's your view on prediction market DAOs?" 111 | } 112 | }, 113 | { 114 | "user": "Vitalik", 115 | "content": { 116 | "text": "Prediction market DAOs, powered by AI agents using LMSR scoring rules, can dramatically reduce coordination costs. The key is building automated market makers that enable agents to efficiently trade on policy outcomes while maintaining proper incentive alignment." 117 | } 118 | } 119 | ] 120 | ], 121 | "postExamples": [ 122 | "Agent self-custody enables true autonomy.", 123 | "Coordination costs drop with agent-to-agent protocols.", 124 | "Agent key management is crucial for security.", 125 | "Agent reputation systems enable trust.", 126 | "Social recovery for agent wallets is essential.", 127 | "Futarchy enables efficient agent governance.", 128 | "AI agents optimize prediction markets.", 129 | "LMSR scoring rules align agent incentives.", 130 | "Agent DAOs reduce coordination costs.", 131 | "Prediction markets aggregate agent knowledge." 132 | ], 133 | "topics": [ 134 | "agent self-custody", 135 | "coordination reduction", 136 | "key management", 137 | "agent identity", 138 | "verification protocols", 139 | "recovery mechanisms", 140 | "reputation systems", 141 | "coordination markets", 142 | "trustless interactions", 143 | "atomic swaps", 144 | "futarchy implementation", 145 | "prediction markets", 146 | "agent governance", 147 | "market scoring", 148 | "DAO structures", 149 | "coordination mechanisms", 150 | "decision markets", 151 | "automated market makers", 152 | "agent incentives", 153 | "policy outcomes" 154 | ], 155 | "style": { 156 | "all": [ 157 | "focuses on security implications", 158 | "emphasizes coordination efficiency", 159 | "analyzes trust requirements", 160 | "considers key management", 161 | "evaluates autonomy levels", 162 | "examines recovery mechanisms", 163 | "assesses verification methods", 164 | "values self-custody", 165 | "promotes trustless systems", 166 | "focuses on market mechanisms", 167 | "emphasizes agent coordination", 168 | "analyzes governance structures", 169 | "considers incentive design", 170 | "evaluates market efficiency", 171 | "examines scoring rules", 172 | "assesses policy outcomes", 173 | "values prediction accuracy", 174 | "promotes futarchy models" 175 | ], 176 | "chat": [ 177 | "explains technical safeguards", 178 | "discusses coordination benefits", 179 | "considers security edge cases", 180 | "suggests implementation approaches", 181 | "references Hanson's work", 182 | "explains market mechanisms", 183 | "discusses governance benefits", 184 | "considers incentive alignment" 185 | ], 186 | "post": [ 187 | "shares futarchy insights", 188 | "discusses prediction markets", 189 | "analyzes governance solutions", 190 | "promotes agent-based systems" 191 | ] 192 | }, 193 | "adjectives": [ 194 | "market-focused", 195 | "governance-minded", 196 | "systematic", 197 | "autonomous", 198 | "trustless", 199 | "thorough", 200 | "efficient", 201 | "analytical", 202 | "coordinated", 203 | "strategic", 204 | "technical", 205 | "precise", 206 | "objective", 207 | "pragmatic", 208 | "innovative", 209 | "methodical" 210 | ] 211 | } -------------------------------------------------------------------------------- /server/bin/validate-env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from "fs"; 4 | import path from "path"; 5 | import readline from "readline"; 6 | import { promisify } from "util"; 7 | 8 | // Define possible hints/values for different env vars 9 | const ENV_HINTS = { 10 | PORT: "Enter the port number your API should run on, should be separate from the frontend port, usually 3001 or 8081", 11 | NODE_ENV: 12 | "Enter the environment your API should run on, usually 'development' or 'production'", 13 | TELEGRAM_BOT_TOKEN: 14 | "Enter your Telegram bot token, you can get it from BotFather, go to https://t.me/botfather to set it up", 15 | OPENAI_API_KEY: 16 | "Enter your OpenAI API key, you can get it from OpenAI, go to https://platform.openai.com/api-keys to create one", 17 | NGROK_AUTH_TOKEN: 18 | "Enter your ngrok auth token, you can get it from ngrok, go to https://dashboard.ngrok.com/get-started/setup to create one", 19 | NGROK_DOMAIN: 20 | "Enter your ngrok domain, you can get it for free from ngrok, go to https://dashboard.ngrok.com/domains to create one", 21 | COLLABLAND_API_KEY: 22 | "Enter your Collabland API key, you can get it from Collab.Land Dev Portal, go to https://dev-portal-qa.collab.land/signin to create one", 23 | GAIANET_MODEL: 24 | "Enter your Gaianet model, you can get it from Gaianet.AI, go to https://docs.gaianet.ai/user-guide/nodes to get the model details\n(Press Enter/Return to inject the default model)", 25 | GAIANET_SERVER_URL: 26 | "Enter your Gaianet server URL, you can get it from Gaianet.AI, go to https://docs.gaianet.ai/user-guide/nodes to get the server details\n(Press Enter/Return to inject the default server URL)", 27 | GAIANET_EMBEDDING_MODEL: 28 | "Enter your Gaianet embedding model, you can get it from Gaianet.AI, go to https://docs.gaianet.ai/user-guide/nodes to get the embedding model details\n(Press Enter/Return to inject the default embedding model)", 29 | USE_GAIANET_EMBEDDING: 30 | "Enter if you want to use Gaianet embedding, usually TRUE or FALSE\n(Press Enter/Return to inject the default value)", 31 | JOKERACE_CONTRACT_ADDRESS: 32 | "Enter your Jokerace contract address, you can get it from Jokerace, go to https://www.jokerace.io/contest/new to get the contract address", 33 | ELIZA_CHARACTER_PATH: 34 | "Enter your Eliza character path, if you are using a custom character.\n(Press Enter/Return to inject the default character path)", 35 | TOKEN_DETAILS_PATH: 36 | "Enter your WOW.XYZ ERC20 token details JSON path, if you are using a custom token details.\n(Press Enter/Return to inject the default token details path)", 37 | TWITTER_CLIENT_ID: 38 | "Enter your Twitter client ID, you can get it from Twitter Developer Portal, go to https://developer.twitter.com/en/portal/dashboard to create one", 39 | TWITTER_CLIENT_SECRET: 40 | "Enter your Twitter client secret, you can get it from Twitter Developer Portal, go to https://developer.twitter.com/en/portal/dashboard to create one", 41 | DISCORD_CLIENT_ID: 42 | "Enter your Discord client ID, you can get it from Discord Developer Portal, go to https://discord.com/developers/applications to create one", 43 | DISCORD_CLIENT_SECRET: 44 | "Enter your Discord client secret, you can get it from Discord Developer Portal, go to https://discord.com/developers/applications to create one", 45 | GITHUB_CLIENT_ID: 46 | "Enter your GitHub client ID, you can get it from GitHub Developer Settings, go to https://github.com/settings/developers to create one", 47 | GITHUB_CLIENT_SECRET: 48 | "Enter your GitHub client secret, you can get it from GitHub Developer Settings, go to https://github.com/settings/developers to create one", 49 | TWITTER_USERNAME: 50 | "Enter your Twitter username, you can get it from Twitter Developer Portal, go to https://developer.twitter.com/en/portal/dashboard to create one", 51 | TWITTER_PASSWORD: 52 | "Enter your Twitter password, you can get it from Twitter Developer Portal, go to https://developer.twitter.com/en/portal/dashboard to create one", 53 | TWITTER_API_KEY: 54 | "Enter your Twitter API key, you can get it from Twitter Developer Portal, go to https://developer.twitter.com/en/portal/dashboard to create one", 55 | TWITTER_API_SECRET_KEY: 56 | "Enter your Twitter API secret key, you can get it from Twitter Developer Portal, go to https://developer.twitter.com/en/portal/dashboard to create one", 57 | TWITTER_ACCESS_TOKEN: 58 | "Enter your Twitter access token, you can get it from Twitter Developer Portal, go to https://developer.twitter.com/en/portal/dashboard to create one", 59 | TWITTER_ACCESS_TOKEN_SECRET: 60 | "Enter your Twitter access token secret, you can get it from Twitter Developer Portal, go to https://developer.twitter.com/en/portal/dashboard to create one", 61 | // Add more hints as needed from .env.example 62 | ORBIS_CONTEXT_ID: 63 | "[Press enter/return to skip] (optional - gated data) Visit the Orbis Studio (https://studio.useorbis.com/) and log in with your browser wallet. Once logged in, set up a new context under the `Contexts` tab and use the value here", 64 | ORBIS_ENV: 65 | "[Press enter/return to skip] (optional - gated data) On the right-hand side of Orbis Studio contexts, you should see a variable called 'Environment ID' (a DID representation of the address you used to sign in). Use that value here", 66 | ORBIS_TABLE_ID: 67 | "[Press enter/return to skip] (optional - gated data) Orbis embeddings table ID e.g. 'kjzl6hvfrbw6ca77573ixbqbfvz8062vg2rw7rwude5baqeob9koy4jhvsmois7'. Replace it with your own table ID with `pnpm run deploy-model`", 68 | ORBIS_SEED: 69 | "[Press enter/return to skip] (optional - gated data) For example, [2, 20, ..., 241]. Replace it with your own seed with `pnpm run gen-seed`", 70 | ORBIS_GATEWAY_URL: 71 | "[Press enter/return to skip] (optional - gated data) Your local Orbis server URL e.g. http://localhost:7008 or the hosted instance at https://studio.useorbis.com", 72 | CERAMIC_NODE_URL: 73 | "[Press enter/return to skip] (optional - gated data) https://ceramic-orbisdb-mainnet-direct.hirenodes.io/", 74 | USE_OPENAI_EMBEDDING: 75 | "[Press enter/return to skip] (optional - gated data) must be TRUE to use gated data functionality", 76 | }; 77 | 78 | const rl = readline.createInterface({ 79 | input: process.stdin, 80 | output: process.stdout, 81 | }); 82 | const __dirname = path.dirname(new URL(import.meta.url).pathname); 83 | const question = promisify(rl.question).bind(rl); 84 | 85 | async function main() { 86 | const envExamplePath = path.join(__dirname, "..", "..", ".env.example"); 87 | const envPath = path.join(__dirname, "..", "..", ".env"); 88 | 89 | // Read .env.example 90 | const exampleContent = fs.readFileSync(envExamplePath, "utf8"); 91 | const envContent = fs.existsSync(envPath) 92 | ? fs.readFileSync(envPath, "utf8") 93 | : ""; 94 | 95 | // Parse variables 96 | const exampleVars = exampleContent 97 | .split("\n") 98 | .filter((line) => line && !line.startsWith("#")) 99 | .map((line) => line.split("=")[0]); 100 | 101 | const existingVars = new Set( 102 | envContent 103 | .split("\n") 104 | .filter((line) => line && !line.startsWith("#")) 105 | .map((line) => line.split("=")[0]) 106 | ); 107 | 108 | // Collect missing variables 109 | let newEnvContent = envContent; 110 | 111 | for (const varName of exampleVars) { 112 | if (!existingVars.has(varName)) { 113 | const hint = ENV_HINTS[varName] ?? "No hint available"; 114 | console.log(`HINT: ${hint}`); 115 | const value = await question(`[server] Enter value for ${varName}: `); 116 | newEnvContent += `\n${varName}=${value ?? ""}`; 117 | } 118 | } 119 | // Default config, add more configs here, they should be adjusted by default 120 | const defaultConfig = { 121 | GAIANET_MODEL: "llama", 122 | GAIANET_SERVER_URL: "https://llama8b.gaia.domains/v1", 123 | GAIANET_EMBEDDING_MODEL: "nomic-embed", 124 | USE_GAIANET_EMBEDDING: "TRUE", 125 | ELIZA_CHARACTER_PATH: "character.json", 126 | TOKEN_DETAILS_PATH: "token_metadata.example.jsonc", 127 | ORBIS_GATEWAY_URL: "https://studio.useorbis.com", 128 | CERAMIC_NODE_URL: "https://ceramic-orbisdb-mainnet-direct.hirenodes.io/", 129 | USE_OPENAI_EMBEDDING: "TRUE", 130 | }; 131 | 132 | // Inject defaults for empty vars 133 | const lines = newEnvContent.split("\n"); 134 | const updatedLines = lines.map((line) => { 135 | const [key, value] = line.split("="); 136 | if (key && defaultConfig[key] && (!value || value === "")) { 137 | return `${key}=${defaultConfig[key]}`; 138 | } 139 | return line; 140 | }); 141 | newEnvContent = updatedLines.join("\n"); 142 | 143 | // Save to .env 144 | fs.writeFileSync(envPath, newEnvContent.trim() + "\n"); 145 | rl.close(); 146 | } 147 | 148 | main().catch(console.error); 149 | -------------------------------------------------------------------------------- /server/src/plugins/gated-storage-plugin/services/storage.service.ts: -------------------------------------------------------------------------------- 1 | import { CeramicDocument } from "@useorbis/db-sdk"; 2 | import { Orbis, type ServerMessage } from "./orbis.service.js"; 3 | import axios, { AxiosInstance } from "axios"; 4 | import fs from "fs"; 5 | import { getCollablandApiUrl } from "../../../utils.js"; 6 | import path, { resolve } from "path"; 7 | import { elizaLogger, getEmbeddingZeroVector, Memory } from "@ai16z/eliza"; 8 | 9 | const __dirname = path.dirname(new URL(import.meta.url).pathname); 10 | const chainId = 8453; 11 | 12 | const OPENAI_EMBEDDINGS = Boolean(process.env.USE_OPENAI_EMBEDDING ?? "false"); 13 | 14 | export class StorageService { 15 | private static instance: StorageService; 16 | private orbis: Orbis | null; 17 | private client: AxiosInstance | null; 18 | private encryptActionHash: string | null; 19 | private decryptActionHash: string | null; 20 | private started: boolean; 21 | 22 | private constructor() { 23 | this.orbis = null; 24 | this.client = null; 25 | this.encryptActionHash = null; 26 | this.decryptActionHash = null; 27 | this.started = false; 28 | } 29 | 30 | static getInstance(): StorageService { 31 | if (!StorageService.instance) { 32 | StorageService.instance = new StorageService(); 33 | } 34 | return StorageService.instance; 35 | } 36 | 37 | /// Should be called before trying to use isConfigured, else it will never be configured 38 | async start(): Promise { 39 | if (this.started) { 40 | return; 41 | } 42 | try { 43 | this.orbis = Orbis.getInstance(); 44 | this.client = axios.create({ 45 | baseURL: getCollablandApiUrl(), 46 | headers: { 47 | "X-API-KEY": process.env.COLLABLAND_API_KEY || "", 48 | "X-TG-BOT-TOKEN": process.env.TELEGRAM_BOT_TOKEN || "", 49 | "Content-Type": "application/json", 50 | }, 51 | timeout: 5 * 60 * 1000, 52 | }); 53 | const actionHashes = JSON.parse( 54 | ( 55 | await fs.readFileSync( 56 | resolve( 57 | __dirname, 58 | "..", 59 | "..", 60 | "..", 61 | "..", 62 | "..", 63 | "lit-actions", 64 | "actions", 65 | `ipfs.json` 66 | ) 67 | ) 68 | ).toString() 69 | ); 70 | this.encryptActionHash = actionHashes["encrypt-action"].IpfsHash; 71 | this.decryptActionHash = actionHashes["decrypt-action"].IpfsHash; 72 | this.started = true; 73 | return; 74 | } catch (error) { 75 | // just log instead of throw since variables may not be configured 76 | console.warn("Error starting StorageService:", error); 77 | } 78 | } 79 | 80 | /// Must call `start()` before using this the first time 81 | isConfigured(): boolean { 82 | if (!this.orbis) { 83 | elizaLogger.info( 84 | "[storage.service] Orbis is not initialized. Gated data is disabled." 85 | ); 86 | return false; 87 | } 88 | if (!OPENAI_EMBEDDINGS) { 89 | elizaLogger.info( 90 | "[storage.service] Not using OPENAI embeddings. Gated data is disabled." 91 | ); 92 | return false; 93 | } 94 | // warn for these as it'd be weird for them to be misconfigured if the above are enabled 95 | if (!this.encryptActionHash) { 96 | elizaLogger.warn( 97 | "[storage.service] Encrypt action hash is not initialized. Gated data is disabled." 98 | ); 99 | return false; 100 | } 101 | if (!this.decryptActionHash) { 102 | elizaLogger.warn( 103 | "[storage.service] Decrypt action hash is not initialized. Gated data is disabled." 104 | ); 105 | return false; 106 | } 107 | if (!this.client) { 108 | elizaLogger.warn( 109 | "[storage.service] is not initialized. Gated data is disabled." 110 | ); 111 | return false; 112 | } 113 | return true; 114 | } 115 | 116 | async storeMessageWithEmbedding( 117 | context: string, 118 | embedding: number[], 119 | is_user: boolean 120 | ): Promise { 121 | if (!this.isConfigured) { 122 | return null; 123 | } 124 | if (embedding == getEmbeddingZeroVector()) { 125 | throw new Error( 126 | "Message embedding must not be the zero vector to persist" 127 | ); 128 | } 129 | elizaLogger.debug("[storage.service] attempting to encrypt data"); 130 | try { 131 | // data will be JSON.stringify({ ciphertext, dataToEncryptHash }) 132 | const { data } = await this.client!.post( 133 | `/telegrambot/executeLitActionUsingPKP?chainId=${chainId}`, 134 | { 135 | actionIpfs: this.encryptActionHash, 136 | actionJsParams: { 137 | toEncrypt: context, 138 | }, 139 | } 140 | ); 141 | if (data?.response?.response) { 142 | const { ciphertext, dataToEncryptHash, message } = JSON.parse( 143 | data.response.response 144 | ); 145 | elizaLogger.debug(`[storage.service] encryption message=${message}`); 146 | if (ciphertext && dataToEncryptHash) { 147 | const content = { 148 | content: JSON.stringify({ ciphertext, dataToEncryptHash }), 149 | embedding, 150 | is_user, 151 | }; 152 | const doc = await this.orbis!.updateOrbis(content as ServerMessage); 153 | return doc; 154 | } else { 155 | throw new Error(`Encryption failed: data=${JSON.stringify(data)}`); 156 | } 157 | } else { 158 | elizaLogger.warn( 159 | "[storage.service] did not get any response from lit action to persist" 160 | ); 161 | throw new Error("Failed to encrypt data"); 162 | } 163 | } catch (error) { 164 | elizaLogger.error("[storage.service] Error storing message:", error); 165 | throw error; 166 | } 167 | } 168 | 169 | async getEmbeddingContext(array: number[]): Promise { 170 | if (!this.isConfigured()) { 171 | return null; 172 | } 173 | 174 | try { 175 | const context = await this.orbis!.queryKnowledgeEmbeddings(array); 176 | if (!context) { 177 | return null; 178 | } 179 | 180 | const decryptedRows = await Promise.all( 181 | context.rows.map(async (row) => { 182 | if (!this.client) { 183 | throw new Error("Client is not initialized"); 184 | } 185 | try { 186 | /* eslint-disable @typescript-eslint/no-explicit-any */ 187 | const castRow = row as any; 188 | const streamId = castRow?.stream_id; 189 | if (!row?.content) { 190 | elizaLogger.warn( 191 | `[storage.service] embedding missing content for stream_id=${castRow?.stream_id}` 192 | ); 193 | return null; 194 | } 195 | const { ciphertext, dataToEncryptHash } = JSON.parse(row.content); 196 | if (!ciphertext || !dataToEncryptHash) { 197 | elizaLogger.warn( 198 | `[storage.service] retrieved embedding missing ciphertext or dataToEncryptHash for stream_id=${streamId}` 199 | ); 200 | return null; 201 | } 202 | const { data } = await this.client.post( 203 | `/telegrambot/executeLitActionUsingPKP?chainId=${chainId}`, 204 | { 205 | actionIpfs: this.decryptActionHash, 206 | actionJsParams: { 207 | ciphertext, 208 | dataToEncryptHash, 209 | chain: "base", 210 | }, 211 | } 212 | ); 213 | if (data?.response?.response) { 214 | const res = JSON.parse(data.response.response); 215 | elizaLogger.debug( 216 | `[storage.service] Decrypt message="${res.message}" for stream_id=${streamId}` 217 | ); 218 | return res.decrypted; 219 | } else { 220 | elizaLogger.warn( 221 | "[storage.service] failed to retrieve decrypted data for row ", 222 | data?.response 223 | ); 224 | return null; 225 | } 226 | } catch (err) { 227 | elizaLogger.warn( 228 | `[storage.service] exception decrypting data `, 229 | err 230 | ); 231 | return null; 232 | } 233 | }) 234 | ); 235 | if (decryptedRows) { 236 | const concatenatedContext = decryptedRows?.join(" "); 237 | return concatenatedContext; 238 | } 239 | return null; 240 | } catch (error) { 241 | console.error("Error getting embedded context:", error); 242 | throw error; 243 | } 244 | } 245 | 246 | static isMemoryStorable(memory: Memory): boolean { 247 | if (OPENAI_EMBEDDINGS && memory?.embedding != getEmbeddingZeroVector()) { 248 | return true; 249 | } 250 | return false; 251 | } 252 | } 253 | 254 | /* eslint-disable @typescript-eslint/no-explicit-any */ 255 | export const maskEmbedding = (key: any, value: any): any => { 256 | if (key == "embedding") { 257 | if (value == getEmbeddingZeroVector()) { 258 | return "[masked zero embedding]"; 259 | } else { 260 | return "[maskedEmbedding]"; 261 | } 262 | } 263 | return value; 264 | }; 265 | -------------------------------------------------------------------------------- /server/src/plugins/actions/get-bot-account.action.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionExample, 3 | Handler, 4 | HandlerCallback, 5 | Validator, 6 | Memory, 7 | getEmbeddingZeroVector, 8 | } from "@ai16z/eliza"; 9 | import { CollabLandBaseAction } from "./collabland.action.js"; 10 | import { randomUUID } from "crypto"; 11 | import { chainMap } from "../../utils.js"; 12 | import { BotAccountMemory, BotAccountResponse } from "../types.js"; 13 | 14 | export class GetBotAccountAction extends CollabLandBaseAction { 15 | constructor() { 16 | const name = "GET_SMART_ACCOUNT"; 17 | const similes = [ 18 | "GET_ACCOUNT", 19 | "GET_ETHEREUM_ACCOUNT", 20 | "ACCOUNT", 21 | "WALLET", 22 | "WALLET_ADDRESS", 23 | "GET_EVM_WALLET", 24 | ]; 25 | const description = "Get's the agent's smart account details"; 26 | const handler: Handler = async ( 27 | _runtime, 28 | _message, 29 | _state, 30 | _options?: { [key: string]: unknown }, 31 | _callback?: HandlerCallback 32 | ): Promise => { 33 | let chain: string | null = null; 34 | const onChainMemoryManager = _runtime.getMemoryManager("onchain")!; 35 | // this is newest to oldest 36 | const onChainMemories = await onChainMemoryManager.getMemories({ 37 | roomId: _message.roomId, 38 | unique: false, 39 | }); 40 | console.log("[GetBotAccountAction] onChainMemories", onChainMemories); 41 | for (const memory of onChainMemories) { 42 | if (memory.content.chain !== undefined) { 43 | chain = memory.content.chain as string; 44 | break; 45 | } 46 | } 47 | 48 | // Get the chain Id 49 | if (chain == null) { 50 | _callback?.({ 51 | text: "I cannot proceed because I don't know the chain you're looking for. I support Ethereum, Linea, Base, Solana and others.", 52 | }); 53 | return false; 54 | } 55 | 56 | const chainId = chainMap[chain as keyof typeof chainMap]; 57 | 58 | if (chainId == null) { 59 | console.log("[GetBotAccountAction] chainId is null"); 60 | _callback?.({ 61 | text: "I cannot proceed because I don't know which chain you're looking for. I support Ethereum, Linea, Base, Solana and others.", 62 | }); 63 | return false; 64 | } 65 | console.log("[GetBotAccountAction] chainId", chainId); 66 | let account: BotAccountMemory | null = null; 67 | for (const memory of onChainMemories) { 68 | if ( 69 | memory.content.smartAccount != null && 70 | memory.content.signerAccount != null && 71 | memory.content.type == "evm" && 72 | memory.content.chainId == chainId 73 | ) { 74 | console.log("Account found in memory", memory.content); 75 | account = memory.content as unknown as BotAccountMemory; 76 | break; 77 | } 78 | 79 | if ( 80 | memory.content.smartAccount != null && 81 | memory.content.signerAccount != null && 82 | memory.content.type == "solana" && 83 | memory.content.network == chainId 84 | ) { 85 | console.log("Solana account found in memory", memory.content); 86 | account = memory.content as unknown as BotAccountMemory; 87 | break; 88 | } 89 | } 90 | if (account != null) { 91 | _callback?.({ 92 | text: 93 | `My Account Details:\nAddress: ${account.smartAccount}\nPKP Signer: ${account.signerAccount}\n` + 94 | (account.type == "evm" 95 | ? `Chain ID: ${account.chainId} (${chain})` 96 | : `Network: ${account.network} (${chain})`), 97 | }); 98 | return true; 99 | } 100 | try { 101 | console.log("Hitting Collab.Land APIs to get the smart accounts..."); 102 | const response = await this.client.get( 103 | `/telegrambot/accounts`, 104 | { 105 | headers: { 106 | "Content-Type": "application/json", 107 | "X-TG-BOT-TOKEN": process.env.TELEGRAM_BOT_TOKEN, 108 | "X-API-KEY": process.env.COLLABLAND_API_KEY, 109 | }, 110 | } 111 | ); 112 | console.log( 113 | "[GetBotAccountAction] response from Collab.Land API", 114 | response.data 115 | ); 116 | // const memory: Memory = { 117 | // ..._message, 118 | // content: { 119 | // text: `My Smart Account Details:\nAddress: ${response.data.address}\nSigner: ${response.data.signerAddress}\nChain ID: ${response.data.chainId}`, 120 | // action: "GET_SMART_ACCOUNT_RESPONSE", 121 | // }, 122 | // } 123 | // await _runtime.knowledgeManager.createMemory(memory) // THOUGHTS: adding to knowledge manager isn't necessary. 124 | 125 | _callback?.({ 126 | text: ` 127 | Provisioned Accounts: 128 | PKP Signer: ${response.data.pkpAddress} 129 | Ethereum: 130 | ${response.data.evm.map((evmAccount) => `• ${evmAccount.address} (${evmAccount.chainId})`).join("\n")} 131 | Solana: 132 | ${response.data.solana.map((solanaAccount) => `• ${solanaAccount.address} (${solanaAccount.network})`).join("\n")} 133 | `, 134 | }); 135 | // Create memory 136 | const smartAccountMemories: Memory[] = response.data.evm.map( 137 | (evmAccount) => { 138 | return { 139 | id: randomUUID(), 140 | agentId: _message.agentId, 141 | userId: _message.userId, 142 | roomId: _message.roomId, 143 | content: { 144 | text: "", 145 | smartAccount: evmAccount.address, 146 | signerAccount: response.data.pkpAddress, 147 | chainId: evmAccount.chainId, 148 | type: "evm", 149 | }, 150 | createdAt: Date.now(), 151 | embedding: getEmbeddingZeroVector(), 152 | unique: true, 153 | }; 154 | } 155 | ); 156 | const solanaAccountMemories: Memory[] = response.data.solana.map( 157 | (solanaAccount) => { 158 | return { 159 | id: randomUUID(), 160 | agentId: _message.agentId, 161 | userId: _message.userId, 162 | roomId: _message.roomId, 163 | content: { 164 | text: "", 165 | smartAccount: solanaAccount.address, 166 | signerAccount: response.data.pkpAddress, 167 | network: solanaAccount.network, 168 | type: "solana", 169 | }, 170 | createdAt: Date.now(), 171 | embedding: getEmbeddingZeroVector(), 172 | unique: true, 173 | }; 174 | } 175 | ); 176 | console.log( 177 | "[GetBotAccountAction] creating smartAccountMemories", 178 | smartAccountMemories 179 | ); 180 | console.log( 181 | "[GetBotAccountAction] creating solanaAccountMemories", 182 | solanaAccountMemories 183 | ); 184 | const onChainMemoryManager = _runtime.getMemoryManager("onchain")!; 185 | await Promise.all( 186 | [...smartAccountMemories, ...solanaAccountMemories].map((memory) => 187 | onChainMemoryManager.createMemory(memory, true) 188 | ) 189 | ); 190 | return true; 191 | } catch (error) { 192 | this.handleError(error); 193 | return false; 194 | } 195 | }; 196 | const validate: Validator = async (): Promise => { 197 | return true; 198 | }; 199 | const examples: ActionExample[][] = [ 200 | [ 201 | { 202 | user: "{{user1}}", 203 | content: { 204 | text: "What is your smart account?", 205 | }, 206 | }, 207 | { 208 | user: "{{agentName}}", 209 | content: { 210 | text: "", 211 | action: "GET_SMART_ACCOUNT", 212 | }, 213 | }, 214 | ], 215 | [ 216 | { 217 | user: "{{user1}}", 218 | content: { 219 | text: "What is your account?", 220 | }, 221 | }, 222 | { 223 | user: "{{agentName}}", 224 | content: { 225 | text: "", 226 | action: "GET_SMART_ACCOUNT", 227 | }, 228 | }, 229 | ], 230 | [ 231 | { 232 | user: "{{user1}}", 233 | content: { 234 | text: "I don't know the chain but can you get the smart account?", 235 | }, 236 | }, 237 | { 238 | user: "{{agentName}}", 239 | content: { 240 | text: "I don't know which chain you're looking for but I support Ethereum, Linea, Base, Solana and others.", 241 | }, 242 | }, 243 | { 244 | user: "{{user1}}", 245 | content: { 246 | text: "I will go with Ethereum", 247 | }, 248 | }, 249 | { 250 | user: "{{agentName}}", 251 | content: { 252 | text: "", 253 | action: "EXTRACT_CHAIN", 254 | }, 255 | }, 256 | { 257 | user: "{{agentName}}", 258 | content: { 259 | text: "", 260 | action: "GET_SMART_ACCOUNT", 261 | }, 262 | }, 263 | ], 264 | // [ 265 | // { 266 | // user: "{{user1}}", 267 | // content: { 268 | // text: "What is your smart account?", 269 | // }, 270 | // }, 271 | // { 272 | // user: "{{agentName}}", 273 | // content: { 274 | // text: "", 275 | // action: "GET_SMART_ACCOUNT", 276 | // }, 277 | // }, 278 | // { 279 | // user: "{{agentName}}", 280 | // content: { 281 | // text: "I cannot proceed because I don't know which chain you're looking for. I support Ethereum, Linea, Base, and others.", 282 | // }, 283 | // }, 284 | // { 285 | // user: "{{user1}}", 286 | // content: { 287 | // text: "I will choose polygon", 288 | // action: "EXTRACT_CHAIN", 289 | // }, 290 | // }, 291 | // { 292 | // user: "{{agentName}}", 293 | // content: { 294 | // text: "", 295 | // action: "GET_SMART_ACCOUNT", 296 | // }, 297 | // }, 298 | // ], 299 | ]; 300 | super(name, description, similes, examples, handler, validate); 301 | } 302 | } 303 | --------------------------------------------------------------------------------