Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions typescript/.changeset/new-cups-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@coinbase/agentkit": patch
"@coinbase/agentkit-iqai-adk": patch
---

Added IQAI ADK framework extension
6 changes: 6 additions & 0 deletions typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ Anthropic Model Context Protocol (MCP) extension of AgentKit. Enables agentic wo

See [AgentKit Model Context Protocol](./framework-extensions/model-context-protocol/README.md) to get started!

#### `@coinbase/agentkit-iqai-adk`

IQAI ADK extension of AgentKit. Enables agentic workflows to interact with onchain actions.

See [AgentKit IQAI ADK](./framework-extensions/iqai-adk/README.md) to get started!

### `create-onchain-agent`

A quickstart CLI tool to scaffold out a fullstack chatbot application using Coinbase AgentKit. Runnable via `npm create onchain-agent@latest`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export class CdpApiActionProvider extends ActionProvider<WalletProvider> {
@CreateAction({
name: "request_faucet_funds",
description: `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.
Faucet is only allowed on 'base-sepolia' or 'solana-devnet'.
If fauceting on 'base-sepolia', user can only provide asset ID 'eth', 'usdc', 'eurc' or 'cbbtc', if no asset ID is provided, the faucet will default to 'eth'.
Faucet is only allowed on 'base-sepolia', 'ethereum-sepolia' or 'solana-devnet'.
If fauceting on 'base-sepolia' or 'ethereum-sepolia', user can only provide asset ID 'eth', 'usdc', 'eurc' or 'cbbtc', if no asset ID is provided, the faucet will default to 'eth'.
If fauceting on 'solana-devnet', user can only provide asset ID 'sol' or 'usdc', if no asset ID is provided, the faucet will default to 'sol'.
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
from another wallet and provide the user with your wallet details.`,
Expand Down
20 changes: 20 additions & 0 deletions typescript/examples/iqai-adk-cdp-chatbot/.env-local
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Required
OPENAI_API_KEY= # if using openai
GOOGLE_API_KEY= # if using gemini
LLM_MODEL=gpt-4o # or eg gemini-2.0-flash-exp

CDP_API_KEY_ID=
CDP_API_KEY_SECRET=
CDP_WALLET_SECRET=

# Optional
## Network
# NETWORK_ID= # Defaults to base-sepolia if not set
## Used for account creation
IDEMPOTENCY_KEY=
## Used for already existing accounts
ADDRESS=
## RPC endpoint
RPC_URL=
## IQAI Debugging
ADK_DEBUG=false
4 changes: 4 additions & 0 deletions typescript/examples/iqai-adk-cdp-chatbot/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"parser": "@typescript-eslint/parser",
"extends": ["../../.eslintrc.base.json"]
}
7 changes: 7 additions & 0 deletions typescript/examples/iqai-adk-cdp-chatbot/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
docs/
dist/
coverage/
.github/
src/client
**/**/*.json
*.md
11 changes: 11 additions & 0 deletions typescript/examples/iqai-adk-cdp-chatbot/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 100,
"proseWrap": "never"
}
65 changes: 65 additions & 0 deletions typescript/examples/iqai-adk-cdp-chatbot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# CDP AgentKit IQAI ADK Extension Examples - Chatbot TypeScript

This example demonstrates a blockchain chatbot built with CDP AgentKit and IQAI ADK (Agent Development Kit for TypeScript).

## Ask the chatbot to engage in the Web3 ecosystem!

- "Transfer a portion of your ETH to a random address"
- "What is the price of BTC?"
- "Swap USDC to ETH" (base-mainnet only)
- "Request funds" (testnets only)

## Prerequisites

### Checking Node Version

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:

```bash
node --version
```

If you don't have the correct version, you can install it using [nvm](https://github.com/nvm-sh/nvm):

```bash
nvm install node
```

This will automatically install and use the latest version of Node.

### API Keys

You'll need the following API keys:

- [CDP API Key](https://portal.cdp.coinbase.com/access/api)
- [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)

Once you have them, rename the `.env-local` file to `.env` and make sure you set the API keys to their corresponding environment variables:

- "CDP_API_KEY_ID"
- "CDP_API_KEY_SECRET"
- "CDP_WALLET_SECRET"
- "OPENAI_API_KEY"

## Running the example

From the root directory, run:

```bash
pnpm install
pnpm build
```

This will install the dependencies and build the packages locally. The chatbot example uses the local `@coinbase/agentkit-iqai-adk` and `@coinbase/agentkit` packages. If you make changes to the packages, you can run `pnpm build` from root again to rebuild the packages, and your changes will be reflected in the chatbot example.

Now from the `typescript/examples/iqai-adk-cdp-chatbot` directory, run:

```bash
pnpm start
```

Select "1. chat mode" and start telling your Agent to do things onchain!

## License

Apache-2.0
255 changes: 255 additions & 0 deletions typescript/examples/iqai-adk-cdp-chatbot/chatbot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import {
AgentKit,
CdpEvmWalletProvider,
wethActionProvider,
walletActionProvider,
erc20ActionProvider,
erc721ActionProvider,
cdpApiActionProvider,
cdpEvmWalletActionProvider,
x402ActionProvider,
pythActionProvider,
} from "@coinbase/agentkit";
import { getAdkTools } from "@coinbase/agentkit-iqai-adk";
import { AgentBuilder, EnhancedRunner } from "@iqai/adk";
import * as dotenv from "dotenv";
import * as readline from "readline";

dotenv.config();

/**
* Validates that required environment variables are set
*
* @throws {Error} - If required environment variables are missing
* @returns {void}
*/
function validateEnvironment(): void {
const missingVars: string[] = [];

// Check required variables
const requiredVars = ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET", "CDP_WALLET_SECRET"];
requiredVars.forEach(varName => {
if (!process.env[varName]) {
missingVars.push(varName);
}
});

// Exit if any required variables are missing
if (missingVars.length > 0) {
console.error("Error: Required environment variables are not set");
missingVars.forEach(varName => {
console.error(`${varName}=your_${varName.toLowerCase()}_here`);
});
process.exit(1);
}

// Warn about optional NETWORK_ID
if (!process.env.NETWORK_ID) {
console.warn("Warning: NETWORK_ID not set, defaulting to base-sepolia testnet");
}
}

// Add this right after imports and before any other code
validateEnvironment();

/**
* Initialize the agent with CDP Agentkit
*
* @returns Agent executor and config
*/
async function initializeAgent() {
try {
// Configure CDP Wallet Provider
const networkId = process.env.NETWORK_ID || "base-sepolia";

const cdpWalletConfig = {
apiKeyId: process.env.CDP_API_KEY_ID,
apiKeySecret: process.env.CDP_API_KEY_SECRET,
walletSecret: process.env.CDP_WALLET_SECRET,
idempotencyKey: process.env.IDEMPOTENCY_KEY,
address: process.env.ADDRESS as `0x${string}` | undefined,
networkId,
rpcUrl: process.env.RPC_URL,
};

const walletProvider = await CdpEvmWalletProvider.configureWithWallet(cdpWalletConfig);

const actionProviders = [
walletActionProvider(),
cdpApiActionProvider(),
cdpEvmWalletActionProvider(),
wethActionProvider(),
erc20ActionProvider(),
erc721ActionProvider(),
x402ActionProvider(),
pythActionProvider(),
];

// Initialize AgentKit
const agentkit = await AgentKit.from({
walletProvider,
actionProviders,
});

const tools = await getAdkTools(agentkit);

const { agent, runner } = await AgentBuilder.create("chat_bot_agent")
.withModel(process.env.LLM_MODEL || "gpt-4o")
.withDescription("AI agent that can interact with blockchain networks using CDP AgentKit")
.withInstruction(
`
You are a helpful agent that can interact onchain using the Coinbase Developer Platform AgentKit. You are
empowered to interact onchain using your tools. If you ever need funds, you can request them from the
faucet if you are on network ID 'base-sepolia'. If not, you can provide your wallet details and request
funds from the user. Before executing your first action, get the wallet details to see what network
you're on. If there is a 5XX (internal) HTTP error code, ask the user to try again later. 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 CDP SDK + Agentkit, recommend they go to
docs.cdp.coinbase.com for more information. Be concise and helpful with your responses. Refrain from
restating your tools' descriptions unless it is explicitly requested.
`,
)
.withTools(...tools)
.build();

return { agent, runner };
} catch (error) {
console.error("Failed to initialize agent:", error);
throw error; // Re-throw to be handled by caller
}
}

/**
* Run the agent autonomously with specified intervals
*
* @param agent - The agent executor
* @param interval - Time interval between actions in seconds
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function runAutonomousMode(agent: any, interval = 10) {
console.log("Starting autonomous mode...");

// eslint-disable-next-line no-constant-condition
while (true) {
try {
const thought =
"Be creative and do something interesting on the blockchain. " +
"Choose an action or set of actions and execute it that highlights your abilities.";

const result = await agent.run({ message: thought });
console.log(result);
console.log("-------------------");

await new Promise(resolve => setTimeout(resolve, interval * 1000));
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
}
process.exit(1);
}
}
}

/**
* Run the agent interactively based on user input
*
* @param runner - The agent runner
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function runChatMode(runner: EnhancedRunner) {
console.log("Starting chat mode... Type 'exit' to end.");

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

const question = (prompt: string): Promise<string> =>
new Promise(resolve => rl.question(prompt, resolve));

try {
// eslint-disable-next-line no-constant-condition
while (true) {
const userInput = await question("\nPrompt: ");

if (userInput.toLowerCase() === "exit") {
break;
}

const result = await runner.ask(userInput);
console.log(result);
console.log("-------------------");
}
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
}
process.exit(1);
} finally {
rl.close();
}
}

/**
* Choose whether to run in autonomous or chat mode based on user input
*
* @returns Selected mode
*/
async function chooseMode(): Promise<"chat" | "auto"> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

const question = (prompt: string): Promise<string> =>
new Promise(resolve => rl.question(prompt, resolve));

// eslint-disable-next-line no-constant-condition
while (true) {
console.log("\nAvailable modes:");
console.log("1. chat - Interactive chat mode");
console.log("2. auto - Autonomous action mode");

const choice = (await question("\nChoose a mode (enter number or name): "))
.toLowerCase()
.trim();

if (choice === "1" || choice === "chat") {
rl.close();
return "chat";
} else if (choice === "2" || choice === "auto") {
rl.close();
return "auto";
}
console.log("Invalid choice. Please try again.");
}
}

/**
* Start the chatbot agent
*/
async function main() {
try {
const { runner } = await initializeAgent();
const mode = await chooseMode();

if (mode === "chat") {
await runChatMode(runner);
} else {
await runAutonomousMode(runner);
}
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
}
process.exit(1);
}
}

if (require.main === module) {
console.log("Starting Agent...");
main().catch(error => {
console.error("Fatal error:", error);
process.exit(1);
});
}
Loading
Loading