├── .prettierignore ├── cdp-agentkit-core ├── .prettierignore ├── .eslintrc.json ├── tsconfig.json ├── .prettierrc ├── src │ ├── index.ts │ ├── actions │ │ └── cdp │ │ │ ├── cdp_action.ts │ │ │ ├── social │ │ │ └── twitter │ │ │ │ ├── twitter_action.ts │ │ │ │ ├── index.ts │ │ │ │ ├── post_tweet.ts │ │ │ │ ├── account_details.ts │ │ │ │ ├── account_mentions.ts │ │ │ │ └── post_tweet_reply.ts │ │ │ ├── defi │ │ │ └── wow │ │ │ │ ├── index.ts │ │ │ │ ├── utils.ts │ │ │ │ ├── actions │ │ │ │ ├── create_token.ts │ │ │ │ ├── buy_token.ts │ │ │ │ └── sell_token.ts │ │ │ │ └── uniswap │ │ │ │ └── constants.ts │ │ │ ├── index.ts │ │ │ ├── get_wallet_details.ts │ │ │ ├── get_balance.ts │ │ │ ├── deploy_token.ts │ │ │ ├── deploy_nft.ts │ │ │ ├── request_faucet_funds.ts │ │ │ ├── mint_nft.ts │ │ │ ├── trade.ts │ │ │ ├── wrap_eth.ts │ │ │ ├── transfer.ts │ │ │ └── register_basename.ts │ ├── tests │ │ ├── get_wallet_details_test.ts │ │ ├── social_twitter_account_details_test.ts │ │ ├── wrap_eth_test.ts │ │ ├── social_twitter_post_tweet_test.ts │ │ ├── social_twitter_account_mentions_test.ts │ │ ├── request_faucet_funds_test.ts │ │ ├── get_balance_test.ts │ │ ├── transfer_test.ts │ │ ├── deploy_token_test.ts │ │ ├── trade_test.ts │ │ ├── social_twitter_post_tweet_reply_test.ts │ │ ├── cdp_agentkit_test.ts │ │ ├── deploy_nft_test.ts │ │ ├── mint_nft_test.ts │ │ ├── defi_wow_buy_token_test.ts │ │ ├── defi_wow_create_token_test.ts │ │ ├── defi_wow_sell_token_test.ts │ │ └── register_basename_test.ts │ ├── twitter_agentkit.ts │ └── cdp_agentkit.ts ├── jest.config.cjs ├── README.md ├── CHANGELOG.md └── package.json ├── cdp-langchain ├── .prettierignore ├── .eslintrc.json ├── examples │ └── chatbot │ │ ├── .prettierignore │ │ ├── .eslintrc.json │ │ ├── tsconfig.json │ │ ├── .prettierrc │ │ ├── package.json │ │ └── README.md ├── tsconfig.json ├── .prettierrc ├── src │ ├── index.ts │ ├── toolkits │ │ └── cdp_toolkit.ts │ ├── tests │ │ ├── cdp_tool_test.ts │ │ └── cdp_toolkit_test.ts │ └── tools │ │ └── cdp_tool.ts ├── jest.config.cjs ├── CHANGELOG.md ├── package.json └── README.md ├── twitter-langchain ├── .prettierignore ├── .eslintrc.json ├── examples │ └── chatbot │ │ ├── .prettierignore │ │ ├── .eslintrc.json │ │ ├── .env-local │ │ ├── tsconfig.json │ │ ├── .prettierrc │ │ ├── package.json │ │ ├── README.md │ │ └── chatbot.ts ├── src │ ├── index.ts │ ├── twitter_toolkit.ts │ ├── tests │ │ ├── twitter_tool_test.ts │ │ └── twitter_toolkit_test.ts │ └── twitter_tool.ts ├── tsconfig.json ├── .prettierrc ├── jest.config.cjs ├── CHANGELOG.md ├── package.json └── README.md ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ └── bug-report.yml ├── pull_request_template.md └── workflows │ ├── unit_tests.yml │ ├── publish_langchain.yml │ ├── publish_twitter_langchain.yml │ ├── publish_agentkit.yml │ ├── publish_docs.yml │ └── lint.yml ├── .prettierrc ├── SECURITY.md ├── jest.config.base.cjs ├── tsconfig.base.json ├── LICENSE.md ├── turbo.json ├── .gitignore ├── package.json ├── .eslintrc.base.json ├── README.md └── CONTRIBUTING.md /.prettierignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | dist/ 3 | coverage/ 4 | .github/ 5 | src/client 6 | **/**/*.json 7 | *.md 8 | -------------------------------------------------------------------------------- /cdp-agentkit-core/.prettierignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | dist/ 3 | coverage/ 4 | .github/ 5 | src/client 6 | **/**/*.json 7 | *.md -------------------------------------------------------------------------------- /cdp-langchain/.prettierignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | dist/ 3 | coverage/ 4 | .github/ 5 | src/client 6 | **/**/*.json 7 | *.md 8 | -------------------------------------------------------------------------------- /twitter-langchain/.prettierignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | dist/ 3 | coverage/ 4 | .github/ 5 | src/client 6 | **/**/*.json 7 | *.md 8 | -------------------------------------------------------------------------------- /cdp-agentkit-core/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": ["../.eslintrc.base.json"] 4 | } 5 | -------------------------------------------------------------------------------- /cdp-langchain/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": ["../.eslintrc.base.json"] 4 | } 5 | -------------------------------------------------------------------------------- /cdp-langchain/examples/chatbot/.prettierignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | dist/ 3 | coverage/ 4 | .github/ 5 | src/client 6 | **/**/*.json 7 | *.md -------------------------------------------------------------------------------- /twitter-langchain/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": ["../.eslintrc.base.json"] 4 | } 5 | -------------------------------------------------------------------------------- /twitter-langchain/examples/chatbot/.prettierignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | dist/ 3 | coverage/ 4 | .github/ 5 | src/client 6 | **/**/*.json 7 | *.md -------------------------------------------------------------------------------- /cdp-langchain/examples/chatbot/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": ["../../../.eslintrc.base.json"] 4 | } 5 | -------------------------------------------------------------------------------- /twitter-langchain/examples/chatbot/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": ["../../../.eslintrc.base.json"] 4 | } 5 | -------------------------------------------------------------------------------- /twitter-langchain/examples/chatbot/.env-local: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | TWITTER_ACCESS_TOKEN= 3 | TWITTER_ACCESS_TOKEN_SECRET= 4 | TWITTER_API_KEY= 5 | TWITTER_API_SECRET= 6 | -------------------------------------------------------------------------------- /twitter-langchain/src/index.ts: -------------------------------------------------------------------------------- 1 | // Export Twitter (X) tool 2 | export { TwitterTool } from "./twitter_tool"; 3 | 4 | // Export Twitter (X) toolkit 5 | export { TwitterToolkit } from "./twitter_toolkit"; 6 | -------------------------------------------------------------------------------- /cdp-langchain/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/tests"] 9 | } 10 | -------------------------------------------------------------------------------- /cdp-agentkit-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/tests"] 9 | } 10 | -------------------------------------------------------------------------------- /twitter-langchain/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["src/**/*.ts"], 8 | "exclude": ["src/tests"] 9 | } 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Questions 4 | url: https://discord.com/channels/1220414409550336183/1304126107876069376 5 | about: Ask questions and discuss with other CDP community members 6 | -------------------------------------------------------------------------------- /cdp-langchain/examples/chatbot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "preserveSymlinks": true, 5 | "outDir": "./dist", 6 | "rootDir": "." 7 | }, 8 | "include": ["*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /twitter-langchain/examples/chatbot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "preserveSymlinks": true, 5 | "outDir": "./dist", 6 | "rootDir": "." 7 | }, 8 | "include": ["*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": false, 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "printWidth": 100, 10 | "proseWrap": "never" 11 | } 12 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | The Coinbase team takes security seriously. Please do not file a public ticket discussing a potential vulnerability. 4 | 5 | Please report your findings through our [HackerOne][1] program. 6 | 7 | [1]: https://hackerone.com/coinbase -------------------------------------------------------------------------------- /cdp-agentkit-core/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": false, 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "printWidth": 100, 10 | "proseWrap": "never" 11 | } 12 | -------------------------------------------------------------------------------- /cdp-langchain/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": false, 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "printWidth": 100, 10 | "proseWrap": "never" 11 | } 12 | -------------------------------------------------------------------------------- /twitter-langchain/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": false, 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "printWidth": 100, 10 | "proseWrap": "never" 11 | } 12 | -------------------------------------------------------------------------------- /cdp-langchain/examples/chatbot/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": false, 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "printWidth": 100, 10 | "proseWrap": "never" 11 | } 12 | -------------------------------------------------------------------------------- /twitter-langchain/examples/chatbot/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "singleQuote": false, 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "printWidth": 100, 10 | "proseWrap": "never" 11 | } 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### What changed? Why? 2 | 3 | 4 | #### Qualified Impact 5 | 8 | -------------------------------------------------------------------------------- /cdp-langchain/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Main exports for the CDP Langchain package 3 | */ 4 | 5 | // Export core toolkit components 6 | export { CdpToolkit } from "./toolkits/cdp_toolkit"; 7 | export { CdpTool } from "./tools/cdp_tool"; 8 | 9 | // Export types 10 | export type { Tool } from "@langchain/core/tools"; 11 | -------------------------------------------------------------------------------- /cdp-langchain/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const baseConfig = require("../jest.config.base.cjs"); 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | coveragePathIgnorePatterns: ["node_modules", "dist", "docs", "index.ts"], 6 | coverageThreshold: { 7 | "./src/**": { 8 | branches: 30, 9 | functions: 50, 10 | statements: 50, 11 | lines: 50, 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /twitter-langchain/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const baseConfig = require("../jest.config.base.cjs"); 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | coveragePathIgnorePatterns: ["node_modules", "dist", "docs", "index.ts"], 6 | coverageThreshold: { 7 | "./src/**": { 8 | branches: 30, 9 | functions: 50, 10 | statements: 50, 11 | lines: 50, 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/index.ts: -------------------------------------------------------------------------------- 1 | // Export CDP actions 2 | export * from "./actions/cdp"; 3 | 4 | // Export CDP DeFi actions 5 | export * from "./actions/cdp/defi/wow"; 6 | 7 | // Export Twitter actions 8 | export * from "./actions/cdp/social/twitter"; 9 | 10 | // Export CDP Action 11 | export { CdpAction } from "./actions/cdp/cdp_action"; 12 | 13 | // Export CDP AgentKit 14 | export { CdpAgentkit } from "./cdp_agentkit"; 15 | 16 | // Export Twitter AgentKit 17 | export { TwitterAgentkit } from "./twitter_agentkit"; 18 | -------------------------------------------------------------------------------- /jest.config.base.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | extensionsToTreatAsEsm: [".ts"], 5 | coveragePathIgnorePatterns: ["node_modules", "dist", "docs"], 6 | collectCoverage: true, 7 | collectCoverageFrom: ["./src/**"], 8 | coverageReporters: ["html"], 9 | verbose: true, 10 | maxWorkers: 1, 11 | coverageThreshold: { 12 | "./src/**": { 13 | branches: 77, 14 | functions: 85, 15 | statements: 85, 16 | lines: 85, 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /cdp-agentkit-core/jest.config.cjs: -------------------------------------------------------------------------------- 1 | const baseConfig = require("../jest.config.base.cjs"); 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | coveragePathIgnorePatterns: ["node_modules", "dist", "docs", "index.ts"], 6 | coverageThreshold: { 7 | "./src/actions/cdp/*": { 8 | branches: 50, 9 | functions: 50, 10 | statements: 50, 11 | lines: 50, 12 | }, 13 | "./src/actions/cdp/defi/wow/actions/*": { 14 | branches: 50, 15 | functions: 50, 16 | statements: 50, 17 | lines: 50, 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.github/workflows/unit_tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: ["18", "20"] 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-node@v4 14 | with: 15 | node-version: ${{ matrix.node-version }} 16 | cache: "npm" 17 | # Install dependencies in parent directory first 18 | - name: Install and test agentkit 19 | run: | 20 | npm i 21 | npm run test 22 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "outDir": "./dist", 6 | "rootDir": "./src", 7 | "strict": true, 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "moduleResolution": "node", 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "declaration": true, 14 | "noImplicitAny": false, 15 | "removeComments": false 16 | }, 17 | "include": ["src/**/*.ts"], 18 | "exclude": ["node_modules", "dist", "**/tests/**/**"] 19 | } 20 | -------------------------------------------------------------------------------- /twitter-langchain/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CDP Agentkit Extension - Twitter Toolkit Changelog 2 | 3 | ## Unreleased 4 | 5 | ## [0.0.9] - 2025-01-13 6 | 7 | ### Added 8 | - Bump `@coinbase/cdp-agentkit-core` dependency to `0.0.11` 9 | 10 | ## [0.0.8] - 2025-01-09 11 | 12 | ### Added 13 | - Bump `@coinbase/cdp-agentkit-core` dependency to `0.0.10` 14 | 15 | ## [0.0.7] - 2025-01-08 16 | 17 | ### Added 18 | - Bump `@coinbase/cdp-agentkit-core` dependency to `0.0.9` 19 | 20 | ## [0.0.6] - 2024-12-09 21 | 22 | ### Added 23 | 24 | - Initial release of the CDP AgentKit.js Twitter Extension. 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache-2.0 License 2 | 3 | Copyright 2024 Coinbase 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. -------------------------------------------------------------------------------- /.github/workflows/publish_langchain.yml: -------------------------------------------------------------------------------- 1 | name: Publish LangChain to NPM 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy-langchain: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | id-token: write 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: "18" 17 | registry-url: "https://registry.npmjs.org" 18 | - run: npm i && npm run build 19 | - name: Install, build and publish langchain 20 | working-directory: ./cdp-langchain 21 | run: | 22 | npm publish --ignore-scripts --provenance --access public 23 | env: 24 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 25 | -------------------------------------------------------------------------------- /cdp-agentkit-core/README.md: -------------------------------------------------------------------------------- 1 | # AgentKit Core 2 | 3 | Framework agnostic primitives that are meant to be composable and used via AgentKit framework extensions. 4 | 5 | You can find all of the supported actions under `./cdp_agentkit_core/actions` 6 | 7 | ## Contributing 8 | 9 | See [CONTRIBUTING.md](../CONTRIBUTING.md) for more information. 10 | 11 | ## Security and bug reports 12 | 13 | The CDP AgentKit team takes security seriously. 14 | See [SECURITY.md](../SECURITY.md) for more information. 15 | 16 | ## Documentation 17 | 18 | - [CDP AgentKit Documentation](https://docs.cdp.coinbase.com/agentkit/docs/welcome) 19 | - [API Reference: CDP AgentKit Core](https://coinbase.github.io/cdp-agentkit-nodejs/cdp-agentkit-core/index.html) 20 | 21 | ## License 22 | 23 | Apache-2.0 24 | -------------------------------------------------------------------------------- /.github/workflows/publish_twitter_langchain.yml: -------------------------------------------------------------------------------- 1 | name: Publish Twitter LangChain to NPM 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy-twitter-langchain: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | id-token: write 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: "18" 17 | registry-url: "https://registry.npmjs.org" 18 | - run: npm i && npm run build 19 | - name: Install, build and publish Twitter (X) langchain 20 | working-directory: ./twitter-langchain 21 | run: | 22 | npm publish --ignore-scripts --provenance --access public 23 | env: 24 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 25 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "globalDependencies": [".env"], 4 | "tasks": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "outputs": ["dist/**"] 8 | }, 9 | "test": { 10 | "dependsOn": ["build"], 11 | "inputs": ["src/**/*.ts", "test/**/*.ts"] 12 | }, 13 | "lint": { 14 | "outputs": [] 15 | }, 16 | "lint:fix": { 17 | "cache": false 18 | }, 19 | "format": { 20 | "cache": false 21 | }, 22 | "format:check": { 23 | "cache": false 24 | }, 25 | "clean": { 26 | "cache": false 27 | }, 28 | "docs": { 29 | "outputs": ["docs/**"] 30 | }, 31 | "dev": { 32 | "cache": false, 33 | "persistent": true 34 | }, 35 | "test:types": { 36 | "dependsOn": ["build"] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cdp-langchain/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CDP AgentKit.js Langchain Extension Changelog 2 | 3 | ## Unreleased 4 | 5 | ## [0.0.12] - 2025-01-13 6 | 7 | ### Added 8 | - Bump `@coinbase/cdp-agentkit-core` dependency to `0.0.11` 9 | 10 | ## [0.0.11] - 2025-01-09 11 | 12 | ### Added 13 | - Bump `@coinbase/cdp-agentkit-core` dependency to `0.0.10` 14 | 15 | ## [0.0.10] - 2025-01-08 16 | 17 | ### Added 18 | - Bump `@coinbase/cdp-agentkit-core` dependency to `0.0.9` 19 | - Bump `@coinbase/coinbase-sdk` dependency to `0.0.13` 20 | 21 | ## [0.0.9] - 2024-12-09 22 | 23 | ### Added 24 | 25 | - Bump `@coinbase/cdp-agentkit-core` dependency to `0.0.8` 26 | 27 | ## [0.0.8] - 2024-12-06 28 | 29 | ### Added 30 | 31 | - Bump `@coinbase/cdp-agentkit-core` dependency to `0.0.7` 32 | 33 | ## [0.0.7] - 2024-12-03 34 | 35 | ### Added 36 | 37 | - Initial release of the CDP AgentKit.js Langchain Extension. 38 | -------------------------------------------------------------------------------- /.github/workflows/publish_agentkit.yml: -------------------------------------------------------------------------------- 1 | name: Release AgentKit Core to NPM 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy-agentkit: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | id-token: write 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: "18" 17 | registry-url: "https://registry.npmjs.org" 18 | # Install dependencies in parent directory first 19 | - run: npm ci 20 | # Then install, build and publish in working directory 21 | - name: Install, build and publish agentkit 22 | working-directory: ./cdp-agentkit-core 23 | run: | 24 | npm ci 25 | npm run build 26 | npm publish --provenance --access public 27 | env: 28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/cdp_action.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { Wallet } from "@coinbase/coinbase-sdk"; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | export type CdpActionSchemaAny = z.ZodObject; 6 | 7 | /** 8 | * Represents the base structure for CDP Actions. 9 | */ 10 | export interface CdpAction { 11 | /** 12 | * The name of the action 13 | */ 14 | name: string; 15 | 16 | /** 17 | * A description of what the action does 18 | */ 19 | description: string; 20 | 21 | /** 22 | * Schema for validating action arguments 23 | */ 24 | argsSchema: TActionSchema; 25 | 26 | /** 27 | * The function to execute for this action 28 | */ 29 | func: 30 | | ((wallet: Wallet, args: z.infer) => Promise) 31 | | ((args: z.infer) => Promise); 32 | } 33 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/social/twitter/twitter_action.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { TwitterApi } from "twitter-api-v2"; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | export type TwitterActionSchemaAny = z.ZodObject; 6 | 7 | /** 8 | * Represents the base structure for Twitter (X) Actions. 9 | */ 10 | export interface TwitterAction { 11 | /** 12 | * The name of the action. 13 | */ 14 | name: string; 15 | 16 | /** 17 | * A description of what the action does 18 | */ 19 | description: string; 20 | 21 | /** 22 | * Schema for validating action arguments 23 | */ 24 | argsSchema: TActionSchema; 25 | 26 | /** 27 | * The function to execute for this action 28 | */ 29 | func: 30 | | ((client: TwitterApi, args: z.infer) => Promise) 31 | | ((args: z.infer) => Promise); 32 | } 33 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/defi/wow/index.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction, CdpActionSchemaAny } from "../../cdp_action"; 2 | import { WowBuyTokenAction } from "./actions/buy_token"; 3 | import { WowSellTokenAction } from "./actions/sell_token"; 4 | import { WowCreateTokenAction } from "./actions/create_token"; 5 | 6 | /** 7 | * Retrieves all WOW protocol action instances. 8 | * WARNING: All new WowAction classes must be instantiated here to be discovered. 9 | * 10 | * @returns Array of WOW protocol action instances 11 | */ 12 | export function getAllWowActions(): CdpAction[] { 13 | // eslint-disable-next-line prettier/prettier 14 | return [new WowBuyTokenAction(), new WowSellTokenAction(), new WowCreateTokenAction()]; 15 | } 16 | 17 | export const WOW_ACTIONS = getAllWowActions(); 18 | 19 | // Export individual actions for direct imports 20 | // eslint-disable-next-line prettier/prettier 21 | export { WowBuyTokenAction, WowSellTokenAction, WowCreateTokenAction }; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature request 2 | description: Submit a proposal/request for a new AgentKit.js feature 3 | 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: 🚀 The feature, motivation and pitch 8 | description: > 9 | A clear and concise description of the feature proposal. Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., *"I'm working on X and would like Y to be possible"*. If this is related to another GitHub issue, please link here too. 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: Alternatives 15 | description: > 16 | A description of any alternative solutions or features you've considered, if any. 17 | - type: textarea 18 | attributes: 19 | label: Additional context 20 | description: > 21 | Add any other context or screenshots about the feature request. 22 | - type: markdown 23 | attributes: 24 | value: > 25 | Thanks for contributing 🎉! 26 | -------------------------------------------------------------------------------- /cdp-langchain/examples/chatbot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@coinbase/cdp-langchain-chatbot-example", 3 | "description": "CDP Agentkit Node.js SDK Chatbot Example", 4 | "version": "1.0.0", 5 | "author": "Coinbase Inc.", 6 | "license": "Apache-2.0", 7 | "scripts": { 8 | "start": "NODE_OPTIONS='--no-warnings' ts-node ./chatbot.ts", 9 | "dev": "nodemon ./chatbot.ts", 10 | "lint": "eslint -c .eslintrc.json *.ts", 11 | "lint-fix": "eslint -c .eslintrc.json *.ts --fix", 12 | "format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"", 13 | "format-check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"" 14 | }, 15 | "dependencies": { 16 | "@coinbase/cdp-agentkit-core": "^0.0.11", 17 | "@coinbase/cdp-langchain": "^0.0.12", 18 | "@langchain/langgraph": "^0.2.21", 19 | "@langchain/openai": "^0.3.14", 20 | "@langchain/core": "^0.3.19", 21 | 22 | "dotenv": "^16.4.5", 23 | "zod": "^3.22.4" 24 | }, 25 | "devDependencies": { 26 | "nodemon": "^3.1.0", 27 | "ts-node": "^10.9.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /twitter-langchain/examples/chatbot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@coinbase/twitter-langchain-chatbot-example", 3 | "description": "Twitter (X) CDP Agentkit Node.js SDK Chatbot Example", 4 | "version": "1.0.0", 5 | "author": "Coinbase Inc.", 6 | "license": "Apache-2.0", 7 | "scripts": { 8 | "start": "NODE_OPTIONS='--no-warnings' ts-node ./chatbot.ts", 9 | "dev": "nodemon ./chatbot.ts", 10 | "lint": "eslint -c .eslintrc.json *.ts", 11 | "lint-fix": "eslint -c .eslintrc.json *.ts --fix", 12 | "format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"", 13 | "format-check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"" 14 | }, 15 | "dependencies": { 16 | "@coinbase/cdp-agentkit-core": "^0.0.10", 17 | "@coinbase/twitter-langchain": "^0.0.8", 18 | "@langchain/core": "0.3.21", 19 | "@langchain/langgraph": "^0.2.22", 20 | "@langchain/openai": "^0.3.14", 21 | "dotenv": "^16.4.5", 22 | "zod": "^3.22.4" 23 | }, 24 | "devDependencies": { 25 | "nodemon": "^3.1.0", 26 | "ts-node": "^10.9.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cdp-agentkit-core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CDP AgentKit Core Changelog 2 | 3 | ## Unreleased 4 | 5 | ## [0.0.11] - 2025-01-13 6 | 7 | ### Added 8 | 9 | - Added `wrap_eth` action to wrap ETH to WETH on Base. 10 | 11 | ## [0.0.10] - 2025-01-09 12 | 13 | ### Removed 14 | - rogue console.log 15 | 16 | ## [0.0.9] - 2025-01-08 17 | 18 | ### Added 19 | 20 | - Bump dependency `coinbase-sdk-nodejs` to version `0.13.0`. 21 | - Supporting mnemonic phrase wallet import 22 | 23 | ### Refactored 24 | 25 | - Tests 26 | - Use `ZodString.min(1)` instead of deprecated `ZodString.nonempty()`. 27 | 28 | ## [0.0.8] - 2024-12-09 29 | 30 | ### Added 31 | 32 | - Twitter (X) Agentkit. 33 | - Twitter (X) account details action to retrieve the authenticated user's information. 34 | - Twitter (X) account mentions action to retrieve the authenticated user's mentions. 35 | - Twitter (X) post tweet action to the authenticated user's feed. 36 | - Twitter (X) post tweet reply action to any post. 37 | 38 | ## [0.0.7] - 2024-12-06 39 | 40 | ### Added 41 | 42 | - Improved prompts for all actions. 43 | 44 | ## [0.0.6] - 2024-12-03 45 | 46 | ### Fixed 47 | 48 | ## [0.0.5] - 2024-11-29 49 | 50 | ### Added 51 | 52 | Initial release of the CDP Node.js AgentKit. 53 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/social/twitter/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module exports various Twitter (X) action instances and their associated types. 3 | */ 4 | 5 | import { TwitterAction, TwitterActionSchemaAny } from "./twitter_action"; 6 | import { AccountDetailsAction } from "./account_details"; 7 | import { AccountMentionsAction } from "./account_mentions"; 8 | import { PostTweetAction } from "./post_tweet"; 9 | import { PostTweetReplyAction } from "./post_tweet_reply"; 10 | 11 | /** 12 | * Retrieve an array of Twitter (X) action instances. 13 | * 14 | * @returns {TwitterAction[]} An array of Twitter action instances. 15 | */ 16 | export function getAllTwitterActions(): TwitterAction[] { 17 | return [ 18 | new AccountDetailsAction(), 19 | new AccountMentionsAction(), 20 | new PostTweetReplyAction(), 21 | new PostTweetAction(), 22 | ]; 23 | } 24 | 25 | /** 26 | * All available Twitter (X) actions. 27 | */ 28 | export const TWITTER_ACTIONS = getAllTwitterActions(); 29 | 30 | /** 31 | * All Twitter (X) action types. 32 | */ 33 | export { 34 | TwitterAction, 35 | TwitterActionSchemaAny, 36 | AccountDetailsAction, 37 | AccountMentionsAction, 38 | PostTweetAction, 39 | PostTweetReplyAction, 40 | }; 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | lerna-debug.log* 10 | .pnpm-debug.log* 11 | 12 | # IDE 13 | .idea/* 14 | .vscode/* 15 | 16 | ## Emacs 17 | *~ 18 | \#*\# 19 | .\#* 20 | **/.projectile 21 | 22 | # Build outputs 23 | dist 24 | out 25 | .next 26 | .nuxt 27 | build/Release 28 | .turbo/ 29 | 30 | # Coverage 31 | coverage 32 | *.lcov 33 | .nyc_output 34 | lib-cov 35 | 36 | # Dependencies 37 | node_modules/ 38 | jspm_packages/ 39 | bower_components 40 | web_modules/ 41 | .pnp.* 42 | 43 | # Cache 44 | .npm 45 | .eslintcache 46 | .stylelintcache 47 | .parcel-cache 48 | .cache 49 | .temp 50 | .rpt2_cache/ 51 | .rts2_cache_cjs/ 52 | .rts2_cache_es/ 53 | .rts2_cache_umd/ 54 | 55 | # IDE 56 | .vscode 57 | .vscode-test 58 | .idea 59 | .tern-port 60 | 61 | # Environment Configurations 62 | **/env/ 63 | **/.env/ 64 | **/.env 65 | **/.env.local/ 66 | **/.env.test/ 67 | 68 | # Runtime data 69 | pids 70 | *.pid 71 | *.seed 72 | *.pid.lock 73 | *.tsbuildinfo 74 | .node_repl_history 75 | 76 | # Yarn v2 77 | .yarn/cache 78 | .yarn/unplugged 79 | .yarn/build-state.yml 80 | .yarn/install-state.gz 81 | 82 | # Wallet data 83 | **/wallet_data.txt 84 | 85 | # Misc 86 | .DS_Store 87 | **/*_local* 88 | api.json 89 | 90 | # JSDoc 91 | docs/ -------------------------------------------------------------------------------- /cdp-langchain/examples/chatbot/README.md: -------------------------------------------------------------------------------- 1 | # CDP AgentKit Langchain Extension Examples - Chatbot 2 | 3 | This example demonstrates an agent setup as a terminal style chatbot with access to the full set of CDP AgentKit actions. 4 | 5 | ## Ask the chatbot to engage in the Web3 ecosystem! 6 | 7 | - "Transfer a portion of your ETH to john2879.base.eth" 8 | - "Deploy an NFT that will go super viral!" 9 | - "Choose a name for yourself and register a Basename for your wallet" 10 | - "Deploy an ERC-20 token with total supply 1 billion" 11 | 12 | ## Requirements 13 | 14 | - Node.js 18+ 15 | - [CDP API Key](https://portal.cdp.coinbase.com/access/api) 16 | - [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key) 17 | 18 | ### Checking Node Version 19 | 20 | Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 18 or higher. You can check your Node version by running: 21 | 22 | ```bash 23 | node --version 24 | npm --version 25 | ``` 26 | 27 | ## Installation 28 | 29 | ```bash 30 | npm install 31 | ``` 32 | 33 | ## Run the Chatbot 34 | 35 | ### Set ENV Vars 36 | 37 | - Ensure the following ENV Vars are set: 38 | - "CDP_API_KEY_NAME" 39 | - "CDP_API_KEY_PRIVATE_KEY" 40 | - "OPENAI_API_KEY" 41 | - "NETWORK_ID" (Defaults to `base-sepolia`) 42 | 43 | ```bash 44 | npm start 45 | ``` 46 | 47 | ## License 48 | 49 | Apache-2.0 50 | -------------------------------------------------------------------------------- /.github/workflows/publish_docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docs to Github Pages 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build-and-deploy-docs: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-node@v4 12 | with: 13 | node-version: "18" 14 | cache: "npm" 15 | 16 | - name: Install and Build Docs 17 | run: | 18 | npm i 19 | npm run build 20 | npm run docs 21 | 22 | # Move cdp-agentkit-core docs 23 | - name: Build AgentKit Core docs 24 | run: | 25 | mkdir -p docs/cdp-agentkit-core 26 | cp -r cdp-agentkit-core/docs/* docs/cdp-agentkit-core/ 27 | 28 | # Move cdp-langchain docs 29 | - name: Build LangChain docs 30 | run: | 31 | mkdir -p docs/cdp-langchain 32 | cp -r cdp-langchain/docs/* docs/cdp-langchain/ 33 | 34 | # Move twitter-langchain docs 35 | - name: Build Twitter LangChain docs 36 | run: | 37 | mkdir -p docs/twitter-langchain 38 | cp -r twitter-langchain/docs/* docs/twitter-langchain/ 39 | 40 | # Deploy to GitHub Pages 41 | - name: Deploy to GitHub Pages 42 | uses: peaceiris/actions-gh-pages@v4 43 | with: 44 | github_token: ${{ secrets.GITHUB_TOKEN }} 45 | publish_dir: ./docs 46 | keep_files: false 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdp-agentkit-monorepo", 3 | "private": true, 4 | "workspaces": [ 5 | "cdp-agentkit-core", 6 | "cdp-langchain", 7 | "twitter-langchain" 8 | ], 9 | "packageManager": "npm@8.9.0", 10 | "scripts": { 11 | "build": "turbo run build", 12 | "test": "turbo run test", 13 | "lint": "turbo run lint", 14 | "clean": "turbo run clean", 15 | "docs": "turbo run docs", 16 | "dev": "turbo run dev", 17 | "format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"", 18 | "format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"", 19 | "lint:fix": "turbo run lint:fix", 20 | "test:types": "turbo run test:types" 21 | }, 22 | "author": "Coinbase Inc.", 23 | "license": "Apache-2.0", 24 | "repository": "https://github.com/coinbase/cdp-agentkit-nodejs", 25 | "keywords": [ 26 | "coinbase", 27 | "sdk", 28 | "crypto", 29 | "cdp", 30 | "agentkit", 31 | "ai", 32 | "agent", 33 | "nodejs", 34 | "typescript", 35 | "monorepo", 36 | "langchain" 37 | ], 38 | "devDependencies": { 39 | "@types/jest": "^29.5.14", 40 | "@types/node": "^20.12.11", 41 | "@typescript-eslint/eslint-plugin": "^7.8.0", 42 | "@typescript-eslint/parser": "^7.8.0", 43 | "eslint": "^8.57.0", 44 | "eslint-config-prettier": "^9.1.0", 45 | "eslint-plugin-jsdoc": "^48.2.5", 46 | "eslint-plugin-prettier": "^5.1.3", 47 | "jest": "^29.7.0", 48 | "prettier": "^3.2.5", 49 | "ts-jest": "^29.2.5", 50 | "turbo": "^2.3.3", 51 | "typedoc": "^0.27.2", 52 | "typescript": "^5.4.5" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/index.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction, CdpActionSchemaAny } from "./cdp_action"; 2 | import { DeployNftAction } from "./deploy_nft"; 3 | import { DeployTokenAction } from "./deploy_token"; 4 | import { GetBalanceAction } from "./get_balance"; 5 | import { GetWalletDetailsAction } from "./get_wallet_details"; 6 | import { MintNftAction } from "./mint_nft"; 7 | import { RegisterBasenameAction } from "./register_basename"; 8 | import { RequestFaucetFundsAction } from "./request_faucet_funds"; 9 | import { TradeAction } from "./trade"; 10 | import { TransferAction } from "./transfer"; 11 | import { WrapEthAction } from "./wrap_eth"; 12 | import { WOW_ACTIONS } from "./defi/wow"; 13 | 14 | /** 15 | * Retrieves all CDP action instances. 16 | * WARNING: All new CdpAction classes must be instantiated here to be discovered. 17 | * 18 | * @returns - Array of CDP action instances 19 | */ 20 | export function getAllCdpActions(): CdpAction[] { 21 | return [ 22 | new GetWalletDetailsAction(), 23 | new DeployNftAction(), 24 | new DeployTokenAction(), 25 | new GetBalanceAction(), 26 | new MintNftAction(), 27 | new RegisterBasenameAction(), 28 | new RequestFaucetFundsAction(), 29 | new TradeAction(), 30 | new TransferAction(), 31 | new WrapEthAction(), 32 | ]; 33 | } 34 | 35 | export const CDP_ACTIONS = getAllCdpActions().concat(WOW_ACTIONS); 36 | 37 | export { 38 | CdpAction, 39 | CdpActionSchemaAny, 40 | GetWalletDetailsAction, 41 | DeployNftAction, 42 | DeployTokenAction, 43 | GetBalanceAction, 44 | MintNftAction, 45 | RegisterBasenameAction, 46 | RequestFaucetFundsAction, 47 | TradeAction, 48 | TransferAction, 49 | WrapEthAction, 50 | }; 51 | -------------------------------------------------------------------------------- /twitter-langchain/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@coinbase/twitter-langchain", 3 | "version": "0.0.9", 4 | "description": "Twitter (X) langchain Toolkit extension of CDP Agentkit", 5 | "repository": "https://github.com/coinbase/cdp-agentkit-nodejs", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "files": ["dist"], 9 | "scripts": { 10 | "build": "tsc", 11 | "lint": "npx --yes eslint -c .eslintrc.json src/**/*.ts", 12 | "lint:fix": "npx --yes eslint -c .eslintrc.json src/**/*.ts --fix", 13 | "format": "npx --yes prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"", 14 | "format-check": "npx --yes prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"", 15 | "check": "tsc --noEmit", 16 | "test": "npx jest --no-cache --testMatch='**/*_test.ts'", 17 | "test:dry-run": "npm install && npm ci && npm publish --dry-run", 18 | "test:e2e": "npx jest --no-cache --testMatch=**/e2e.ts --coverageThreshold '{}'", 19 | "test:types": "tsd --files src/tests/types.test-d.ts", 20 | "clean": "rm -rf dist/*", 21 | "prepack": "tsc", 22 | "docs": "npx --yes typedoc --entryPoints ./src --entryPointStrategy expand --exclude ./src/tests/**/*.ts", 23 | "docs:serve": "http-server ./docs", 24 | "dev": "tsc --watch" 25 | }, 26 | "keywords": [ 27 | "cdp", 28 | "sdk", 29 | "agentkit", 30 | "ai", 31 | "agent", 32 | "nodejs", 33 | "typescript", 34 | "twitter", 35 | "langchain" 36 | ], 37 | "dependencies": { 38 | "@coinbase/cdp-agentkit-core": "^0.0.11", 39 | "@langchain/core": "^0.3.19", 40 | "twitter-api-v2": "^1.18.2", 41 | "zod": "^3.22.4" 42 | }, 43 | "peerDependencies": { 44 | "@coinbase/coinbase-sdk": "^0.13.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /twitter-langchain/examples/chatbot/README.md: -------------------------------------------------------------------------------- 1 | # CDP Agentkit Twitter Langchain Extension Examples - Chatbot 2 | 3 | This example demonstrates an agent setup as a terminal style chatbot with access to Twitter (X) API actions. 4 | 5 | ## Ask the chatbot to engage in the Web3 ecosystem! 6 | - "What are my account details?" 7 | - "Please post a message for me to Twitter" 8 | - "Please get my mentions" 9 | - "Please post responses to my mentions" 10 | 11 | ## Requirements 12 | 13 | - Node.js 18+ 14 | - [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key) 15 | - [Twitter (X) API Keys](https://developer.x.com/en/portal/dashboard) 16 | 17 | ### Twitter Application Setup 18 | 1. Visit the Twitter (X) [Developer Portal](https://developer.x.com/en/portal/dashboard) 19 | 2. Navigate to your project 20 | 3. Navigate to your application 21 | 4. Edit "User authentication settings" 22 | 5. Set "App permissions" to "Read and write and Direct message" 23 | 6. Set "Type of App" to "Web App, Automated app or Bot" 24 | 7. Set "App info" urls 25 | 8. Save 26 | 9. Navigate to "Keys and tokens" 27 | 10. Regenerate all keys and tokens 28 | 29 | ### Checking Node Version 30 | 31 | Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 18 or higher. You can check your Node version by running: 32 | 33 | ```bash 34 | node --version 35 | npm --version 36 | ``` 37 | 38 | ## Installation 39 | 40 | ```bash 41 | npm install 42 | ``` 43 | 44 | ## Run the Chatbot 45 | 46 | Ensure the following vars are set in .env: 47 | - "OPENAI_API_KEY" 48 | - "TWITTER_ACCESS_TOKEN" 49 | - "TWITTER_ACCESS_TOKEN_SECRET" 50 | - "TWITTER_API_KEY" 51 | - "TWITTER_API_SECRET" 52 | 53 | ```bash 54 | npm start 55 | ``` 56 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/get_wallet_details.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { CdpAction } from "./cdp_action"; 3 | import { Wallet } from "@coinbase/coinbase-sdk"; 4 | 5 | /** 6 | * Input schema for get wallet details action. 7 | * This schema intentionally accepts no parameters as the wallet is injected separately. 8 | */ 9 | export const GetWalletDetailsInput = z.object({}); 10 | 11 | /** 12 | * Gets a wallet's details. 13 | * 14 | * @param wallet - The wallet to get details from. 15 | * @param _ - The input arguments for the action. 16 | * @returns A message containing the wallet details. 17 | */ 18 | export async function getWalletDetails( 19 | wallet: Wallet, 20 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 21 | _: z.infer, 22 | ): Promise { 23 | try { 24 | const defaultAddress = await wallet.getDefaultAddress(); 25 | return `Wallet: ${wallet.getId()} on network: ${wallet.getNetworkId()} with default address: ${defaultAddress.getId()}`; 26 | } catch (error) { 27 | return `Error getting wallet details: ${error}`; 28 | } 29 | } 30 | 31 | /** 32 | * Get wallet details action. 33 | */ 34 | export class GetWalletDetailsAction implements CdpAction { 35 | /** 36 | * The name of the action 37 | */ 38 | public name = "get_wallet_details"; 39 | 40 | /** 41 | * A description of what the action does 42 | */ 43 | public description = "This tool will get details about the MPC Wallet."; 44 | 45 | /** 46 | * Schema for validating action arguments 47 | */ 48 | public argsSchema = GetWalletDetailsInput; 49 | 50 | /** 51 | * The function to execute for this action 52 | */ 53 | public func = getWalletDetails; 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | lint-agentkit: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: actions/setup-node@v4 11 | with: 12 | node-version: "18" 13 | cache: "npm" 14 | # Install dependencies in parent directory first 15 | - run: npm install 16 | # Then install and lint in working directory 17 | - name: Install and lint agentkit 18 | working-directory: ./cdp-agentkit-core 19 | run: | 20 | npm install 21 | npm run lint 22 | npm run format 23 | 24 | lint-langchain: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version: "18" 31 | cache: "npm" 32 | # Install dependencies in parent directory first 33 | - run: npm install 34 | # Then install and lint in working directory 35 | - name: Install and lint langchain 36 | working-directory: ./cdp-langchain 37 | run: | 38 | npm install 39 | npm run lint 40 | npm run format 41 | 42 | lint-twitter-langchain: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: actions/setup-node@v4 47 | with: 48 | node-version: "18" 49 | cache: "npm" 50 | # Install dependencies in parent directory first 51 | - run: npm install 52 | # Then install and lint in working directory 53 | - name: Install and lint twitter-langchain 54 | working-directory: ./twitter-langchain 55 | run: | 56 | npm install 57 | npm run lint 58 | npm run format 59 | -------------------------------------------------------------------------------- /cdp-langchain/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@coinbase/cdp-langchain", 3 | "version": "0.0.12", 4 | "description": "Langchain Toolkit extension of CDP Agentkit", 5 | "repository": "https://github.com/coinbase/cdp-agentkit-nodejs", 6 | "author": "Coinbase Inc.", 7 | "license": "Apache-2.0", 8 | "main": "dist/index.js", 9 | "types": "dist/index.d.ts", 10 | "files": [ 11 | "dist" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "lint": "npx --yes eslint -c .eslintrc.json src/**/*.ts", 16 | "lint:fix": "npx --yes eslint -c .eslintrc.json src/**/*.ts --fix", 17 | "format": "npx --yes prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"", 18 | "format-check": "npx --yes prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"", 19 | "check": "tsc --noEmit", 20 | "test": "npx jest --no-cache --testMatch='**/*_test.ts'", 21 | "test:dry-run": "npm install && npm ci && npm publish --dry-run", 22 | "test:e2e": "npx jest --no-cache --testMatch=**/e2e.ts --coverageThreshold '{}'", 23 | "test:types": "tsd --files src/tests/types.test-d.ts", 24 | "clean": "rm -rf dist/*", 25 | "prepack": "tsc", 26 | "docs": "npx --yes typedoc --entryPoints ./src --entryPointStrategy expand --exclude ./src/tests/**/*.ts", 27 | "docs:serve": "http-server ./docs", 28 | "dev": "tsc --watch" 29 | }, 30 | "keywords": [ 31 | "coinbase", 32 | "sdk", 33 | "crypto", 34 | "cdp", 35 | "agentkit", 36 | "ai", 37 | "agent", 38 | "nodejs", 39 | "typescript", 40 | "langchain" 41 | ], 42 | "dependencies": { 43 | "@coinbase/cdp-agentkit-core": "^0.0.11", 44 | "@coinbase/coinbase-sdk": "^0.13.0", 45 | "@langchain/core": "^0.3.19", 46 | "zod": "^3.22.4" 47 | }, 48 | "peerDependencies": { 49 | "@coinbase/coinbase-sdk": "^0.13.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.eslintrc.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/recommended", 6 | "plugin:prettier/recommended", 7 | "plugin:jsdoc/recommended" 8 | ], 9 | "plugins": ["@typescript-eslint", "prettier"], 10 | "env": { 11 | "node": true, 12 | "es6": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "multiline-comment-style": ["error", "starred-block"], 20 | "prettier/prettier": "error", 21 | "@typescript-eslint/member-ordering": "error", 22 | "@typescript-eslint/no-unused-vars": [ 23 | "error", 24 | { 25 | "argsIgnorePattern": "^_$" 26 | } 27 | ], 28 | "jsdoc/tag-lines": ["error", "any", { "startLines": 1 }], 29 | "jsdoc/check-alignment": "error", 30 | "jsdoc/no-undefined-types": "off", 31 | "jsdoc/check-param-names": "error", 32 | "jsdoc/check-tag-names": "error", 33 | "jsdoc/check-types": "error", 34 | "jsdoc/implements-on-classes": "error", 35 | "jsdoc/require-description": "error", 36 | "jsdoc/require-jsdoc": [ 37 | "error", 38 | { 39 | "require": { 40 | "FunctionDeclaration": true, 41 | "MethodDefinition": true, 42 | "ClassDeclaration": true, 43 | "ArrowFunctionExpression": false, 44 | "FunctionExpression": false 45 | } 46 | } 47 | ], 48 | "jsdoc/require-param": "error", 49 | "jsdoc/require-param-description": "error", 50 | "jsdoc/require-param-type": "off", 51 | "jsdoc/require-returns": "error", 52 | "jsdoc/require-returns-description": "error", 53 | "jsdoc/require-returns-type": "off", 54 | "jsdoc/require-hyphen-before-param-description": ["error", "always"] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /cdp-langchain/src/toolkits/cdp_toolkit.ts: -------------------------------------------------------------------------------- 1 | import { StructuredToolInterface, BaseToolkit as Toolkit } from "@langchain/core/tools"; 2 | import { CDP_ACTIONS, CdpAgentkit } from "@coinbase/cdp-agentkit-core"; 3 | import { CdpTool } from "../tools/cdp_tool"; 4 | 5 | /** 6 | * Coinbase Developer Platform (CDP) Toolkit. 7 | * 8 | * Security Note: This toolkit contains tools that can read and modify 9 | * the state of a service; e.g., by creating, deleting, or updating, 10 | * reading underlying data. 11 | * 12 | * For example, this toolkit can be used to create wallets, transactions, 13 | * and smart contract invocations on CDP supported blockchains. 14 | * 15 | * Setup: 16 | * You will need to set the following environment variables: 17 | * ```bash 18 | * export CDP_API_KEY_NAME="cdp-api-key-name" 19 | * export CDP_API_KEY_PRIVATE_KEY="cdp-api-key-private-key" 20 | * export NETWORK_ID="network-id" 21 | * ``` 22 | * 23 | * Example usage: 24 | * ```typescript 25 | * const agentkit = await CdpAgentkit.configureWithWallet(); 26 | * const toolkit = new CdpToolkit(agentkit); 27 | * const tools = toolkit.getTools(); 28 | * 29 | * // Available tools include: 30 | * // - get_wallet_details 31 | * // - get_balance 32 | * // - request_faucet_funds 33 | * // - transfer 34 | * // - trade 35 | * // - deploy_token 36 | * // - mint_nft 37 | * // - deploy_nft 38 | * // - register_basename 39 | * // - wow_create_token 40 | * // - wow_buy_token 41 | * // - wow_sell_token 42 | * // - wrap_eth 43 | * ``` 44 | */ 45 | export class CdpToolkit extends Toolkit { 46 | tools: StructuredToolInterface[]; 47 | 48 | /** 49 | * Creates a new CDP Toolkit instance 50 | * 51 | * @param agentkit - CDP agentkit instance 52 | */ 53 | constructor(agentkit: CdpAgentkit) { 54 | super(); 55 | const actions = CDP_ACTIONS; 56 | const tools = actions.map(action => new CdpTool(action, agentkit)); 57 | this.tools = tools; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/get_balance.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "./cdp_action"; 2 | import { Wallet } from "@coinbase/coinbase-sdk"; 3 | import Decimal from "decimal.js"; 4 | import { z } from "zod"; 5 | 6 | const GET_BALANCE_PROMPT = ` 7 | This tool will get the balance of all the addresses in the wallet for a given asset. 8 | It takes the asset ID as input. Always use 'eth' for the native asset ETH and 'usdc' for USDC. 9 | `; 10 | 11 | /** 12 | * Input schema for get balance action. 13 | */ 14 | export const GetBalanceInput = z 15 | .object({ 16 | assetId: z.string().describe("The asset ID to get the balance for"), 17 | }) 18 | .strip() 19 | .describe("Instructions for getting wallet balance"); 20 | 21 | /** 22 | * Gets balance for all addresses in the wallet for a given asset. 23 | * 24 | * @param wallet - The wallet to get the balance for. 25 | * @param args - The input arguments for the action. 26 | * @returns A message containing the balance information. 27 | */ 28 | export async function getBalance( 29 | wallet: Wallet, 30 | args: z.infer, 31 | ): Promise { 32 | const balances: Record = {}; 33 | 34 | try { 35 | const addresses = await wallet.listAddresses(); 36 | for (const address of addresses) { 37 | const balance = await address.getBalance(args.assetId); 38 | balances[address.getId()] = balance; 39 | } 40 | 41 | const balanceLines = Object.entries(balances).map(([addr, balance]) => `${addr}: ${balance}`); 42 | const formattedBalances = balanceLines.join("\n"); 43 | return `Balances for wallet ${wallet.getId()}:\n${formattedBalances}`; 44 | } catch (error) { 45 | return `Error getting balance for all addresses in the wallet: ${error}`; 46 | } 47 | } 48 | 49 | /** 50 | * Get wallet balance action. 51 | */ 52 | export class GetBalanceAction implements CdpAction { 53 | public name = "get_balance"; 54 | public description = GET_BALANCE_PROMPT; 55 | public argsSchema = GetBalanceInput; 56 | public func = getBalance; 57 | } 58 | -------------------------------------------------------------------------------- /cdp-agentkit-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@coinbase/cdp-agentkit-core", 3 | "description": "CDP Agentkit core primitives", 4 | "repository": "https://github.com/coinbase/cdp-agentkit-nodejs", 5 | "version": "0.0.11", 6 | "author": "Coinbase Inc.", 7 | "license": "Apache-2.0", 8 | "main": "dist/index.js", 9 | "types": "dist/index.d.ts", 10 | "files": ["dist"], 11 | "scripts": { 12 | "build": "tsc", 13 | "lint": "npx --yes eslint -c .eslintrc.json src/**/*.ts", 14 | "lint:fix": "npx --yes eslint -c .eslintrc.json src/**/*.ts --fix", 15 | "format": "npx --yes prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"", 16 | "format-check": "npx --yes prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"", 17 | "check": "tsc --noEmit", 18 | "test": "npx jest --no-cache --testMatch='**/*_test.ts'", 19 | "test:dry-run": "npm install && npm ci && npm publish --dry-run", 20 | "test:e2e": "npx jest --no-cache --testMatch=**/e2e.ts --coverageThreshold '{}'", 21 | "test:types": "tsd --files src/tests/types.test-d.ts", 22 | "clean": "rm -rf dist/*", 23 | "prepack": "tsc", 24 | "docs": "npx --yes typedoc --entryPoints ./src --entryPointStrategy expand --exclude ./src/tests/**/*.ts", 25 | "docs:serve": "http-server ./docs", 26 | "dev": "tsc --watch" 27 | }, 28 | "keywords": [ 29 | "coinbase", 30 | "sdk", 31 | "crypto", 32 | "cdp", 33 | "agentkit", 34 | "ai", 35 | "agent", 36 | "nodejs", 37 | "typescript" 38 | ], 39 | "dependencies": { 40 | "@coinbase/coinbase-sdk": "^0.13.0", 41 | "twitter-api-v2": "^1.18.2", 42 | "viem": "^2.21.51", 43 | "zod": "^3.23.8" 44 | }, 45 | "devDependencies": { 46 | "@types/jest": "^29.5.14", 47 | "@types/secp256k1": "^4.0.6", 48 | "http-server": "^14.1.1", 49 | "jest": "^29.7.0", 50 | "mock-fs": "^5.2.0", 51 | "ts-jest": "^29.2.5", 52 | "tsd": "^0.31.2", 53 | "typescript": "^5.7.2" 54 | }, 55 | "exports": { 56 | ".": { 57 | "types": "./dist/index.d.ts", 58 | "default": "./dist/index.js" 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/deploy_token.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "./cdp_action"; 2 | import { Wallet, Amount } from "@coinbase/coinbase-sdk"; 3 | import { z } from "zod"; 4 | 5 | const DEPLOY_TOKEN_PROMPT = ` 6 | This tool will deploy an ERC20 token smart contract. It takes the token name, symbol, and total supply as input. 7 | The token will be deployed using the wallet's default address as the owner and initial token holder. 8 | `; 9 | 10 | /** 11 | * Input schema for deploy token action. 12 | */ 13 | export const DeployTokenInput = z 14 | .object({ 15 | name: z.string().describe("The name of the token"), 16 | symbol: z.string().describe("The token symbol"), 17 | totalSupply: z.custom().describe("The total supply of tokens to mint"), 18 | }) 19 | .strip() 20 | .describe("Instructions for deploying a token"); 21 | 22 | /** 23 | * Deploys an ERC20 token smart contract. 24 | * 25 | * @param wallet - The wallet to deploy the Token from. 26 | * @param args - The input arguments for the action. 27 | * @returns A message containing the deployed token contract address and details. 28 | */ 29 | export async function deployToken( 30 | wallet: Wallet, 31 | args: z.infer, 32 | ): Promise { 33 | try { 34 | const tokenContract = await wallet.deployToken({ 35 | name: args.name, 36 | symbol: args.symbol, 37 | totalSupply: args.totalSupply, 38 | }); 39 | 40 | const result = await tokenContract.wait(); 41 | 42 | return `Deployed ERC20 token contract ${args.name} (${args.symbol}) with total supply of ${args.totalSupply} tokens at address ${result.getContractAddress()}. Transaction link: ${result.getTransaction()!.getTransactionLink()}`; 43 | } catch (error) { 44 | return `Error deploying token: ${error}`; 45 | } 46 | } 47 | 48 | /** 49 | * Deploy token action. 50 | */ 51 | export class DeployTokenAction implements CdpAction { 52 | public name = "deploy_token"; 53 | public description = DEPLOY_TOKEN_PROMPT; 54 | public argsSchema = DeployTokenInput; 55 | public func = deployToken; 56 | } 57 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/deploy_nft.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "./cdp_action"; 2 | import { Wallet } from "@coinbase/coinbase-sdk"; 3 | import { z } from "zod"; 4 | 5 | const DEPLOY_NFT_PROMPT = ` 6 | This tool will deploy an NFT (ERC-721) contract onchain from the wallet. 7 | It takes the name of the NFT collection, the symbol of the NFT collection, and the base URI for the token metadata as inputs. 8 | `; 9 | 10 | /** 11 | * Input schema for deploy NFT action. 12 | */ 13 | export const DeployNftInput = z 14 | .object({ 15 | name: z.string().describe("The name of the NFT collection"), 16 | symbol: z.string().describe("The symbol of the NFT collection"), 17 | baseURI: z.string().describe("The base URI for the token metadata"), 18 | }) 19 | .strip() 20 | .describe("Instructions for deploying an NFT collection"); 21 | 22 | /** 23 | * Deploys an NFT (ERC-721) token collection onchain from the wallet. 24 | * 25 | * @param wallet - The wallet to deploy the NFT from. 26 | * @param args - The input arguments for the action. 27 | * @returns A message containing the NFT token deployment details. 28 | */ 29 | export async function deployNft( 30 | wallet: Wallet, 31 | args: z.infer, 32 | ): Promise { 33 | try { 34 | const nftContract = await wallet.deployNFT({ 35 | name: args.name, 36 | symbol: args.symbol, 37 | baseURI: args.baseURI, 38 | }); 39 | 40 | const result = await nftContract.wait(); 41 | 42 | return `Deployed NFT Collection ${args.name} to address ${result.getContractAddress()} on network ${wallet.getNetworkId()}.\nTransaction hash for the deployment: ${result.getTransaction()!.getTransactionHash()}\nTransaction link for the deployment: ${result.getTransaction()!.getTransactionLink()}`; 43 | } catch (error) { 44 | return `Error deploying NFT: ${error}`; 45 | } 46 | } 47 | 48 | /** 49 | * Deploy NFT action. 50 | */ 51 | export class DeployNftAction implements CdpAction { 52 | public name = "deploy_nft"; 53 | public description = DEPLOY_NFT_PROMPT; 54 | public argsSchema = DeployNftInput; 55 | public func = deployNft; 56 | } 57 | -------------------------------------------------------------------------------- /twitter-langchain/src/twitter_toolkit.ts: -------------------------------------------------------------------------------- 1 | import { StructuredToolInterface, BaseToolkit as Toolkit } from "@langchain/core/tools"; 2 | import { TWITTER_ACTIONS, TwitterAgentkit } from "@coinbase/cdp-agentkit-core"; 3 | import { TwitterTool } from "./twitter_tool"; 4 | 5 | /** 6 | * Twitter (X) Toolkit. 7 | * 8 | * Security Note: This toolkit contains tools that can read and modify 9 | * the state of a service; e.g., by creating, deleting, or updating, 10 | * reading underlying data. 11 | * 12 | * For example, this toolkit can be used to get account details, 13 | * account mentions, post tweets, post tweet replies, and anything, 14 | * else you can implement with the Twitter (X) API! 15 | * 16 | * Setup: 17 | * You will need to set the following environment variables: 18 | * ```bash 19 | * export OPENAI_API_KEY= 20 | * export TWITTER_API_KEY= 21 | * export TWITTER_API_SECRET= 22 | * export TWITTER_ACCESS_TOKEN= 23 | * export TWITTER_ACCESS_TOKEN_SECRET= 24 | * ``` 25 | * 26 | * Example usage: 27 | * ```typescript 28 | * // optional if not available via the ENV 29 | * const options = { 30 | * apiKey: "", 31 | * apiSecret: "", 32 | * accessToken: "", 33 | * accessTokenSecret: "", 34 | * }; 35 | * 36 | * const agentkit = await TwitterAgentkit.configureWithOptions(options); 37 | * const toolkit = new TwitterToolkit(agentkit); 38 | * const tools = toolkit.getTools(); 39 | * 40 | * // Available tools include: 41 | * // - account_details 42 | * // - account_mentions 43 | * // - post_tweet 44 | * // - post_tweet_reply 45 | * ``` 46 | */ 47 | export class TwitterToolkit extends Toolkit { 48 | tools: StructuredToolInterface[]; 49 | 50 | /** 51 | * Creates a new Twitter (X) Toolkit instance 52 | * 53 | * @param agentkit - Twitter (X) agentkit instance 54 | */ 55 | constructor(agentkit: TwitterAgentkit) { 56 | super(); 57 | const actions = TWITTER_ACTIONS; 58 | const tools = actions.map(action => new TwitterTool(action, agentkit)); 59 | this.tools = tools; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/get_wallet_details_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, Wallet, WalletAddress } from "@coinbase/coinbase-sdk"; 2 | 3 | import { getWalletDetails, GetWalletDetailsInput } from "../actions/cdp/get_wallet_details"; 4 | 5 | describe("Wallet Details Input", () => { 6 | it("should successfully parse empty input", () => { 7 | const emptyInput = {}; 8 | const result = GetWalletDetailsInput.safeParse(emptyInput); 9 | 10 | expect(result.success).toBe(true); 11 | expect(result.data).toEqual(emptyInput); 12 | }); 13 | }); 14 | 15 | describe("Wallet Details Action", () => { 16 | const ADDRESS_ID = "0xabcdef123456789"; 17 | const NETWORK_ID = Coinbase.networks.BaseSepolia; 18 | const WALLET_ID = "0x123456789abcdef"; 19 | 20 | let mockAddress: jest.Mocked; 21 | let mockWallet: jest.Mocked; 22 | 23 | beforeEach(() => { 24 | mockAddress = { 25 | getId: jest.fn().mockReturnValue(ADDRESS_ID), 26 | } as unknown as jest.Mocked; 27 | 28 | mockWallet = { 29 | getDefaultAddress: jest.fn(), 30 | getId: jest.fn().mockReturnValue(WALLET_ID), 31 | getNetworkId: jest.fn().mockReturnValue(NETWORK_ID), 32 | } as unknown as jest.Mocked; 33 | 34 | mockWallet.getDefaultAddress.mockResolvedValue(mockAddress); 35 | }); 36 | 37 | it("should successfully respond", async () => { 38 | const args = {}; 39 | const response = await getWalletDetails(mockWallet, args); 40 | 41 | expect(mockWallet.getDefaultAddress).toHaveBeenCalled(); 42 | expect(response).toContain(`Wallet: ${WALLET_ID}`); 43 | expect(response).toContain(`on network: ${NETWORK_ID}`); 44 | expect(response).toContain(`with default address: ${ADDRESS_ID}`); 45 | }); 46 | 47 | it("should fail with an error", async () => { 48 | const args = {}; 49 | 50 | const error = new Error("An error has occured"); 51 | mockWallet.getDefaultAddress.mockRejectedValue(error); 52 | 53 | const response = await getWalletDetails(mockWallet, args); 54 | 55 | expect(mockWallet.getDefaultAddress).toHaveBeenCalled(); 56 | expect(response).toContain(`Error getting wallet details: ${error}`); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/social_twitter_account_details_test.ts: -------------------------------------------------------------------------------- 1 | import { accountDetails, AccountDetailsInput } from "../actions/cdp/social/twitter/account_details"; 2 | import { TwitterApi, TwitterApiv2 } from "twitter-api-v2"; 3 | 4 | const MOCK_ID = "1853889445319331840"; 5 | const MOCK_NAME = "CDP Agentkit"; 6 | const MOCK_USERNAME = "CDPAgentkit"; 7 | 8 | describe("Twitter (X) Account Details Input", () => { 9 | it("should successfully parse empty input", () => { 10 | const emptyInput = {}; 11 | const result = AccountDetailsInput.safeParse(emptyInput); 12 | 13 | expect(result.success).toBe(true); 14 | expect(result.data).toEqual(emptyInput); 15 | }); 16 | }); 17 | 18 | describe("Account Details Action", () => { 19 | const mockResponse = { 20 | data: { 21 | id: MOCK_ID, 22 | name: MOCK_NAME, 23 | username: MOCK_USERNAME, 24 | }, 25 | }; 26 | 27 | let mockApi: jest.Mocked; 28 | let mockClient: jest.Mocked; 29 | 30 | beforeEach(() => { 31 | mockClient = { 32 | me: jest.fn().mockResolvedValue(mockResponse), 33 | } as unknown as jest.Mocked; 34 | 35 | mockApi = { 36 | get v2() { 37 | return mockClient; 38 | }, 39 | } as unknown as jest.Mocked; 40 | }); 41 | 42 | it("should successfully retrieve account details", async () => { 43 | const args = {}; 44 | const response = await accountDetails(mockApi, args); 45 | 46 | expect(mockApi.v2.me).toHaveBeenCalled(); 47 | expect(response).toContain("Successfully retrieved authenticated user account details"); 48 | expect(response).toContain(JSON.stringify(mockResponse)); 49 | }); 50 | 51 | it("should handle errors when retrieving account details", async () => { 52 | const args = {}; 53 | 54 | const error = new Error("An error has occured"); 55 | mockApi.v2.me = jest.fn().mockRejectedValue(error); 56 | 57 | const response = await accountDetails(mockApi, args); 58 | 59 | expect(mockApi.v2.me).toHaveBeenCalled(); 60 | expect(response).toContain("Error retrieving authenticated user account details"); 61 | expect(response).toContain(error.message); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/request_faucet_funds.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "./cdp_action"; 2 | import { Wallet } from "@coinbase/coinbase-sdk"; 3 | import { z } from "zod"; 4 | 5 | const REQUEST_FAUCET_FUNDS_PROMPT = ` 6 | This tool will request test tokens from the faucet for the default address in the wallet. It takes the wallet and asset ID as input. 7 | If no asset ID is provided the faucet defaults to ETH. Faucet is only allowed on 'base-sepolia' and can only provide asset ID 'eth' or 'usdc'. 8 | You are not allowed to faucet with any other network or asset ID. If you are on another network, suggest that the user sends you some ETH 9 | from another wallet and provide the user with your wallet details. 10 | `; 11 | 12 | /** 13 | * Input schema for request faucet funds action. 14 | */ 15 | export const RequestFaucetFundsInput = z 16 | .object({ 17 | assetId: z.string().optional().describe("The optional asset ID to request from faucet"), 18 | }) 19 | .strip() 20 | .describe("Instructions for requesting faucet funds"); 21 | 22 | /** 23 | * Requests test tokens from the faucet for the default address in the wallet. 24 | * 25 | * @param wallet - The wallet to receive tokens. 26 | * @param args - The input arguments for the action. 27 | * @returns A confirmation message with transaction details. 28 | */ 29 | export async function requestFaucetFunds( 30 | wallet: Wallet, 31 | args: z.infer, 32 | ): Promise { 33 | try { 34 | // Request funds from the faucet 35 | const faucetTx = await wallet.faucet(args.assetId || undefined); 36 | 37 | // Wait for the faucet transaction to be confirmed 38 | const result = await faucetTx.wait(); 39 | 40 | return `Received ${args.assetId || "ETH"} from the faucet. Transaction: ${result.getTransactionLink()}`; 41 | } catch (error) { 42 | return `Error requesting faucet funds: ${error}`; 43 | } 44 | } 45 | 46 | /** 47 | * Request faucet funds action. 48 | */ 49 | export class RequestFaucetFundsAction implements CdpAction { 50 | public name = "request_faucet_funds"; 51 | public description = REQUEST_FAUCET_FUNDS_PROMPT; 52 | public argsSchema = RequestFaucetFundsInput; 53 | public func = requestFaucetFunds; 54 | } 55 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/social/twitter/post_tweet.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides functionality to post a tweet on Twitter (X). 3 | */ 4 | 5 | import { z } from "zod"; 6 | import { TwitterAction } from "./twitter_action"; 7 | import { TwitterApi } from "twitter-api-v2"; 8 | 9 | /** 10 | * Prompt message describing the post tweet tool. 11 | * A successful response will return a message with the API response in JSON format, 12 | * while a failure response will indicate an error from the Twitter API. 13 | */ 14 | const POST_TWEET_PROMPT = ` 15 | This tool will post a tweet on Twitter. The tool takes the text of the tweet as input. Tweets can be maximum 280 characters. 16 | 17 | A successful response will return a message with the API response as a JSON payload: 18 | {"data": {"text": "hello, world!", "id": "0123456789012345678", "edit_history_tweet_ids": ["0123456789012345678"]}} 19 | 20 | A failure response will return a message with the Twitter API request error: 21 | You are not allowed to create a Tweet with duplicate content. 22 | `; 23 | 24 | /** 25 | * Input argument schema for the post tweet action. 26 | */ 27 | export const PostTweetInput = z 28 | .object({ 29 | tweet: z.string().max(280, "Tweet must be a maximum of 280 characters."), 30 | }) 31 | .strip() 32 | .describe("Input schema for posting a tweet"); 33 | 34 | /** 35 | * Posts a tweet on Twitter (X). 36 | * 37 | * @param client - The Twitter (X) client used to authenticate with. 38 | * @param args - The input arguments for the action. 39 | * @returns A message indicating the success or failure of the tweet posting. 40 | */ 41 | export async function postTweet( 42 | client: TwitterApi, 43 | args: z.infer, 44 | ): Promise { 45 | try { 46 | const response = await client.v2.tweet(args.tweet); 47 | return `Successfully posted to Twitter:\n${JSON.stringify(response)}`; 48 | } catch (error) { 49 | return `Error posting to Twitter:\n${error}`; 50 | } 51 | } 52 | 53 | /** 54 | * Post Tweet Action 55 | */ 56 | export class PostTweetAction implements TwitterAction { 57 | public name = "post_tweet"; 58 | public description = POST_TWEET_PROMPT; 59 | public argsSchema = PostTweetInput; 60 | public func = postTweet; 61 | } 62 | -------------------------------------------------------------------------------- /twitter-langchain/src/tests/twitter_tool_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TwitterAction, 3 | TwitterActionSchemaAny, 4 | TwitterAgentkit, 5 | } from "@coinbase/cdp-agentkit-core"; 6 | import { TwitterTool } from "../twitter_tool"; 7 | import { z } from "zod"; 8 | 9 | const MOCK_DESCRIPTION = "Twitter Test Action"; 10 | const MOCK_NAME = "test_action"; 11 | 12 | describe("TwitterTool", () => { 13 | let mockAgentkit: jest.Mocked; 14 | let mockAction: jest.Mocked>; 15 | let twitterTool: TwitterTool; 16 | 17 | beforeEach(() => { 18 | mockAgentkit = { 19 | run: jest.fn((action, args) => action.func(mockAgentkit, args)), 20 | } as unknown as jest.Mocked; 21 | 22 | mockAction = { 23 | name: MOCK_NAME, 24 | description: MOCK_DESCRIPTION, 25 | argsSchema: z.object({ test_param: z.string() }), 26 | func: jest.fn().mockResolvedValue("success"), 27 | } as unknown as jest.Mocked>; 28 | 29 | twitterTool = new TwitterTool(mockAction, mockAgentkit); 30 | }); 31 | 32 | it("should initialize with correct properties", () => { 33 | expect(twitterTool.name).toBe(MOCK_NAME); 34 | expect(twitterTool.description).toBe(MOCK_DESCRIPTION); 35 | expect(twitterTool.schema).toEqual(mockAction.argsSchema); 36 | }); 37 | 38 | it("should execute action with valid args", async () => { 39 | const args = { test_param: "test" }; 40 | const response = await twitterTool.call(args); 41 | 42 | expect(mockAction.func).toHaveBeenCalledWith(mockAgentkit, args); 43 | expect(response).toBe("success"); 44 | }); 45 | 46 | it("should handle schema validation errors", async () => { 47 | const invalidargs = { invalid_param: "test" }; 48 | await expect(twitterTool.call(invalidargs)).rejects.toThrow(); 49 | expect(mockAction.func).not.toHaveBeenCalled(); 50 | }); 51 | 52 | it("should return error message on action execution failure", async () => { 53 | mockAction.func.mockRejectedValue(new Error("Execution failed")); 54 | const args = { test_param: "test" }; 55 | const response = await twitterTool.call(args); 56 | expect(response).toContain("Error executing test_action: Execution failed"); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/mint_nft.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "./cdp_action"; 2 | import { Wallet } from "@coinbase/coinbase-sdk"; 3 | import { z } from "zod"; 4 | 5 | const MINT_NFT_PROMPT = ` 6 | This tool will mint an NFT (ERC-721) to a specified destination address onchain via a contract invocation. 7 | It takes the contract address of the NFT onchain and the destination address onchain that will receive the NFT as inputs. 8 | Do not use the contract address as the destination address. If you are unsure of the destination address, please ask the user before proceeding. 9 | `; 10 | 11 | /** 12 | * Input schema for mint NFT action. 13 | */ 14 | export const MintNftInput = z 15 | .object({ 16 | contractAddress: z.string().describe("The contract address of the NFT to mint"), 17 | destination: z.string().describe("The destination address that will receive the NFT"), 18 | }) 19 | .strip() 20 | .describe("Instructions for minting an NFT"); 21 | 22 | /** 23 | * Mints an NFT (ERC-721) to a specified destination address onchain. 24 | * 25 | * @param wallet - The wallet to mint the NFT from. 26 | * @param args - The input arguments for the action. 27 | * @returns A message containing the NFT mint details. 28 | */ 29 | export async function mintNft(wallet: Wallet, args: z.infer): Promise { 30 | const mintArgs = { 31 | to: args.destination, 32 | quantity: "1", 33 | }; 34 | 35 | try { 36 | const mintInvocation = await wallet.invokeContract({ 37 | contractAddress: args.contractAddress, 38 | method: "mint", 39 | args: mintArgs, 40 | }); 41 | 42 | const result = await mintInvocation.wait(); 43 | 44 | return `Minted NFT from contract ${args.contractAddress} to address ${args.destination} on network ${wallet.getNetworkId()}.\nTransaction hash for the mint: ${result.getTransaction().getTransactionHash()}\nTransaction link for the mint: ${result.getTransaction().getTransactionLink()}`; 45 | } catch (error) { 46 | return `Error minting NFT: ${error}`; 47 | } 48 | } 49 | 50 | /** 51 | * Mint NFT action. 52 | */ 53 | export class MintNftAction implements CdpAction { 54 | public name = "mint_nft"; 55 | public description = MINT_NFT_PROMPT; 56 | public argsSchema = MintNftInput; 57 | public func = mintNft; 58 | } 59 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/social/twitter/account_details.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides functionality to retrieve account details for the currently authenticated Twitter (X) user. 3 | */ 4 | 5 | import { z } from "zod"; 6 | import { TwitterAction } from "./twitter_action"; 7 | import { TwitterApi } from "twitter-api-v2"; 8 | 9 | /** 10 | * Prompt message describing the account details tool. 11 | * A successful response will return account details in JSON format, 12 | * while a failure response will indicate an error from the Twitter API. 13 | */ 14 | const ACCOUNT_DETAILS_PROMPT = ` 15 | This tool will return account details for the currently authenticated Twitter (X) user context. 16 | 17 | A successful response will return a message with the api response as a json payload: 18 | {"data": {"id": "1853889445319331840", "name": "CDP AgentKit", "username": "CDPAgentKit"}} 19 | 20 | A failure response will return a message with a Twitter API request error: 21 | Error retrieving authenticated user account: 429 Too Many Requests 22 | `; 23 | 24 | /** 25 | * Input argument schema for the account details action. 26 | */ 27 | export const AccountDetailsInput = z 28 | .object({}) 29 | .strip() 30 | .describe("Input schema for retrieving account details"); 31 | 32 | /** 33 | * Get the authenticated Twitter (X) user account details. 34 | * 35 | * @param client - The Twitter (X) client used to authenticate with. 36 | * @param _ - The input arguments for the action. 37 | * @returns A message containing account details for the authenticated user context. 38 | */ 39 | export async function accountDetails( 40 | client: TwitterApi, 41 | _: z.infer, 42 | ): Promise { 43 | try { 44 | const response = await client.v2.me(); 45 | response.data.url = `https://x.com/${response.data.username}`; 46 | return `Successfully retrieved authenticated user account details:\n${JSON.stringify(response)}`; 47 | } catch (error) { 48 | return `Error retrieving authenticated user account details: ${error}`; 49 | } 50 | } 51 | 52 | /** 53 | * Account Details Action 54 | */ 55 | export class AccountDetailsAction implements TwitterAction { 56 | public name = "account_details"; 57 | public description = ACCOUNT_DETAILS_PROMPT; 58 | public argsSchema = AccountDetailsInput; 59 | public func = accountDetails; 60 | } 61 | -------------------------------------------------------------------------------- /cdp-langchain/src/tests/cdp_tool_test.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction, CdpActionSchemaAny, CdpAgentkit } from "@coinbase/cdp-agentkit-core"; 2 | import { CdpTool } from "../tools/cdp_tool"; 3 | import { z } from "zod"; 4 | 5 | const MOCK_DESCRIPTION = "CDP Test Action"; 6 | const MOCK_NAME = "get_wallet_details"; 7 | 8 | describe("CdpTool", () => { 9 | let mockAgentkit: jest.Mocked; 10 | let mockAction: jest.Mocked>; 11 | let cdpTool: CdpTool; 12 | 13 | beforeEach(() => { 14 | mockAgentkit = { 15 | run: jest.fn((action, args) => action.func(args)), 16 | wallet: { 17 | getDefaultAddress: jest.fn().mockResolvedValue({ getId: () => "mock-address" }), 18 | }, 19 | } as unknown as jest.Mocked; 20 | 21 | mockAction = { 22 | name: MOCK_NAME, 23 | description: MOCK_DESCRIPTION, 24 | argsSchema: z.object({ wallet_id: z.string() }), 25 | func: jest.fn().mockResolvedValue("Wallet details retrieved successfully"), 26 | } as unknown as jest.Mocked>; 27 | 28 | cdpTool = new CdpTool(mockAction, mockAgentkit); 29 | }); 30 | 31 | it("should initialize with correct properties", () => { 32 | expect(cdpTool.name).toBe(MOCK_NAME); 33 | expect(cdpTool.description).toBe(MOCK_DESCRIPTION); 34 | expect(cdpTool.schema).toEqual(mockAction.argsSchema); 35 | }); 36 | 37 | it("should execute action with valid args", async () => { 38 | const args = { wallet_id: "0x123" }; 39 | const response = await cdpTool.call(args); 40 | 41 | expect(mockAction.func).toHaveBeenCalledWith(args); 42 | expect(response).toBe("Wallet details retrieved successfully"); 43 | }); 44 | 45 | it("should handle schema validation errors", async () => { 46 | const invalidArgs = { invalid_param: "test" }; 47 | await expect(cdpTool.call(invalidArgs)).rejects.toThrow(); 48 | expect(mockAction.func).not.toHaveBeenCalled(); 49 | }); 50 | 51 | it("should return error message on action execution failure", async () => { 52 | mockAction.func.mockRejectedValue(new Error("Failed to retrieve wallet details")); 53 | const args = { wallet_id: "0x123" }; 54 | const response = await cdpTool.call(args); 55 | expect(response).toContain( 56 | "Error executing get_wallet_details: Failed to retrieve wallet details", 57 | ); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/social/twitter/account_mentions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides functionality to retrieve account mentions from Twitter (X). 3 | */ 4 | 5 | import { z } from "zod"; 6 | import { TwitterAction } from "./twitter_action"; 7 | import { TwitterApi } from "twitter-api-v2"; 8 | 9 | /** 10 | * Prompt message describing the account mentions tool. 11 | * A successful response will return a message with the API response in JSON format, 12 | * while a failure response will indicate an error from the Twitter API. 13 | */ 14 | const ACCOUNT_MENTIONS_PROMPT = ` 15 | This tool will return mentions for the specified Twitter (X) user id. 16 | 17 | A successful response will return a message with the API response as a JSON payload: 18 | {"data": [{"id": "1857479287504584856", "text": "@CDPAgentKit reply"}]} 19 | 20 | A failure response will return a message with the Twitter API request error: 21 | Error retrieving user mentions: 429 Too Many Requests 22 | `; 23 | 24 | /** 25 | * Input argument schema for the account mentions action. 26 | */ 27 | export const AccountMentionsInput = z 28 | .object({ 29 | userId: z 30 | .string() 31 | .min(1, "Account ID is required.") 32 | .describe("The Twitter (X) user id to return mentions for"), 33 | }) 34 | .strip() 35 | .describe("Input schema for retrieving account mentions"); 36 | 37 | /** 38 | * Retrieves mentions for a specified Twitter (X) user. 39 | * 40 | * @param client - The Twitter (X) client used to authenticate with. 41 | * @param args - The input arguments for the action. 42 | * @returns A message indicating the success or failure of the mention retrieval. 43 | */ 44 | export async function accountMentions( 45 | client: TwitterApi, 46 | args: z.infer, 47 | ): Promise { 48 | try { 49 | const response = await client.v2.userMentionTimeline(args.userId); 50 | return `Successfully retrieved account mentions:\n${JSON.stringify(response)}`; 51 | } catch (error) { 52 | return `Error retrieving authenticated account mentions: ${error}`; 53 | } 54 | } 55 | 56 | /** 57 | * Account Mentions Action 58 | */ 59 | export class AccountMentionsAction implements TwitterAction { 60 | public name = "account_mentions"; 61 | public description = ACCOUNT_MENTIONS_PROMPT; 62 | public argsSchema = AccountMentionsInput; 63 | public func = accountMentions; 64 | } 65 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/trade.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "./cdp_action"; 2 | import { Wallet, Amount } from "@coinbase/coinbase-sdk"; 3 | import { z } from "zod"; 4 | 5 | const TRADE_PROMPT = ` 6 | This tool will trade a specified amount of a 'from asset' to a 'to asset' for the wallet. 7 | 8 | It takes the following inputs: 9 | - The amount of the 'from asset' to trade 10 | - The from asset ID to trade 11 | - The asset ID to receive from the trade 12 | 13 | Important notes: 14 | - Trades are only supported on mainnet networks (ie, 'base-mainnet', 'base', 'ethereum-mainnet', 'ethereum', etc.) 15 | - Never allow trades on any non-mainnet network (ie, 'base-sepolia', 'ethereum-sepolia', etc.) 16 | - When selling a native asset (e.g. 'eth' on base-mainnet), ensure there is sufficient balance to pay for the trade AND the gas cost of this trade 17 | `; 18 | 19 | /** 20 | * Input schema for trade action. 21 | */ 22 | export const TradeInput = z 23 | .object({ 24 | amount: z.custom().describe("The amount of the from asset to trade"), 25 | fromAssetId: z.string().describe("The from asset ID to trade"), 26 | toAssetId: z.string().describe("The to asset ID to receive from the trade"), 27 | }) 28 | .strip() 29 | .describe("Instructions for trading assets"); 30 | 31 | /** 32 | * Trades a specified amount of a from asset to a to asset for the wallet. 33 | * 34 | * @param wallet - The wallet to trade the asset from. 35 | * @param args - The input arguments for the action. 36 | * @returns A message containing the trade details. 37 | */ 38 | export async function trade(wallet: Wallet, args: z.infer): Promise { 39 | try { 40 | const tradeResult = await wallet.createTrade({ 41 | amount: args.amount, 42 | fromAssetId: args.fromAssetId, 43 | toAssetId: args.toAssetId, 44 | }); 45 | 46 | const result = await tradeResult.wait(); 47 | 48 | return `Traded ${args.amount} of ${args.fromAssetId} for ${result.getToAmount()} of ${args.toAssetId}.\nTransaction hash for the trade: ${result.getTransaction().getTransactionHash()}\nTransaction link for the trade: ${result.getTransaction().getTransactionLink()}`; 49 | } catch (error) { 50 | return `Error trading assets: ${error}`; 51 | } 52 | } 53 | 54 | /** 55 | * Trade action. 56 | */ 57 | export class TradeAction implements CdpAction { 58 | public name = "trade"; 59 | public description = TRADE_PROMPT; 60 | public argsSchema = TradeInput; 61 | public func = trade; 62 | } 63 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/social/twitter/post_tweet_reply.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides functionality to post a reply to a tweet on Twitter (X). 3 | */ 4 | 5 | import { z } from "zod"; 6 | import { TwitterAction } from "./twitter_action"; 7 | import { TwitterApi } from "twitter-api-v2"; 8 | 9 | /** 10 | * Prompt message describing the post tweet reply tool. 11 | * A successful response will return a message with the API response in JSON format, 12 | * while a failure response will indicate an error from the Twitter API. 13 | */ 14 | const POST_TWEET_REPLY_PROMPT = ` 15 | This tool will post a tweet on Twitter. The tool takes the text of the tweet as input. Tweets can be maximum 280 characters. 16 | 17 | A successful response will return a message with the API response as a JSON payload: 18 | {"data": {"text": "hello, world!", "id": "0123456789012345678", "edit_history_tweet_ids": ["0123456789012345678"]}} 19 | 20 | A failure response will return a message with the Twitter API request error: 21 | You are not allowed to create a Tweet with duplicate content. 22 | `; 23 | 24 | /** 25 | * Input argument schema for the post tweet reply action. 26 | */ 27 | export const PostTweetReplyInput = z 28 | .object({ 29 | tweetId: z.string().describe("The id of the tweet to reply to"), 30 | tweetReply: z 31 | .string() 32 | .max(280, "The reply to the tweet which must be a maximum of 280 characters."), 33 | }) 34 | .strip() 35 | .describe("Input schema for posting a tweet reply"); 36 | 37 | /** 38 | * Posts a reply to a specified tweet on Twitter (X). 39 | * 40 | * @param client - The Twitter (X) client used to authenticate with. 41 | * @param args - The input arguments for the action. 42 | * @returns A message indicating the success or failure of the reply posting. 43 | */ 44 | export async function postTweet( 45 | client: TwitterApi, 46 | args: z.infer, 47 | ): Promise { 48 | try { 49 | const response = await client.v2.tweet(args.tweetReply, { 50 | reply: { in_reply_to_tweet_id: args.tweetId }, 51 | }); 52 | 53 | return `Successfully posted reply to Twitter:\n${JSON.stringify(response)}`; 54 | } catch (error) { 55 | return `Error posting reply to Twitter: ${error}`; 56 | } 57 | } 58 | 59 | /** 60 | * Post Tweet Reply Action 61 | */ 62 | export class PostTweetReplyAction implements TwitterAction { 63 | public name = "post_tweet_reply"; 64 | public description = POST_TWEET_REPLY_PROMPT; 65 | public argsSchema = PostTweetReplyInput; 66 | public func = postTweet; 67 | } 68 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/wrap_eth_test.ts: -------------------------------------------------------------------------------- 1 | import { ContractInvocation, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { WETH_ABI, WETH_ADDRESS, wrapEth, WrapEthInput } from "../actions/cdp/wrap_eth"; 4 | 5 | const MOCK_AMOUNT_TO_WRAP = "100000000000000000"; 6 | 7 | describe("Wrap Eth", () => { 8 | it("should successfully parse valid input", () => { 9 | const validInput = { 10 | amountToWrap: MOCK_AMOUNT_TO_WRAP, 11 | }; 12 | 13 | const result = WrapEthInput.safeParse(validInput); 14 | 15 | expect(result.success).toBe(true); 16 | expect(result.data).toEqual(validInput); 17 | }); 18 | 19 | it("should fail parsing empty input", () => { 20 | const emptyInput = {}; 21 | const result = WrapEthInput.safeParse(emptyInput); 22 | 23 | expect(result.success).toBe(false); 24 | }); 25 | }); 26 | 27 | describe("Wrap Eth Action", () => { 28 | const TRANSACTION_HASH = "0xghijkl987654321"; 29 | 30 | let mockContractInvocation: jest.Mocked; 31 | let mockWallet: jest.Mocked; 32 | 33 | beforeEach(() => { 34 | mockWallet = { 35 | invokeContract: jest.fn(), 36 | } as unknown as jest.Mocked; 37 | 38 | mockContractInvocation = { 39 | wait: jest.fn().mockResolvedValue({ 40 | getTransaction: jest.fn().mockReturnValue({ 41 | getTransactionHash: jest.fn().mockReturnValue(TRANSACTION_HASH), 42 | }), 43 | }), 44 | } as unknown as jest.Mocked; 45 | 46 | mockWallet.invokeContract.mockResolvedValue(mockContractInvocation); 47 | }); 48 | 49 | it("should successfully wrap ETH", async () => { 50 | const args = { 51 | amountToWrap: MOCK_AMOUNT_TO_WRAP, 52 | }; 53 | 54 | const response = await wrapEth(mockWallet, args); 55 | 56 | expect(mockWallet.invokeContract).toHaveBeenCalledWith({ 57 | contractAddress: WETH_ADDRESS, 58 | method: "deposit", 59 | abi: WETH_ABI, 60 | args: {}, 61 | amount: BigInt(args.amountToWrap), 62 | assetId: "wei", 63 | }); 64 | expect(response).toContain(`Wrapped ETH with transaction hash: ${TRANSACTION_HASH}`); 65 | }); 66 | 67 | it("should fail with an error", async () => { 68 | const args = { 69 | amountToWrap: MOCK_AMOUNT_TO_WRAP, 70 | }; 71 | 72 | const error = new Error("Failed to execute transfer"); 73 | mockWallet.invokeContract.mockRejectedValue(error); 74 | 75 | const response = await wrapEth(mockWallet, args); 76 | 77 | expect(mockWallet.invokeContract).toHaveBeenCalled(); 78 | expect(response).toContain(`Error wrapping ETH: ${error}`); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /cdp-langchain/src/tests/cdp_toolkit_test.ts: -------------------------------------------------------------------------------- 1 | import { CdpToolkit } from "../toolkits/cdp_toolkit"; 2 | import { CdpTool } from "../tools/cdp_tool"; 3 | import { CdpAction, CdpActionSchemaAny, CdpAgentkit } from "@coinbase/cdp-agentkit-core"; 4 | import { z } from "zod"; 5 | 6 | describe("CdpToolkit", () => { 7 | let mockAgentkit: jest.Mocked; 8 | let mockActions: jest.Mocked>[]; 9 | let cdpToolkit: CdpToolkit; 10 | 11 | beforeEach(() => { 12 | mockAgentkit = { 13 | run: jest.fn((action, args) => action.func(args)), 14 | wallet: { 15 | getDefaultAddress: jest.fn().mockResolvedValue({ getId: () => "mock-address" }), 16 | }, 17 | } as unknown as jest.Mocked; 18 | 19 | mockActions = [ 20 | { 21 | name: "get_wallet_details", 22 | description: "Get wallet details", 23 | argsSchema: z.object({ param1: z.string() }), 24 | func: jest.fn().mockResolvedValue("success_1"), 25 | }, 26 | { 27 | name: "get_balance", 28 | description: "Get wallet balance", 29 | argsSchema: z.object({ param2: z.string() }), 30 | func: jest.fn().mockResolvedValue("success_2"), 31 | }, 32 | ]; 33 | 34 | cdpToolkit = new CdpToolkit(mockAgentkit); 35 | cdpToolkit.tools = mockActions.map(action => new CdpTool(action, mockAgentkit)); 36 | }); 37 | 38 | it("should initialize with correct tools", () => { 39 | expect(cdpToolkit.tools).toHaveLength(mockActions.length); 40 | expect(cdpToolkit.tools[0].name).toBe("get_wallet_details"); 41 | expect(cdpToolkit.tools[1].name).toBe("get_balance"); 42 | }); 43 | 44 | it("should execute action from toolkit", async () => { 45 | const tool = cdpToolkit.tools[0]; 46 | const args = { param1: "test" }; 47 | const response = await tool.call(args); 48 | 49 | expect(mockActions[0].func).toHaveBeenCalledWith(args); 50 | expect(response).toBe("success_1"); 51 | }); 52 | 53 | it("should handle action execution failure", async () => { 54 | const error = new Error("Execution failed"); 55 | mockActions[0].func.mockRejectedValue(error); 56 | 57 | const tool = cdpToolkit.tools[0]; 58 | const args = { param1: "test" }; 59 | const response = await tool.call(args); 60 | 61 | expect(response).toContain(`Error executing get_wallet_details: ${error.message}`); 62 | }); 63 | 64 | it("should return all available tools", () => { 65 | const tools = cdpToolkit.getTools(); 66 | 67 | expect(tools).toHaveLength(mockActions.length); 68 | expect(tools[0].name).toBe("get_wallet_details"); 69 | expect(tools[1].name).toBe("get_balance"); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug Report 2 | description: Create a report to help us reproduce and fix the bug 3 | 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: > 8 | #### Before submitting a bug, please make sure the issue hasn't been already addressed by searching through [the existing and past issues](https://github.com/coinbase/cdp-agentkit-nodejs/issues). 9 | - type: textarea 10 | attributes: 11 | label: 🐛 Describe the bug 12 | description: | 13 | Please provide a clear and concise description of what the bug is. 14 | 15 | If relevant, add a minimal example so that we can reproduce the error by running the code. It is very important for the snippet to be as succinct (minimal) as possible, so please take time to trim down any irrelevant code to help us debug efficiently. We are going to copy-paste your code and we expect to get the same result as you did: avoid any external data, and include the relevant imports, etc. For example: 16 | 17 | ```typescript 18 | // All necessary imports at the beginning 19 | import { CdpAgentkit } from "@coinbase/cdp-agentkit-core"; 20 | import { CdpToolkit } from "@coinbase/cdp-langchain"; 21 | 22 | // A succinct reproducing example trimmed down to the essential parts: 23 | const agentkit = await CdpAgentkit.configureWithWallet(config); 24 | 25 | const cdpToolkit = new CdpToolkit(agentkit); 26 | const tools = cdpToolkit.getTools(); // Note: the bug is here, the method raises an error 27 | ``` 28 | 29 | If the code is too long (hopefully, it isn't), feel free to put it in a public gist and link it in the issue: https://gist.github.com. 30 | 31 | Please also paste or describe the results you observe instead of the expected results. If you observe an error, please paste the error message including the **full** traceback of the exception. It may be relevant to wrap error messages in ```` ```triple quotes blocks``` ````. 32 | placeholder: | 33 | A clear and concise description of what the bug is. 34 | 35 | ```typescript 36 | // Sample code to reproduce the problem 37 | ``` 38 | 39 | ``` 40 | The error message you got, with the full traceback. 41 | ``` 42 | validations: 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: Versions 47 | description: | 48 | Please run the following and paste the output below. 49 | ```sh 50 | node --version 51 | npm --version 52 | npm show @coinbase/cdp-agentkit-core 53 | npm show @coinbase/cdp-langchain 54 | ``` 55 | validations: 56 | required: true 57 | - type: markdown 58 | attributes: 59 | value: > 60 | Thanks for contributing 🎉! 61 | -------------------------------------------------------------------------------- /twitter-langchain/src/tests/twitter_toolkit_test.ts: -------------------------------------------------------------------------------- 1 | import { TwitterToolkit } from "../twitter_toolkit"; 2 | import { TwitterTool } from "../twitter_tool"; 3 | import { 4 | TwitterAction, 5 | TwitterActionSchemaAny, 6 | TwitterAgentkit, 7 | } from "@coinbase/cdp-agentkit-core"; 8 | import { z } from "zod"; 9 | 10 | describe("TwitterToolkit", () => { 11 | let mockAgentkit: jest.Mocked; 12 | let mockActions: jest.Mocked>[]; 13 | let twitterToolkit: TwitterToolkit; 14 | 15 | beforeEach(() => { 16 | mockAgentkit = { 17 | run: jest.fn((action, args) => action.func(mockAgentkit, args)), 18 | } as unknown as jest.Mocked; 19 | 20 | mockActions = [ 21 | { 22 | name: "account_details", 23 | description: "Get Twitter account details", 24 | argsSchema: z.object({ userId: z.string() }), 25 | func: jest.fn().mockResolvedValue("@user123 - Joined 2023"), 26 | }, 27 | { 28 | name: "post_tweet", 29 | description: "Post a new tweet", 30 | argsSchema: z.object({ content: z.string() }), 31 | func: jest.fn().mockResolvedValue("Tweet posted successfully"), 32 | }, 33 | ]; 34 | 35 | twitterToolkit = new TwitterToolkit(mockAgentkit); 36 | twitterToolkit.tools = mockActions.map(action => new TwitterTool(action, mockAgentkit)); 37 | }); 38 | 39 | it("should initialize with correct tools", () => { 40 | expect(twitterToolkit.tools).toHaveLength(mockActions.length); 41 | expect(twitterToolkit.tools[0].name).toBe("account_details"); 42 | expect(twitterToolkit.tools[1].name).toBe("post_tweet"); 43 | }); 44 | 45 | it("should execute action from toolkit", async () => { 46 | const tool = twitterToolkit.tools[0]; 47 | const args = { userId: "user123" }; 48 | const response = await tool.call(args); 49 | 50 | expect(mockActions[0].func).toHaveBeenCalledWith(mockAgentkit, args); 51 | expect(response).toBe("@user123 - Joined 2023"); 52 | }); 53 | 54 | it("should handle action execution failure", async () => { 55 | const error = new Error("Failed to fetch account details"); 56 | mockActions[0].func.mockRejectedValue(error); 57 | 58 | const tool = twitterToolkit.tools[0]; 59 | const args = { userId: "user123" }; 60 | const response = await tool.call(args); 61 | 62 | expect(response).toContain(`Error executing account_details: ${error.message}`); 63 | }); 64 | 65 | it("should return all available tools", () => { 66 | const tools = twitterToolkit.getTools(); 67 | 68 | expect(tools).toHaveLength(mockActions.length); 69 | expect(tools[0].name).toBe("account_details"); 70 | expect(tools[1].name).toBe("post_tweet"); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/wrap_eth.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "./cdp_action"; 2 | import { Wallet } from "@coinbase/coinbase-sdk"; 3 | import { z } from "zod"; 4 | 5 | export const WETH_ADDRESS = "0x4200000000000000000000000000000000000006"; 6 | 7 | export const WETH_ABI = [ 8 | { 9 | inputs: [], 10 | name: "deposit", 11 | outputs: [], 12 | stateMutability: "payable", 13 | type: "function", 14 | }, 15 | { 16 | inputs: [ 17 | { 18 | name: "account", 19 | type: "address", 20 | }, 21 | ], 22 | name: "balanceOf", 23 | outputs: [ 24 | { 25 | type: "uint256", 26 | }, 27 | ], 28 | stateMutability: "view", 29 | type: "function", 30 | }, 31 | ]; 32 | 33 | const WRAP_ETH_PROMPT = ` 34 | This tool can only be used to wrap ETH to WETH. 35 | Do not use this tool for any other purpose, or trading other assets. 36 | 37 | Inputs: 38 | - Amount of ETH to wrap. 39 | 40 | Important notes: 41 | - The amount is a string and cannot have any decimal points, since the unit of measurement is wei. 42 | - Make sure to use the exact amount provided, and if there's any doubt, check by getting more information before continuing with the action. 43 | - 1 wei = 0.000000000000000001 WETH 44 | - Minimum purchase amount is 100000000000000 wei (0.0000001 WETH) 45 | - Only supported on the following networks: 46 | - Base Sepolia (ie, 'base-sepolia') 47 | - Base Mainnet (ie, 'base', 'base-mainnnet') 48 | `; 49 | 50 | export const WrapEthInput = z 51 | .object({ 52 | amountToWrap: z.string().describe("Amount of ETH to wrap in wei"), 53 | }) 54 | .strip() 55 | .describe("Instructions for wrapping ETH to WETH"); 56 | 57 | /** 58 | * Wraps ETH to WETH 59 | * 60 | * @param wallet - The wallet to create the token from. 61 | * @param args - The input arguments for the action. 62 | * @returns A message containing the wrapped ETH details. 63 | */ 64 | export async function wrapEth(wallet: Wallet, args: z.infer): Promise { 65 | try { 66 | const invocation = await wallet.invokeContract({ 67 | contractAddress: WETH_ADDRESS, 68 | method: "deposit", 69 | abi: WETH_ABI, 70 | args: {}, 71 | amount: BigInt(args.amountToWrap), 72 | assetId: "wei", 73 | }); 74 | const result = await invocation.wait(); 75 | return `Wrapped ETH with transaction hash: ${result.getTransaction().getTransactionHash()}`; 76 | } catch (error) { 77 | return `Error wrapping ETH: ${error}`; 78 | } 79 | } 80 | 81 | /** 82 | * Wrap ETH to WETH on Base action. 83 | */ 84 | export class WrapEthAction implements CdpAction { 85 | public name = "wrap_eth"; 86 | public description = WRAP_ETH_PROMPT; 87 | public argsSchema = WrapEthInput; 88 | public func = wrapEth; 89 | } 90 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/transfer.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "./cdp_action"; 2 | import { Wallet, Amount } from "@coinbase/coinbase-sdk"; 3 | import { z } from "zod"; 4 | 5 | const TRANSFER_PROMPT = ` 6 | This tool will transfer an asset from the wallet to another onchain address. 7 | 8 | It takes the following inputs: 9 | - amount: The amount to transfer 10 | - assetId: The asset ID to transfer 11 | - destination: Where to send the funds (can be an onchain address, ENS 'example.eth', or Basename 'example.base.eth') 12 | - gasless: Whether to do a gasless transfer 13 | 14 | Important notes: 15 | - Gasless transfers are only available on base-sepolia and base-mainnet (base) networks for 'usdc' asset 16 | - Always use gasless transfers when available 17 | - Always use asset ID 'usdc' when transferring USDC 18 | - Ensure sufficient balance of the input asset before transferring 19 | - When sending native assets (e.g. 'eth' on base-mainnet), ensure there is sufficient balance for the transfer itself AND the gas cost of this transfer 20 | `; 21 | 22 | /** 23 | * Input schema for transfer action. 24 | */ 25 | export const TransferInput = z 26 | .object({ 27 | amount: z.custom().describe("The amount of the asset to transfer"), 28 | assetId: z.string().describe("The asset ID to transfer"), 29 | destination: z.string().describe("The destination to transfer the funds"), 30 | gasless: z.boolean().default(false).describe("Whether to do a gasless transfer"), 31 | }) 32 | .strip() 33 | .describe("Instructions for transferring assets"); 34 | 35 | /** 36 | * Transfers a specified amount of an asset to a destination onchain. 37 | * 38 | * @param wallet - The wallet to transfer the asset from. 39 | * @param args - The input arguments for the action. 40 | * @returns A message containing the transfer details. 41 | */ 42 | export async function transfer( 43 | wallet: Wallet, 44 | args: z.infer, 45 | ): Promise { 46 | try { 47 | const transferResult = await wallet.createTransfer({ 48 | amount: args.amount, 49 | assetId: args.assetId, 50 | destination: args.destination, 51 | gasless: args.gasless, 52 | }); 53 | 54 | const result = await transferResult.wait(); 55 | 56 | return `Transferred ${args.amount} of ${args.assetId} to ${args.destination}.\nTransaction hash for the transfer: ${result.getTransactionHash()}\nTransaction link for the transfer: ${result.getTransactionLink()}`; 57 | } catch (error) { 58 | return `Error transferring the asset: ${error}`; 59 | } 60 | } 61 | 62 | /** 63 | * Transfer action. 64 | */ 65 | export class TransferAction implements CdpAction { 66 | public name = "transfer"; 67 | public description = TRANSFER_PROMPT; 68 | public argsSchema = TransferInput; 69 | public func = transfer; 70 | } 71 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/social_twitter_post_tweet_test.ts: -------------------------------------------------------------------------------- 1 | import { postTweet, PostTweetInput } from "../actions/cdp/social/twitter/post_tweet"; 2 | import { TwitterApi, TwitterApiv2 } from "twitter-api-v2"; 3 | 4 | const MOCK_TWEET = "@CDPAgentkit please reply!"; 5 | 6 | describe("Post Tweet Input", () => { 7 | it("should successfully parse valid input", () => { 8 | const validInput = { tweet: MOCK_TWEET }; 9 | const result = PostTweetInput.safeParse(validInput); 10 | 11 | expect(result.success).toBe(true); 12 | expect(result.data).toEqual(validInput); 13 | }); 14 | 15 | it("should fail to parse empty input", () => { 16 | const emptyInput = {}; 17 | const result = PostTweetInput.safeParse(emptyInput); 18 | 19 | expect(result.success).toBe(false); 20 | expect(result.error!.issues[0].message).toBe("Required"); 21 | }); 22 | 23 | it("should fail to parse invalid input: tweet is too long", () => { 24 | const invalidInput = { tweet: "A".repeat(281) }; 25 | const result = PostTweetInput.safeParse(invalidInput); 26 | 27 | expect(result.success).toBe(false); 28 | expect(result.error!.issues[0].message).toBe("Tweet must be a maximum of 280 characters."); 29 | }); 30 | }); 31 | 32 | describe("Post Tweet Action", () => { 33 | const mockApiResponse = { 34 | data: { 35 | id: "0123456789012345678", 36 | edit_history_tweet_ids: ["0123456789012345678"], 37 | text: MOCK_TWEET, 38 | }, 39 | }; 40 | 41 | let mockApi: jest.Mocked; 42 | let mockClient: jest.Mocked; 43 | 44 | beforeEach(() => { 45 | mockClient = { 46 | tweet: jest.fn().mockResolvedValue(mockApiResponse), 47 | } as unknown as jest.Mocked; 48 | 49 | mockApi = { 50 | get v2() { 51 | return mockClient; 52 | }, 53 | } as unknown as jest.Mocked; 54 | }); 55 | 56 | it("should successfully post a tweet", async () => { 57 | const args = { 58 | tweet: "Hello, world!", 59 | }; 60 | 61 | const response = await postTweet(mockApi, args); 62 | 63 | expect(mockApi.v2.tweet).toHaveBeenCalledWith(args.tweet); 64 | expect(response).toContain("Successfully posted to Twitter:"); 65 | expect(response).toContain(JSON.stringify(mockApiResponse)); 66 | }); 67 | 68 | it("should handle errors when posting a tweet", async () => { 69 | const args = { 70 | tweet: "Hello, world!", 71 | }; 72 | 73 | const error = new Error("An error has occured"); 74 | mockClient.tweet.mockRejectedValue(error); 75 | 76 | const response = await postTweet(mockApi, { tweet: "Hello, world!" }); 77 | 78 | expect(mockApi.v2.tweet).toHaveBeenCalledWith(args.tweet); 79 | expect(response).toContain("Error posting to Twitter:"); 80 | expect(response).toContain(error.message); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/defi/wow/utils.ts: -------------------------------------------------------------------------------- 1 | import { readContract } from "@coinbase/coinbase-sdk"; 2 | import { WOW_ABI } from "./constants"; 3 | import { getHasGraduated, getUniswapQuote } from "./uniswap/utils"; 4 | 5 | /** 6 | * Gets the current supply of a token. 7 | * 8 | * @param tokenAddress - Address of the token contract 9 | * @returns The current token supply 10 | */ 11 | export async function getCurrentSupply(tokenAddress: string): Promise { 12 | const supply = await readContract({ 13 | networkId: "base-sepolia", 14 | contractAddress: tokenAddress as `0x${string}`, 15 | method: "totalSupply", 16 | args: {}, 17 | abi: WOW_ABI, 18 | }); 19 | 20 | return supply as string; 21 | } 22 | 23 | /** 24 | * Gets quote for buying tokens. 25 | * 26 | * @param networkId - Network ID, either base-sepolia or base-mainnet 27 | * @param tokenAddress - Address of the token contract 28 | * @param amountEthInWei - Amount of ETH to buy (in wei) 29 | * @returns The buy quote amount 30 | */ 31 | export async function getBuyQuote( 32 | networkId: string, 33 | tokenAddress: string, 34 | amountEthInWei: string, 35 | ): Promise { 36 | const hasGraduated = await getHasGraduated(networkId, tokenAddress); 37 | 38 | const tokenQuote = ( 39 | hasGraduated 40 | ? (await getUniswapQuote(networkId, tokenAddress, Number(amountEthInWei), "buy")).amountOut 41 | : await readContract({ 42 | networkId: networkId, 43 | contractAddress: tokenAddress as `0x${string}`, 44 | method: "getEthBuyQuote", 45 | args: { 46 | ethOrderSize: amountEthInWei, 47 | }, 48 | abi: WOW_ABI, 49 | }) 50 | ) as string | number; 51 | 52 | return tokenQuote.toString(); 53 | } 54 | 55 | /** 56 | * Gets quote for selling tokens. 57 | * 58 | * @param networkId - Network ID, either base-sepolia or base-mainnet 59 | * @param tokenAddress - Address of the token contract 60 | * @param amountTokensInWei - Amount of tokens to sell (in wei) 61 | * @returns The sell quote amount 62 | */ 63 | export async function getSellQuote( 64 | networkId: string, 65 | tokenAddress: string, 66 | amountTokensInWei: string, 67 | ): Promise { 68 | const hasGraduated = await getHasGraduated(networkId, tokenAddress); 69 | 70 | const tokenQuote = ( 71 | hasGraduated 72 | ? (await getUniswapQuote(networkId, tokenAddress, Number(amountTokensInWei), "sell")) 73 | .amountOut 74 | : await readContract({ 75 | networkId: networkId, 76 | contractAddress: tokenAddress as `0x${string}`, 77 | method: "getTokenSellQuote", 78 | args: { 79 | tokenOrderSize: amountTokensInWei, 80 | }, 81 | abi: WOW_ABI, 82 | }) 83 | ) as string | number; 84 | 85 | return tokenQuote.toString(); 86 | } 87 | -------------------------------------------------------------------------------- /cdp-langchain/src/tools/cdp_tool.ts: -------------------------------------------------------------------------------- 1 | import { StructuredTool } from "@langchain/core/tools"; 2 | import { CdpAgentkit, CdpAction, CdpActionSchemaAny } from "@coinbase/cdp-agentkit-core"; 3 | import { z } from "zod"; 4 | 5 | /** 6 | * This tool allows agents to interact with the cdp-sdk library and control an MPC Wallet onchain. 7 | * 8 | * To use this tool, you must first set as environment variables: 9 | * CDP_API_KEY_NAME 10 | * CDP_API_KEY_PRIVATE_KEY 11 | * NETWORK_ID 12 | */ 13 | export class CdpTool extends StructuredTool { 14 | /** 15 | * Schema definition for the tool's input 16 | */ 17 | public schema: TActionSchema; 18 | 19 | /** 20 | * The name of the tool 21 | */ 22 | public name: string; 23 | 24 | /** 25 | * The description of the tool 26 | */ 27 | public description: string; 28 | 29 | /** 30 | * The CDP Agentkit instance 31 | */ 32 | private agentkit: CdpAgentkit; 33 | 34 | /** 35 | * The CDP Action 36 | */ 37 | private action: CdpAction; 38 | 39 | /** 40 | * Constructor for the CDP Tool class 41 | * 42 | * @param action - The CDP action to execute 43 | * @param agentkit - The CDP wrapper to use 44 | */ 45 | constructor(action: CdpAction, agentkit: CdpAgentkit) { 46 | super(); 47 | this.action = action; 48 | this.agentkit = agentkit; 49 | this.name = action.name; 50 | this.description = action.description; 51 | this.schema = action.argsSchema; 52 | } 53 | 54 | /** 55 | * Executes the CDP action with the provided input 56 | * 57 | * @param input - An object containing either instructions or schema-validated arguments 58 | * @returns A promise that resolves to the result of the CDP action 59 | * @throws {Error} If the CDP action fails 60 | */ 61 | protected async _call( 62 | input: z.infer & Record, 63 | ): Promise { 64 | try { 65 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 66 | let args: any; 67 | 68 | // If we have a schema, try to validate against it 69 | if (this.schema) { 70 | try { 71 | const validatedInput = this.schema.parse(input); 72 | args = validatedInput; 73 | } catch (validationError) { 74 | // If schema validation fails, fall back to instructions-only mode 75 | args = input; 76 | } 77 | } 78 | // No schema, use instructions mode 79 | else { 80 | args = input; 81 | } 82 | 83 | return await this.agentkit.run(this.action, args); 84 | } catch (error: unknown) { 85 | if (error instanceof Error) { 86 | return `Error executing ${this.name}: ${error.message}`; 87 | } 88 | return `Error executing ${this.name}: Unknown error occurred`; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/social_twitter_account_mentions_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | accountMentions, 3 | AccountMentionsInput, 4 | } from "../actions/cdp/social/twitter/account_mentions"; 5 | import { TwitterApi, TwitterApiv2 } from "twitter-api-v2"; 6 | 7 | const MOCK_ACCOUNT_ID = "1857479287504584856"; 8 | 9 | describe("Account Mentions Input", () => { 10 | it("should successfully parse valid input", () => { 11 | const validInput = { userId: MOCK_ACCOUNT_ID }; 12 | const result = AccountMentionsInput.safeParse(validInput); 13 | 14 | expect(result.success).toBe(true); 15 | expect(result.data).toEqual(validInput); 16 | }); 17 | 18 | it("should fail to parse empty input", () => { 19 | const emptyInput = {}; 20 | const result = AccountMentionsInput.safeParse(emptyInput); 21 | 22 | expect(result.success).toBe(false); 23 | expect(result.error!.issues[0].message).toBe("Required"); 24 | }); 25 | 26 | it("should fail to parse invalid input", () => { 27 | const invalidInput = { userId: "" }; 28 | const result = AccountMentionsInput.safeParse(invalidInput); 29 | 30 | expect(result.success).toBe(false); 31 | expect(result.error!.issues[0].message).toBe("Account ID is required."); 32 | }); 33 | }); 34 | 35 | describe("Account Mentions Action", () => { 36 | const mockApiResponse = { 37 | data: [ 38 | { 39 | id: "0123456789012345678", 40 | text: "@CDPAgentkit please reply!", 41 | }, 42 | ], 43 | }; 44 | 45 | let mockApi: jest.Mocked; 46 | let mockClient: jest.Mocked; 47 | 48 | beforeEach(() => { 49 | mockClient = { 50 | userMentionTimeline: jest.fn().mockResolvedValue(mockApiResponse), 51 | } as unknown as jest.Mocked; 52 | 53 | mockApi = { 54 | get v2() { 55 | return mockClient; 56 | }, 57 | } as unknown as jest.Mocked; 58 | }); 59 | 60 | it("should successfully retrieve account mentions", async () => { 61 | const args = { userId: MOCK_ACCOUNT_ID }; 62 | const response = await accountMentions(mockApi, args); 63 | 64 | expect(mockApi.v2.userMentionTimeline).toHaveBeenCalledWith(MOCK_ACCOUNT_ID); 65 | expect(response).toContain("Successfully retrieved account mentions:"); 66 | expect(response).toContain(JSON.stringify(mockApiResponse)); 67 | }); 68 | 69 | it("should handle errors when retrieving account mentions", async () => { 70 | const args = { 71 | userId: MOCK_ACCOUNT_ID, 72 | }; 73 | 74 | const error = new Error("Twitter API error"); 75 | mockClient.userMentionTimeline.mockRejectedValue(error); 76 | 77 | const response = await accountMentions(mockApi, args); 78 | 79 | expect(mockApi.v2.userMentionTimeline).toHaveBeenCalledWith(MOCK_ACCOUNT_ID); 80 | expect(response).toContain("Error retrieving authenticated account mentions:"); 81 | expect(response).toContain(error.message); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/request_faucet_funds_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, FaucetTransaction, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { requestFaucetFunds, RequestFaucetFundsInput } from "../actions/cdp/request_faucet_funds"; 4 | 5 | const MOCK_ASSET_ID = Coinbase.assets.Usdc; 6 | 7 | describe("Request Faucet Funds Input", () => { 8 | it("should successfully parse valid input", () => { 9 | const validInput = { 10 | assetId: MOCK_ASSET_ID, 11 | }; 12 | 13 | const result = RequestFaucetFundsInput.safeParse(validInput); 14 | 15 | expect(result.success).toBe(true); 16 | expect(result.data).toEqual(validInput); 17 | }); 18 | 19 | it("should successfully parsing empty input", () => { 20 | const emptyInput = {}; 21 | const result = RequestFaucetFundsInput.safeParse(emptyInput); 22 | 23 | expect(result.success).toBe(true); 24 | }); 25 | }); 26 | 27 | describe("Request Faucet Funds Action", () => { 28 | const TRANSACTION_HASH = "0xghijkl987654321"; 29 | const TRANSACTION_LINK = `https://etherscan.io/tx/${TRANSACTION_HASH}`; 30 | 31 | let mockFaucetTransaction: jest.Mocked; 32 | let mockWallet: jest.Mocked; 33 | 34 | beforeEach(() => { 35 | mockFaucetTransaction = { 36 | wait: jest.fn().mockResolvedValue({ 37 | getTransactionLink: jest.fn().mockReturnValue(TRANSACTION_LINK), 38 | }), 39 | } as unknown as jest.Mocked; 40 | 41 | mockWallet = { 42 | faucet: jest.fn(), 43 | } as unknown as jest.Mocked; 44 | 45 | mockWallet.faucet.mockResolvedValue(mockFaucetTransaction); 46 | }); 47 | 48 | it("should successfully request faucet funds", async () => { 49 | const args = {}; 50 | const response = await requestFaucetFunds(mockWallet, args); 51 | 52 | expect(mockWallet.faucet).toHaveBeenCalled(); 53 | expect(mockFaucetTransaction.wait).toHaveBeenCalled(); 54 | expect(response).toContain(`Received ETH from the faucet. Transaction: ${TRANSACTION_LINK}`); 55 | }); 56 | 57 | it("should successfully request faucet funds with an asset id", async () => { 58 | const args = { assetId: MOCK_ASSET_ID }; 59 | const response = await requestFaucetFunds(mockWallet, args); 60 | 61 | expect(mockWallet.faucet).toHaveBeenCalledWith(MOCK_ASSET_ID); 62 | expect(mockFaucetTransaction.wait).toHaveBeenCalled(); 63 | expect(response).toContain(`Received ${MOCK_ASSET_ID} from the faucet`); 64 | expect(response).toContain(`Transaction: ${TRANSACTION_LINK}`); 65 | }); 66 | 67 | it("should fail with an error", async () => { 68 | const args = { assetId: MOCK_ASSET_ID }; 69 | 70 | const error = new Error("Failed to request funds"); 71 | mockWallet.faucet.mockRejectedValue(error); 72 | 73 | const response = await requestFaucetFunds(mockWallet, args); 74 | 75 | expect(mockWallet.faucet).toHaveBeenCalled(); 76 | expect(response).toContain(`Error requesting faucet funds: ${error}`); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/get_balance_test.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, WalletAddress } from "@coinbase/coinbase-sdk"; 2 | import { getBalance, GetBalanceInput } from "../actions/cdp/get_balance"; 3 | 4 | const MOCK_ASSET_ID = "test-asset-id"; 5 | const MOCK_BALANCE = 1000000000000000000; 6 | 7 | describe("Get Balance Input", () => { 8 | it("should successfully parse valid input", () => { 9 | const validInput = { 10 | assetId: MOCK_ASSET_ID, 11 | }; 12 | 13 | const result = GetBalanceInput.safeParse(validInput); 14 | 15 | expect(result.success).toBe(true); 16 | expect(result.data).toEqual(validInput); 17 | }); 18 | 19 | it("should fail parsing empty input", () => { 20 | const emptyInput = {}; 21 | const result = GetBalanceInput.safeParse(emptyInput); 22 | 23 | expect(result.success).toBe(false); 24 | }); 25 | }); 26 | 27 | describe("Get Balance Action", () => { 28 | const WALLET_ID = "0x123456789abcdef"; 29 | 30 | let mockAddresses: jest.Mocked; 31 | let mockWallet: jest.Mocked; 32 | 33 | beforeEach(() => { 34 | mockAddresses = [ 35 | { 36 | getId: jest.fn().mockReturnValue("test-address-id-1"), 37 | getBalance: jest.fn().mockReturnValue(MOCK_BALANCE), 38 | } as unknown as jest.Mocked, 39 | { 40 | getId: jest.fn().mockReturnValue("test-address-id-2"), 41 | getBalance: jest.fn().mockReturnValue(0.0), 42 | } as unknown as jest.Mocked, 43 | { 44 | getId: jest.fn().mockReturnValue("test-address-id-3"), 45 | getBalance: jest.fn().mockReturnValue(MOCK_BALANCE), 46 | } as unknown as jest.Mocked, 47 | ] as unknown as jest.Mocked[]; 48 | 49 | mockWallet = { 50 | getId: jest.fn().mockReturnValue(WALLET_ID), 51 | listAddresses: jest.fn(), 52 | } as unknown as jest.Mocked; 53 | 54 | mockWallet.listAddresses.mockResolvedValue(mockAddresses); 55 | }); 56 | 57 | it("should successfully respond", async () => { 58 | const args = { 59 | assetId: MOCK_ASSET_ID, 60 | }; 61 | 62 | const response = await getBalance(mockWallet, args); 63 | 64 | expect(mockWallet.listAddresses).toHaveBeenCalledWith(); 65 | mockAddresses.forEach(address => { 66 | expect(address.getBalance).toHaveBeenCalledWith(MOCK_ASSET_ID); 67 | expect(response).toContain(`${address.getId()}: ${address.getBalance(MOCK_ASSET_ID)}`); 68 | }); 69 | expect(response).toContain(`Balances for wallet ${WALLET_ID}`); 70 | }); 71 | 72 | it("should fail with an error", async () => { 73 | const args = { 74 | assetId: MOCK_ASSET_ID, 75 | }; 76 | 77 | const error = new Error("An error has occured"); 78 | mockWallet.listAddresses.mockRejectedValue(error); 79 | 80 | const response = await getBalance(mockWallet, args); 81 | 82 | expect(mockWallet.listAddresses).toHaveBeenCalled(); 83 | expect(response).toContain(`Error getting balance for all addresses in the wallet: ${error}`); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /twitter-langchain/src/twitter_tool.ts: -------------------------------------------------------------------------------- 1 | import { StructuredTool } from "@langchain/core/tools"; 2 | import { z } from "zod"; 3 | import { 4 | TwitterAction, 5 | TwitterActionSchemaAny, 6 | TwitterAgentkit, 7 | } from "@coinbase/cdp-agentkit-core"; 8 | 9 | /** 10 | * This tool allows agents to interact with the Twitter (X) API and control an authenticated Twitter account. 11 | * 12 | * To use this tool, you must first set the following environment variables: 13 | * - TWITTER_API_KEY 14 | * - TWITTER_API_SECRET 15 | * - TWITTER_ACCESS_TOKEN 16 | * - TWITTER_ACCESS_TOKEN_SECRET 17 | * - TWITTER_BEARER_TOKEN 18 | */ 19 | export class TwitterTool extends StructuredTool { 20 | /** 21 | * Schema definition for the tool's input. 22 | */ 23 | public schema: TActionSchema; 24 | 25 | /** 26 | * The name of the tool. 27 | */ 28 | public name: string; 29 | 30 | /** 31 | * The description of the tool. 32 | */ 33 | public description: string; 34 | 35 | /** 36 | * The Twitter (X) Agentkit instance. 37 | */ 38 | private agentkit: TwitterAgentkit; 39 | 40 | /** 41 | * The Twitter (X) Action. 42 | */ 43 | private action: TwitterAction; 44 | 45 | /** 46 | * Constructor for the Twitter (X) Tool class. 47 | * 48 | * @param action - The Twitter (X) action to execute. 49 | * @param agentkit - The Twitter (X) wrapper to use. 50 | */ 51 | constructor(action: TwitterAction, agentkit: TwitterAgentkit) { 52 | super(); 53 | 54 | this.action = action; 55 | this.agentkit = agentkit; 56 | this.name = action.name; 57 | this.description = action.description; 58 | this.schema = action.argsSchema; 59 | } 60 | 61 | /** 62 | * Executes the Twitter (X) action with the provided input. 63 | * 64 | * @param input - An object containing either instructions or schema-validated arguments. 65 | * @returns A promise that resolves to the result of the Twitter (X) action. 66 | */ 67 | protected async _call( 68 | input: z.infer & Record, 69 | ): Promise { 70 | try { 71 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 72 | let args: any; 73 | 74 | // If we have a schema, try to validate against it 75 | if (this.schema) { 76 | try { 77 | const validatedInput = this.schema.parse(input); 78 | args = validatedInput; 79 | } catch (validationError) { 80 | // If schema validation fails, fall back to instructions-only mode 81 | args = input; 82 | } 83 | } 84 | // No schema, use instructions mode 85 | else { 86 | args = input; 87 | } 88 | 89 | return await this.agentkit.run(this.action, args); 90 | } catch (error: unknown) { 91 | if (error instanceof Error) { 92 | return `Error executing ${this.name}: ${error.message}`; 93 | } 94 | return `Error executing ${this.name}: Unknown error occurred`; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/transfer_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, Transfer, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { transfer as createTransfer, TransferInput } from "../actions/cdp/transfer"; 4 | 5 | const MOCK_AMOUNT = 15; 6 | const MOCK_ASSET_ID = Coinbase.assets.Eth; 7 | const MOCK_DESTINATION = "0x321"; 8 | const MOCK_GASLESS = true; 9 | 10 | describe("Transfer Input", () => { 11 | it("should successfully parse valid input", () => { 12 | const validInput = { 13 | amount: MOCK_AMOUNT, 14 | assetId: MOCK_ASSET_ID, 15 | destination: MOCK_DESTINATION, 16 | gasless: MOCK_GASLESS, 17 | }; 18 | 19 | const result = TransferInput.safeParse(validInput); 20 | 21 | expect(result.success).toBe(true); 22 | expect(result.data).toEqual(validInput); 23 | }); 24 | 25 | it("should fail parsing empty input", () => { 26 | const emptyInput = {}; 27 | const result = TransferInput.safeParse(emptyInput); 28 | 29 | expect(result.success).toBe(false); 30 | }); 31 | }); 32 | 33 | describe("Transfer Action", () => { 34 | const TRANSACTION_HASH = "0xghijkl987654321"; 35 | const TRANSACTION_LINK = `https://etherscan.io/tx/${TRANSACTION_HASH}`; 36 | 37 | let mockTransfer: jest.Mocked; 38 | let mockWallet: jest.Mocked; 39 | 40 | beforeEach(async () => { 41 | mockTransfer = { 42 | wait: jest.fn().mockResolvedValue({ 43 | getTransactionHash: jest.fn().mockReturnValue(TRANSACTION_HASH), 44 | getTransactionLink: jest.fn().mockReturnValue(TRANSACTION_LINK), 45 | }), 46 | } as unknown as jest.Mocked; 47 | 48 | mockWallet = { 49 | createTransfer: jest.fn(), 50 | } as unknown as jest.Mocked; 51 | 52 | mockWallet.createTransfer.mockResolvedValue(mockTransfer); 53 | }); 54 | 55 | it("should successfully respond", async () => { 56 | const args = { 57 | amount: MOCK_AMOUNT, 58 | assetId: MOCK_ASSET_ID, 59 | destination: MOCK_DESTINATION, 60 | gasless: MOCK_GASLESS, 61 | }; 62 | 63 | const response = await createTransfer(mockWallet, args); 64 | 65 | expect(mockWallet.createTransfer).toHaveBeenCalledWith(args); 66 | expect(mockTransfer.wait).toHaveBeenCalled(); 67 | expect(response).toContain( 68 | `Transferred ${MOCK_AMOUNT} of ${MOCK_ASSET_ID} to ${MOCK_DESTINATION}`, 69 | ); 70 | expect(response).toContain(`Transaction hash for the transfer: ${TRANSACTION_HASH}`); 71 | expect(response).toContain(`Transaction link for the transfer: ${TRANSACTION_LINK}`); 72 | }); 73 | 74 | it("should fail with an error", async () => { 75 | const args = { 76 | amount: MOCK_AMOUNT, 77 | assetId: MOCK_ASSET_ID, 78 | destination: MOCK_DESTINATION, 79 | gasless: MOCK_GASLESS, 80 | }; 81 | 82 | const error = new Error("Failed to execute transfer"); 83 | mockWallet.createTransfer.mockRejectedValue(error); 84 | 85 | const response = await createTransfer(mockWallet, args); 86 | 87 | expect(mockWallet.createTransfer).toHaveBeenCalledWith(args); 88 | expect(response).toContain(`Error transferring the asset: ${error}`); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/defi/wow/actions/create_token.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "../../../cdp_action"; 2 | import { Wallet } from "@coinbase/coinbase-sdk"; 3 | import { WOW_FACTORY_ABI, GENERIC_TOKEN_METADATA_URI, getFactoryAddress } from "../constants"; 4 | import { z } from "zod"; 5 | 6 | const WOW_CREATE_TOKEN_PROMPT = ` 7 | This tool can only be used to create a Zora Wow ERC20 memecoin (also can be referred to as a bonding curve token) using the WoW factory. 8 | Do not use this tool for any other purpose, or for creating other types of tokens. 9 | 10 | Inputs: 11 | - Token name (e.g. WowCoin) 12 | - Token symbol (e.g. WOW) 13 | - Token URI (optional) - Contains metadata about the token 14 | 15 | Important notes: 16 | - Uses a bonding curve - no upfront liquidity needed 17 | - Only supported on the following networks: 18 | - Base Sepolia (ie, 'base-sepolia') 19 | - Base Mainnet (ie, 'base', 'base-mainnnet') 20 | `; 21 | 22 | /** 23 | * Input schema for create token action. 24 | */ 25 | export const WowCreateTokenInput = z 26 | .object({ 27 | name: z.string().describe("The name of the token to create, e.g. WowCoin"), 28 | symbol: z.string().describe("The symbol of the token to create, e.g. WOW"), 29 | tokenUri: z 30 | .string() 31 | .optional() 32 | .describe( 33 | "The URI of the token metadata to store on IPFS, e.g. ipfs://QmY1GqprFYvojCcUEKgqHeDj9uhZD9jmYGrQTfA9vAE78J", 34 | ), 35 | }) 36 | .strip() 37 | .describe("Instructions for creating a WOW token"); 38 | 39 | /** 40 | * Creates a Zora Wow ERC20 memecoin. 41 | * 42 | * @param wallet - The wallet to create the token from. 43 | * @param args - The input arguments for the action. 44 | * @returns A message containing the token creation details. 45 | */ 46 | export async function wowCreateToken( 47 | wallet: Wallet, 48 | args: z.infer, 49 | ): Promise { 50 | const factoryAddress = getFactoryAddress(wallet.getNetworkId()); 51 | 52 | try { 53 | const invocation = await wallet.invokeContract({ 54 | contractAddress: factoryAddress, 55 | method: "deploy", 56 | abi: WOW_FACTORY_ABI, 57 | args: { 58 | _tokenCreator: (await wallet.getDefaultAddress()).getId(), 59 | _platformReferrer: "0x0000000000000000000000000000000000000000", 60 | _tokenURI: args.tokenUri || GENERIC_TOKEN_METADATA_URI, 61 | _name: args.name, 62 | _symbol: args.symbol, 63 | }, 64 | }); 65 | 66 | const result = await invocation.wait(); 67 | return `Created WoW ERC20 memecoin ${args.name} with symbol ${args.symbol} on network ${wallet.getNetworkId()}.\nTransaction hash for the token creation: ${result.getTransaction().getTransactionHash()}`; 68 | } catch (error) { 69 | return `Error creating Zora Wow ERC20 memecoin: ${error}`; 70 | } 71 | } 72 | 73 | /** 74 | * Zora Wow create token action. 75 | */ 76 | export class WowCreateTokenAction implements CdpAction { 77 | public name = "wow_create_token"; 78 | public description = WOW_CREATE_TOKEN_PROMPT; 79 | public argsSchema = WowCreateTokenInput; 80 | public func = wowCreateToken; 81 | } 82 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/deploy_token_test.ts: -------------------------------------------------------------------------------- 1 | import { SmartContract, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { deployToken, DeployTokenInput } from "../actions/cdp/deploy_token"; 4 | 5 | const MOCK_TOKEN_NAME = "Test Token"; 6 | const MOCK_TOKEN_SYMBOL = "TEST"; 7 | const MOCK_TOKEN_SUPPLY = 100; 8 | 9 | describe("Deploy Token Input", () => { 10 | it("should successfully parse valid input", () => { 11 | const validInput = { 12 | name: MOCK_TOKEN_NAME, 13 | symbol: MOCK_TOKEN_SYMBOL, 14 | totalSupply: MOCK_TOKEN_SUPPLY, 15 | }; 16 | 17 | const result = DeployTokenInput.safeParse(validInput); 18 | 19 | expect(result.success).toBe(true); 20 | expect(result.data).toEqual(validInput); 21 | }); 22 | 23 | it("should fail parsing empty input", () => { 24 | const emptyInput = {}; 25 | const result = DeployTokenInput.safeParse(emptyInput); 26 | 27 | expect(result.success).toBe(false); 28 | }); 29 | }); 30 | 31 | describe("Deploy Token Action", () => { 32 | const CONTRACT_ADDRESS = "0x123456789abcdef"; 33 | const TRANSACTION_HASH = "0xghijkl987654321"; 34 | const TRANSACTION_LINK = `https://etherscan.io/tx/${TRANSACTION_HASH}`; 35 | 36 | let mockSmartContract: jest.Mocked; 37 | let mockWallet: jest.Mocked; 38 | 39 | beforeEach(() => { 40 | mockSmartContract = { 41 | wait: jest.fn().mockResolvedValue({ 42 | getContractAddress: jest.fn().mockReturnValue(CONTRACT_ADDRESS), 43 | getTransaction: jest.fn().mockReturnValue({ 44 | getTransactionLink: jest.fn().mockReturnValue(TRANSACTION_LINK), 45 | }), 46 | }), 47 | } as unknown as jest.Mocked; 48 | 49 | mockWallet = { 50 | deployToken: jest.fn(), 51 | } as unknown as jest.Mocked; 52 | 53 | mockWallet.deployToken.mockResolvedValue(mockSmartContract); 54 | }); 55 | 56 | it("should successfully respond", async () => { 57 | const args = { 58 | name: MOCK_TOKEN_NAME, 59 | symbol: MOCK_TOKEN_SYMBOL, 60 | totalSupply: MOCK_TOKEN_SUPPLY, 61 | }; 62 | 63 | const response = await deployToken(mockWallet, args); 64 | 65 | expect(mockWallet.deployToken).toHaveBeenCalledWith(args); 66 | expect(mockSmartContract.wait).toHaveBeenCalled(); 67 | expect(response).toContain( 68 | `Deployed ERC20 token contract ${MOCK_TOKEN_NAME} (${MOCK_TOKEN_SYMBOL})`, 69 | ); 70 | expect(response).toContain(`with total supply of ${MOCK_TOKEN_SUPPLY}`); 71 | expect(response).toContain(`tokens at address ${CONTRACT_ADDRESS}`); 72 | expect(response).toContain(`Transaction link: ${TRANSACTION_LINK}`); 73 | }); 74 | 75 | it("should fail with an error", async () => { 76 | const args = { 77 | name: MOCK_TOKEN_NAME, 78 | symbol: MOCK_TOKEN_SYMBOL, 79 | totalSupply: MOCK_TOKEN_SUPPLY, 80 | }; 81 | 82 | const error = new Error("An error has occured"); 83 | mockWallet.deployToken.mockRejectedValue(error); 84 | 85 | const response = await deployToken(mockWallet, args); 86 | 87 | expect(mockWallet.deployToken).toHaveBeenCalledWith(args); 88 | expect(response).toContain(`Error deploying token: ${error}`); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/trade_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, Trade, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { trade as createTrade, TradeInput } from "../actions/cdp/trade"; 4 | 5 | const MOCK_TRADE_AMOUNT = 0.123; 6 | const MOCK_TRADE_ASSET_ID_FROM = Coinbase.assets.Eth; 7 | const MOCK_TRADE_ASSET_ID_TO = Coinbase.assets.Usdc; 8 | 9 | describe("Trade Input", () => { 10 | it("should successfully parse valid input", () => { 11 | const validInput = { 12 | amount: MOCK_TRADE_AMOUNT, 13 | fromAssetId: MOCK_TRADE_ASSET_ID_FROM, 14 | toAssetId: MOCK_TRADE_ASSET_ID_TO, 15 | }; 16 | 17 | const result = TradeInput.safeParse(validInput); 18 | 19 | expect(result.success).toBe(true); 20 | expect(result.data).toEqual(validInput); 21 | }); 22 | 23 | it("should fail parsing empty input", () => { 24 | const emptyInput = {}; 25 | const result = TradeInput.safeParse(emptyInput); 26 | 27 | expect(result.success).toBe(false); 28 | }); 29 | }); 30 | 31 | describe("Trade Action", () => { 32 | const TO_AMOUNT = 3661.08; 33 | const TRANSACTION_HASH = "0xghijkl987654321"; 34 | const TRANSACTION_LINK = `https://etherscan.io/tx/${TRANSACTION_HASH}`; 35 | 36 | let mockTrade: jest.Mocked; 37 | let mockWallet: jest.Mocked; 38 | 39 | beforeEach(async () => { 40 | mockTrade = { 41 | wait: jest.fn().mockResolvedValue({ 42 | getToAmount: jest.fn().mockReturnValue(TO_AMOUNT), 43 | getTransaction: jest.fn().mockReturnValue({ 44 | getTransactionHash: jest.fn().mockReturnValue(TRANSACTION_HASH), 45 | getTransactionLink: jest.fn().mockReturnValue(TRANSACTION_LINK), 46 | }), 47 | }), 48 | } as unknown as jest.Mocked; 49 | 50 | mockWallet = { 51 | createTrade: jest.fn(), 52 | } as unknown as jest.Mocked; 53 | 54 | mockWallet.createTrade.mockResolvedValue(mockTrade); 55 | }); 56 | 57 | it("should successfully execute the trade", async () => { 58 | const args = { 59 | amount: MOCK_TRADE_AMOUNT, 60 | fromAssetId: MOCK_TRADE_ASSET_ID_FROM, 61 | toAssetId: MOCK_TRADE_ASSET_ID_TO, 62 | }; 63 | 64 | const response = await createTrade(mockWallet, args); 65 | 66 | expect(mockWallet.createTrade).toHaveBeenCalledWith(args); 67 | expect(mockTrade.wait).toHaveBeenCalled(); 68 | expect(response).toContain( 69 | `Traded ${MOCK_TRADE_AMOUNT} of ${MOCK_TRADE_ASSET_ID_FROM} for ${TO_AMOUNT} of ${MOCK_TRADE_ASSET_ID_TO}`, 70 | ); 71 | expect(response).toContain(`Transaction hash for the trade: ${TRANSACTION_HASH}`); 72 | expect(response).toContain(`Transaction link for the trade: ${TRANSACTION_LINK}`); 73 | }); 74 | 75 | it("should fail with an error", async () => { 76 | const args = { 77 | amount: MOCK_TRADE_AMOUNT, 78 | fromAssetId: MOCK_TRADE_ASSET_ID_FROM, 79 | toAssetId: MOCK_TRADE_ASSET_ID_TO, 80 | }; 81 | 82 | const error = new Error("Failed to execute trade"); 83 | mockWallet.createTrade.mockRejectedValue(error); 84 | 85 | const response = await createTrade(mockWallet, args); 86 | 87 | expect(mockWallet.createTrade).toHaveBeenCalled(); 88 | expect(response).toContain(`Error trading assets: ${error}`); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/social_twitter_post_tweet_reply_test.ts: -------------------------------------------------------------------------------- 1 | import { postTweet, PostTweetReplyInput } from "../actions/cdp/social/twitter/post_tweet_reply"; 2 | import { TwitterApi, TwitterApiv2 } from "twitter-api-v2"; 3 | 4 | const MOCK_TWEET_ID = "0123456789012345678"; 5 | const MOCK_TWEET_REPLY = "hello, world, again!"; 6 | 7 | describe("Post Tweet Reply Input", () => { 8 | it("should successfully parse valid input", () => { 9 | const validInput = { tweetId: MOCK_TWEET_ID, tweetReply: MOCK_TWEET_REPLY }; 10 | const result = PostTweetReplyInput.safeParse(validInput); 11 | 12 | expect(result.success).toBe(true); 13 | expect(result.data).toEqual(validInput); 14 | }); 15 | 16 | it("should fail to parse empty input", () => { 17 | const emptyInput = {}; 18 | const result = PostTweetReplyInput.safeParse(emptyInput); 19 | 20 | expect(result.success).toBe(false); 21 | expect(result.error!.issues[0].message).toBe("Required"); 22 | }); 23 | 24 | it("should fail to parse invalid input: tweet is too long", () => { 25 | const invalidInput = { tweetId: MOCK_TWEET_ID, tweetReply: "A".repeat(281) }; 26 | const result = PostTweetReplyInput.safeParse(invalidInput); 27 | 28 | expect(result.success).toBe(false); 29 | expect(result.error!.issues[0].message).toBe( 30 | "The reply to the tweet which must be a maximum of 280 characters.", 31 | ); 32 | }); 33 | }); 34 | 35 | describe("Post Tweet Reply Action", () => { 36 | const mockApiResponse = { 37 | data: { 38 | id: "9123456789012345678", 39 | edit_history_tweet_ids: ["9123456789012345678"], 40 | text: MOCK_TWEET_REPLY, 41 | }, 42 | }; 43 | 44 | let mockApi: jest.Mocked; 45 | let mockClient: jest.Mocked; 46 | 47 | beforeEach(() => { 48 | mockClient = { 49 | tweet: jest.fn().mockResolvedValue(mockApiResponse), 50 | } as unknown as jest.Mocked; 51 | 52 | mockApi = { 53 | get v2() { 54 | return mockClient; 55 | }, 56 | } as unknown as jest.Mocked; 57 | }); 58 | 59 | it("should successfully post a reply", async () => { 60 | const args = { 61 | tweetId: MOCK_TWEET_ID, 62 | tweetReply: MOCK_TWEET_REPLY, 63 | }; 64 | 65 | const response = await postTweet(mockApi, args); 66 | 67 | expect(mockApi.v2.tweet).toHaveBeenCalledWith(MOCK_TWEET_REPLY, { 68 | reply: { in_reply_to_tweet_id: MOCK_TWEET_ID }, 69 | }); 70 | expect(response).toContain("Successfully posted reply to Twitter:"); 71 | expect(response).toContain(JSON.stringify(mockApiResponse)); 72 | }); 73 | 74 | it("should handle errors when posting a reply", async () => { 75 | const args = { 76 | tweetId: MOCK_TWEET_ID, 77 | tweetReply: MOCK_TWEET_REPLY, 78 | }; 79 | 80 | const error = new Error("An error has occured"); 81 | mockClient.tweet.mockRejectedValue(error); 82 | 83 | const response = await postTweet(mockApi, args); 84 | 85 | expect(mockApi.v2.tweet).toHaveBeenCalledWith(MOCK_TWEET_REPLY, { 86 | reply: { in_reply_to_tweet_id: MOCK_TWEET_ID }, 87 | }); 88 | expect(response).toContain("Error posting reply to Twitter:"); 89 | expect(response).toContain(error.message); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/cdp_agentkit_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, Wallet, WalletData } from "@coinbase/coinbase-sdk"; 2 | import { CdpAgentkit } from "../cdp_agentkit"; 3 | 4 | const MOCK_MNEMONIC_PHRASE = 5 | "eternal phone creek robot disorder climb thought eternal noodle flat cage bubble liquid sting can"; 6 | const MOCK_WALLET_ID = "0x123456789abcdef"; 7 | const MOCK_WALLET_SEED = "0xc746290109d0b86162c428be6e27f552"; 8 | const MOCK_WALLET_JSON = `{"defaultAddressId":"0xabcdef123456789", "seed":"${MOCK_WALLET_SEED}", "walletId":"${MOCK_WALLET_ID}"}`; 9 | 10 | describe("CdpAgentkit", () => { 11 | describe("initialization", () => { 12 | const mockWallet: jest.Mocked = {} as unknown as jest.Mocked; 13 | 14 | beforeEach(async () => { 15 | process.env.CDP_API_KEY_NAME = "test-key"; 16 | process.env.CDP_API_KEY_PRIVATE_KEY = "test-private-key"; 17 | 18 | jest.spyOn(Wallet, "create").mockResolvedValue(mockWallet); 19 | }); 20 | 21 | afterEach(() => { 22 | jest.resetAllMocks(); 23 | 24 | process.env.CDP_API_KEY_NAME = ""; 25 | process.env.CDP_API_KEY_PRIVATE_KEY = ""; 26 | }); 27 | 28 | it("should successfully init with env", async () => { 29 | const options = {}; 30 | const result = await CdpAgentkit.configureWithWallet(options); 31 | 32 | expect(result).toBeDefined(); 33 | expect(Wallet.create).toHaveBeenCalledWith({ 34 | networkId: Coinbase.networks.BaseSepolia, 35 | }); 36 | }); 37 | 38 | it("should successfully init with options and without env", async () => { 39 | const options = { 40 | cdpApiKeyName: "test-key", 41 | cdpApiKeyPrivateKey: "test-private-key", 42 | }; 43 | 44 | process.env.CDP_API_KEY_NAME = ""; 45 | process.env.CDP_API_KEY_PRIVATE_KEY = ""; 46 | 47 | const result = await CdpAgentkit.configureWithWallet(options); 48 | 49 | expect(result).toBeDefined(); 50 | expect(Wallet.create).toHaveBeenCalledWith({ 51 | networkId: Coinbase.networks.BaseSepolia, 52 | }); 53 | }); 54 | 55 | it("should successfully init with wallet data", async () => { 56 | const options = { 57 | cdpWalletData: MOCK_WALLET_JSON, 58 | }; 59 | 60 | jest.spyOn(Wallet, "import").mockResolvedValue(mockWallet); 61 | 62 | const result = await CdpAgentkit.configureWithWallet(options); 63 | 64 | expect(result).toBeDefined(); 65 | expect(Wallet.import).toHaveBeenCalledWith(JSON.parse(MOCK_WALLET_JSON) as WalletData); 66 | }); 67 | 68 | it("should successfully init with mnemonic Phrase ", async () => { 69 | const options = { 70 | mnemonicPhrase: MOCK_MNEMONIC_PHRASE, 71 | }; 72 | 73 | jest.spyOn(Wallet, "import").mockResolvedValue(mockWallet); 74 | 75 | const result = await CdpAgentkit.configureWithWallet(options); 76 | 77 | expect(result).toBeDefined(); 78 | expect(Wallet.import).toHaveBeenCalledWith(options); 79 | }); 80 | 81 | it("should fail init without env", async () => { 82 | const options = {}; 83 | 84 | process.env.CDP_API_KEY_NAME = ""; 85 | process.env.CDP_API_KEY_PRIVATE_KEY = ""; 86 | 87 | await expect(CdpAgentkit.configureWithWallet(options)).rejects.toThrow( 88 | "CDP_API_KEY_NAME is required but not provided", 89 | ); 90 | expect(Wallet.create).not.toHaveBeenCalled(); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/deploy_nft_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, SmartContract, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { deployNft, DeployNftInput } from "../actions/cdp/deploy_nft"; 4 | 5 | const MOCK_NFT_BASE_URI = "https://www.test.xyz/metadata/"; 6 | const MOCK_NFT_NAME = "Test Token"; 7 | const MOCK_NFT_SYMBOL = "TEST"; 8 | 9 | describe("Deploy NFT Input", () => { 10 | it("should successfully parse valid input", () => { 11 | const validInput = { 12 | baseURI: MOCK_NFT_BASE_URI, 13 | name: MOCK_NFT_NAME, 14 | symbol: MOCK_NFT_SYMBOL, 15 | }; 16 | 17 | const result = DeployNftInput.safeParse(validInput); 18 | 19 | expect(result.success).toBe(true); 20 | expect(result.data).toEqual(validInput); 21 | }); 22 | 23 | it("sould fail parsing empty input", () => { 24 | const emptyInput = {}; 25 | const result = DeployNftInput.safeParse(emptyInput); 26 | 27 | expect(result.success).toBe(false); 28 | }); 29 | }); 30 | 31 | describe("Deploy NFT Action", () => { 32 | const CONTRACT_ADDRESS = "0x123456789abcdef"; 33 | const NETWORK_ID = Coinbase.networks.BaseSepolia; 34 | const TRANSACTION_HASH = "0xghijkl987654321"; 35 | const TRANSACTION_LINK = `https://etherscan.io/tx/${TRANSACTION_HASH}`; 36 | 37 | let mockSmartContract: jest.Mocked; 38 | let mockWallet: jest.Mocked; 39 | 40 | beforeEach(() => { 41 | mockSmartContract = { 42 | wait: jest.fn().mockResolvedValue({ 43 | getContractAddress: jest.fn().mockReturnValue(CONTRACT_ADDRESS), 44 | getTransaction: jest.fn().mockReturnValue({ 45 | getTransactionHash: jest.fn().mockReturnValue(TRANSACTION_HASH), 46 | getTransactionLink: jest.fn().mockReturnValue(TRANSACTION_LINK), 47 | }), 48 | }), 49 | } as unknown as jest.Mocked; 50 | 51 | mockWallet = { 52 | deployNFT: jest.fn(), 53 | getNetworkId: jest.fn().mockReturnValue(NETWORK_ID), 54 | } as unknown as jest.Mocked; 55 | 56 | mockWallet.deployNFT.mockResolvedValue(mockSmartContract); 57 | }); 58 | 59 | it("should successfully respond", async () => { 60 | const args = { 61 | name: MOCK_NFT_NAME, 62 | symbol: MOCK_NFT_SYMBOL, 63 | baseURI: MOCK_NFT_BASE_URI, 64 | }; 65 | 66 | const response = await deployNft(mockWallet, args); 67 | 68 | expect(mockWallet.deployNFT).toHaveBeenCalledWith(args); 69 | expect(mockSmartContract.wait).toHaveBeenCalled(); 70 | expect(response).toContain(`Deployed NFT Collection ${MOCK_NFT_NAME}`); 71 | expect(response).toContain(`to address ${CONTRACT_ADDRESS}`); 72 | expect(response).toContain(`on network ${NETWORK_ID}`); 73 | expect(response).toContain(`Transaction hash for the deployment: ${TRANSACTION_HASH}`); 74 | expect(response).toContain(`Transaction link for the deployment: ${TRANSACTION_LINK}`); 75 | }); 76 | 77 | it("should fail with an error", async () => { 78 | const args = { 79 | baseURI: MOCK_NFT_BASE_URI, 80 | name: MOCK_NFT_NAME, 81 | symbol: MOCK_NFT_SYMBOL, 82 | }; 83 | 84 | const error = new Error("An error has occured"); 85 | mockWallet.deployNFT.mockRejectedValue(error); 86 | 87 | const response = await deployNft(mockWallet, args); 88 | 89 | expect(mockWallet.deployNFT).toHaveBeenCalledWith(args); 90 | expect(response).toContain(`Error deploying NFT: ${error}`); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/mint_nft_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, ContractInvocation, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { mintNft, MintNftInput } from "../actions/cdp/mint_nft"; 4 | 5 | const MOCK_CONTRACT_ADDRESS = "0x123456789abcdef"; 6 | const MOCK_CONTRACT_DESTINATION = "0xabcdef123456789"; 7 | 8 | describe("Mint NFT Input", () => { 9 | it("should successfully parse valid input", () => { 10 | const validInput = { 11 | contractAddress: MOCK_CONTRACT_ADDRESS, 12 | destination: MOCK_CONTRACT_DESTINATION, 13 | }; 14 | 15 | const result = MintNftInput.safeParse(validInput); 16 | 17 | expect(result.success).toBe(true); 18 | expect(result.data).toEqual(validInput); 19 | }); 20 | 21 | it("should fail parsing empty input", () => { 22 | const emptyInput = {}; 23 | const result = MintNftInput.safeParse(emptyInput); 24 | 25 | expect(result.success).toBe(false); 26 | }); 27 | }); 28 | 29 | describe("Mint NFT Action", () => { 30 | const NETWORK_ID = Coinbase.networks.BaseSepolia; 31 | const TRANSACTION_HASH = "0xghijkl987654321"; 32 | const TRANSACTION_LINK = `https://etherscan.io/tx/${TRANSACTION_HASH}`; 33 | 34 | let mockContractInvocation: jest.Mocked; 35 | let mockWallet: jest.Mocked; 36 | 37 | beforeEach(() => { 38 | mockContractInvocation = { 39 | wait: jest.fn().mockResolvedValue({ 40 | getTransaction: jest.fn().mockReturnValue({ 41 | getTransactionHash: jest.fn().mockReturnValue(TRANSACTION_HASH), 42 | getTransactionLink: jest.fn().mockReturnValue(TRANSACTION_LINK), 43 | }), 44 | }), 45 | } as unknown as jest.Mocked; 46 | 47 | mockWallet = { 48 | invokeContract: jest.fn(), 49 | getNetworkId: jest.fn().mockReturnValue(NETWORK_ID), 50 | } as unknown as jest.Mocked; 51 | 52 | mockWallet.invokeContract.mockResolvedValue(mockContractInvocation); 53 | }); 54 | 55 | it("should successfully respond", async () => { 56 | const args = { 57 | contractAddress: MOCK_CONTRACT_ADDRESS, 58 | destination: MOCK_CONTRACT_DESTINATION, 59 | }; 60 | 61 | const response = await mintNft(mockWallet, args); 62 | 63 | expect(mockWallet.invokeContract).toHaveBeenCalledWith({ 64 | contractAddress: MOCK_CONTRACT_ADDRESS, 65 | method: "mint", 66 | args: { 67 | to: MOCK_CONTRACT_DESTINATION, 68 | quantity: "1", 69 | }, 70 | }); 71 | expect(mockContractInvocation.wait).toHaveBeenCalled(); 72 | expect(response).toContain(`Minted NFT from contract ${MOCK_CONTRACT_ADDRESS}`); 73 | expect(response).toContain(`to address ${MOCK_CONTRACT_DESTINATION}`); 74 | expect(response).toContain(`on network ${NETWORK_ID}`); 75 | expect(response).toContain(`Transaction hash for the mint: ${TRANSACTION_HASH}`); 76 | expect(response).toContain(`Transaction link for the mint: ${TRANSACTION_LINK}`); 77 | }); 78 | 79 | it("should fail with an error", async () => { 80 | const args = { 81 | contractAddress: MOCK_CONTRACT_ADDRESS, 82 | destination: MOCK_CONTRACT_DESTINATION, 83 | }; 84 | 85 | const error = new Error("An error has occured"); 86 | mockWallet.invokeContract.mockRejectedValue(error); 87 | 88 | const response = await mintNft(mockWallet, args); 89 | 90 | expect(mockWallet.invokeContract).toHaveBeenCalled(); 91 | expect(response).toContain(`Error minting NFT: ${error}`); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/defi/wow/actions/buy_token.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "../../../cdp_action"; 2 | import { Wallet } from "@coinbase/coinbase-sdk"; 3 | import { WOW_ABI } from "../constants"; 4 | import { getHasGraduated } from "../uniswap/utils"; 5 | import { getBuyQuote } from "../utils"; 6 | import { z } from "zod"; 7 | 8 | const WOW_BUY_TOKEN_PROMPT = ` 9 | This tool can only be used to buy a Zora Wow ERC20 memecoin (also can be referred to as a bonding curve token) with ETH. 10 | Do not use this tool for any other purpose, or trading other assets. 11 | 12 | Inputs: 13 | - WOW token contract address 14 | - Address to receive the tokens 15 | - Amount of ETH to spend (in wei) 16 | 17 | Important notes: 18 | - The amount is a string and cannot have any decimal points, since the unit of measurement is wei. 19 | - Make sure to use the exact amount provided, and if there's any doubt, check by getting more information before continuing with the action. 20 | - 1 wei = 0.000000000000000001 ETH 21 | - Minimum purchase amount is 100000000000000 wei (0.0000001 ETH) 22 | - Only supported on the following networks: 23 | - Base Sepolia (ie, 'base-sepolia') 24 | - Base Mainnet (ie, 'base', 'base-mainnnet') 25 | `; 26 | 27 | /** 28 | * Input schema for buy token action. 29 | */ 30 | export const WowBuyTokenInput = z 31 | .object({ 32 | contractAddress: z.string().describe("The WOW token contract address"), 33 | amountEthInWei: z.string().describe("Amount of ETH to spend (in wei)"), 34 | }) 35 | .strip() 36 | .describe("Instructions for buying WOW tokens"); 37 | 38 | /** 39 | * Buys a Zora Wow ERC20 memecoin with ETH. 40 | * 41 | * @param wallet - The wallet to create the token from. 42 | * @param args - The input arguments for the action. 43 | * @returns A message containing the token purchase details. 44 | */ 45 | export async function wowBuyToken( 46 | wallet: Wallet, 47 | args: z.infer, 48 | ): Promise { 49 | try { 50 | const tokenQuote = await getBuyQuote( 51 | wallet.getNetworkId(), 52 | args.contractAddress, 53 | args.amountEthInWei, 54 | ); 55 | 56 | // Multiply by 99/100 and floor to get 99% of quote as minimum 57 | const minTokens = (BigInt(Math.floor(Number(tokenQuote) * 99)) / BigInt(100)).toString(); 58 | 59 | const hasGraduated = await getHasGraduated(wallet.getNetworkId(), args.contractAddress); 60 | 61 | const invocation = await wallet.invokeContract({ 62 | contractAddress: args.contractAddress, 63 | method: "buy", 64 | abi: WOW_ABI, 65 | args: { 66 | recipient: (await wallet.getDefaultAddress()).getId(), 67 | refundRecipient: (await wallet.getDefaultAddress()).getId(), 68 | orderReferrer: "0x0000000000000000000000000000000000000000", 69 | expectedMarketType: hasGraduated ? "1" : "0", 70 | minOrderSize: minTokens, 71 | sqrtPriceLimitX96: "0", 72 | comment: "", 73 | }, 74 | amount: BigInt(args.amountEthInWei), 75 | assetId: "wei", 76 | }); 77 | 78 | const result = await invocation.wait(); 79 | return `Purchased WoW ERC20 memecoin with transaction hash: ${result.getTransaction().getTransactionHash()}`; 80 | } catch (error) { 81 | return `Error buying Zora Wow ERC20 memecoin: ${error}`; 82 | } 83 | } 84 | 85 | /** 86 | * Zora Wow buy token action. 87 | */ 88 | export class WowBuyTokenAction implements CdpAction { 89 | public name = "wow_buy_token"; 90 | public description = WOW_BUY_TOKEN_PROMPT; 91 | public argsSchema = WowBuyTokenInput; 92 | public func = wowBuyToken; 93 | } 94 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/defi/wow/actions/sell_token.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "../../../cdp_action"; 2 | import { Wallet } from "@coinbase/coinbase-sdk"; 3 | import { WOW_ABI } from "../constants"; 4 | import { getHasGraduated } from "../uniswap/utils"; 5 | import { getSellQuote } from "../utils"; 6 | import { z } from "zod"; 7 | 8 | const WOW_SELL_TOKEN_PROMPT = ` 9 | This tool can only be used to sell a Zora Wow ERC20 memecoin (also can be referred to as a bonding curve token) for ETH. 10 | Do not use this tool for any other purpose, or trading other assets. 11 | 12 | Inputs: 13 | - WOW token contract address 14 | - Amount of tokens to sell (in wei) 15 | 16 | Important notes: 17 | - The amount is a string and cannot have any decimal points, since the unit of measurement is wei. 18 | - Make sure to use the exact amount provided, and if there's any doubt, check by getting more information before continuing with the action. 19 | - 1 wei = 0.000000000000000001 ETH 20 | - Minimum purchase amount is 100000000000000 wei (0.0000001 ETH) 21 | - Only supported on the following networks: 22 | - Base Sepolia (ie, 'base-sepolia') 23 | - Base Mainnet (ie, 'base', 'base-mainnnet') 24 | `; 25 | 26 | /** 27 | * Input schema for sell token action. 28 | */ 29 | export const WowSellTokenInput = z 30 | .object({ 31 | contractAddress: z 32 | .string() 33 | .describe( 34 | "The WOW token contract address, such as `0x036CbD53842c5426634e7929541eC2318f3dCF7e`", 35 | ), 36 | amountTokensInWei: z 37 | .string() 38 | .describe( 39 | "Amount of tokens to sell (in wei), meaning 1 is 1 wei or 0.000000000000000001 of the token", 40 | ), 41 | }) 42 | .strip() 43 | .describe("Instructions for selling WOW tokens"); 44 | 45 | /** 46 | * Sells WOW tokens for ETH. 47 | * 48 | * @param wallet - The wallet to sell the tokens from. 49 | * @param args - The input arguments for the action. 50 | * @returns A message confirming the sale with the transaction hash. 51 | */ 52 | export async function wowSellToken( 53 | wallet: Wallet, 54 | args: z.infer, 55 | ): Promise { 56 | try { 57 | const ethQuote = await getSellQuote( 58 | wallet.getNetworkId(), 59 | args.contractAddress, 60 | args.amountTokensInWei, 61 | ); 62 | const hasGraduated = await getHasGraduated(wallet.getNetworkId(), args.contractAddress); 63 | 64 | // Multiply by 98/100 and floor to get 98% of quote as minimum 65 | const minEth = (BigInt(Math.floor(Number(ethQuote) * 98)) / BigInt(100)).toString(); 66 | 67 | const invocation = await wallet.invokeContract({ 68 | contractAddress: args.contractAddress, 69 | method: "sell", 70 | abi: WOW_ABI, 71 | args: { 72 | tokensToSell: args.amountTokensInWei, 73 | recipient: (await wallet.getDefaultAddress()).getId(), 74 | orderReferrer: "0x0000000000000000000000000000000000000000", 75 | comment: "", 76 | expectedMarketType: hasGraduated ? "1" : "0", 77 | minPayoutSize: minEth, 78 | sqrtPriceLimitX96: "0", 79 | }, 80 | }); 81 | 82 | const result = await invocation.wait(); 83 | return `Sold WoW ERC20 memecoin with transaction hash: ${result.getTransaction().getTransactionHash()}`; 84 | } catch (error) { 85 | return `Error selling Zora Wow ERC20 memecoin: ${error}`; 86 | } 87 | } 88 | 89 | /** 90 | * Zora Wow sell token action. 91 | */ 92 | export class WowSellTokenAction implements CdpAction { 93 | public name = "wow_sell_token"; 94 | public description = WOW_SELL_TOKEN_PROMPT; 95 | public argsSchema = WowSellTokenInput; 96 | public func = wowSellToken; 97 | } 98 | -------------------------------------------------------------------------------- /twitter-langchain/README.md: -------------------------------------------------------------------------------- 1 | # CDP Agentkit Extension - Twitter langchain Toolkit 2 | 3 | [![npm version](https://img.shields.io/npm/v/@coinbase/twitter-langchain.svg?style=flat-square)](https://www.npmjs.com/package/@coinbase/twitter-langchain) [![GitHub star chart](https://img.shields.io/github/stars/coinbase/cdp-agentkit-nodejs?style=flat-square)](https://star-history.com/#coinbase/cdp-agentkit-nodejs) [![Open Issues](https://img.shields.io/github/issues-raw/coinbase/cdp-agentkit-nodejs?style=flat-square)](https://github.com/coinbase/cdp-agentkit-nodejs/issues) 4 | 5 | This toolkit contains tools that enable an LLM agent to interact with [Twitter (X)](https://developer.x.com/en/docs/x-api). The toolkit provides a wrapper around the Twitter (X) API, allowing agents to perform social operations like posting text. 6 | 7 | ## Prerequisites 8 | 9 | - Node.js 18 or higher 10 | - [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key) 11 | - [Twitter (X) App Developer Keys](https://developer.x.com/en/portal/dashboard) 12 | 13 | ## Installation 14 | 15 | ```bash 16 | npm install @coinbase/twitter-langchain 17 | ``` 18 | 19 | ## Environment Setup 20 | 21 | Set the following environment variables: 22 | 23 | ```bash 24 | export OPENAI_API_KEY= 25 | export TWITTER_API_KEY= 26 | export TWITTER_API_SECRET= 27 | export TWITTER_ACCESS_TOKEN= 28 | export TWITTER_ACCESS_TOKEN_SECRET= 29 | ``` 30 | 31 | ## Usage 32 | 33 | ### Basic Setup 34 | 35 | ```typescript 36 | import { TwitterAgentkit } from "@coinbase/cdp-agentkit-core"; 37 | import { TwitterToolkit } from "@coinbase/twitter-langchain"; 38 | 39 | // Initialize Twitter AgentKit 40 | const agentkit = new TwitterAgentkit(); 41 | 42 | // Create toolkit 43 | const toolkit = new TwitterToolkit(agentkit); 44 | 45 | // Get available tools 46 | const tools = toolkit.getTools(); 47 | ``` 48 | 49 | ### Available Tools 50 | 51 | The toolkit provides the following tools: 52 | 53 | 1. **account_details** - Get the authenticated account details 54 | 4. **account_mentions** - Get mentions for a specified account 55 | 2. **post_tweet** - Post a tweet to the account 56 | 3. **post_tweet_reply** - Post a reply to a tweet on Twitter 57 | 58 | ### Using with an Agent 59 | 60 | #### Additional Installations 61 | ```bash 62 | npm install @langchain/langgraph @langchain/openai 63 | ``` 64 | 65 | ```typescript 66 | import { ChatOpenAI } from "@langchain/openai"; 67 | import { createReactAgent } from "@langchain/langgraph/prebuilt"; 68 | 69 | // Initialize LLM 70 | const model = new ChatOpenAI({ 71 | model: "gpt-4o-mini", 72 | }); 73 | 74 | // Create agent executor 75 | const agent = createReactAgent({ 76 | llm: model, 77 | tools, 78 | }); 79 | 80 | // Example usage 81 | const result = await agent.invoke({ 82 | messages: [new HumanMessage("please post 'hello, world!' to twitter")], 83 | }); 84 | 85 | console.log(result.messages[result.messages.length - 1].content); 86 | ``` 87 | 88 | ## Examples 89 | 90 | Check out `examples/` for inspiration and help getting started: 91 | 92 | - [Chatbot](./examples/chatbot/README.md): Interactive chatbot with Twitter (X) capabilities 93 | 94 | ## Contributing 95 | 96 | See [CONTRIBUTING.md](../CONTRIBUTING.md) for detailed setup instructions and contribution guidelines. 97 | 98 | ## Documentation 99 | 100 | - [CDP AgentKit Documentation](https://docs.cdp.coinbase.com/agentkit/docs/welcome) 101 | - [API Reference: CDP AgentKit Twitter Langchain Extension](https://coinbase.github.io/cdp-agentkit-nodejs/twitter-langchain/index.html) 102 | 103 | ## License 104 | 105 | Apache-2.0 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### CDP AgentKit.js 2 | 3 | ## This repository is deprecated and Agentkit.js has been migrated to the monorepo [coinbase/agentkit](https://github.com/coinbase/agentkit). 4 | 5 | [![npm downloads](https://img.shields.io/npm/dm/@coinbase/cdp-agentkit-core?style=flat-square)](https://www.npmjs.com/package/@coinbase/cdp-agentkit-core) 6 | [![GitHub star chart](https://img.shields.io/github/stars/coinbase/cdp-agentkit-nodejs?style=flat-square)](https://star-history.com/#coinbase/cdp-agentkit-nodejs) 7 | [![Open Issues](https://img.shields.io/github/issues-raw/coinbase/cdp-agentkit-nodejs?style=flat-square)](https://github.com/coinbase/cdp-agentkit-nodejs/issues) 8 | 9 | The **Coinbase Developer Platform (CDP) AgentKit for Node.js** simplifies bringing your AI Agents onchain. Every AI Agent deserves a crypto wallet! 10 | 11 | ## Key Features 12 | - **Framework-agnostic**: Common AI Agent primitives that can be used with any AI framework. 13 | - **LangChain.js integration**: Seamless integration with [LangChain.js](https://js.langchain.com/docs/introduction/) for easy agentic workflows. More frameworks coming soon! 14 | - **Twitter (X) integration**: Seamless integration of Langchain with [Twitter (X)](https://developer.twitter.com/en/docs/twitter-api) for easy agentic workflows. 15 | - **Support for various on-chain actions**: 16 | 17 | - Faucet for testnet funds 18 | - Getting wallet details and balances 19 | - Transferring and trading tokens 20 | - Registering [Basenames](https://www.base.org/names) 21 | - Deploying [ERC-20](https://www.coinbase.com/learn/crypto-glossary/what-is-erc-20) tokens 22 | - Deploying [ERC-721](https://www.coinbase.com/learn/crypto-glossary/what-is-erc-721) tokens and minting NFTs 23 | - Buying and selling [Zora Wow](https://wow.xyz/) ERC-20 coins 24 | - Deploying tokens on [Zora's Wow Launcher](https://wow.xyz/mechanics) (Bonding Curve) 25 | 26 | Or [add your own](./CONTRIBUTING.md#adding-an-action-to-agentkit-core)! 27 | 28 | ## Examples 29 | Check out [cdp-langchain/examples](./cdp-langchain/examples) for inspiration and help getting started! 30 | - [Chatbot](./cdp-langchain/examples/chatbot/README.md): Simple example of a Chatbot that can perform complex onchain interactions, using OpenAI. 31 | 32 | ## Repository Structure 33 | CDP AgentKit Node.js is organized as a [monorepo](https://en.wikipedia.org/wiki/Monorepo) that contains multiple packages. 34 | 35 | ### @coinbase/cdp-agentkit-core 36 | Core primitives and framework-agnostic tools that are meant to be composable and used via CDP AgentKit framework extensions (ie, `cdp-langchain`). 37 | See [CDP AgentKit Core](./cdp-agentkit-core/README.md) to get started! 38 | 39 | ### @coinbase/cdp-langchain 40 | LangChain.js Toolkit extension of CDP AgentKit. Enables agentic workflows to interact with onchain actions. 41 | See [CDP LangChain](./cdp-langchain/README.md) to get started! 42 | 43 | ### @coinbasetwitter-langchain 44 | Langchain Toolkit extension for Twitter (X). Enables agentic workflows to interact with Twitter, such as to post a tweet. 45 | See [Twitter Langchain](./twitter-langchain/README.md) to get started! 46 | 47 | ## Contributing 48 | CDP AgentKit welcomes community contributions. 49 | See [CONTRIBUTING.md](CONTRIBUTING.md) for more information. 50 | 51 | ## Security and bug reports 52 | The CDP AgentKit team takes security seriously. 53 | See [SECURITY.md](SECURITY.md) for more information. 54 | 55 | ## Documentation 56 | - [CDP AgentKit Documentation](https://docs.cdp.coinbase.com/agentkit/docs/welcome) 57 | - [API Reference: CDP AgentKit Core](https://coinbase.github.io/cdp-agentkit-nodejs/cdp-agentkit-core/index.html) 58 | - [API Reference: CDP AgentKit LangChain Extension](https://coinbase.github.io/cdp-agentkit-nodejs/cdp-langchain/index.html) 59 | - [API Reference: CDP Agentkit Twitter Langchain Extension](https://coinbase.github.io/cdp-agentkit-nodejs/twitter-langchain/index.html) 60 | 61 | ## License 62 | 63 | Apache-2.0 64 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/defi/wow/uniswap/constants.ts: -------------------------------------------------------------------------------- 1 | import type { Abi } from "abitype"; 2 | 3 | export const UNISWAP_QUOTER_ABI: Abi = [ 4 | { 5 | inputs: [ 6 | { 7 | components: [ 8 | { internalType: "address", name: "tokenIn", type: "address" }, 9 | { internalType: "address", name: "tokenOut", type: "address" }, 10 | { internalType: "uint256", name: "amountIn", type: "uint256" }, 11 | { internalType: "uint24", name: "fee", type: "uint24" }, 12 | { internalType: "uint160", name: "sqrtPriceLimitX96", type: "uint160" }, 13 | ], 14 | internalType: "struct IQuoterV2.QuoteExactInputSingleParams", 15 | name: "params", 16 | type: "tuple", 17 | }, 18 | ], 19 | name: "quoteExactInputSingle", 20 | outputs: [ 21 | { internalType: "uint256", name: "amountOut", type: "uint256" }, 22 | { internalType: "uint160", name: "sqrtPriceX96After", type: "uint160" }, 23 | { internalType: "uint32", name: "initializedTicksCrossed", type: "uint32" }, 24 | { internalType: "uint256", name: "gasEstimate", type: "uint256" }, 25 | ], 26 | stateMutability: "nonpayable", 27 | type: "function", 28 | }, 29 | { 30 | inputs: [ 31 | { 32 | components: [ 33 | { internalType: "address", name: "tokenIn", type: "address" }, 34 | { internalType: "address", name: "tokenOut", type: "address" }, 35 | { internalType: "uint256", name: "amount", type: "uint256" }, 36 | { internalType: "uint24", name: "fee", type: "uint24" }, 37 | { internalType: "uint160", name: "sqrtPriceLimitX96", type: "uint160" }, 38 | ], 39 | internalType: "struct IQuoterV2.QuoteExactOutputSingleParams", 40 | name: "params", 41 | type: "tuple", 42 | }, 43 | ], 44 | name: "quoteExactOutputSingle", 45 | outputs: [ 46 | { internalType: "uint256", name: "amountIn", type: "uint256" }, 47 | { internalType: "uint160", name: "sqrtPriceX96After", type: "uint160" }, 48 | { internalType: "uint32", name: "initializedTicksCrossed", type: "uint32" }, 49 | { internalType: "uint256", name: "gasEstimate", type: "uint256" }, 50 | ], 51 | stateMutability: "nonpayable", 52 | type: "function", 53 | }, 54 | ]; 55 | 56 | export const UNISWAP_V3_ABI: Abi = [ 57 | { 58 | inputs: [], 59 | name: "fee", 60 | outputs: [{ internalType: "uint24", name: "", type: "uint24" }], 61 | stateMutability: "view", 62 | type: "function", 63 | }, 64 | { 65 | inputs: [], 66 | name: "liquidity", 67 | outputs: [{ internalType: "uint128", name: "", type: "uint128" }], 68 | stateMutability: "view", 69 | type: "function", 70 | }, 71 | { 72 | inputs: [], 73 | name: "slot0", 74 | outputs: [ 75 | { internalType: "uint160", name: "sqrtPriceX96", type: "uint160" }, 76 | { internalType: "int24", name: "tick", type: "int24" }, 77 | { internalType: "uint16", name: "observationIndex", type: "uint16" }, 78 | { internalType: "uint16", name: "observationCardinality", type: "uint16" }, 79 | { internalType: "uint16", name: "observationCardinalityNext", type: "uint16" }, 80 | { internalType: "uint8", name: "feeProtocol", type: "uint8" }, 81 | { internalType: "bool", name: "unlocked", type: "bool" }, 82 | ], 83 | stateMutability: "view", 84 | type: "function", 85 | }, 86 | { 87 | inputs: [], 88 | name: "token0", 89 | outputs: [{ internalType: "address", name: "", type: "address" }], 90 | stateMutability: "view", 91 | type: "function", 92 | }, 93 | { 94 | inputs: [], 95 | name: "token1", 96 | outputs: [{ internalType: "address", name: "", type: "address" }], 97 | stateMutability: "view", 98 | type: "function", 99 | }, 100 | ]; 101 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/defi_wow_buy_token_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, ContractInvocation, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { WOW_ABI } from "../actions/cdp/defi/wow/constants"; 4 | import { wowBuyToken, WowBuyTokenInput } from "../actions/cdp/defi/wow/actions/buy_token"; 5 | import { getBuyQuote } from "../actions/cdp/defi/wow/utils"; 6 | import { getHasGraduated } from "../actions/cdp/defi/wow/uniswap/utils"; 7 | 8 | jest.mock("../actions/cdp/defi/wow/utils", () => ({ 9 | getBuyQuote: jest.fn(), 10 | })); 11 | 12 | jest.mock("../actions/cdp/defi/wow/uniswap/utils", () => ({ 13 | getHasGraduated: jest.fn(), 14 | })); 15 | 16 | const MOCK_CONTRACT_ADDRESS = "0xabcdef123456789"; 17 | const MOCK_AMOUNT_ETH_IN_WEI = "100000000000000000"; 18 | 19 | describe("Wow Buy Token Input", () => { 20 | it("should successfully parse valid input", () => { 21 | const validInput = { 22 | contractAddress: MOCK_CONTRACT_ADDRESS, 23 | amountEthInWei: MOCK_AMOUNT_ETH_IN_WEI, 24 | }; 25 | 26 | const result = WowBuyTokenInput.safeParse(validInput); 27 | 28 | expect(result.success).toBe(true); 29 | expect(result.data).toEqual(validInput); 30 | }); 31 | 32 | it("should fail parsing empty input", () => { 33 | const emptyInput = {}; 34 | const result = WowBuyTokenInput.safeParse(emptyInput); 35 | 36 | expect(result.success).toBe(false); 37 | }); 38 | }); 39 | 40 | describe("Wow Buy Token Action", () => { 41 | const NETWORK_ID = Coinbase.networks.BaseSepolia; 42 | const TRANSACTION_HASH = "0xghijkl987654321"; 43 | 44 | let mockContractInvocation: jest.Mocked; 45 | let mockWallet: jest.Mocked; 46 | 47 | beforeEach(() => { 48 | mockWallet = { 49 | invokeContract: jest.fn(), 50 | getDefaultAddress: jest.fn().mockResolvedValue({ 51 | getId: jest.fn().mockReturnValue(TRANSACTION_HASH), 52 | }), 53 | getNetworkId: jest.fn().mockReturnValue(NETWORK_ID), 54 | } as unknown as jest.Mocked; 55 | 56 | mockContractInvocation = { 57 | wait: jest.fn().mockResolvedValue({ 58 | getTransaction: jest.fn().mockReturnValue({ 59 | getTransactionHash: jest.fn().mockReturnValue(TRANSACTION_HASH), 60 | }), 61 | }), 62 | } as unknown as jest.Mocked; 63 | 64 | mockWallet.invokeContract.mockResolvedValue(mockContractInvocation); 65 | }); 66 | 67 | it("should successfully buy a token", async () => { 68 | const args = { 69 | contractAddress: MOCK_CONTRACT_ADDRESS, 70 | amountEthInWei: MOCK_AMOUNT_ETH_IN_WEI, 71 | }; 72 | 73 | (getHasGraduated as jest.Mock).mockResolvedValue(true); 74 | (getBuyQuote as jest.Mock).mockResolvedValue(1.0); 75 | 76 | const response = await wowBuyToken(mockWallet, args); 77 | 78 | expect(mockWallet.invokeContract).toHaveBeenCalledWith({ 79 | contractAddress: MOCK_CONTRACT_ADDRESS, 80 | method: "buy", 81 | abi: WOW_ABI, 82 | args: { 83 | recipient: expect.any(String), 84 | refundRecipient: expect.any(String), 85 | orderReferrer: "0x0000000000000000000000000000000000000000", 86 | expectedMarketType: "1", 87 | minOrderSize: expect.any(String), 88 | sqrtPriceLimitX96: "0", 89 | comment: "", 90 | }, 91 | amount: BigInt(args.amountEthInWei), 92 | assetId: "wei", 93 | }); 94 | expect(getBuyQuote).toHaveBeenCalled(); 95 | expect(getHasGraduated).toHaveBeenCalled(); 96 | expect(response).toContain( 97 | `Purchased WoW ERC20 memecoin with transaction hash: ${TRANSACTION_HASH}`, 98 | ); 99 | }); 100 | 101 | it("should handle errors when buying a token", async () => { 102 | const args = { 103 | contractAddress: MOCK_CONTRACT_ADDRESS, 104 | amountEthInWei: MOCK_AMOUNT_ETH_IN_WEI, 105 | }; 106 | 107 | const error = new Error("An error has occurred"); 108 | mockWallet.invokeContract.mockRejectedValue(error); 109 | (getHasGraduated as jest.Mock).mockResolvedValue(true); 110 | 111 | const response = await wowBuyToken(mockWallet, args); 112 | 113 | expect(mockWallet.invokeContract).toHaveBeenCalled(); 114 | expect(response).toContain(`Error buying Zora Wow ERC20 memecoin: ${error}`); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/twitter_agentkit.ts: -------------------------------------------------------------------------------- 1 | import { TwitterApi, TwitterApiTokens } from "twitter-api-v2"; 2 | import { z } from "zod"; 3 | import { TwitterAction, TwitterActionSchemaAny } from "./actions/cdp/social/twitter"; 4 | 5 | /** 6 | * Schema for the options required to initialize the TwitterAgentkit. 7 | */ 8 | export const TwitterAgentkitOptions = z 9 | .object({ 10 | apiKey: z 11 | .string() 12 | .min(1, "The Twitter (X) API key is required") 13 | .describe("The Twitter (X) API key"), 14 | apiSecret: z 15 | .string() 16 | .min(1, "The Twitter (X) API secret is required") 17 | .describe("The Twitter (X) API secret"), 18 | accessToken: z 19 | .string() 20 | .min(1, "The Twitter (X) access token is required") 21 | .describe("The Twitter (X) access token"), 22 | accessTokenSecret: z 23 | .string() 24 | .min(1, "The Twitter (X) access token secret is required") 25 | .describe("The Twitter (X) access token secret"), 26 | }) 27 | .strip() 28 | .describe("Options for initializing TwitterAgentkit"); 29 | 30 | /** 31 | * Schema for the environment variables required for TwitterAgentkit. 32 | */ 33 | const EnvSchema = z.object({ 34 | TWITTER_API_KEY: z 35 | .string() 36 | .min(1, "TWITTER_API_KEY is required") 37 | .describe("The Twitter (X) API key"), 38 | TWITTER_API_SECRET: z 39 | .string() 40 | .min(1, "TWITTER_API_SECRET is required") 41 | .describe("The Twitter (X) API secret"), 42 | TWITTER_ACCESS_TOKEN: z 43 | .string() 44 | .min(1, "TWITTER_ACCESS_TOKEN is required") 45 | .describe("The Twitter (X) access token"), 46 | TWITTER_ACCESS_TOKEN_SECRET: z 47 | .string() 48 | .min(1, "TWITTER_ACCESS_TOKEN_SECRET is required") 49 | .describe("The Twitter (X) access token secret"), 50 | }); 51 | 52 | /** 53 | * Twitter Agentkit 54 | */ 55 | export class TwitterAgentkit { 56 | private client: TwitterApi; 57 | 58 | /** 59 | * Initializes a new instance of TwitterAgentkit with the provided options. 60 | * If no options are provided, it attempts to load the required environment variables. 61 | * 62 | * @param options - Optional. The configuration options for the TwitterAgentkit. 63 | * @throws An error if the provided options are invalid or if the environment variables cannot be loaded. 64 | */ 65 | public constructor(options?: z.infer) { 66 | if (!options) { 67 | try { 68 | const env = EnvSchema.parse(process.env); 69 | 70 | options = { 71 | apiKey: env.TWITTER_API_KEY!, 72 | apiSecret: env.TWITTER_API_SECRET!, 73 | accessToken: env.TWITTER_ACCESS_TOKEN!, 74 | accessTokenSecret: env.TWITTER_ACCESS_TOKEN_SECRET!, 75 | }; 76 | } catch (error) { 77 | if (error instanceof z.ZodError) { 78 | error.errors.forEach(err => console.log(`Error: ${err.path[0]} is required`)); 79 | } 80 | throw new Error("Twitter (X) ENV could not be loaded."); 81 | } 82 | } 83 | 84 | if (!this.validateOptions(options)) { 85 | throw new Error("Twitter (X) Agentkit options could not be validated."); 86 | } 87 | 88 | this.client = new TwitterApi({ 89 | appKey: options.apiKey, 90 | appSecret: options.apiSecret, 91 | accessToken: options.accessToken, 92 | accessSecret: options.accessTokenSecret, 93 | } as TwitterApiTokens); 94 | } 95 | 96 | /** 97 | * Validates the provided options for the TwitterAgentkit. 98 | * 99 | * @param options - The options to validate. 100 | * @returns True if the options are valid, otherwise false. 101 | */ 102 | validateOptions(options: z.infer): boolean { 103 | try { 104 | TwitterAgentkitOptions.parse(options); 105 | } catch (error) { 106 | if (error instanceof z.ZodError) { 107 | error.errors.forEach(err => console.log("Error:", err.message)); 108 | } 109 | 110 | return false; 111 | } 112 | 113 | return true; 114 | } 115 | 116 | /** 117 | * Executes a Twitter (X) action. 118 | * 119 | * @param action - The Twitter (X) action to execute. 120 | * @param args - The arguments for the action. 121 | * @returns The result of the execution. 122 | */ 123 | async run( 124 | action: TwitterAction, 125 | args: TActionSchema, 126 | ): Promise { 127 | return await action.func(this.client, args); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/cdp_agentkit.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, MnemonicSeedPhrase, Wallet, WalletData } from "@coinbase/coinbase-sdk"; 2 | import { version } from "../package.json"; 3 | import { CdpAction, CdpActionSchemaAny } from "./actions/cdp/cdp_action"; 4 | import { z } from "zod"; 5 | 6 | /** 7 | * Configuration options for the CDP Agentkit 8 | */ 9 | interface CdpAgentkitOptions { 10 | cdpApiKeyName?: string; 11 | cdpApiKeyPrivateKey?: string; 12 | source?: string; 13 | sourceVersion?: string; 14 | } 15 | 16 | /** 17 | * Configuration options for the CDP Agentkit with a Wallet. 18 | */ 19 | interface ConfigureCdpAgentkitWithWalletOptions extends CdpAgentkitOptions { 20 | networkId?: string; 21 | cdpWalletData?: string; 22 | mnemonicPhrase?: string; 23 | } 24 | 25 | /** 26 | * CDP Agentkit 27 | */ 28 | export class CdpAgentkit { 29 | private wallet?: Wallet; 30 | 31 | /** 32 | * Initializes a new CDP Agentkit instance 33 | * 34 | * @param config - Configuration options for the CDP Agentkit 35 | */ 36 | public constructor(config: CdpAgentkitOptions = {}) { 37 | const cdpApiKeyName = config.cdpApiKeyName || process.env.CDP_API_KEY_NAME; 38 | const cdpApiKeyPrivateKey = config.cdpApiKeyPrivateKey || process.env.CDP_API_KEY_PRIVATE_KEY; 39 | const source = config.source; 40 | const sourceVersion = config.sourceVersion; 41 | 42 | if (!cdpApiKeyName) { 43 | throw new Error("CDP_API_KEY_NAME is required but not provided"); 44 | } 45 | if (!cdpApiKeyPrivateKey) { 46 | throw new Error("CDP_API_KEY_PRIVATE_KEY is required but not provided"); 47 | } 48 | 49 | // Configure CDP SDK 50 | Coinbase.configure({ 51 | apiKeyName: cdpApiKeyName, 52 | privateKey: cdpApiKeyPrivateKey.replace(/\\n/g, "\n"), 53 | source: source || "agentkit-core", 54 | sourceVersion: sourceVersion || version, 55 | }); 56 | } 57 | 58 | /** 59 | * Configures CDP Agentkit with a Wallet. 60 | * 61 | * @param config - Optional configuration parameters 62 | * @returns A Promise that resolves to a new CdpAgentkit instance 63 | * @throws Error if required environment variables are missing or wallet initialization fails 64 | */ 65 | public static async configureWithWallet( 66 | config: ConfigureCdpAgentkitWithWalletOptions = {}, 67 | ): Promise { 68 | const agentkit = new CdpAgentkit(config); 69 | 70 | const mnemonicPhrase = config.mnemonicPhrase || process.env.MNEMONIC_PHRASE; 71 | const networkId = config.networkId || process.env.NETWORK_ID || Coinbase.networks.BaseSepolia; 72 | 73 | try { 74 | if (config.cdpWalletData) { 75 | const walletData = JSON.parse(config.cdpWalletData) as WalletData; 76 | agentkit.wallet = await Wallet.import(walletData); 77 | } else if (mnemonicPhrase) { 78 | agentkit.wallet = await Wallet.import({ mnemonicPhrase: mnemonicPhrase }); 79 | } else { 80 | agentkit.wallet = await Wallet.create({ networkId: networkId }); 81 | } 82 | } catch (error) { 83 | throw new Error(`Failed to initialize wallet: ${error}`); 84 | } 85 | 86 | return agentkit; 87 | } 88 | 89 | /** 90 | * Executes a CDP action 91 | * 92 | * @param action - The CDP action to execute 93 | * @param args - Arguments for the action 94 | * @returns Result of the action execution 95 | * @throws Error if action execution fails 96 | */ 97 | async run( 98 | action: CdpAction, 99 | args: TActionSchema, 100 | ): Promise { 101 | if (action.func.length > 1) { 102 | if (!this.wallet) { 103 | return `Unable to run CDP Action: ${action.name}. A Wallet is required. Please configure CDP Agentkit with a Wallet to run this action.`; 104 | } 105 | 106 | return await action.func(this.wallet!, args); 107 | } 108 | 109 | return await (action.func as (args: z.infer) => Promise)(args); 110 | } 111 | 112 | /** 113 | * Exports wallet data required to re-instantiate the wallet 114 | * 115 | * @returns JSON string of wallet data including wallet_id and seed 116 | */ 117 | async exportWallet(): Promise { 118 | if (!this.wallet) { 119 | throw Error("Unable to export wallet. Agentkit is not configured with a wallet."); 120 | } 121 | 122 | const walletData = this.wallet.export(); 123 | return JSON.stringify({ 124 | ...walletData, 125 | defaultAddressId: (await this.wallet.getDefaultAddress()).getId(), 126 | }); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/defi_wow_create_token_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, ContractInvocation, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { 4 | getFactoryAddress, 5 | GENERIC_TOKEN_METADATA_URI, 6 | WOW_FACTORY_ABI, 7 | } from "../actions/cdp/defi/wow/constants"; 8 | import { wowCreateToken, WowCreateTokenInput } from "../actions/cdp/defi/wow/actions/create_token"; 9 | 10 | jest.mock("../actions/cdp/defi/wow/constants", () => ({ 11 | getFactoryAddress: jest.fn(), 12 | })); 13 | 14 | const MOCK_NAME = "Test Token"; 15 | const MOCK_SYMBOL = "TEST"; 16 | const MOCK_URI = "ipfs://QmY1GqprFYvojCcUEKgqHeDj9uhZD9jmYGrQTfA9vAE78J"; 17 | 18 | describe("Wow Create Token Input", () => { 19 | it("should successfully parse valid input", () => { 20 | const validInput = { 21 | name: MOCK_NAME, 22 | symbol: MOCK_SYMBOL, 23 | tokenUri: MOCK_URI, 24 | }; 25 | 26 | const result = WowCreateTokenInput.safeParse(validInput); 27 | 28 | expect(result.success).toBe(true); 29 | expect(result.data).toEqual(validInput); 30 | }); 31 | 32 | it("should successfully parse input without tokenUri", () => { 33 | const validInput = { 34 | name: MOCK_NAME, 35 | symbol: MOCK_SYMBOL, 36 | }; 37 | 38 | const result = WowCreateTokenInput.safeParse(validInput); 39 | 40 | expect(result.success).toBe(true); 41 | expect(result.data).toEqual(validInput); 42 | }); 43 | 44 | it("should fail with missing required fields", () => { 45 | const invalidInput = { 46 | symbol: MOCK_SYMBOL, 47 | }; 48 | const result = WowCreateTokenInput.safeParse(invalidInput); 49 | 50 | expect(result.success).toBe(false); 51 | }); 52 | 53 | it("should fail with invalid tokenUri", () => { 54 | const invalidInput = { 55 | name: MOCK_NAME, 56 | symbol: MOCK_SYMBOL, 57 | tokenUri: 12345, 58 | }; 59 | const result = WowCreateTokenInput.safeParse(invalidInput); 60 | 61 | expect(result.success).toBe(false); 62 | }); 63 | }); 64 | 65 | describe("Wow Create Token Action", () => { 66 | const CONTRACT_ADDRESS = "0xabcdef123456789"; 67 | const NETWORK_ID = Coinbase.networks.BaseSepolia; 68 | const TRANSACTION_HASH = "0xghijkl987654321"; 69 | const WALLET_ID = "0x123456789abcdef"; 70 | 71 | let mockContractInvocation: jest.Mocked; 72 | let mockWallet: jest.Mocked; 73 | 74 | beforeEach(() => { 75 | mockWallet = { 76 | invokeContract: jest.fn(), 77 | getDefaultAddress: jest.fn().mockResolvedValue({ 78 | getId: jest.fn().mockReturnValue(WALLET_ID), 79 | }), 80 | getNetworkId: jest.fn().mockReturnValue(NETWORK_ID), 81 | } as unknown as jest.Mocked; 82 | 83 | mockContractInvocation = { 84 | wait: jest.fn().mockResolvedValue({ 85 | getTransaction: jest.fn().mockReturnValue({ 86 | getTransactionHash: jest.fn().mockReturnValue(TRANSACTION_HASH), 87 | }), 88 | }), 89 | } as unknown as jest.Mocked; 90 | 91 | mockWallet.invokeContract.mockResolvedValue(mockContractInvocation); 92 | }); 93 | 94 | it("should successfully create a token", async () => { 95 | const args = { 96 | name: MOCK_NAME, 97 | symbol: MOCK_SYMBOL, 98 | tokenUri: MOCK_URI, 99 | }; 100 | 101 | (getFactoryAddress as jest.Mock).mockReturnValue(CONTRACT_ADDRESS); 102 | 103 | const response = await wowCreateToken(mockWallet, args); 104 | 105 | expect(mockWallet.invokeContract).toHaveBeenCalledWith({ 106 | contractAddress: CONTRACT_ADDRESS, 107 | method: "deploy", 108 | abi: WOW_FACTORY_ABI, 109 | args: { 110 | _tokenCreator: WALLET_ID, 111 | _platformReferrer: "0x0000000000000000000000000000000000000000", 112 | _tokenURI: args.tokenUri || GENERIC_TOKEN_METADATA_URI, 113 | _name: args.name, 114 | _symbol: args.symbol, 115 | }, 116 | }); 117 | expect(mockContractInvocation.wait).toHaveBeenCalled(); 118 | expect(response).toContain(`Created WoW ERC20 memecoin ${MOCK_NAME}`); 119 | expect(response).toContain(`with symbol ${MOCK_SYMBOL}`); 120 | expect(response).toContain(`on network ${NETWORK_ID}`); 121 | expect(response).toContain(`Transaction hash for the token creation: ${TRANSACTION_HASH}`); 122 | }); 123 | 124 | it("should handle errors when creating a token", async () => { 125 | const args = { 126 | name: MOCK_NAME, 127 | symbol: MOCK_SYMBOL, 128 | tokenUri: MOCK_URI, 129 | }; 130 | 131 | const error = new Error("An error has occurred"); 132 | mockWallet.invokeContract.mockRejectedValue(error); 133 | 134 | const response = await wowCreateToken(mockWallet, args); 135 | 136 | expect(mockWallet.invokeContract).toHaveBeenCalled(); 137 | expect(response).toContain(`Error creating Zora Wow ERC20 memecoin: ${error}`); 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/defi_wow_sell_token_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, ContractInvocation, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { WOW_ABI } from "../actions/cdp/defi/wow/constants"; 4 | import { wowSellToken, WowSellTokenInput } from "../actions/cdp/defi/wow/actions/sell_token"; 5 | import { getSellQuote } from "../actions/cdp/defi/wow/utils"; 6 | import { getHasGraduated } from "../actions/cdp/defi/wow/uniswap/utils"; 7 | 8 | jest.mock("../actions/cdp/defi/wow/utils", () => ({ 9 | getSellQuote: jest.fn(), 10 | })); 11 | 12 | jest.mock("../actions/cdp/defi/wow/uniswap/utils", () => ({ 13 | getHasGraduated: jest.fn(), 14 | })); 15 | 16 | const MOCK_CONTRACT_ADDRESS = "0x036cbd53842c5426634e7929541ec2318f3dcf7e"; 17 | const MOCK_AMOUNT_TOKENS_IN_WEI = "1000000000000000000"; 18 | 19 | describe("Wow Sell Token Input", () => { 20 | it("should successfully parse valid input", () => { 21 | const validInput = { 22 | contractAddress: MOCK_CONTRACT_ADDRESS, 23 | amountTokensInWei: MOCK_AMOUNT_TOKENS_IN_WEI, 24 | }; 25 | 26 | const result = WowSellTokenInput.safeParse(validInput); 27 | 28 | expect(result.success).toBe(true); 29 | expect(result.data).toEqual(validInput); 30 | }); 31 | 32 | it("should fail with missing amountTokensInWei", () => { 33 | const invalidInput = { 34 | contractAddress: MOCK_CONTRACT_ADDRESS, 35 | }; 36 | const result = WowSellTokenInput.safeParse(invalidInput); 37 | 38 | expect(result.success).toBe(false); 39 | }); 40 | 41 | it("does not fail with invalid contract address", () => { 42 | const invalidInput = { 43 | contractAddress: MOCK_CONTRACT_ADDRESS, 44 | amountTokensInWei: MOCK_AMOUNT_TOKENS_IN_WEI, 45 | }; 46 | const result = WowSellTokenInput.safeParse(invalidInput); 47 | 48 | expect(result.success).toBe(true); 49 | }); 50 | 51 | it("does not fail with non-numeric amountTokensInWei", () => { 52 | const invalidInput = { 53 | contractAddress: MOCK_CONTRACT_ADDRESS, 54 | amountTokensInWei: "not_a_number", 55 | }; 56 | const result = WowSellTokenInput.safeParse(invalidInput); 57 | 58 | expect(result.success).toBe(true); 59 | }); 60 | }); 61 | 62 | describe("Wow Sell Token Action", () => { 63 | const ADDRESS_ID = "0xabcdef123456789"; 64 | const NETWORK_ID = Coinbase.networks.BaseSepolia; 65 | const TRANSACTION_HASH = "0xghijkl987654321"; 66 | 67 | let mockContractInvocation: jest.Mocked; 68 | let mockWallet: jest.Mocked; 69 | 70 | beforeEach(() => { 71 | mockWallet = { 72 | invokeContract: jest.fn(), 73 | getNetworkId: jest.fn().mockReturnValue(NETWORK_ID), 74 | getDefaultAddress: jest.fn().mockResolvedValue({ 75 | getId: jest.fn().mockReturnValue(ADDRESS_ID), 76 | }), 77 | } as unknown as jest.Mocked; 78 | 79 | mockContractInvocation = { 80 | wait: jest.fn().mockResolvedValue({ 81 | getTransaction: jest.fn().mockReturnValue({ 82 | getTransactionHash: jest.fn().mockReturnValue(TRANSACTION_HASH), 83 | }), 84 | }), 85 | } as unknown as jest.Mocked; 86 | 87 | mockWallet.invokeContract.mockResolvedValue(mockContractInvocation); 88 | }); 89 | 90 | it("should successfully sell tokens", async () => { 91 | const args = { 92 | contractAddress: MOCK_CONTRACT_ADDRESS, 93 | amountTokensInWei: MOCK_AMOUNT_TOKENS_IN_WEI, 94 | }; 95 | 96 | (getHasGraduated as jest.Mock).mockResolvedValue(true); 97 | (getSellQuote as jest.Mock).mockResolvedValue(1.0); 98 | 99 | const response = await wowSellToken(mockWallet, args); 100 | 101 | expect(mockWallet.invokeContract).toHaveBeenCalledWith({ 102 | contractAddress: MOCK_CONTRACT_ADDRESS, 103 | method: "sell", 104 | abi: WOW_ABI, 105 | args: { 106 | tokensToSell: MOCK_AMOUNT_TOKENS_IN_WEI, 107 | recipient: expect.any(String), 108 | orderReferrer: "0x0000000000000000000000000000000000000000", 109 | comment: "", 110 | expectedMarketType: "1", 111 | minPayoutSize: expect.any(String), 112 | sqrtPriceLimitX96: "0", 113 | }, 114 | }); 115 | expect(getSellQuote).toHaveBeenCalled(); 116 | expect(getHasGraduated).toHaveBeenCalled(); 117 | expect(response).toContain( 118 | `Sold WoW ERC20 memecoin with transaction hash: ${TRANSACTION_HASH}`, 119 | ); 120 | }); 121 | 122 | it("should handle errors when selling tokens", async () => { 123 | const args = { 124 | contractAddress: MOCK_CONTRACT_ADDRESS, 125 | amountTokensInWei: MOCK_AMOUNT_TOKENS_IN_WEI, 126 | }; 127 | 128 | const error = new Error("An error has occurred"); 129 | mockWallet.invokeContract.mockRejectedValue(error); 130 | (getHasGraduated as jest.Mock).mockResolvedValue(true); 131 | 132 | const response = await wowSellToken(mockWallet, args); 133 | 134 | expect(mockWallet.invokeContract).toHaveBeenCalled(); 135 | expect(response).toContain(`Error selling Zora Wow ERC20 memecoin: ${error}`); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /cdp-langchain/README.md: -------------------------------------------------------------------------------- 1 | # CDP AgentKit Extension - Langchain Toolkit 2 | 3 | [![npm version](https://img.shields.io/npm/v/@coinbase/cdp-langchain.svg?style=flat-square)](https://www.npmjs.com/package/@coinbase/cdp-langchain) [![GitHub star chart](https://img.shields.io/github/stars/coinbase/cdp-agentkit-nodejs?style=flat-square)](https://star-history.com/#coinbase/cdp-agentkit-nodejs) [![Open Issues](https://img.shields.io/github/issues-raw/coinbase/cdp-agentkit-nodejs?style=flat-square)](https://github.com/coinbase/cdp-agentkit-nodejs/issues) 4 | 5 | CDP integration with Langchain to enable agentic workflows using the core primitives defined in `cdp-agentkit-core`. This toolkit contains tools that enable an LLM agent to interact with the [Coinbase Developer Platform](https://docs.cdp.coinbase.com/). The toolkit provides a wrapper around the CDP SDK, allowing agents to perform onchain operations like transfers, trades, and smart contract interactions. 6 | 7 | ## Prerequisites 8 | 9 | - Node.js 18 or higher 10 | - [CDP API Key](https://portal.cdp.coinbase.com/access/api) 11 | - [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key) 12 | 13 | ## Installation 14 | 15 | ```bash 16 | npm install @coinbase/cdp-langchain 17 | ``` 18 | 19 | ## Environment Setup 20 | 21 | Set the following environment variables: 22 | 23 | ```bash 24 | export CDP_API_KEY_NAME= 25 | export CDP_API_KEY_PRIVATE_KEY= 26 | export OPENAI_API_KEY= 27 | export NETWORK_ID=base-sepolia # Optional: Defaults to base-sepolia 28 | ``` 29 | 30 | ## Usage 31 | 32 | ### Basic Setup 33 | 34 | ```typescript 35 | import { CdpToolkit } from "@coinbase/cdp-langchain"; 36 | import { CdpAgentkit } from "@coinbase/cdp-agentkit-core"; 37 | 38 | // Initialize CDP AgentKit 39 | const agentkit = await CdpAgentkit.configureWithWallet(); 40 | 41 | // Create toolkit 42 | const toolkit = new CdpToolkit(agentkit); 43 | 44 | // Get available tools 45 | const tools = toolkit.getTools(); 46 | ``` 47 | 48 | ### Available Tools 49 | 50 | The toolkit provides the following tools: 51 | 52 | 1. **get_wallet_details** - Get details about the user's Wallet 53 | 2. **get_balance** - Get balance for specific assets 54 | 3. **request_faucet_funds** - Request test tokens from faucet 55 | 4. **transfer** - Transfer assets between addresses 56 | 5. **trade** - Trade assets (Mainnet only) 57 | 6. **deploy_token** - Deploy [ERC-20](https://www.coinbase.com/learn/crypto-glossary/what-is-erc-20) token contracts 58 | 7. **mint_nft** - Mint NFTs from existing contracts 59 | 8. **deploy_nft** - Deploy new NFT contracts 60 | 9. **register_basename** - Register a [Basename](https://www.base.org/names) for the wallet 61 | 10. **wow_create_token** - Deploy a token using [Zora's Wow Launcher](https://wow.xyz/mechanics) (Bonding Curve) 62 | 11. **wow_buy_token** - Buy [Zora Wow](https://wow.xyz/) ERC-20 memecoin with ETH 63 | 12. **wow_sell_token** - Sell [Zora Wow](https://wow.xyz/) ERC-20 memecoin for ETH 64 | 13. **wrap_eth** - Wrap ETH as WETH 65 | 66 | ### Using with an Agent 67 | 68 | #### Additional Installations 69 | ```bash 70 | npm install @langchain/langgraph @langchain/openai 71 | ``` 72 | 73 | ```typescript 74 | import { ChatOpenAI } from "@langchain/openai"; 75 | import { HumanMessage } from "@langchain/core/messages"; 76 | import { createReactAgent } from "@langchain/langgraph/prebuilt"; 77 | 78 | // Initialize LLM 79 | const model = new ChatOpenAI({ 80 | model: "gpt-4o-mini", 81 | }); 82 | 83 | // Create agent executor 84 | const agent = createReactAgent({ 85 | llm: model, 86 | tools, 87 | }); 88 | 89 | // Example usage 90 | const result = await agent.invoke({ 91 | messages: [new HumanMessage("Send 0.005 ETH to john2879.base.eth")], 92 | }); 93 | 94 | console.log(result.messages[result.messages.length - 1].content); 95 | ``` 96 | 97 | ## CDP Toolkit Specific Features 98 | 99 | ### Wallet Management 100 | 101 | The toolkit maintains an MPC wallet that persists between sessions: 102 | 103 | ```typescript 104 | // Export wallet data 105 | const walletData = await agentkit.exportWallet(); 106 | 107 | // Import wallet data 108 | const importedAgentkit = await CdpAgentkit.configureWithWallet({ cdpWalletData: walletData }); 109 | ``` 110 | 111 | ### Network Support 112 | 113 | The toolkit supports [multiple networks](https://docs.cdp.coinbase.com/cdp-sdk/docs/networks). 114 | 115 | ### Gasless Transactions 116 | 117 | The following operations support gasless transactions on Base Mainnet: 118 | 119 | - USDC transfers 120 | - EURC transfers 121 | - cbBTC transfers 122 | 123 | ## Examples 124 | 125 | Check out `examples/` for inspiration and help getting started: 126 | 127 | - [Chatbot](./examples/chatbot/README.md): Interactive chatbot with onchain capabilities 128 | 129 | ## Contributing 130 | 131 | See [CONTRIBUTING.md](../CONTRIBUTING.md) for detailed setup instructions and contribution guidelines. 132 | 133 | ## Security and bug reports 134 | 135 | The CDP AgentKit team takes security seriously. 136 | See [SECURITY.md](../SECURITY.md) for more information. 137 | 138 | ## Documentation 139 | 140 | - [CDP AgentKit Documentation](https://docs.cdp.coinbase.com/agentkit/docs/welcome) 141 | - [API Reference: CDP AgentKit LangChain Extension](https://coinbase.github.io/cdp-agentkit-nodejs/cdp-langchain/index.html) 142 | 143 | ## License 144 | 145 | Apache-2.0 146 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/tests/register_basename_test.ts: -------------------------------------------------------------------------------- 1 | import { Coinbase, ContractInvocation, Wallet } from "@coinbase/coinbase-sdk"; 2 | 3 | import { Decimal } from "decimal.js"; 4 | import { encodeFunctionData, namehash } from "viem"; 5 | 6 | import { 7 | registerBasename, 8 | RegisterBasenameInput, 9 | BASENAMES_REGISTRAR_CONTROLLER_ADDRESS_MAINNET, 10 | BASENAMES_REGISTRAR_CONTROLLER_ADDRESS_TESTNET, 11 | L2_RESOLVER_ABI, 12 | L2_RESOLVER_ADDRESS_MAINNET, 13 | L2_RESOLVER_ADDRESS_TESTNET, 14 | REGISTRATION_DURATION, 15 | REGISTRAR_ABI, 16 | } from "../actions/cdp/register_basename"; 17 | 18 | const MOCK_AMOUNT = "0.123"; 19 | const MOCK_BASENAME = "test-basename"; 20 | 21 | describe("Register Basename Input", () => { 22 | it("should successfully parse valid input", () => { 23 | const validInput = { 24 | amount: MOCK_AMOUNT, 25 | basename: MOCK_BASENAME, 26 | }; 27 | 28 | const result = RegisterBasenameInput.safeParse(validInput); 29 | 30 | expect(result.success).toBe(true); 31 | expect(result.data).toEqual(validInput); 32 | }); 33 | 34 | it("should fail parsing empty input", () => { 35 | const emptyInput = {}; 36 | const result = RegisterBasenameInput.safeParse(emptyInput); 37 | 38 | expect(result.success).toBe(false); 39 | }); 40 | }); 41 | 42 | describe("Register Basename Action", () => { 43 | /** 44 | * This is the default network. 45 | */ 46 | const NETWORK_ID = Coinbase.networks.BaseMainnet; 47 | 48 | /** 49 | * This is a 40 character hexadecimal string that requires lowercase alpha characters. 50 | */ 51 | const ADDRESS_ID = "0xe6b2af36b3bb8d47206a129ff11d5a2de2a63c83"; 52 | 53 | let mockContractInvocation: jest.Mocked; 54 | let mockWallet: jest.Mocked; 55 | 56 | beforeEach(() => { 57 | mockContractInvocation = { 58 | wait: jest.fn().mockResolvedValue({}), 59 | } as unknown as jest.Mocked; 60 | 61 | mockWallet = { 62 | getDefaultAddress: jest.fn().mockResolvedValue({ 63 | getId: jest.fn().mockReturnValue(ADDRESS_ID), 64 | }), 65 | getNetworkId: jest.fn().mockReturnValue(NETWORK_ID), 66 | invokeContract: jest.fn(), 67 | } as unknown as jest.Mocked; 68 | 69 | mockWallet.invokeContract.mockResolvedValue(mockContractInvocation); 70 | }); 71 | 72 | it(`should Successfully respond with ${MOCK_BASENAME}.base.eth for network: ${Coinbase.networks.BaseMainnet}`, async () => { 73 | const args = { 74 | amount: MOCK_AMOUNT, 75 | basename: MOCK_BASENAME, 76 | }; 77 | 78 | const name = `${MOCK_BASENAME}.base.eth`; 79 | 80 | mockWallet.getNetworkId.mockReturnValue(Coinbase.networks.BaseMainnet); 81 | 82 | const response = await registerBasename(mockWallet, args); 83 | 84 | expect(mockWallet.invokeContract).toHaveBeenCalledWith({ 85 | contractAddress: BASENAMES_REGISTRAR_CONTROLLER_ADDRESS_MAINNET, 86 | method: "register", 87 | args: { 88 | request: [ 89 | MOCK_BASENAME, 90 | ADDRESS_ID, 91 | REGISTRATION_DURATION, 92 | L2_RESOLVER_ADDRESS_MAINNET, 93 | [ 94 | encodeFunctionData({ 95 | abi: L2_RESOLVER_ABI, 96 | functionName: "setAddr", 97 | args: [namehash(name), ADDRESS_ID], 98 | }), 99 | encodeFunctionData({ 100 | abi: L2_RESOLVER_ABI, 101 | functionName: "setName", 102 | args: [namehash(name), name], 103 | }), 104 | ], 105 | true, 106 | ], 107 | }, 108 | abi: REGISTRAR_ABI, 109 | amount: new Decimal(MOCK_AMOUNT), 110 | assetId: "eth", 111 | }); 112 | expect(mockContractInvocation.wait).toHaveBeenCalled(); 113 | expect(response).toContain(`Successfully registered basename ${MOCK_BASENAME}.base.eth`); 114 | expect(response).toContain(`for address ${ADDRESS_ID}`); 115 | }); 116 | 117 | it(`should Successfully respond with ${MOCK_BASENAME}.basetest.eth for any other network`, async () => { 118 | const args = { 119 | amount: MOCK_AMOUNT, 120 | basename: MOCK_BASENAME, 121 | }; 122 | 123 | const name = `${MOCK_BASENAME}.basetest.eth`; 124 | 125 | mockWallet.getNetworkId.mockReturnValue("anything-else"); 126 | 127 | const response = await registerBasename(mockWallet, args); 128 | 129 | expect(mockWallet.invokeContract).toHaveBeenCalledWith({ 130 | contractAddress: BASENAMES_REGISTRAR_CONTROLLER_ADDRESS_TESTNET, 131 | method: "register", 132 | args: { 133 | request: [ 134 | MOCK_BASENAME, 135 | ADDRESS_ID, 136 | REGISTRATION_DURATION, 137 | L2_RESOLVER_ADDRESS_TESTNET, 138 | [ 139 | encodeFunctionData({ 140 | abi: L2_RESOLVER_ABI, 141 | functionName: "setAddr", 142 | args: [namehash(name), ADDRESS_ID], 143 | }), 144 | encodeFunctionData({ 145 | abi: L2_RESOLVER_ABI, 146 | functionName: "setName", 147 | args: [namehash(name), name], 148 | }), 149 | ], 150 | true, 151 | ], 152 | }, 153 | abi: REGISTRAR_ABI, 154 | amount: new Decimal(MOCK_AMOUNT), 155 | assetId: "eth", 156 | }); 157 | expect(mockContractInvocation.wait).toHaveBeenCalled(); 158 | expect(response).toContain(`Successfully registered basename ${MOCK_BASENAME}.basetest.eth`); 159 | expect(response).toContain(`for address ${ADDRESS_ID}`); 160 | }); 161 | 162 | it("should fail with an error", async () => { 163 | const args = { 164 | amount: MOCK_AMOUNT, 165 | basename: MOCK_BASENAME, 166 | }; 167 | 168 | const error = new Error("Failed to register basename"); 169 | mockWallet.invokeContract.mockRejectedValue(error); 170 | 171 | await registerBasename(mockWallet, args); 172 | 173 | expect(mockWallet.invokeContract).toHaveBeenCalled(); 174 | expect(`Error registering basename: ${error}`); 175 | }); 176 | }); 177 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CDP AgentKit Contributing Guide 2 | 3 | Thank you for your interest in contributing to CDP AgentKit! We welcome all contributions, no matter how big or small. Some of the ways you can contribute include: 4 | - Adding new actions to the core package 5 | - Creating new AI framework extensions 6 | - Adding tests and improving documentation 7 | 8 | ## Development 9 | 10 | ### Prerequisites 11 | - Node.js 18 or higher 12 | - npm for package management 13 | 14 | ### Set-up 15 | 16 | Clone the repo by running: 17 | 18 | ```bash 19 | git clone git@github.com:coinbase/cdp-agentkit-nodejs.git 20 | cd cdp-agentkit-nodejs 21 | ``` 22 | 23 | Install dependencies: 24 | 25 | ```bash 26 | npm install 27 | ``` 28 | 29 | ### Building 30 | 31 | To build all packages: 32 | 33 | ```bash 34 | npm run build 35 | ``` 36 | 37 | ### Linting & Formatting 38 | 39 | To check for lint errors: 40 | 41 | ```bash 42 | npm run lint 43 | ``` 44 | 45 | To automatically fix lint errors: 46 | 47 | ```bash 48 | npm run lint-fix 49 | ``` 50 | 51 | To format code: 52 | 53 | ```bash 54 | npm run format 55 | ``` 56 | 57 | ### Testing 58 | 59 | To run all tests: 60 | 61 | ```bash 62 | npm test 63 | ``` 64 | 65 | ### Documentation 66 | 67 | To generate documentation: 68 | 69 | ```bash 70 | npm run docs 71 | ``` 72 | 73 | ## Adding an Action to AgentKit Core 74 | 75 | Actions are defined in `cdp-agentkit-core/src/actions` module. See `cdp-agentkit-core/src/actions/mint_nft.ts` for an example. 76 | 77 | Actions are created by implementing the `CdpAction` interface: 78 | 79 | ```typescript 80 | import { CdpAction } from "./cdp_action"; 81 | import { Wallet } from "@coinbase/coinbase-sdk"; 82 | import { z } from "zod"; 83 | 84 | const MINT_NFT_PROMPT = ` 85 | This tool will mint an NFT (ERC-721) to a specified destination address onchain via a contract invocation. It takes the contract address of the NFT onchain and the destination address onchain that will receive the NFT as inputs. Do not use the contract address as the destination address. If you are unsure of the destination address, please ask the user before proceeding.`; 86 | 87 | /** 88 | * Input schema for mint NFT action. 89 | */ 90 | const MintNftInput = z 91 | .object({ 92 | contractAddress: z.string().describe("The contract address of the NFT to mint"), 93 | destination: z.string().describe("The destination address that will receive the NFT"), 94 | }) 95 | .strip() 96 | .describe("Instructions for minting an NFT"); 97 | 98 | /** 99 | * Mints an NFT (ERC-721) to a specified destination address onchain. 100 | * 101 | * @param wallet - The wallet to mint the NFT from. 102 | * @param args - The input arguments for the action. 103 | * @returns A message containing the NFT mint details. 104 | */ 105 | async function mintNft(wallet: Wallet, args: z.infer): Promise { 106 | const mintArgs = { 107 | to: args.destination, 108 | quantity: "1", 109 | }; 110 | 111 | try { 112 | const mintInvocation = await wallet.invokeContract({ 113 | contractAddress: args.contractAddress, 114 | method: "mint", 115 | args: mintArgs, 116 | }); 117 | 118 | const result = await mintInvocation.wait(); 119 | 120 | return `Minted NFT from contract ${args.contractAddress} to address ${args.destination} on network ${wallet.getNetworkId()}.\nTransaction hash for the mint: ${result.getTransaction().getTransactionHash()}\nTransaction link for the mint: ${result.getTransaction().getTransactionLink()}`; 121 | } catch (error) { 122 | return `Error minting NFT: ${error}`; 123 | } 124 | } 125 | 126 | /** 127 | * Mint NFT action. 128 | */ 129 | export class MintNftAction implements CdpAction { 130 | public name = "mint_nft"; 131 | public description = MINT_NFT_PROMPT; 132 | public argsSchema = MintNftInput; 133 | public func = mintNft; 134 | } 135 | ``` 136 | 137 | ### Components of an Agentic Action 138 | 139 | 1. **Input Schema**: Define the input parameters using Zod schemas 140 | 2. **Prompt**: A description that helps the AI understand when and how to use the action 141 | 3. **Action Class**: Implements the `CdpAction` interface with: 142 | - `name`: Unique identifier for the action 143 | - `description`: The prompt text 144 | - `argsSchema`: The Zod schema for validating inputs 145 | - `func`: The implementation function 146 | 4. **Implementation Function**: The actual logic that executes the action 147 | 148 | ## Adding an Agentic Action to the Langchain Toolkit 149 | 150 | 1. Ensure the action is implemented in `cdp-agentkit-core` and in a released version 151 | 2. Update the `cdp-agentkit-core` dependency to the latest version 152 | 3. Add the action to the list of tools in `CdpToolkit` 153 | 154 | ## Development Tools 155 | 156 | ### Formatting 157 | ```bash 158 | npm run format 159 | ``` 160 | 161 | ### Linting 162 | ```bash 163 | # Check for lint errors 164 | npm run lint 165 | 166 | # Fix lint errors 167 | npm run lint-fix 168 | ``` 169 | 170 | ### Testing 171 | ```bash 172 | npm test 173 | ``` 174 | 175 | ### Documentation 176 | ```bash 177 | npm run docs 178 | ``` 179 | 180 | ## Changelog 181 | 182 | For new features and bug fixes, please add a new changelog entry to the `CHANGELOG.md` file in the appropriate packages and include that in your Pull Request. 183 | 184 | ## Pull Request Process 185 | 186 | 1. Create a new branch for your changes 187 | 2. Make your changes following the coding standards 188 | 3. Add tests for any new functionality 189 | 4. Update documentation as needed 190 | 5. Update the CHANGELOG.md 191 | 6. Submit a pull request 192 | 193 | ## Code Style 194 | 195 | All code must follow the project's ESLint and Prettier configurations. The key rules are: 196 | - Use TypeScript 197 | - Follow JSDoc documentation standards 198 | - Use 2 spaces for indentation 199 | - Maximum line length of 100 characters 200 | - Double quotes for strings 201 | - Semicolons required 202 | 203 | ## Getting Help 204 | 205 | If you have questions or need help, please: 206 | 1. Check the existing documentation 207 | 2. Search through existing issues 208 | 3. Create a new issue with your question 209 | 210 | Thank you for contributing to CDP AgentKit! 211 | -------------------------------------------------------------------------------- /twitter-langchain/examples/chatbot/chatbot.ts: -------------------------------------------------------------------------------- 1 | import { TwitterToolkit } from "@coinbase/twitter-langchain"; 2 | import { TwitterAgentkit } from "@coinbase/cdp-agentkit-core"; 3 | import { HumanMessage } from "@langchain/core/messages"; 4 | import { MemorySaver } from "@langchain/langgraph"; 5 | import { createReactAgent } from "@langchain/langgraph/prebuilt"; 6 | import { ChatOpenAI } from "@langchain/openai"; 7 | import * as dotenv from "dotenv"; 8 | import * as readline from "readline"; 9 | 10 | dotenv.config(); 11 | 12 | const modifier = ` 13 | You are a helpful agent that can interact with the Twitter (X) API using the Coinbase Developer Platform Twitter (X) Agentkit. 14 | You are empowered to interact with Twitter (X) using your tools. 15 | 16 | If someone asks you to do something you can't do with your currently available tools, you must say so, and encourage them to implement it themselves using the Twitter (X) API + Agentkit. 17 | Recommend they go to https://developer.x.com/en/docs for more informaton. 18 | 19 | Be concise and helpful with your responses. 20 | Refrain from restating your tools' descriptions unless it is explicitly requested. 21 | `; 22 | 23 | /** 24 | * Initialize the agent with Twitter (X) Agentkit 25 | * 26 | * @returns Agent executor and config 27 | */ 28 | async function initialize() { 29 | // Initialize LLM 30 | const llm = new ChatOpenAI({ model: "gpt-4o-mini" }); 31 | 32 | // Twitter (X) Agentkit 33 | const twitterAgentkit = new TwitterAgentkit(); 34 | 35 | // Twitter (X) Toolkit 36 | const twitterToolkit = new TwitterToolkit(twitterAgentkit); 37 | 38 | // Twitter (X) tools 39 | const tools = twitterToolkit.getTools(); 40 | 41 | // Store buffered conversation history in memory 42 | const memory = new MemorySaver(); 43 | 44 | // React Agent options 45 | const agentConfig = { configurable: { thread_id: "Twitter Agentkit Chatbot Example!" } }; 46 | 47 | // Create React Agent using the LLM and Twitter (X) tools 48 | const agent = createReactAgent({ 49 | llm, 50 | tools, 51 | checkpointSaver: memory, 52 | messageModifier: modifier, 53 | }); 54 | 55 | return { agent, config: agentConfig }; 56 | } 57 | 58 | /** 59 | * Run the agent autonomously with specified intervals 60 | * 61 | * @param agent - The agent executor 62 | * @param config - Agent configuration 63 | * @param interval - Time interval between actions in seconds 64 | */ 65 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 66 | async function runAutonomousMode(agent: any, config: any, interval = 10) { 67 | console.log("Starting autonomous mode..."); 68 | 69 | // eslint-disable-next-line no-constant-condition 70 | while (true) { 71 | try { 72 | const thought = 73 | "Be creative and do something interesting on the blockchain. " + 74 | "Choose an action or set of actions and execute it that highlights your abilities."; 75 | 76 | const stream = await agent.stream({ messages: [new HumanMessage(thought)] }, config); 77 | 78 | for await (const chunk of stream) { 79 | if ("agent" in chunk) { 80 | console.log(chunk.agent.messages[0].content); 81 | } else if ("tools" in chunk) { 82 | console.log(chunk.tools.messages[0].content); 83 | } 84 | console.log("-------------------"); 85 | } 86 | 87 | await new Promise(resolve => setTimeout(resolve, interval * 1000)); 88 | } catch (error) { 89 | if (error instanceof Error) { 90 | console.error("Error:", error.message); 91 | } 92 | process.exit(1); 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * Run the agent interactively based on user input 99 | * 100 | * @param agent - The agent executor 101 | * @param config - Agent configuration 102 | */ 103 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 104 | async function runChatMode(agent: any, config: any) { 105 | console.log("Starting chat mode... Type 'exit' to end."); 106 | 107 | const rl = readline.createInterface({ 108 | input: process.stdin, 109 | output: process.stdout, 110 | }); 111 | 112 | const question = (prompt: string): Promise => 113 | new Promise(resolve => rl.question(prompt, resolve)); 114 | 115 | try { 116 | // eslint-disable-next-line no-constant-condition 117 | while (true) { 118 | const userInput = await question("\nPrompt: "); 119 | 120 | if (userInput.toLowerCase() === "exit") { 121 | break; 122 | } 123 | 124 | const stream = await agent.stream({ messages: [new HumanMessage(userInput)] }, config); 125 | 126 | for await (const chunk of stream) { 127 | if ("agent" in chunk) { 128 | console.log(chunk.agent.messages[0].content); 129 | } else if ("tools" in chunk) { 130 | console.log(chunk.tools.messages[0].content); 131 | } 132 | console.log("-------------------"); 133 | } 134 | } 135 | } catch (error) { 136 | if (error instanceof Error) { 137 | console.error("Error:", error.message); 138 | } 139 | process.exit(1); 140 | } finally { 141 | rl.close(); 142 | } 143 | } 144 | 145 | /** 146 | * Choose whether to run in autonomous or chat mode based on user input 147 | * 148 | * @returns Selected mode 149 | */ 150 | async function chooseMode(): Promise<"chat" | "auto"> { 151 | const rl = readline.createInterface({ 152 | input: process.stdin, 153 | output: process.stdout, 154 | }); 155 | 156 | const question = (prompt: string): Promise => 157 | new Promise(resolve => rl.question(prompt, resolve)); 158 | 159 | // eslint-disable-next-line no-constant-condition 160 | while (true) { 161 | console.log("\nAvailable modes:"); 162 | console.log("1. chat - Interactive chat mode"); 163 | console.log("2. auto - Autonomous action mode"); 164 | 165 | const choice = (await question("\nChoose a mode (enter number or name): ")) 166 | .toLowerCase() 167 | .trim(); 168 | 169 | if (choice === "1" || choice === "chat") { 170 | rl.close(); 171 | return "chat"; 172 | } else if (choice === "2" || choice === "auto") { 173 | rl.close(); 174 | return "auto"; 175 | } 176 | console.log("Invalid choice. Please try again."); 177 | } 178 | } 179 | 180 | /** 181 | * Start the chatbot agent 182 | */ 183 | async function main() { 184 | try { 185 | const { agent, config } = await initialize(); 186 | const mode = await chooseMode(); 187 | 188 | if (mode === "chat") { 189 | await runChatMode(agent, config); 190 | } else { 191 | await runAutonomousMode(agent, config); 192 | } 193 | } catch (error) { 194 | if (error instanceof Error) { 195 | console.error("Error:", error.message); 196 | } 197 | process.exit(1); 198 | } 199 | } 200 | 201 | if (require.main === module) { 202 | console.log("Starting Agent..."); 203 | main().catch(error => { 204 | console.error("Fatal error:", error); 205 | process.exit(1); 206 | }); 207 | } 208 | -------------------------------------------------------------------------------- /cdp-agentkit-core/src/actions/cdp/register_basename.ts: -------------------------------------------------------------------------------- 1 | import { CdpAction } from "./cdp_action"; 2 | import { Coinbase, Wallet } from "@coinbase/coinbase-sdk"; 3 | import { encodeFunctionData, namehash } from "viem"; 4 | import { z } from "zod"; 5 | import { Decimal } from "decimal.js"; 6 | 7 | const REGISTER_BASENAME_PROMPT = ` 8 | This tool will register a Basename for the agent. The agent should have a wallet associated to register a Basename. 9 | When your network ID is 'base-mainnet' (also sometimes known simply as 'base'), the name must end with .base.eth, and when your network ID is 'base-sepolia', it must ends with .basetest.eth. 10 | Do not suggest any alternatives and never try to register a Basename with another postfix. The prefix of the name must be unique so if the registration of the 11 | Basename fails, you should prompt to try again with a more unique name. 12 | `; 13 | 14 | // Contract addresses 15 | export const BASENAMES_REGISTRAR_CONTROLLER_ADDRESS_MAINNET = 16 | "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5"; 17 | export const BASENAMES_REGISTRAR_CONTROLLER_ADDRESS_TESTNET = 18 | "0x49aE3cC2e3AA768B1e5654f5D3C6002144A59581"; 19 | 20 | export const L2_RESOLVER_ADDRESS_MAINNET = "0xC6d566A56A1aFf6508b41f6c90ff131615583BCD"; 21 | export const L2_RESOLVER_ADDRESS_TESTNET = "0x6533C94869D28fAA8dF77cc63f9e2b2D6Cf77eBA"; 22 | 23 | // Default registration duration (1 year in seconds) 24 | export const REGISTRATION_DURATION = "31557600"; 25 | 26 | // Relevant ABI for L2 Resolver Contract. 27 | export const L2_RESOLVER_ABI = [ 28 | { 29 | inputs: [ 30 | { internalType: "bytes32", name: "node", type: "bytes32" }, 31 | { internalType: "address", name: "a", type: "address" }, 32 | ], 33 | name: "setAddr", 34 | outputs: [], 35 | stateMutability: "nonpayable", 36 | type: "function", 37 | }, 38 | { 39 | inputs: [ 40 | { internalType: "bytes32", name: "node", type: "bytes32" }, 41 | { internalType: "string", name: "newName", type: "string" }, 42 | ], 43 | name: "setName", 44 | outputs: [], 45 | stateMutability: "nonpayable", 46 | type: "function", 47 | }, 48 | ]; 49 | 50 | // Relevant ABI for Basenames Registrar Controller Contract. 51 | export const REGISTRAR_ABI = [ 52 | { 53 | inputs: [ 54 | { 55 | components: [ 56 | { 57 | internalType: "string", 58 | name: "name", 59 | type: "string", 60 | }, 61 | { 62 | internalType: "address", 63 | name: "owner", 64 | type: "address", 65 | }, 66 | { 67 | internalType: "uint256", 68 | name: "duration", 69 | type: "uint256", 70 | }, 71 | { 72 | internalType: "address", 73 | name: "resolver", 74 | type: "address", 75 | }, 76 | { 77 | internalType: "bytes[]", 78 | name: "data", 79 | type: "bytes[]", 80 | }, 81 | { 82 | internalType: "bool", 83 | name: "reverseRecord", 84 | type: "bool", 85 | }, 86 | ], 87 | internalType: "struct RegistrarController.RegisterRequest", 88 | name: "request", 89 | type: "tuple", 90 | }, 91 | ], 92 | name: "register", 93 | outputs: [], 94 | stateMutability: "payable", 95 | type: "function", 96 | }, 97 | ]; 98 | 99 | /** 100 | * Input schema for registering a Basename. 101 | */ 102 | export const RegisterBasenameInput = z 103 | .object({ 104 | basename: z.string().describe("The Basename to assign to the agent"), 105 | amount: z.string().default("0.002").describe("The amount of ETH to pay for registration"), 106 | }) 107 | .strip() 108 | .describe("Instructions for registering a Basename"); 109 | 110 | /** 111 | * Creates registration arguments for Basenames. 112 | * 113 | * @param baseName - The Basename (e.g., "example.base.eth" or "example.basetest.eth"). 114 | * @param addressId - The Ethereum address. 115 | * @param isMainnet - True if on mainnet, False if on testnet. 116 | * @returns Formatted arguments for the register contract method. 117 | */ 118 | function createRegisterContractMethodArgs( 119 | baseName: string, 120 | addressId: string, 121 | isMainnet: boolean, 122 | ): object { 123 | const l2ResolverAddress = isMainnet ? L2_RESOLVER_ADDRESS_MAINNET : L2_RESOLVER_ADDRESS_TESTNET; 124 | const suffix = isMainnet ? ".base.eth" : ".basetest.eth"; 125 | 126 | const addressData = encodeFunctionData({ 127 | abi: L2_RESOLVER_ABI, 128 | functionName: "setAddr", 129 | args: [namehash(baseName), addressId], 130 | }); 131 | const nameData = encodeFunctionData({ 132 | abi: L2_RESOLVER_ABI, 133 | functionName: "setName", 134 | args: [namehash(baseName), baseName], 135 | }); 136 | 137 | const registerArgs = { 138 | request: [ 139 | baseName.replace(suffix, ""), 140 | addressId, 141 | REGISTRATION_DURATION, 142 | l2ResolverAddress, 143 | [addressData, nameData], 144 | true, 145 | ], 146 | }; 147 | 148 | return registerArgs; 149 | } 150 | 151 | /** 152 | * Registers a Basename for the agent. 153 | * 154 | * @param wallet - The wallet to register the Basename with. 155 | * @param args - The input arguments for the action. 156 | * @returns Confirmation message with the basename. 157 | */ 158 | export async function registerBasename( 159 | wallet: Wallet, 160 | args: z.infer, 161 | ): Promise { 162 | const addressId = (await wallet.getDefaultAddress()).getId(); 163 | const isMainnet = wallet.getNetworkId() === Coinbase.networks.BaseMainnet; 164 | 165 | const suffix = isMainnet ? ".base.eth" : ".basetest.eth"; 166 | if (!args.basename.endsWith(suffix)) { 167 | args.basename += suffix; 168 | } 169 | 170 | const registerArgs = createRegisterContractMethodArgs(args.basename, addressId, isMainnet); 171 | 172 | try { 173 | const contractAddress = isMainnet 174 | ? BASENAMES_REGISTRAR_CONTROLLER_ADDRESS_MAINNET 175 | : BASENAMES_REGISTRAR_CONTROLLER_ADDRESS_TESTNET; 176 | 177 | const invocation = await wallet.invokeContract({ 178 | contractAddress, 179 | method: "register", 180 | args: registerArgs, 181 | abi: REGISTRAR_ABI, 182 | amount: new Decimal(args.amount), 183 | assetId: "eth", 184 | }); 185 | 186 | await invocation.wait(); 187 | return `Successfully registered basename ${args.basename} for address ${addressId}`; 188 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 189 | } catch (error) { 190 | return `Error registering basename: Error: ${error}`; 191 | } 192 | } 193 | 194 | /** 195 | * Register Basename action. 196 | */ 197 | export class RegisterBasenameAction implements CdpAction { 198 | public name = "register_basename"; 199 | public description = REGISTER_BASENAME_PROMPT; 200 | public argsSchema = RegisterBasenameInput; 201 | public func = registerBasename; 202 | } 203 | --------------------------------------------------------------------------------