Skip to content

Commit

Permalink
add external router path and new tweet generation
Browse files Browse the repository at this point in the history
  • Loading branch information
0x369D committed Jan 9, 2025
1 parent 2790821 commit 27bd9c3
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 14 deletions.
113 changes: 101 additions & 12 deletions packages/client-coinbase/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import {
Memory,
Content,
HandlerCallback,
stringToUuid
stringToUuid,
composeContext,
generateText,
ModelClass
} from "@elizaos/core";
import { postTweet } from "@elizaos/plugin-twitter";
import express from "express";
Expand All @@ -17,7 +20,6 @@ export class CoinbaseClient implements Client {
private port: number;

constructor(runtime: IAgentRuntime) {

this.runtime = runtime;
this.server = express();
this.port = Number(runtime.getSetting("COINBASE_WEBHOOK_PORT")) || 3001;
Expand All @@ -37,6 +39,17 @@ export class CoinbaseClient implements Client {
private setupWebhookEndpoint() {
this.server.use(express.json());

// Add CORS middleware to allow external requests
this.server.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'POST');
res.header('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});

// Add webhook validation middleware
const validateWebhook = (req: express.Request, res: express.Response, next: express.NextFunction) => {
const event = req.body as WebhookEvent;
Expand All @@ -51,20 +64,26 @@ export class CoinbaseClient implements Client {
next();
};

// Add health check endpoint
this.server.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});

// Main webhook endpoint
this.server.post("/webhook", validateWebhook, async (req, res) => {
try {
const event = req.body as WebhookEvent;
await this.handleWebhookEvent(event);
res.status(200).send("OK");
res.status(200).json({ status: "success" });
} catch (error) {
elizaLogger.error("Error processing webhook:", error);
res.status(500).send("Internal Server Error");
res.status(500).json({ error: "Internal Server Error" });
}
});

return new Promise<void>((resolve, reject) => {
try {
this.server.listen(this.port, () => {
this.server.listen(this.port, '0.0.0.0', () => {
elizaLogger.info(`Webhook server listening on port ${this.port}`);
resolve();
});
Expand All @@ -74,12 +93,86 @@ export class CoinbaseClient implements Client {
});
}

private async generateTweetContent(event: WebhookEvent, _tradeAmount: number, formattedTimestamp: string): Promise<string> {
try {
const roomId = stringToUuid("coinbase-trading");
const amount = Number(this.runtime.getSetting('COINBASE_TRADING_AMOUNT')) ?? 1;

const tradeTweetTemplate = `
# Task
Create an engaging and unique tweet announcing a Coinbase trade. Be creative but professional.
Trade details:
- ${event.event.toUpperCase()} order for ${event.ticker}
- Trading amount: $${amount.toFixed(2)}
- Current price: $${Number(event.price).toFixed(2)}
- Time: ${formattedTimestamp}
Requirements:
1. Must be under 180 characters
2. Use 1-2 relevant emojis
3. No hashtags
4. Vary the wording each time to keep it fresh and engaging
5. Can mention market conditions, timing, or strategy when relevant
6. Keep it professional but conversational
7. Include the key information: action, amount, ticker, and price
Example variations for buys:
"📈 Just added $1,000 of BTC to the portfolio at $50,000.00"
"🎯 Strategic BTC purchase: $1,000 at $50,000.00"
Example variations for sells:
"💫 Executed BTC position: Sold $1,000 at $52,000.00"
"📊 Strategic exit: Released $1,000 of BTC at $52,000.00"
Generate only the tweet text, no commentary or markdown.`;

const context = composeContext({
template: tradeTweetTemplate,
state: {
event: event.event.toUpperCase(),
ticker: event.ticker,
amount: `${amount.toFixed(2)}`,
price: `${Number(event.price).toFixed(2)}`,
timestamp: formattedTimestamp,
bio: '',
lore: '',
messageDirections: '',
postDirections: '',
persona: '',
personality: '',
role: '',
scenario: '',
roomId,
actors: '',
recentMessages: '',
recentMessagesData: []
}
});

const tweetContent = await generateText({
runtime: this.runtime,
context,
modelClass: ModelClass.SMALL,
});

const trimmedContent = tweetContent.trim();
return trimmedContent.length > 180 ? trimmedContent.substring(0, 177) + "..." : trimmedContent;

} catch (error) {
elizaLogger.error("Error generating tweet content:", error);
const amount = Number(this.runtime.getSetting('COINBASE_TRADING_AMOUNT')) ?? 1;
const fallbackTweet = `🚀 ${event.event.toUpperCase()}: $${amount.toFixed(2)} of ${event.ticker} at $${Number(event.price).toFixed(2)}`;
return fallbackTweet;
}
}

private async handleWebhookEvent(event: WebhookEvent) {
const roomId = stringToUuid("coinbase-trading");
await this.runtime.ensureRoomExists(roomId);
await this.runtime.ensureParticipantInRoom(this.runtime.agentId, roomId);

const amount = this.runtime.getSetting('COINBASE_TRADING_AMOUNT') ?? 1;
const amount = Number(this.runtime.getSetting('COINBASE_TRADING_AMOUNT')) ?? 1;
const memory: Memory = {
id: stringToUuid(`coinbase-${event.timestamp}`),
userId: this.runtime.agentId,
Expand Down Expand Up @@ -118,13 +211,9 @@ export class CoinbaseClient implements Client {
timeZoneName: 'short'
}).format(new Date(event.timestamp));

const tweetContent = `🚀 ${event.event.toUpperCase()} for ${event.ticker}!
Amount: $${amount}.
Price: $${event.price}.
Time: ${formattedTimestamp} 🌀`;

try {
elizaLogger.info("Tweet content:", tweetContent);
const tweetContent = await this.generateTweetContent(event, amount, formattedTimestamp);
elizaLogger.info("Generated tweet content:", tweetContent);
const response = await postTweet(tweetContent);
elizaLogger.info("Tweet response:", response);
} catch (error) {
Expand Down
3 changes: 2 additions & 1 deletion packages/client-direct/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"cors": "2.8.5",
"discord.js": "14.16.3",
"express": "4.21.1",
"multer": "1.4.5-lts.1"
"multer": "1.4.5-lts.1",
"@elizaos/client-coinbase": "workspace:*"
},
"devDependencies": {
"tsup": "8.3.5",
Expand Down
87 changes: 87 additions & 0 deletions packages/client-direct/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { REST, Routes } from "discord.js";
import { DirectClient } from ".";
import { stringToUuid } from "@elizaos/core";
import { WebhookEvent } from "@elizaos/client-coinbase";

export function createApiRouter(
agents: Map<string, AgentRuntime>,
Expand All @@ -28,6 +29,49 @@ export function createApiRouter(
})
);

router.get("/webhook/coinbase/health", (req, res) => {
elizaLogger.info("Health check received");
res.status(200).json({ status: "ok" });
});

router.post("/webhook/coinbase/:agentId", async (req, res) => {
elizaLogger.info("Webhook received for agent:", req.params.agentId);
const agentId = req.params.agentId;
const runtime = agents.get(agentId);

if (!runtime) {
res.status(404).json({ error: "Agent not found" });
return;
}

// Validate the webhook payload
const event = req.body as WebhookEvent;
if (!event.event || !event.ticker || !event.timestamp || !event.price) {
res.status(400).json({ error: "Invalid webhook payload" });
return;
}
if (event.event !== 'buy' && event.event !== 'sell') {
res.status(400).json({ error: "Invalid event type" });
return;
}

try {
// Access the coinbase client through the runtime
const coinbaseClient = runtime.clients.coinbase as any;
if (!coinbaseClient) {
res.status(400).json({ error: "Coinbase client not initialized for this agent" });
return;
}

// Forward the webhook event to the client's handleWebhookEvent method
await coinbaseClient.handleWebhookEvent(event);
res.status(200).json({ status: "success" });
} catch (error) {
elizaLogger.error("Error processing Coinbase webhook:", error);
res.status(500).json({ error: "Internal Server Error" });
}
});

router.get("/", (req, res) => {
res.send("Welcome, this is the REST API!");
});
Expand Down Expand Up @@ -183,5 +227,48 @@ export function createApiRouter(
}
});

// Add Coinbase webhook forwarding endpoint
router.post("/webhook/coinbase/:agentId", async (req, res) => {
const agentId = req.params.agentId;
const runtime = agents.get(agentId);

if (!runtime) {
res.status(404).json({ error: "Agent not found" });
return;
}

// Validate the webhook payload
const event = req.body as WebhookEvent;
if (!event.event || !event.ticker || !event.timestamp || !event.price) {
res.status(400).json({ error: "Invalid webhook payload" });
return;
}
if (event.event !== 'buy' && event.event !== 'sell') {
res.status(400).json({ error: "Invalid event type" });
return;
}

try {
// Access the coinbase client through the runtime
const coinbaseClient = runtime.clients.coinbase as any;
if (!coinbaseClient) {
res.status(400).json({ error: "Coinbase client not initialized for this agent" });
return;
}

// Forward the webhook event to the client's handleWebhookEvent method
await coinbaseClient.handleWebhookEvent(event);
res.status(200).json({ status: "success" });
} catch (error) {
elizaLogger.error("Error processing Coinbase webhook:", error);
res.status(500).json({ error: "Internal Server Error" });
}
});

// Add health check endpoint for Coinbase webhook
router.get("/webhook/coinbase/health", (req, res) => {
res.status(200).json({ status: "ok" });
});

return router;
}
8 changes: 7 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 27bd9c3

Please sign in to comment.