@@ -5,7 +5,10 @@ import {
5
5
Memory ,
6
6
Content ,
7
7
HandlerCallback ,
8
- stringToUuid
8
+ stringToUuid ,
9
+ composeContext ,
10
+ generateText ,
11
+ ModelClass
9
12
} from "@elizaos/core" ;
10
13
import { postTweet } from "@elizaos/plugin-twitter" ;
11
14
import express from "express" ;
@@ -17,7 +20,6 @@ export class CoinbaseClient implements Client {
17
20
private port : number ;
18
21
19
22
constructor ( runtime : IAgentRuntime ) {
20
-
21
23
this . runtime = runtime ;
22
24
this . server = express ( ) ;
23
25
this . port = Number ( runtime . getSetting ( "COINBASE_WEBHOOK_PORT" ) ) || 3001 ;
@@ -37,6 +39,17 @@ export class CoinbaseClient implements Client {
37
39
private setupWebhookEndpoint ( ) {
38
40
this . server . use ( express . json ( ) ) ;
39
41
42
+ // Add CORS middleware to allow external requests
43
+ this . server . use ( ( req , res , next ) => {
44
+ res . header ( 'Access-Control-Allow-Origin' , '*' ) ;
45
+ res . header ( 'Access-Control-Allow-Methods' , 'POST' ) ;
46
+ res . header ( 'Access-Control-Allow-Headers' , 'Content-Type' ) ;
47
+ if ( req . method === 'OPTIONS' ) {
48
+ return res . sendStatus ( 200 ) ;
49
+ }
50
+ next ( ) ;
51
+ } ) ;
52
+
40
53
// Add webhook validation middleware
41
54
const validateWebhook = ( req : express . Request , res : express . Response , next : express . NextFunction ) => {
42
55
const event = req . body as WebhookEvent ;
@@ -51,20 +64,26 @@ export class CoinbaseClient implements Client {
51
64
next ( ) ;
52
65
} ;
53
66
67
+ // Add health check endpoint
68
+ this . server . get ( '/health' , ( req , res ) => {
69
+ res . status ( 200 ) . json ( { status : 'ok' } ) ;
70
+ } ) ;
71
+
72
+ // Main webhook endpoint
54
73
this . server . post ( "/webhook" , validateWebhook , async ( req , res ) => {
55
74
try {
56
75
const event = req . body as WebhookEvent ;
57
76
await this . handleWebhookEvent ( event ) ;
58
- res . status ( 200 ) . send ( "OK" ) ;
77
+ res . status ( 200 ) . json ( { status : "success" } ) ;
59
78
} catch ( error ) {
60
79
elizaLogger . error ( "Error processing webhook:" , error ) ;
61
- res . status ( 500 ) . send ( "Internal Server Error" ) ;
80
+ res . status ( 500 ) . json ( { error : "Internal Server Error" } ) ;
62
81
}
63
82
} ) ;
64
83
65
84
return new Promise < void > ( ( resolve , reject ) => {
66
85
try {
67
- this . server . listen ( this . port , ( ) => {
86
+ this . server . listen ( this . port , '0.0.0.0' , ( ) => {
68
87
elizaLogger . info ( `Webhook server listening on port ${ this . port } ` ) ;
69
88
resolve ( ) ;
70
89
} ) ;
@@ -74,12 +93,86 @@ export class CoinbaseClient implements Client {
74
93
} ) ;
75
94
}
76
95
96
+ private async generateTweetContent ( event : WebhookEvent , _tradeAmount : number , formattedTimestamp : string ) : Promise < string > {
97
+ try {
98
+ const roomId = stringToUuid ( "coinbase-trading" ) ;
99
+ const amount = Number ( this . runtime . getSetting ( 'COINBASE_TRADING_AMOUNT' ) ) ?? 1 ;
100
+
101
+ const tradeTweetTemplate = `
102
+ # Task
103
+ Create an engaging and unique tweet announcing a Coinbase trade. Be creative but professional.
104
+
105
+ Trade details:
106
+ - ${ event . event . toUpperCase ( ) } order for ${ event . ticker }
107
+ - Trading amount: $${ amount . toFixed ( 2 ) }
108
+ - Current price: $${ Number ( event . price ) . toFixed ( 2 ) }
109
+ - Time: ${ formattedTimestamp }
110
+
111
+ Requirements:
112
+ 1. Must be under 180 characters
113
+ 2. Use 1-2 relevant emojis
114
+ 3. No hashtags
115
+ 4. Vary the wording each time to keep it fresh and engaging
116
+ 5. Can mention market conditions, timing, or strategy when relevant
117
+ 6. Keep it professional but conversational
118
+ 7. Include the key information: action, amount, ticker, and price
119
+
120
+ Example variations for buys:
121
+ "📈 Just added $1,000 of BTC to the portfolio at $50,000.00"
122
+ "🎯 Strategic BTC purchase: $1,000 at $50,000.00"
123
+
124
+ Example variations for sells:
125
+ "💫 Executed BTC position: Sold $1,000 at $52,000.00"
126
+ "📊 Strategic exit: Released $1,000 of BTC at $52,000.00"
127
+
128
+ Generate only the tweet text, no commentary or markdown.` ;
129
+
130
+ const context = composeContext ( {
131
+ template : tradeTweetTemplate ,
132
+ state : {
133
+ event : event . event . toUpperCase ( ) ,
134
+ ticker : event . ticker ,
135
+ amount : `${ amount . toFixed ( 2 ) } ` ,
136
+ price : `${ Number ( event . price ) . toFixed ( 2 ) } ` ,
137
+ timestamp : formattedTimestamp ,
138
+ bio : '' ,
139
+ lore : '' ,
140
+ messageDirections : '' ,
141
+ postDirections : '' ,
142
+ persona : '' ,
143
+ personality : '' ,
144
+ role : '' ,
145
+ scenario : '' ,
146
+ roomId,
147
+ actors : '' ,
148
+ recentMessages : '' ,
149
+ recentMessagesData : [ ]
150
+ }
151
+ } ) ;
152
+
153
+ const tweetContent = await generateText ( {
154
+ runtime : this . runtime ,
155
+ context,
156
+ modelClass : ModelClass . SMALL ,
157
+ } ) ;
158
+
159
+ const trimmedContent = tweetContent . trim ( ) ;
160
+ return trimmedContent . length > 180 ? trimmedContent . substring ( 0 , 177 ) + "..." : trimmedContent ;
161
+
162
+ } catch ( error ) {
163
+ elizaLogger . error ( "Error generating tweet content:" , error ) ;
164
+ const amount = Number ( this . runtime . getSetting ( 'COINBASE_TRADING_AMOUNT' ) ) ?? 1 ;
165
+ const fallbackTweet = `🚀 ${ event . event . toUpperCase ( ) } : $${ amount . toFixed ( 2 ) } of ${ event . ticker } at $${ Number ( event . price ) . toFixed ( 2 ) } ` ;
166
+ return fallbackTweet ;
167
+ }
168
+ }
169
+
77
170
private async handleWebhookEvent ( event : WebhookEvent ) {
78
171
const roomId = stringToUuid ( "coinbase-trading" ) ;
79
172
await this . runtime . ensureRoomExists ( roomId ) ;
80
173
await this . runtime . ensureParticipantInRoom ( this . runtime . agentId , roomId ) ;
81
174
82
- const amount = this . runtime . getSetting ( 'COINBASE_TRADING_AMOUNT' ) ?? 1 ;
175
+ const amount = Number ( this . runtime . getSetting ( 'COINBASE_TRADING_AMOUNT' ) ) ?? 1 ;
83
176
const memory : Memory = {
84
177
id : stringToUuid ( `coinbase-${ event . timestamp } ` ) ,
85
178
userId : this . runtime . agentId ,
@@ -118,13 +211,9 @@ export class CoinbaseClient implements Client {
118
211
timeZoneName : 'short'
119
212
} ) . format ( new Date ( event . timestamp ) ) ;
120
213
121
- const tweetContent = `🚀 ${ event . event . toUpperCase ( ) } for ${ event . ticker } !
122
- Amount: $${ amount } .
123
- Price: $${ event . price } .
124
- Time: ${ formattedTimestamp } 🌀` ;
125
-
126
214
try {
127
- elizaLogger . info ( "Tweet content:" , tweetContent ) ;
215
+ const tweetContent = await this . generateTweetContent ( event , amount , formattedTimestamp ) ;
216
+ elizaLogger . info ( "Generated tweet content:" , tweetContent ) ;
128
217
const response = await postTweet ( tweetContent ) ;
129
218
elizaLogger . info ( "Tweet response:" , response ) ;
130
219
} catch ( error ) {
0 commit comments