Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cointoss example integration #47

Merged
merged 26 commits into from
Mar 23, 2025
Merged
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
4 changes: 2 additions & 2 deletions .yarn/releases/yarn-4.6.0.cjs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This repository contains examples of agents that use the [XMTP](https://docs.xmt
- [grok](/examples/grok/): Integrate your agent with the Grok API
- [gaia](/examples/gaia/): Integrate with the Gaia API
- [coinbase-langchain](/examples/coinbase-langchain/): Agent that uses a CDP for gassless USDC on base
- [cointoss](/examples/cointoss/): Enabling group cointosses with friends inside a group chat

## Getting started

Expand Down
62 changes: 25 additions & 37 deletions examples/coinbase-langchain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,46 @@

A DeFi agent built using Langchain and powered by CDP SDK, operating over the XMTP messaging protocol.

## Features

- Process blockchain payments using natural language commands
- Advanced language processing using LangChain and OpenAI
- User-specific wallet management with flexible storage options (Redis or local file)
- XMTP messaging for secure, decentralized chat interactions
- Powered by CDP SDK for reliable blockchain operations and Langchain for AI Agent
![](./screenshot.png)

## Prerequisites

- Node.js (v20+)
- XMTP `node-sdk`
- [OpenAI](https://platform.openai.com/) API key
- [Coinbase Developer Platform](https://portal.cdp.coinbase.com) (CDP) API credentials
- Yarn package manager

## Usage Examples

Once the agent is running, you can interact with it using natural language commands:

### Basic prompts

- "Send 0.01 USDC to 0x1234..."
- "Check my wallet balance"
- [USDC Faucet](https://portal.cdp.coinbase.com/products/faucet)

## How it works
### Environment variables

This agent combines key technologies:
To run your XMTP agent, you must create a `.env` file with the following variables:

1. **XMTP protocol**: Secure decentralized messaging
2. **Langchain**: AI processing and conversation management
3. **CDP SDK**: Blockchain transaction handling
4. **Storage**: Redis or local file options
5. **OpenAI**: Natural language understanding

## Troubleshooting
```bash
WALLET_KEY= # the private key for the wallet
ENCRYPTION_KEY= # the encryption key for the wallet
# public key is

NETWORK_ID=base-sepolia # base-mainnet or others
OPENAI_API_KEY= # the OpenAI API key
CDP_API_KEY_NAME= # the name of the CDP API key
CDP_API_KEY_PRIVATE_KEY= # the private key for the CDP API key
XMTP_ENV=local # local, dev, production
```

### Connection issues
You can generate random xmtp keys with the following command:

- Verify XMTP environment settings (`local`, `dev`, or `production`)
- Check API credentials and connection strings
```tsx
yarn gen:keys <name>
```

### Transaction issues
> [!WARNING]
> Running the `gen:keys` or `gen:keys <name>` command will append keys to your existing `.env` file.

- Ensure sufficient wallet balance
- Verify network settings (default: base-sepolia)
- For testnets, obtain tokens from faucets
### Usage

### Storage issues
Example prompts:

- System falls back to local storage if Redis fails
- Check permissions and connection URLs
- "Send 0.01 USDC to 0x1234..."
- "Check my wallet balance"

## Run the agent

Expand Down
Binary file added examples/coinbase-langchain/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 11 additions & 47 deletions examples/coinbase-langchain/src/cdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,11 @@ import {
type WalletData,
} from "@coinbase/coinbase-sdk";
import { isAddress } from "viem";
import { validateEnvironment } from ".";
import { getWalletData, saveWalletData } from "./storage";
import type { XMTPUser } from "./types";

const coinbaseApiKeyName = process.env.CDP_API_KEY_NAME;
let coinbaseApiKeyPrivateKey = process.env.CDP_API_KEY_PRIVATE_KEY;
const networkId = process.env.NETWORK_ID ?? "base-sepolia";

if (!coinbaseApiKeyName || !coinbaseApiKeyPrivateKey || !networkId) {
console.error(
"Either networkId, CDP_API_KEY_NAME or CDP_API_KEY_PRIVATE_KEY must be set",
);
process.exit(1);
}
const { coinbaseApiKeyName, coinbaseApiKeyPrivateKey, networkId } =
validateEnvironment();

class WalletStorage {
async get(inboxId: string): Promise<string | undefined> {
Expand Down Expand Up @@ -56,14 +48,10 @@ class WalletStorage {

// Initialize Coinbase SDK
function initializeCoinbaseSDK(): boolean {
// Replace \\n with actual newlines if present in the private key
if (coinbaseApiKeyPrivateKey) {
coinbaseApiKeyPrivateKey = coinbaseApiKeyPrivateKey.replace(/\\n/g, "\n");
}
try {
Coinbase.configure({
apiKeyName: coinbaseApiKeyName as string,
privateKey: coinbaseApiKeyPrivateKey as string,
apiKeyName: coinbaseApiKeyName,
privateKey: coinbaseApiKeyPrivateKey,
});
console.log("Coinbase SDK initialized successfully, network:", networkId);
return true;
Expand All @@ -79,7 +67,6 @@ export type AgentWalletData = {
id: string;
wallet: Wallet;
data: WalletData;
human_address: string;
agent_address: string;
blockchain?: string;
state?: string;
Expand All @@ -89,21 +76,14 @@ export type AgentWalletData = {
// Wallet service class based on cointoss implementation
export class WalletService {
private walletStorage: WalletStorage;
private humanAddress: string;
private inboxId: string;
private sdkInitialized: boolean;

constructor(xmtpUser: XMTPUser) {
constructor(inboxId: string) {
this.sdkInitialized = initializeCoinbaseSDK();
this.walletStorage = new WalletStorage();
this.humanAddress = xmtpUser.address;
this.inboxId = xmtpUser.inboxId;
console.log(
"WalletService initialized with sender address",
this.humanAddress,
"and inboxId",
this.inboxId,
);
this.inboxId = inboxId;
console.log("WalletService initialized with sender inboxId", this.inboxId);
}

async createWallet(): Promise<AgentWalletData> {
Expand Down Expand Up @@ -135,27 +115,17 @@ export class WalletService {
const address = await wallet.getDefaultAddress();
const walletAddress = address.getId();

// Make the wallet address visible in the logs for funding
console.log("-----------------------------------------------------");
console.log(`NEW WALLET CREATED FOR USER: ${this.humanAddress}`);
console.log(`WALLET ADDRESS: ${walletAddress}`);
console.log(`NETWORK: ${networkId}`);
console.log(`SEND FUNDS TO THIS ADDRESS TO TEST: ${walletAddress}`);
console.log("-----------------------------------------------------");

const walletInfo: AgentWalletData = {
id: walletAddress,
wallet: wallet,
data: data,
human_address: this.humanAddress,
agent_address: walletAddress,
inboxId: this.inboxId,
};

console.log("Saving wallet data to storage...");
const walletInfoToStore = {
data: data,
human_address: this.humanAddress,
agent_address: walletAddress,
inboxId: this.inboxId,
};
Expand Down Expand Up @@ -222,7 +192,6 @@ export class WalletService {
id: importedWallet.getId() ?? "",
wallet: importedWallet,
data: walletInfo.data,
human_address: walletInfo.human_address,
agent_address: walletInfo.agent_address,
inboxId: walletInfo.inboxId,
};
Expand Down Expand Up @@ -286,23 +255,21 @@ export class WalletService {

async transfer(
inboxId: string,
humanAddress: string,
toAddress: string,
amount: number,
): Promise<Transfer | undefined> {
humanAddress = humanAddress.toLowerCase();
toAddress = toAddress.toLowerCase();

console.log("📤 TRANSFER INITIATED");
console.log(`💸 Amount: ${amount} USDC`);
console.log(`🔍 From user: ${humanAddress}`);
console.log(`🔍 From user: ${inboxId}`);
console.log(`🔍 To: ${toAddress}`);

// Get the source wallet
console.log(`🔑 Retrieving source wallet for user: ${humanAddress}...`);
console.log(`🔑 Retrieving source wallet for user: ${inboxId}...`);
const from = await this.getWallet(inboxId);
if (!from) {
console.error(`❌ No wallet found for sender: ${humanAddress}`);
console.error(`❌ No wallet found for sender: ${inboxId}`);
return undefined;
}
console.log(`✅ Source wallet found: ${from.agent_address}`);
Expand Down Expand Up @@ -365,9 +332,6 @@ export class WalletService {
try {
await transfer.wait();
console.log(`✅ Transfer completed successfully!`);
console.log(
`📝 Transaction details: ${JSON.stringify(transfer, null, 2)}`,
);
} catch (err) {
if (err instanceof TimeoutError) {
console.log(
Expand Down
44 changes: 22 additions & 22 deletions examples/coinbase-langchain/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,42 @@ import { getAddressOfMember } from "@helpers";
import type { Conversation, DecodedMessage } from "@xmtp/node-sdk";
import { initializeAgent, processMessage } from "./langchain";
import { initializeStorage } from "./storage";
import type { XMTPUser } from "./types";
import { initializeXmtpClient, startMessageListener } from "./xmtp";

/**
* Validates that required environment variables are set
*/
function validateEnvironment(): void {
const missingVars: string[] = [];

// Check required variables
export function validateEnvironment(): {
coinbaseApiKeyName: string;
coinbaseApiKeyPrivateKey: string;
networkId: string;
} {
const requiredVars = [
"OPENAI_API_KEY",
"CDP_API_KEY_NAME",
"CDP_API_KEY_PRIVATE_KEY",
"WALLET_KEY",
"XMTP_ENV",
"OPENAI_API_KEY",
"ENCRYPTION_KEY",
];

requiredVars.forEach((varName) => {
if (!process.env[varName]) {
missingVars.push(varName);
}
});
const missing = requiredVars.filter((v) => !process.env[v]);

// 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`);
});
if (missing.length) {
console.error("Missing env vars:", missing.join(", "));
process.exit(1);
}

// Replace \\n with actual newlines if present in the private key
if (process.env.CDP_API_KEY_PRIVATE_KEY) {
process.env.CDP_API_KEY_PRIVATE_KEY =
process.env.CDP_API_KEY_PRIVATE_KEY.replace(/\\n/g, "\n");
}
return {
coinbaseApiKeyName: process.env.CDP_API_KEY_NAME as string,
coinbaseApiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY as string,
networkId: process.env.NETWORK_ID as string,
};
}

/**
Expand All @@ -52,12 +56,8 @@ async function handleMessage(
console.log("Unable to find address, skipping");
return;
}
const xmtpUser: XMTPUser = {
inboxId,
address,
};
// Initialize or get the agent for this user
const { agent, config } = await initializeAgent(xmtpUser);
const { agent, config } = await initializeAgent(inboxId);

// Process the message with the agent
const response = await processMessage(
Expand Down
Loading