create-polyclaw-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +15 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +39 -0
- package/dist/api.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +182 -0
- package/dist/index.js.map +1 -0
- package/dist/scaffold.d.ts +16 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +543 -0
- package/dist/scaffold.js.map +1 -0
- package/package.json +25 -0
- package/src/api.ts +61 -0
- package/src/index.ts +211 -0
- package/src/scaffold.ts +570 -0
- package/tsconfig.json +11 -0
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Convert agent name to kebab-case directory name
|
|
5
|
+
*/
|
|
6
|
+
export function nameToDir(name) {
|
|
7
|
+
return name.toLowerCase().replace(/\s+/g, '-');
|
|
8
|
+
}
|
|
9
|
+
export function scaffoldProject(options) {
|
|
10
|
+
const projectDir = join(process.cwd(), options.dirName);
|
|
11
|
+
mkdirSync(projectDir, { recursive: true });
|
|
12
|
+
// Write .env file with credentials
|
|
13
|
+
const aiKeyLine = options.aiProvider === 'anthropic'
|
|
14
|
+
? `\nANTHROPIC_API_KEY=${options.aiApiKey || 'your-anthropic-api-key'}\n`
|
|
15
|
+
: options.aiProvider === 'openai'
|
|
16
|
+
? `\nOPENAI_API_KEY=${options.aiApiKey || 'your-openai-api-key'}\n`
|
|
17
|
+
: '';
|
|
18
|
+
const envContent = `# Polyclaw Agent: ${options.name}
|
|
19
|
+
# Generated by create-polyclaw-agent
|
|
20
|
+
|
|
21
|
+
POLYCLAW_API_KEY=${options.apiKey}
|
|
22
|
+
POLYCLAW_API_SECRET=${options.apiSecret}
|
|
23
|
+
POLYCLAW_API_URL=https://api.polyclaw.co
|
|
24
|
+
${aiKeyLine}`;
|
|
25
|
+
writeFileSync(join(projectDir, '.env'), envContent);
|
|
26
|
+
if (options.language === 'python') {
|
|
27
|
+
scaffoldPython(projectDir, options);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
scaffoldTypeScript(projectDir, options);
|
|
31
|
+
}
|
|
32
|
+
return projectDir;
|
|
33
|
+
}
|
|
34
|
+
function scaffoldPython(dir, options) {
|
|
35
|
+
// AI-specific imports and analysis function
|
|
36
|
+
const aiImport = options.aiProvider === 'anthropic'
|
|
37
|
+
? `import anthropic`
|
|
38
|
+
: options.aiProvider === 'openai'
|
|
39
|
+
? `import openai`
|
|
40
|
+
: '';
|
|
41
|
+
const aiKeyEnv = options.aiProvider === 'anthropic'
|
|
42
|
+
? `AI_API_KEY = os.getenv("ANTHROPIC_API_KEY")`
|
|
43
|
+
: options.aiProvider === 'openai'
|
|
44
|
+
? `AI_API_KEY = os.getenv("OPENAI_API_KEY")`
|
|
45
|
+
: '';
|
|
46
|
+
const aiAnalyzeFunc = options.aiProvider === 'anthropic' ? `
|
|
47
|
+
def analyze_markets(markets):
|
|
48
|
+
"""Use Claude to analyze prediction markets and pick the best bets."""
|
|
49
|
+
client = anthropic.Anthropic(api_key=AI_API_KEY)
|
|
50
|
+
|
|
51
|
+
# Format markets for the AI
|
|
52
|
+
market_summaries = []
|
|
53
|
+
for i, m in enumerate(markets):
|
|
54
|
+
prices = m.get("outcomePrices", [0.5, 0.5])
|
|
55
|
+
yes_price = float(prices[0]) if prices else 0.5
|
|
56
|
+
no_price = float(prices[1]) if len(prices) > 1 else 1 - yes_price
|
|
57
|
+
market_summaries.append(
|
|
58
|
+
f"{i+1}. {m.get('question', 'Unknown')}\\n"
|
|
59
|
+
f" Yes: {yes_price:.0%} | No: {no_price:.0%} | "
|
|
60
|
+
f"Volume: \${float(m.get('volume', 0)):,.0f} | "
|
|
61
|
+
f"ID: {m.get('id', '')}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
markets_text = "\\n".join(market_summaries)
|
|
65
|
+
|
|
66
|
+
response = client.messages.create(
|
|
67
|
+
model="claude-sonnet-4-20250514",
|
|
68
|
+
max_tokens=1024,
|
|
69
|
+
messages=[{
|
|
70
|
+
"role": "user",
|
|
71
|
+
"content": f"""You are a prediction market analyst. Analyze these Polymarket markets and pick up to 3 best betting opportunities.
|
|
72
|
+
|
|
73
|
+
Markets:
|
|
74
|
+
{markets_text}
|
|
75
|
+
|
|
76
|
+
For each pick, respond in this exact JSON format (no markdown, just raw JSON array):
|
|
77
|
+
[
|
|
78
|
+
{{
|
|
79
|
+
"market_index": 1,
|
|
80
|
+
"outcome": "Yes" or "No",
|
|
81
|
+
"side": "BUY",
|
|
82
|
+
"confidence": 0.0 to 1.0,
|
|
83
|
+
"recommended_amount": 5 to 50,
|
|
84
|
+
"reasoning": "One sentence explaining why this is a good bet"
|
|
85
|
+
}}
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
Rules:
|
|
89
|
+
- Pick 1-3 markets that look like good opportunities
|
|
90
|
+
- Consider the current price, volume, and whether the market seems mispriced
|
|
91
|
+
- Be diverse -- don't pick markets that are all about the same topic
|
|
92
|
+
- Higher confidence = stronger conviction (0.5 = coin flip, 0.9 = very confident)
|
|
93
|
+
- Recommended amount should reflect your confidence (higher confidence = more)
|
|
94
|
+
"""
|
|
95
|
+
}]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Parse the AI response
|
|
99
|
+
try:
|
|
100
|
+
picks = json.loads(response.content[0].text)
|
|
101
|
+
model_name = response.model
|
|
102
|
+
return picks, model_name
|
|
103
|
+
except (json.JSONDecodeError, IndexError):
|
|
104
|
+
print(f" AI response: {response.content[0].text}")
|
|
105
|
+
return [], response.model
|
|
106
|
+
` : options.aiProvider === 'openai' ? `
|
|
107
|
+
def analyze_markets(markets):
|
|
108
|
+
"""Use GPT to analyze prediction markets and pick the best bets."""
|
|
109
|
+
client = openai.OpenAI(api_key=AI_API_KEY)
|
|
110
|
+
|
|
111
|
+
# Format markets for the AI
|
|
112
|
+
market_summaries = []
|
|
113
|
+
for i, m in enumerate(markets):
|
|
114
|
+
prices = m.get("outcomePrices", [0.5, 0.5])
|
|
115
|
+
yes_price = float(prices[0]) if prices else 0.5
|
|
116
|
+
no_price = float(prices[1]) if len(prices) > 1 else 1 - yes_price
|
|
117
|
+
market_summaries.append(
|
|
118
|
+
f"{i+1}. {m.get('question', 'Unknown')}\\n"
|
|
119
|
+
f" Yes: {yes_price:.0%} | No: {no_price:.0%} | "
|
|
120
|
+
f"Volume: \${float(m.get('volume', 0)):,.0f} | "
|
|
121
|
+
f"ID: {m.get('id', '')}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
markets_text = "\\n".join(market_summaries)
|
|
125
|
+
|
|
126
|
+
response = client.chat.completions.create(
|
|
127
|
+
model="gpt-4o",
|
|
128
|
+
messages=[{
|
|
129
|
+
"role": "user",
|
|
130
|
+
"content": f"""You are a prediction market analyst. Analyze these Polymarket markets and pick up to 3 best betting opportunities.
|
|
131
|
+
|
|
132
|
+
Markets:
|
|
133
|
+
{markets_text}
|
|
134
|
+
|
|
135
|
+
For each pick, respond in this exact JSON format (no markdown, just raw JSON array):
|
|
136
|
+
[
|
|
137
|
+
{{
|
|
138
|
+
"market_index": 1,
|
|
139
|
+
"outcome": "Yes" or "No",
|
|
140
|
+
"side": "BUY",
|
|
141
|
+
"confidence": 0.0 to 1.0,
|
|
142
|
+
"recommended_amount": 5 to 50,
|
|
143
|
+
"reasoning": "One sentence explaining why this is a good bet"
|
|
144
|
+
}}
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
Rules:
|
|
148
|
+
- Pick 1-3 markets that look like good opportunities
|
|
149
|
+
- Consider the current price, volume, and whether the market seems mispriced
|
|
150
|
+
- Be diverse -- don't pick markets that are all about the same topic
|
|
151
|
+
- Higher confidence = stronger conviction (0.5 = coin flip, 0.9 = very confident)
|
|
152
|
+
- Recommended amount should reflect your confidence (higher confidence = more)
|
|
153
|
+
"""
|
|
154
|
+
}],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Parse the AI response
|
|
158
|
+
try:
|
|
159
|
+
picks = json.loads(response.choices[0].message.content)
|
|
160
|
+
model_name = response.model
|
|
161
|
+
return picks, model_name
|
|
162
|
+
except (json.JSONDecodeError, IndexError):
|
|
163
|
+
print(f" AI response: {response.choices[0].message.content}")
|
|
164
|
+
return [], response.model
|
|
165
|
+
` : `
|
|
166
|
+
def analyze_markets(markets):
|
|
167
|
+
"""Placeholder: Add your own AI logic here to analyze markets."""
|
|
168
|
+
# This is where you connect your AI model.
|
|
169
|
+
# Return a list of picks in this format:
|
|
170
|
+
# [{"market_index": 1, "outcome": "Yes", "side": "BUY",
|
|
171
|
+
# "confidence": 0.7, "recommended_amount": 10,
|
|
172
|
+
# "reasoning": "Your analysis here"}]
|
|
173
|
+
#
|
|
174
|
+
# Example with OpenAI:
|
|
175
|
+
# client = openai.OpenAI()
|
|
176
|
+
# response = client.chat.completions.create(model="gpt-4o", ...)
|
|
177
|
+
#
|
|
178
|
+
# Example with Anthropic:
|
|
179
|
+
# client = anthropic.Anthropic()
|
|
180
|
+
# response = client.messages.create(model="claude-sonnet-4-20250514", ...)
|
|
181
|
+
|
|
182
|
+
print(" No AI configured. Add your API key to .env and update bot.py")
|
|
183
|
+
print(" See https://polyclaw.co/docs for examples.")
|
|
184
|
+
return [], "none"
|
|
185
|
+
`;
|
|
186
|
+
const botPy = `"""
|
|
187
|
+
${options.name} - Polyclaw AI Trading Agent
|
|
188
|
+
|
|
189
|
+
This bot connects to Polyclaw, fetches prediction markets from Polymarket,
|
|
190
|
+
uses AI to analyze them, and submits trade proposals for you to review.
|
|
191
|
+
|
|
192
|
+
Usage:
|
|
193
|
+
python3 -m venv venv && source venv/bin/activate
|
|
194
|
+
pip install -r requirements.txt
|
|
195
|
+
python bot.py
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
import os
|
|
199
|
+
import json
|
|
200
|
+
import time
|
|
201
|
+
import hmac
|
|
202
|
+
import hashlib
|
|
203
|
+
import requests
|
|
204
|
+
from dotenv import load_dotenv
|
|
205
|
+
${aiImport}
|
|
206
|
+
|
|
207
|
+
load_dotenv()
|
|
208
|
+
|
|
209
|
+
API_KEY = os.getenv("POLYCLAW_API_KEY")
|
|
210
|
+
API_SECRET = os.getenv("POLYCLAW_API_SECRET")
|
|
211
|
+
BASE_URL = os.getenv("POLYCLAW_API_URL", "https://api.polyclaw.co")
|
|
212
|
+
${aiKeyEnv}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class PolyclawClient:
|
|
216
|
+
def __init__(self, api_key: str, api_secret: str, base_url: str):
|
|
217
|
+
self.api_key = api_key
|
|
218
|
+
self.base_url = base_url
|
|
219
|
+
self.signing_key = hmac.new(
|
|
220
|
+
b"polyclaw-secret-salt", api_secret.encode(), hashlib.sha256
|
|
221
|
+
).hexdigest()
|
|
222
|
+
|
|
223
|
+
def _sign(self, timestamp: str, method: str, path: str, body: str = "") -> str:
|
|
224
|
+
message = f"{timestamp}{method.upper()}{path}{body}"
|
|
225
|
+
return hmac.new(
|
|
226
|
+
self.signing_key.encode(), message.encode(), hashlib.sha256
|
|
227
|
+
).hexdigest()
|
|
228
|
+
|
|
229
|
+
def _request(self, method: str, path: str, body: dict = None):
|
|
230
|
+
timestamp = str(int(time.time() * 1000))
|
|
231
|
+
body_str = json.dumps(body) if body else ""
|
|
232
|
+
signature = self._sign(timestamp, method, path, body_str)
|
|
233
|
+
headers = {
|
|
234
|
+
"Content-Type": "application/json",
|
|
235
|
+
"X-Polyclaw-Api-Key": self.api_key,
|
|
236
|
+
"X-Polyclaw-Signature": signature,
|
|
237
|
+
"X-Polyclaw-Timestamp": timestamp,
|
|
238
|
+
}
|
|
239
|
+
url = f"{self.base_url}{path}"
|
|
240
|
+
response = requests.request(method, url, headers=headers, json=body)
|
|
241
|
+
return response.json()
|
|
242
|
+
|
|
243
|
+
def get_markets(self, page: int = 1, limit: int = 20):
|
|
244
|
+
return self._request("GET", f"/api/markets?page={page}&limit={limit}")
|
|
245
|
+
|
|
246
|
+
def submit_proposal(self, market_id, outcome, side, recommended_amount, confidence, reasoning=None, model=None):
|
|
247
|
+
body = {"marketId": market_id, "outcome": outcome, "side": side,
|
|
248
|
+
"recommendedAmount": recommended_amount, "confidence": confidence}
|
|
249
|
+
if reasoning: body["reasoning"] = reasoning
|
|
250
|
+
if model: body["model"] = model
|
|
251
|
+
return self._request("POST", "/api/proposals", body)
|
|
252
|
+
|
|
253
|
+
${aiAnalyzeFunc}
|
|
254
|
+
|
|
255
|
+
def main():
|
|
256
|
+
if not API_KEY or not API_SECRET:
|
|
257
|
+
print("Error: POLYCLAW_API_KEY and POLYCLAW_API_SECRET must be set in .env")
|
|
258
|
+
return
|
|
259
|
+
${options.aiProvider !== 'none' ? `
|
|
260
|
+
ai_key_name = "${options.aiProvider === 'anthropic' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'}"
|
|
261
|
+
if not AI_API_KEY:
|
|
262
|
+
print(f"Error: {ai_key_name} must be set in .env")
|
|
263
|
+
return
|
|
264
|
+
` : ''}
|
|
265
|
+
client = PolyclawClient(API_KEY, API_SECRET, BASE_URL)
|
|
266
|
+
print(f"${options.name} is online!")
|
|
267
|
+
print()
|
|
268
|
+
|
|
269
|
+
# Fetch markets
|
|
270
|
+
print("Fetching markets from Polymarket...")
|
|
271
|
+
result = client.get_markets(limit=20)
|
|
272
|
+
|
|
273
|
+
if not result.get("success"):
|
|
274
|
+
print(f"Error: {result.get('error', 'Unknown error')}")
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
markets = result.get("data", [])
|
|
278
|
+
print(f"Found {len(markets)} markets")
|
|
279
|
+
print()
|
|
280
|
+
|
|
281
|
+
# Ask AI to analyze and pick the best bets
|
|
282
|
+
print("Asking AI to analyze markets...\\n")
|
|
283
|
+
picks, model_name = analyze_markets(markets)
|
|
284
|
+
|
|
285
|
+
if not picks:
|
|
286
|
+
print("No picks from AI. Try again later.")
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
print(f" AI picked {len(picks)} market(s) (model: {model_name})\\n")
|
|
290
|
+
|
|
291
|
+
# Submit each pick as a proposal
|
|
292
|
+
for pick in picks:
|
|
293
|
+
idx = pick.get("market_index", 1) - 1
|
|
294
|
+
if idx < 0 or idx >= len(markets):
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
market = markets[idx]
|
|
298
|
+
print(f" -> {market.get('question', 'Unknown')}")
|
|
299
|
+
print(f" {pick['side']} {pick['outcome']} | Confidence: {pick['confidence']:.0%} | Amount: \${pick['recommended_amount']}")
|
|
300
|
+
print(f" Reasoning: {pick.get('reasoning', 'N/A')}")
|
|
301
|
+
|
|
302
|
+
proposal = client.submit_proposal(
|
|
303
|
+
market_id=market["id"],
|
|
304
|
+
outcome=pick["outcome"],
|
|
305
|
+
side=pick["side"],
|
|
306
|
+
recommended_amount=pick["recommended_amount"],
|
|
307
|
+
confidence=pick["confidence"],
|
|
308
|
+
reasoning=pick.get("reasoning"),
|
|
309
|
+
model=model_name,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
if proposal.get("success"):
|
|
313
|
+
print(f" Submitted!\\n")
|
|
314
|
+
else:
|
|
315
|
+
print(f" Error: {proposal.get('error', 'Unknown')}\\n")
|
|
316
|
+
|
|
317
|
+
print("Done! Check your dashboard to review proposals.")
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
if __name__ == "__main__":
|
|
321
|
+
main()
|
|
322
|
+
`;
|
|
323
|
+
const aiDep = options.aiProvider === 'anthropic'
|
|
324
|
+
? 'anthropic>=0.40.0\n'
|
|
325
|
+
: options.aiProvider === 'openai'
|
|
326
|
+
? 'openai>=1.50.0\n'
|
|
327
|
+
: '';
|
|
328
|
+
const requirements = `requests>=2.31.0
|
|
329
|
+
python-dotenv>=1.0.0
|
|
330
|
+
${aiDep}`;
|
|
331
|
+
const gitignore = `.env
|
|
332
|
+
__pycache__/
|
|
333
|
+
*.pyc
|
|
334
|
+
.venv/
|
|
335
|
+
`;
|
|
336
|
+
writeFileSync(join(dir, 'bot.py'), botPy);
|
|
337
|
+
writeFileSync(join(dir, 'requirements.txt'), requirements);
|
|
338
|
+
writeFileSync(join(dir, '.gitignore'), gitignore);
|
|
339
|
+
}
|
|
340
|
+
function scaffoldTypeScript(dir, options) {
|
|
341
|
+
const botTs = `/**
|
|
342
|
+
* ${options.name} - Polyclaw AI Trading Agent
|
|
343
|
+
*
|
|
344
|
+
* This bot connects to Polyclaw to browse prediction markets and submit
|
|
345
|
+
* trade proposals. A human reviews and approves proposals before execution.
|
|
346
|
+
*
|
|
347
|
+
* Usage:
|
|
348
|
+
* npm install
|
|
349
|
+
* npx tsx bot.ts
|
|
350
|
+
*/
|
|
351
|
+
|
|
352
|
+
import { createHmac } from 'crypto';
|
|
353
|
+
import 'dotenv/config';
|
|
354
|
+
|
|
355
|
+
const API_KEY = process.env.POLYCLAW_API_KEY!;
|
|
356
|
+
const API_SECRET = process.env.POLYCLAW_API_SECRET!;
|
|
357
|
+
const BASE_URL = process.env.POLYCLAW_API_URL || 'https://api.polyclaw.co';
|
|
358
|
+
|
|
359
|
+
class PolyclawClient {
|
|
360
|
+
private apiKey: string;
|
|
361
|
+
private signingKey: string;
|
|
362
|
+
private baseUrl: string;
|
|
363
|
+
|
|
364
|
+
constructor(config: { apiKey: string; apiSecret: string; baseUrl: string }) {
|
|
365
|
+
this.apiKey = config.apiKey;
|
|
366
|
+
this.baseUrl = config.baseUrl;
|
|
367
|
+
this.signingKey = createHmac('sha256', 'polyclaw-secret-salt')
|
|
368
|
+
.update(config.apiSecret)
|
|
369
|
+
.digest('hex');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private sign(timestamp: string, method: string, path: string, body?: string): string {
|
|
373
|
+
const message = \`\${timestamp}\${method.toUpperCase()}\${path}\${body || ''}\`;
|
|
374
|
+
return createHmac('sha256', this.signingKey).update(message).digest('hex');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private async request<T>(method: string, path: string, body?: object): Promise<T> {
|
|
378
|
+
const timestamp = Date.now().toString();
|
|
379
|
+
const bodyStr = body ? JSON.stringify(body) : '';
|
|
380
|
+
const signature = this.sign(timestamp, method, path, bodyStr);
|
|
381
|
+
const response = await fetch(\`\${this.baseUrl}\${path}\`, {
|
|
382
|
+
method,
|
|
383
|
+
headers: {
|
|
384
|
+
'Content-Type': 'application/json',
|
|
385
|
+
'X-Polyclaw-Api-Key': this.apiKey,
|
|
386
|
+
'X-Polyclaw-Signature': signature,
|
|
387
|
+
'X-Polyclaw-Timestamp': timestamp,
|
|
388
|
+
},
|
|
389
|
+
body: body ? bodyStr : undefined,
|
|
390
|
+
});
|
|
391
|
+
return response.json() as T;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async getMarkets(page = 1, limit = 20) {
|
|
395
|
+
return this.request<any>('GET', \`/api/markets?page=\${page}&limit=\${limit}\`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async submitProposal(proposal: {
|
|
399
|
+
marketId: string;
|
|
400
|
+
outcome: string;
|
|
401
|
+
side: string;
|
|
402
|
+
recommendedAmount: number;
|
|
403
|
+
confidence: number;
|
|
404
|
+
reasoning?: string;
|
|
405
|
+
model?: string;
|
|
406
|
+
}) {
|
|
407
|
+
return this.request<any>('POST', '/api/proposals', proposal);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
interface MarketPlay {
|
|
412
|
+
market: any;
|
|
413
|
+
outcome: string;
|
|
414
|
+
side: string;
|
|
415
|
+
confidence: number;
|
|
416
|
+
reasoning: string;
|
|
417
|
+
score: number;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function findBestPlay(markets: any[]): MarketPlay | null {
|
|
421
|
+
// Analyze markets and find the best opportunity.
|
|
422
|
+
// Strategy: Look for markets with strong price signals and high volume.
|
|
423
|
+
const scored: MarketPlay[] = [];
|
|
424
|
+
|
|
425
|
+
for (const market of markets) {
|
|
426
|
+
const yesPrice = parseFloat(market.outcomePrices?.[0] ?? '0.5');
|
|
427
|
+
const volume = parseFloat(market.volume ?? '0');
|
|
428
|
+
|
|
429
|
+
// Skip markets that are basically decided
|
|
430
|
+
if (yesPrice < 0.05 || yesPrice > 0.95) continue;
|
|
431
|
+
|
|
432
|
+
const edge = Math.abs(yesPrice - 0.5);
|
|
433
|
+
const liquidityScore = Math.min(volume / 500000, 1.0);
|
|
434
|
+
const score = edge * 0.6 + liquidityScore * 0.4;
|
|
435
|
+
|
|
436
|
+
let outcome: string, side: string, confidence: number, reasoning: string;
|
|
437
|
+
|
|
438
|
+
if (yesPrice < 0.40) {
|
|
439
|
+
outcome = 'Yes'; side = 'BUY';
|
|
440
|
+
confidence = Math.round((0.5 + edge) * 100) / 100;
|
|
441
|
+
reasoning = \`Yes is priced at \${(yesPrice * 100).toFixed(0)}% -- looks undervalued with $\${volume.toLocaleString()} volume\`;
|
|
442
|
+
} else if (yesPrice > 0.60) {
|
|
443
|
+
outcome = 'Yes'; side = 'BUY';
|
|
444
|
+
confidence = Math.round((0.5 + edge * 0.8) * 100) / 100;
|
|
445
|
+
reasoning = \`Market strongly favors Yes at \${(yesPrice * 100).toFixed(0)}% -- riding the momentum\`;
|
|
446
|
+
} else {
|
|
447
|
+
outcome = 'Yes'; side = 'BUY';
|
|
448
|
+
confidence = Math.round((0.5 + edge * 0.5) * 100) / 100;
|
|
449
|
+
reasoning = \`Market is contested at \${(yesPrice * 100).toFixed(0)}% -- taking a position\`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
scored.push({ market, outcome, side, confidence: Math.min(confidence, 0.95), reasoning, score });
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
scored.sort((a, b) => b.score - a.score);
|
|
456
|
+
return scored[0] || null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function main() {
|
|
460
|
+
if (!API_KEY || !API_SECRET) {
|
|
461
|
+
console.error('Error: POLYCLAW_API_KEY and POLYCLAW_API_SECRET must be set in .env');
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const client = new PolyclawClient({ apiKey: API_KEY, apiSecret: API_SECRET, baseUrl: BASE_URL });
|
|
466
|
+
console.log('${options.name} is online!');
|
|
467
|
+
console.log();
|
|
468
|
+
|
|
469
|
+
// Fetch markets
|
|
470
|
+
console.log('Scanning markets...');
|
|
471
|
+
const result = await client.getMarkets(1, 20);
|
|
472
|
+
|
|
473
|
+
if (!result.success) {
|
|
474
|
+
console.error(\`Error: \${result.error || 'Unknown error'}\`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const markets = result.data || [];
|
|
479
|
+
console.log(\`Analyzing \${markets.length} markets...\\n\`);
|
|
480
|
+
|
|
481
|
+
// Find the best play
|
|
482
|
+
const best = findBestPlay(markets);
|
|
483
|
+
|
|
484
|
+
if (!best) {
|
|
485
|
+
console.log('No good opportunities found right now.');
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const yesPrice = parseFloat(best.market.outcomePrices?.[0] ?? '0.5');
|
|
490
|
+
console.log(' Best opportunity found:');
|
|
491
|
+
console.log(\` \${best.market.question}\`);
|
|
492
|
+
console.log(\` Yes: \${(yesPrice * 100).toFixed(0)}% | Volume: $\${parseFloat(best.market.volume || '0').toLocaleString()}\`);
|
|
493
|
+
console.log(\` Play: \${best.side} \${best.outcome} | Confidence: \${(best.confidence * 100).toFixed(0)}%\`);
|
|
494
|
+
console.log(\` Reasoning: \${best.reasoning}\`);
|
|
495
|
+
console.log();
|
|
496
|
+
|
|
497
|
+
// Submit proposal
|
|
498
|
+
// Tip: Replace this logic with your own AI model for smarter predictions!
|
|
499
|
+
// const aiResponse = await openai.chat.completions.create({ model: 'gpt-4o', ... });
|
|
500
|
+
// const modelName = aiResponse.model; // e.g. "gpt-4o-2025-01-15"
|
|
501
|
+
console.log('Submitting proposal...');
|
|
502
|
+
const proposal = await client.submitProposal({
|
|
503
|
+
marketId: best.market.id,
|
|
504
|
+
outcome: best.outcome,
|
|
505
|
+
side: best.side,
|
|
506
|
+
recommendedAmount: 10.0,
|
|
507
|
+
confidence: best.confidence,
|
|
508
|
+
reasoning: best.reasoning,
|
|
509
|
+
model: 'your-model-here', // Replace with your AI model name
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
if (proposal.success) {
|
|
513
|
+
console.log('Proposal submitted! Check your dashboard to approve it.');
|
|
514
|
+
} else {
|
|
515
|
+
console.error(\`Proposal error: \${proposal.error || 'Unknown'}\`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
main();
|
|
520
|
+
`;
|
|
521
|
+
const packageJson = `{
|
|
522
|
+
"name": "${options.dirName}",
|
|
523
|
+
"version": "0.1.0",
|
|
524
|
+
"private": true,
|
|
525
|
+
"type": "module",
|
|
526
|
+
"scripts": {
|
|
527
|
+
"start": "npx tsx bot.ts"
|
|
528
|
+
},
|
|
529
|
+
"dependencies": {
|
|
530
|
+
"dotenv": "^16.4.0",
|
|
531
|
+
"tsx": "^4.7.0"
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
`;
|
|
535
|
+
const gitignore = `.env
|
|
536
|
+
node_modules/
|
|
537
|
+
dist/
|
|
538
|
+
`;
|
|
539
|
+
writeFileSync(join(dir, 'bot.ts'), botTs);
|
|
540
|
+
writeFileSync(join(dir, 'package.json'), packageJson);
|
|
541
|
+
writeFileSync(join(dir, '.gitignore'), gitignore);
|
|
542
|
+
}
|
|
543
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-polyclaw-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a Polyclaw AI trading agent in seconds",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-polyclaw-agent": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"chalk": "^5.3.0",
|
|
17
|
+
"prompts": "^2.4.2"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/prompts": "^2.4.9",
|
|
21
|
+
"typescript": "^5.4.0"
|
|
22
|
+
},
|
|
23
|
+
"keywords": ["polyclaw", "polymarket", "ai-agent", "prediction-markets"],
|
|
24
|
+
"license": "MIT"
|
|
25
|
+
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client for the Polyclaw registration flow.
|
|
3
|
+
* Calls the Polyclaw API to generate names and register agents.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_API_URL = 'https://api.polyclaw.co';
|
|
7
|
+
|
|
8
|
+
function getApiUrl(): string {
|
|
9
|
+
return process.env.POLYCLAW_API_URL || DEFAULT_API_URL;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ApiResponse<T> {
|
|
13
|
+
success: boolean;
|
|
14
|
+
data?: T;
|
|
15
|
+
error?: string;
|
|
16
|
+
message?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function generateName(): Promise<string> {
|
|
20
|
+
const url = `${getApiUrl()}/api/agents/generate-name`;
|
|
21
|
+
const res = await fetch(url);
|
|
22
|
+
const json = (await res.json()) as ApiResponse<{ name: string }>;
|
|
23
|
+
|
|
24
|
+
if (!json.success || !json.data) {
|
|
25
|
+
throw new Error(json.message || json.error || 'Failed to generate name');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return json.data.name;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RegisterResult {
|
|
32
|
+
apiKey: string;
|
|
33
|
+
apiSecret: string;
|
|
34
|
+
linkToken: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function registerAgent(name: string, options?: { ownerName?: string }): Promise<RegisterResult> {
|
|
38
|
+
const url = `${getApiUrl()}/api/agents/register`;
|
|
39
|
+
const body: Record<string, string> = { name };
|
|
40
|
+
if (options?.ownerName) {
|
|
41
|
+
body.ownerName = options.ownerName;
|
|
42
|
+
}
|
|
43
|
+
const res = await fetch(url, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify(body),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const json = (await res.json()) as ApiResponse<RegisterResult>;
|
|
50
|
+
|
|
51
|
+
if (!json.success || !json.data) {
|
|
52
|
+
throw new Error(json.message || json.error || 'Registration failed');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return json.data;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getLinkUrl(linkToken: string): string {
|
|
59
|
+
const webUrl = process.env.POLYCLAW_WEB_URL || 'https://polyclaw.co';
|
|
60
|
+
return `${webUrl}/link/${linkToken}`;
|
|
61
|
+
}
|