Skip to content

Commit 27e7a1d

Browse files
cointoss example integration (#47)
* cointoss working * moved cointoss to examples * build passing * lint passing * removed bet and game mentions * refactors * removed redis * unified storage * tosses * reivew ready * review * Revert "review" This reverts commit 8605c36. * fix lint * updated toss closing logic and fixed toss list issue * lint fixed * Removed human address * troubleshooting * fix duplicated wallets * Improved naming * fix tosses * fix tosses * images * added to readme --------- Co-authored-by: Fabri <fguespe@gmail.com>
1 parent 63f4282 commit 27e7a1d

26 files changed

+2324
-140
lines changed

.yarn/releases/yarn-4.6.0.cjs

+2-2
Large diffs are not rendered by default.

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This repository contains examples of agents that use the [XMTP](https://docs.xmt
1818
- [grok](/examples/grok/): Integrate your agent with the Grok API
1919
- [gaia](/examples/gaia/): Integrate with the Gaia API
2020
- [coinbase-langchain](/examples/coinbase-langchain/): Agent that uses a CDP for gassless USDC on base
21+
- [cointoss](/examples/cointoss/): Enabling group cointosses with friends inside a group chat
2122

2223
## Getting started
2324

examples/coinbase-langchain/README.md

+25-37
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,46 @@
22

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

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

137
## Prerequisites
148

159
- Node.js (v20+)
16-
- XMTP `node-sdk`
1710
- [OpenAI](https://platform.openai.com/) API key
1811
- [Coinbase Developer Platform](https://portal.cdp.coinbase.com) (CDP) API credentials
19-
- Yarn package manager
20-
21-
## Usage Examples
22-
23-
Once the agent is running, you can interact with it using natural language commands:
24-
25-
### Basic prompts
26-
27-
- "Send 0.01 USDC to 0x1234..."
28-
- "Check my wallet balance"
12+
- [USDC Faucet](https://portal.cdp.coinbase.com/products/faucet)
2913

30-
## How it works
14+
### Environment variables
3115

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

34-
1. **XMTP protocol**: Secure decentralized messaging
35-
2. **Langchain**: AI processing and conversation management
36-
3. **CDP SDK**: Blockchain transaction handling
37-
4. **Storage**: Redis or local file options
38-
5. **OpenAI**: Natural language understanding
39-
40-
## Troubleshooting
18+
```bash
19+
WALLET_KEY= # the private key for the wallet
20+
ENCRYPTION_KEY= # the encryption key for the wallet
21+
# public key is
22+
23+
NETWORK_ID=base-sepolia # base-mainnet or others
24+
OPENAI_API_KEY= # the OpenAI API key
25+
CDP_API_KEY_NAME= # the name of the CDP API key
26+
CDP_API_KEY_PRIVATE_KEY= # the private key for the CDP API key
27+
XMTP_ENV=local # local, dev, production
28+
```
4129

42-
### Connection issues
30+
You can generate random xmtp keys with the following command:
4331

44-
- Verify XMTP environment settings (`local`, `dev`, or `production`)
45-
- Check API credentials and connection strings
32+
```tsx
33+
yarn gen:keys <name>
34+
```
4635

47-
### Transaction issues
36+
> [!WARNING]
37+
> Running the `gen:keys` or `gen:keys <name>` command will append keys to your existing `.env` file.
4838
49-
- Ensure sufficient wallet balance
50-
- Verify network settings (default: base-sepolia)
51-
- For testnets, obtain tokens from faucets
39+
### Usage
5240

53-
### Storage issues
41+
Example prompts:
5442

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

5846
## Run the agent
5947

247 KB
Loading

examples/coinbase-langchain/src/cdp.ts

+11-47
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,11 @@ import {
66
type WalletData,
77
} from "@coinbase/coinbase-sdk";
88
import { isAddress } from "viem";
9+
import { validateEnvironment } from ".";
910
import { getWalletData, saveWalletData } from "./storage";
10-
import type { XMTPUser } from "./types";
1111

12-
const coinbaseApiKeyName = process.env.CDP_API_KEY_NAME;
13-
let coinbaseApiKeyPrivateKey = process.env.CDP_API_KEY_PRIVATE_KEY;
14-
const networkId = process.env.NETWORK_ID ?? "base-sepolia";
15-
16-
if (!coinbaseApiKeyName || !coinbaseApiKeyPrivateKey || !networkId) {
17-
console.error(
18-
"Either networkId, CDP_API_KEY_NAME or CDP_API_KEY_PRIVATE_KEY must be set",
19-
);
20-
process.exit(1);
21-
}
12+
const { coinbaseApiKeyName, coinbaseApiKeyPrivateKey, networkId } =
13+
validateEnvironment();
2214

2315
class WalletStorage {
2416
async get(inboxId: string): Promise<string | undefined> {
@@ -56,14 +48,10 @@ class WalletStorage {
5648

5749
// Initialize Coinbase SDK
5850
function initializeCoinbaseSDK(): boolean {
59-
// Replace \\n with actual newlines if present in the private key
60-
if (coinbaseApiKeyPrivateKey) {
61-
coinbaseApiKeyPrivateKey = coinbaseApiKeyPrivateKey.replace(/\\n/g, "\n");
62-
}
6351
try {
6452
Coinbase.configure({
65-
apiKeyName: coinbaseApiKeyName as string,
66-
privateKey: coinbaseApiKeyPrivateKey as string,
53+
apiKeyName: coinbaseApiKeyName,
54+
privateKey: coinbaseApiKeyPrivateKey,
6755
});
6856
console.log("Coinbase SDK initialized successfully, network:", networkId);
6957
return true;
@@ -79,7 +67,6 @@ export type AgentWalletData = {
7967
id: string;
8068
wallet: Wallet;
8169
data: WalletData;
82-
human_address: string;
8370
agent_address: string;
8471
blockchain?: string;
8572
state?: string;
@@ -89,21 +76,14 @@ export type AgentWalletData = {
8976
// Wallet service class based on cointoss implementation
9077
export class WalletService {
9178
private walletStorage: WalletStorage;
92-
private humanAddress: string;
9379
private inboxId: string;
9480
private sdkInitialized: boolean;
9581

96-
constructor(xmtpUser: XMTPUser) {
82+
constructor(inboxId: string) {
9783
this.sdkInitialized = initializeCoinbaseSDK();
9884
this.walletStorage = new WalletStorage();
99-
this.humanAddress = xmtpUser.address;
100-
this.inboxId = xmtpUser.inboxId;
101-
console.log(
102-
"WalletService initialized with sender address",
103-
this.humanAddress,
104-
"and inboxId",
105-
this.inboxId,
106-
);
85+
this.inboxId = inboxId;
86+
console.log("WalletService initialized with sender inboxId", this.inboxId);
10787
}
10888

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

138-
// Make the wallet address visible in the logs for funding
139-
console.log("-----------------------------------------------------");
140-
console.log(`NEW WALLET CREATED FOR USER: ${this.humanAddress}`);
141-
console.log(`WALLET ADDRESS: ${walletAddress}`);
142-
console.log(`NETWORK: ${networkId}`);
143-
console.log(`SEND FUNDS TO THIS ADDRESS TO TEST: ${walletAddress}`);
144-
console.log("-----------------------------------------------------");
145-
146118
const walletInfo: AgentWalletData = {
147119
id: walletAddress,
148120
wallet: wallet,
149121
data: data,
150-
human_address: this.humanAddress,
151122
agent_address: walletAddress,
152123
inboxId: this.inboxId,
153124
};
154125

155126
console.log("Saving wallet data to storage...");
156127
const walletInfoToStore = {
157128
data: data,
158-
human_address: this.humanAddress,
159129
agent_address: walletAddress,
160130
inboxId: this.inboxId,
161131
};
@@ -222,7 +192,6 @@ export class WalletService {
222192
id: importedWallet.getId() ?? "",
223193
wallet: importedWallet,
224194
data: walletInfo.data,
225-
human_address: walletInfo.human_address,
226195
agent_address: walletInfo.agent_address,
227196
inboxId: walletInfo.inboxId,
228197
};
@@ -286,23 +255,21 @@ export class WalletService {
286255

287256
async transfer(
288257
inboxId: string,
289-
humanAddress: string,
290258
toAddress: string,
291259
amount: number,
292260
): Promise<Transfer | undefined> {
293-
humanAddress = humanAddress.toLowerCase();
294261
toAddress = toAddress.toLowerCase();
295262

296263
console.log("📤 TRANSFER INITIATED");
297264
console.log(`💸 Amount: ${amount} USDC`);
298-
console.log(`🔍 From user: ${humanAddress}`);
265+
console.log(`🔍 From user: ${inboxId}`);
299266
console.log(`🔍 To: ${toAddress}`);
300267

301268
// Get the source wallet
302-
console.log(`🔑 Retrieving source wallet for user: ${humanAddress}...`);
269+
console.log(`🔑 Retrieving source wallet for user: ${inboxId}...`);
303270
const from = await this.getWallet(inboxId);
304271
if (!from) {
305-
console.error(`❌ No wallet found for sender: ${humanAddress}`);
272+
console.error(`❌ No wallet found for sender: ${inboxId}`);
306273
return undefined;
307274
}
308275
console.log(`✅ Source wallet found: ${from.agent_address}`);
@@ -365,9 +332,6 @@ export class WalletService {
365332
try {
366333
await transfer.wait();
367334
console.log(`✅ Transfer completed successfully!`);
368-
console.log(
369-
`📝 Transaction details: ${JSON.stringify(transfer, null, 2)}`,
370-
);
371335
} catch (err) {
372336
if (err instanceof TimeoutError) {
373337
console.log(

examples/coinbase-langchain/src/index.ts

+22-22
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,42 @@ import { getAddressOfMember } from "@helpers";
33
import type { Conversation, DecodedMessage } from "@xmtp/node-sdk";
44
import { initializeAgent, processMessage } from "./langchain";
55
import { initializeStorage } from "./storage";
6-
import type { XMTPUser } from "./types";
76
import { initializeXmtpClient, startMessageListener } from "./xmtp";
87

98
/**
109
* Validates that required environment variables are set
1110
*/
12-
function validateEnvironment(): void {
13-
const missingVars: string[] = [];
14-
15-
// Check required variables
11+
export function validateEnvironment(): {
12+
coinbaseApiKeyName: string;
13+
coinbaseApiKeyPrivateKey: string;
14+
networkId: string;
15+
} {
1616
const requiredVars = [
17-
"OPENAI_API_KEY",
1817
"CDP_API_KEY_NAME",
1918
"CDP_API_KEY_PRIVATE_KEY",
2019
"WALLET_KEY",
20+
"XMTP_ENV",
21+
"OPENAI_API_KEY",
2122
"ENCRYPTION_KEY",
2223
];
2324

24-
requiredVars.forEach((varName) => {
25-
if (!process.env[varName]) {
26-
missingVars.push(varName);
27-
}
28-
});
25+
const missing = requiredVars.filter((v) => !process.env[v]);
2926

30-
// Exit if any required variables are missing
31-
if (missingVars.length > 0) {
32-
console.error("Error: Required environment variables are not set");
33-
missingVars.forEach((varName) => {
34-
console.error(`${varName}=your_${varName.toLowerCase()}_here`);
35-
});
27+
if (missing.length) {
28+
console.error("Missing env vars:", missing.join(", "));
3629
process.exit(1);
3730
}
31+
32+
// Replace \\n with actual newlines if present in the private key
33+
if (process.env.CDP_API_KEY_PRIVATE_KEY) {
34+
process.env.CDP_API_KEY_PRIVATE_KEY =
35+
process.env.CDP_API_KEY_PRIVATE_KEY.replace(/\\n/g, "\n");
36+
}
37+
return {
38+
coinbaseApiKeyName: process.env.CDP_API_KEY_NAME as string,
39+
coinbaseApiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY as string,
40+
networkId: process.env.NETWORK_ID as string,
41+
};
3842
}
3943

4044
/**
@@ -52,12 +56,8 @@ async function handleMessage(
5256
console.log("Unable to find address, skipping");
5357
return;
5458
}
55-
const xmtpUser: XMTPUser = {
56-
inboxId,
57-
address,
58-
};
5959
// Initialize or get the agent for this user
60-
const { agent, config } = await initializeAgent(xmtpUser);
60+
const { agent, config } = await initializeAgent(inboxId);
6161

6262
// Process the message with the agent
6363
const response = await processMessage(

0 commit comments

Comments
 (0)