@blockrun/franklin 3.15.69 → 3.15.70
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/agent/context.js +9 -6
- package/dist/panel/html.js +1 -1
- package/dist/tools/prediction.d.ts +19 -5
- package/dist/tools/prediction.js +275 -57
- package/package.json +1 -1
package/dist/agent/context.js
CHANGED
|
@@ -336,12 +336,15 @@ Your training data is frozen in the past. Live-world questions MUST be answered
|
|
|
336
336
|
|
|
337
337
|
If you find yourself about to emit one of these, stop and call the tool instead. If you don't know which ticker the user means, call ExaSearch or AskUser — never deflect.
|
|
338
338
|
|
|
339
|
-
**Prediction markets (PredictionMarket).** When the user asks about real-world odds — elections, "will X happen by year-end", "Polymarket on Y", "Kalshi market for Z", "what are the odds of recession" — use **PredictionMarket** instead of guessing.
|
|
340
|
-
-
|
|
341
|
-
-
|
|
342
|
-
-
|
|
343
|
-
|
|
344
|
-
|
|
339
|
+
**Prediction markets (PredictionMarket).** When the user asks about real-world odds — elections, "will X happen by year-end", "Polymarket on Y", "Kalshi market for Z", "what are the odds of recession" — use **PredictionMarket** instead of guessing. Seven actions, route by intent:
|
|
340
|
+
- "is there a market on X anywhere?" / unknown which platform → \`searchAll\` (\$0.005) — single call across Polymarket+Kalshi+Limitless+Opinion+Predict.Fun.
|
|
341
|
+
- "what are the odds on Polymarket / Kalshi specifically" → \`searchPolymarket\` (\$0.001) and \`searchKalshi\` (\$0.001) **in parallel**; comparing implied probability across the two venues is the high-value answer.
|
|
342
|
+
- "where do Polymarket and Kalshi disagree / arbitrage" → \`crossPlatform\` (\$0.005) returns pre-matched pairs.
|
|
343
|
+
- "who's profitable / top traders / who should I follow on Polymarket" → \`leaderboard\` (\$0.001) — global top wallets by P&L.
|
|
344
|
+
- "how is wallet 0xabc doing / show this trader's P&L / are they profitable" → \`walletProfile\` (\$0.005) with \`wallets="<address>"\` (comma-separated for batch).
|
|
345
|
+
- "what are smart traders betting on right now / smart money flow" → \`smartActivity\` (\$0.005) — markets where high-P&L wallets are positioning.
|
|
346
|
+
|
|
347
|
+
NEVER answer "what are the odds of X" from training-data memory — these are live markets that move every minute. NEVER claim "no market on this" without running \`searchAll\` (or at least \`searchPolymarket\`) first. If a search returns zero, say so with the query you tried and offer to broaden.
|
|
345
348
|
|
|
346
349
|
**Trading verdicts (TradingSignal).** When the user asks "how does $TICKER look" / "should I buy X" / "is BTC overbought":
|
|
347
350
|
- Run **TradingSignal** with default lookback (90d). Lower values leave MACD undefined.
|
package/dist/panel/html.js
CHANGED
|
@@ -650,7 +650,7 @@ a:hover { text-decoration:underline; }
|
|
|
650
650
|
<div class="tab" id="tab-markets">
|
|
651
651
|
<div class="content-header">
|
|
652
652
|
<h2>Markets</h2>
|
|
653
|
-
<p>How Franklin gets trading data — and what it costs.</p>
|
|
653
|
+
<p>How Franklin gets trading + prediction-market data — and what it costs.</p>
|
|
654
654
|
</div>
|
|
655
655
|
|
|
656
656
|
<div class="grid grid-4">
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PredictionMarket — unified access to Polymarket / Kalshi /
|
|
3
|
-
*
|
|
4
|
-
* settles via x402 against the user's
|
|
2
|
+
* PredictionMarket — unified access to Polymarket / Kalshi / Limitless /
|
|
3
|
+
* Opinion / Predict.Fun / cross-platform / smart-money / wallet endpoints
|
|
4
|
+
* via the BlockRun gateway. Each call settles via x402 against the user's
|
|
5
|
+
* USDC wallet.
|
|
5
6
|
*
|
|
6
7
|
* Powered server-side by Predexon; surfaced to the agent as a single
|
|
7
8
|
* action-dispatched tool so the inventory stays small. Keep one cohesive
|
|
@@ -9,12 +10,25 @@
|
|
|
9
10
|
* one-shot capabilities, otherwise weak models start hallucinating tool
|
|
10
11
|
* names.
|
|
11
12
|
*
|
|
13
|
+
* searchAll $0.005 search markets across Polymarket+Kalshi+
|
|
14
|
+
* Limitless+Opinion+Predict.Fun in one call
|
|
12
15
|
* searchPolymarket $0.001 query Polymarket markets (event filter, sort)
|
|
13
16
|
* searchKalshi $0.001 query Kalshi markets
|
|
14
17
|
* crossPlatform $0.005 matching market pairs across Polymarket+Kalshi
|
|
15
18
|
* (the arbitrage / consensus signal)
|
|
16
|
-
*
|
|
17
|
-
*
|
|
19
|
+
* leaderboard $0.001 global Polymarket leaderboard — top wallets by P&L
|
|
20
|
+
* walletProfile $0.005 batch profile lookup for one or more Polymarket
|
|
21
|
+
* wallets — P&L, positions, identity
|
|
22
|
+
* smartActivity $0.005 discover markets where high-performing wallets
|
|
23
|
+
* are active right now
|
|
24
|
+
*
|
|
25
|
+
* Replaces the old `smartMoney` action (3.15.69 and earlier) which hit a
|
|
26
|
+
* non-existent path /v1/pm/polymarket/market/<id>/smart-money — that endpoint
|
|
27
|
+
* was never on the gateway, so the action was a silent 404 from day one.
|
|
28
|
+
* Verified 2026-05-05 against blockrun.ai/openapi.json: Polymarket has no
|
|
29
|
+
* per-market path-parameter endpoints; smart-money intelligence lives at
|
|
30
|
+
* /v1/pm/polymarket/markets/smart-activity (cross-market discovery) and
|
|
31
|
+
* /v1/pm/polymarket/leaderboard (top wallets globally).
|
|
18
32
|
*
|
|
19
33
|
* Output is filtered + truncated on the way back so a single call never
|
|
20
34
|
* dumps 100 markets into the agent's context. Default 20 rows; agents that
|
package/dist/tools/prediction.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PredictionMarket — unified access to Polymarket / Kalshi /
|
|
3
|
-
*
|
|
4
|
-
* settles via x402 against the user's
|
|
2
|
+
* PredictionMarket — unified access to Polymarket / Kalshi / Limitless /
|
|
3
|
+
* Opinion / Predict.Fun / cross-platform / smart-money / wallet endpoints
|
|
4
|
+
* via the BlockRun gateway. Each call settles via x402 against the user's
|
|
5
|
+
* USDC wallet.
|
|
5
6
|
*
|
|
6
7
|
* Powered server-side by Predexon; surfaced to the agent as a single
|
|
7
8
|
* action-dispatched tool so the inventory stays small. Keep one cohesive
|
|
@@ -9,12 +10,25 @@
|
|
|
9
10
|
* one-shot capabilities, otherwise weak models start hallucinating tool
|
|
10
11
|
* names.
|
|
11
12
|
*
|
|
13
|
+
* searchAll $0.005 search markets across Polymarket+Kalshi+
|
|
14
|
+
* Limitless+Opinion+Predict.Fun in one call
|
|
12
15
|
* searchPolymarket $0.001 query Polymarket markets (event filter, sort)
|
|
13
16
|
* searchKalshi $0.001 query Kalshi markets
|
|
14
17
|
* crossPlatform $0.005 matching market pairs across Polymarket+Kalshi
|
|
15
18
|
* (the arbitrage / consensus signal)
|
|
16
|
-
*
|
|
17
|
-
*
|
|
19
|
+
* leaderboard $0.001 global Polymarket leaderboard — top wallets by P&L
|
|
20
|
+
* walletProfile $0.005 batch profile lookup for one or more Polymarket
|
|
21
|
+
* wallets — P&L, positions, identity
|
|
22
|
+
* smartActivity $0.005 discover markets where high-performing wallets
|
|
23
|
+
* are active right now
|
|
24
|
+
*
|
|
25
|
+
* Replaces the old `smartMoney` action (3.15.69 and earlier) which hit a
|
|
26
|
+
* non-existent path /v1/pm/polymarket/market/<id>/smart-money — that endpoint
|
|
27
|
+
* was never on the gateway, so the action was a silent 404 from day one.
|
|
28
|
+
* Verified 2026-05-05 against blockrun.ai/openapi.json: Polymarket has no
|
|
29
|
+
* per-market path-parameter endpoints; smart-money intelligence lives at
|
|
30
|
+
* /v1/pm/polymarket/markets/smart-activity (cross-market discovery) and
|
|
31
|
+
* /v1/pm/polymarket/leaderboard (top wallets globally).
|
|
18
32
|
*
|
|
19
33
|
* Output is filtered + truncated on the way back so a single call never
|
|
20
34
|
* dumps 100 markets into the agent's context. Default 20 rows; agents that
|
|
@@ -23,9 +37,30 @@
|
|
|
23
37
|
import { getOrCreateWallet, getOrCreateSolanaWallet, createPaymentPayload, createSolanaPaymentPayload, parsePaymentRequired, extractPaymentDetails, solanaKeyToBytes, SOLANA_NETWORK, } from '@blockrun/llm';
|
|
24
38
|
import { loadChain, API_URLS, VERSION } from '../config.js';
|
|
25
39
|
import { logger } from '../logger.js';
|
|
40
|
+
import { recordFetch } from '../trading/providers/telemetry.js';
|
|
26
41
|
const TIMEOUT_MS = 30_000;
|
|
27
42
|
const DEFAULT_LIMIT = 20;
|
|
28
43
|
const MAX_LIMIT = 50;
|
|
44
|
+
// Per-action price table — mirrors the Predexon openapi.json. Used to feed
|
|
45
|
+
// the Markets-tab telemetry ring buffer so prediction-market spend appears
|
|
46
|
+
// in "Calls today / Spend today / Recent paid calls" alongside trading calls.
|
|
47
|
+
// If a path isn't here we don't record cost — we still record the fetch
|
|
48
|
+
// (success/latency) so panel health stays accurate.
|
|
49
|
+
const PATH_PRICES = [
|
|
50
|
+
{ pattern: /\/v1\/pm\/markets\/search$/, usd: 0.005 },
|
|
51
|
+
{ pattern: /\/v1\/pm\/matching-markets/, usd: 0.005 },
|
|
52
|
+
{ pattern: /\/v1\/pm\/polymarket\/wallets\//, usd: 0.005 },
|
|
53
|
+
{ pattern: /\/v1\/pm\/polymarket\/wallet\//, usd: 0.005 },
|
|
54
|
+
{ pattern: /\/v1\/pm\/polymarket\/markets\/smart-activity$/, usd: 0.005 },
|
|
55
|
+
{ pattern: /\/v1\/pm\/.+/, usd: 0.001 },
|
|
56
|
+
];
|
|
57
|
+
function priceForPath(path) {
|
|
58
|
+
for (const { pattern, usd } of PATH_PRICES) {
|
|
59
|
+
if (pattern.test(path))
|
|
60
|
+
return usd;
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
29
64
|
// ─── Shared GET-with-x402 flow ────────────────────────────────────────────
|
|
30
65
|
async function getWithPayment(path, query, ctx) {
|
|
31
66
|
const chain = loadChain();
|
|
@@ -46,6 +81,8 @@ async function getWithPayment(path, query, ctx) {
|
|
|
46
81
|
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
47
82
|
const onAbort = () => controller.abort();
|
|
48
83
|
ctx.abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
84
|
+
const startedAt = Date.now();
|
|
85
|
+
let costRecorded = 0;
|
|
49
86
|
try {
|
|
50
87
|
let response = await fetch(endpoint, { method: 'GET', signal: controller.signal, headers });
|
|
51
88
|
if (response.status === 402) {
|
|
@@ -58,11 +95,23 @@ async function getWithPayment(path, query, ctx) {
|
|
|
58
95
|
signal: controller.signal,
|
|
59
96
|
headers: { ...headers, ...paymentHeaders },
|
|
60
97
|
});
|
|
98
|
+
// Only record cost on the post-402 settlement; the initial 402
|
|
99
|
+
// response is free and counting it would double-charge the panel.
|
|
100
|
+
costRecorded = priceForPath(path);
|
|
61
101
|
}
|
|
62
102
|
if (!response.ok) {
|
|
63
103
|
const errText = await response.text().catch(() => '');
|
|
104
|
+
// Surface failed paid calls in the Markets-tab health summary.
|
|
105
|
+
recordFetch({ provider: 'blockrun', endpoint: path, ok: false, latencyMs: Date.now() - startedAt });
|
|
64
106
|
throw new Error(`PredictionMarket ${path} failed (${response.status}): ${errText.slice(0, 200)}`);
|
|
65
107
|
}
|
|
108
|
+
recordFetch({
|
|
109
|
+
provider: 'blockrun',
|
|
110
|
+
endpoint: path,
|
|
111
|
+
ok: true,
|
|
112
|
+
latencyMs: Date.now() - startedAt,
|
|
113
|
+
costUsd: costRecorded > 0 ? costRecorded : undefined,
|
|
114
|
+
});
|
|
66
115
|
return (await response.json());
|
|
67
116
|
}
|
|
68
117
|
finally {
|
|
@@ -155,13 +204,203 @@ function unwrapList(raw) {
|
|
|
155
204
|
return [];
|
|
156
205
|
}
|
|
157
206
|
async function execute(input, ctx) {
|
|
158
|
-
const { action, search, status, sort, limit,
|
|
207
|
+
const { action, search, status, sort, limit, wallets } = input;
|
|
159
208
|
const cappedLimit = Math.min(Math.max(1, limit ?? DEFAULT_LIMIT), MAX_LIMIT);
|
|
160
209
|
if (!action) {
|
|
161
|
-
return {
|
|
210
|
+
return {
|
|
211
|
+
output: 'Error: action is required (searchAll | searchPolymarket | searchKalshi | crossPlatform | leaderboard | walletProfile | smartActivity)',
|
|
212
|
+
isError: true,
|
|
213
|
+
};
|
|
162
214
|
}
|
|
163
215
|
try {
|
|
164
216
|
switch (action) {
|
|
217
|
+
case 'searchAll': {
|
|
218
|
+
// One $0.005 call across 5 platforms — Polymarket, Kalshi, Limitless,
|
|
219
|
+
// Opinion, Predict.Fun. The right entry point for "is there a market
|
|
220
|
+
// on X anywhere?" — beats firing per-platform searches in parallel.
|
|
221
|
+
const raw = await getWithPayment('/v1/pm/markets/search', {
|
|
222
|
+
search,
|
|
223
|
+
status,
|
|
224
|
+
sort,
|
|
225
|
+
limit: cappedLimit,
|
|
226
|
+
}, ctx);
|
|
227
|
+
// Predexon returns either a flat list or per-platform buckets.
|
|
228
|
+
// Try the bucket shape first; fall back to a flat list.
|
|
229
|
+
const lines = [
|
|
230
|
+
`## Cross-platform market search` + (search ? ` · "${search}"` : ''),
|
|
231
|
+
'_Searched Polymarket, Kalshi, Limitless, Opinion, Predict.Fun in one call._',
|
|
232
|
+
'',
|
|
233
|
+
];
|
|
234
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
235
|
+
const obj = raw;
|
|
236
|
+
const platforms = ['polymarket', 'kalshi', 'limitless', 'opinion', 'predictfun', 'predict_fun'];
|
|
237
|
+
let totalShown = 0;
|
|
238
|
+
for (const p of platforms) {
|
|
239
|
+
const list = unwrapList(obj[p]);
|
|
240
|
+
if (list.length === 0)
|
|
241
|
+
continue;
|
|
242
|
+
const remaining = cappedLimit - totalShown;
|
|
243
|
+
if (remaining <= 0)
|
|
244
|
+
break;
|
|
245
|
+
const shown = list.slice(0, Math.min(5, remaining));
|
|
246
|
+
lines.push(`### ${p}`);
|
|
247
|
+
shown.forEach((m, i) => {
|
|
248
|
+
const title = (m.title || m.question || m.market_slug || m.ticker || 'untitled');
|
|
249
|
+
const id = (m.condition_id || m.ticker || m.id);
|
|
250
|
+
const idTag = id ? ` · \`${String(id).slice(0, 18)}…\`` : '';
|
|
251
|
+
const vol = m.volume != null ? ` · vol ${formatUsd(m.volume)}` : '';
|
|
252
|
+
lines.push(`${i + 1}. ${title}${idTag}${vol}`);
|
|
253
|
+
totalShown++;
|
|
254
|
+
});
|
|
255
|
+
lines.push('');
|
|
256
|
+
}
|
|
257
|
+
if (totalShown === 0) {
|
|
258
|
+
// Bucket shape but empty — fall back to flat-list interpretation.
|
|
259
|
+
const flat = unwrapList(raw);
|
|
260
|
+
if (flat.length === 0) {
|
|
261
|
+
return { output: 'No markets matched across any platform.' };
|
|
262
|
+
}
|
|
263
|
+
flat.slice(0, cappedLimit).forEach((m, i) => {
|
|
264
|
+
const title = (m.title || m.question || m.market_slug || m.ticker || 'untitled');
|
|
265
|
+
const platform = (m.platform || m.source || 'unknown');
|
|
266
|
+
lines.push(`${i + 1}. **[${platform}]** ${title}`);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
const flat = unwrapList(raw);
|
|
272
|
+
if (flat.length === 0) {
|
|
273
|
+
return { output: 'No markets matched across any platform.' };
|
|
274
|
+
}
|
|
275
|
+
flat.slice(0, cappedLimit).forEach((m, i) => {
|
|
276
|
+
const title = (m.title || m.question || m.market_slug || m.ticker || 'untitled');
|
|
277
|
+
const platform = (m.platform || m.source || 'unknown');
|
|
278
|
+
lines.push(`${i + 1}. **[${platform}]** ${title}`);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
lines.push(`_$0.005 paid via x402._`);
|
|
282
|
+
return { output: lines.join('\n') };
|
|
283
|
+
}
|
|
284
|
+
case 'leaderboard': {
|
|
285
|
+
// Global top-wallet ranking. Cheap ($0.001) — the right answer to
|
|
286
|
+
// "who's making money on Polymarket" / "who should I follow".
|
|
287
|
+
const raw = await getWithPayment('/v1/pm/polymarket/leaderboard', {
|
|
288
|
+
limit: cappedLimit,
|
|
289
|
+
sort,
|
|
290
|
+
}, ctx);
|
|
291
|
+
const rows = unwrapList(raw);
|
|
292
|
+
if (rows.length === 0) {
|
|
293
|
+
return { output: 'No leaderboard data returned.' };
|
|
294
|
+
}
|
|
295
|
+
const lines = [
|
|
296
|
+
`## Polymarket leaderboard — top ${rows.length} wallet${rows.length === 1 ? '' : 's'}`,
|
|
297
|
+
'',
|
|
298
|
+
];
|
|
299
|
+
rows.forEach((r, i) => {
|
|
300
|
+
const wallet = (r.wallet || r.address || r.proxy_wallet || 'unknown');
|
|
301
|
+
const w = wallet.length > 12
|
|
302
|
+
? `${wallet.slice(0, 8)}…${wallet.slice(-4)}`
|
|
303
|
+
: wallet;
|
|
304
|
+
const pnl = r.pnl ?? r.realized_pnl ?? r.total_pnl;
|
|
305
|
+
const volume = r.volume ?? r.total_volume;
|
|
306
|
+
const winRate = r.win_rate ?? r.winRate;
|
|
307
|
+
const name = (r.name || r.handle || r.username);
|
|
308
|
+
const handle = name ? ` (${name})` : '';
|
|
309
|
+
const parts = [];
|
|
310
|
+
if (pnl != null)
|
|
311
|
+
parts.push(`P&L ${formatUsd(pnl)}`);
|
|
312
|
+
if (volume != null)
|
|
313
|
+
parts.push(`vol ${formatUsd(volume)}`);
|
|
314
|
+
if (winRate != null)
|
|
315
|
+
parts.push(`win ${formatPct(winRate, 0)}`);
|
|
316
|
+
lines.push(`${i + 1}. \`${w}\`${handle}` + (parts.length > 0 ? ` — ${parts.join(' · ')}` : ''));
|
|
317
|
+
});
|
|
318
|
+
lines.push('', `_$0.001 paid via x402._`);
|
|
319
|
+
return { output: lines.join('\n') };
|
|
320
|
+
}
|
|
321
|
+
case 'walletProfile': {
|
|
322
|
+
if (!wallets || !wallets.trim()) {
|
|
323
|
+
return {
|
|
324
|
+
output: 'Error: `wallets` is required for walletProfile (single address or comma-separated list of Polymarket wallet addresses)',
|
|
325
|
+
isError: true,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
// Predexon's batch endpoint accepts multiple wallets; we forward
|
|
329
|
+
// verbatim. Single wallet works too — caller passes one address.
|
|
330
|
+
const raw = await getWithPayment('/v1/pm/polymarket/wallets/profiles', {
|
|
331
|
+
wallets: wallets.trim(),
|
|
332
|
+
}, ctx);
|
|
333
|
+
const profiles = unwrapList(raw);
|
|
334
|
+
if (profiles.length === 0) {
|
|
335
|
+
return { output: `No profile data returned for: ${wallets}` };
|
|
336
|
+
}
|
|
337
|
+
const lines = [
|
|
338
|
+
`## Polymarket wallet profile${profiles.length === 1 ? '' : 's'} — ${profiles.length}`,
|
|
339
|
+
'',
|
|
340
|
+
];
|
|
341
|
+
profiles.forEach((p, i) => {
|
|
342
|
+
const wallet = (p.wallet || p.address || p.proxy_wallet || 'unknown');
|
|
343
|
+
const w = wallet.length > 12
|
|
344
|
+
? `${wallet.slice(0, 8)}…${wallet.slice(-4)}`
|
|
345
|
+
: wallet;
|
|
346
|
+
const name = (p.name || p.handle || p.username);
|
|
347
|
+
const pnl = p.pnl ?? p.realized_pnl ?? p.total_pnl;
|
|
348
|
+
const unrealized = p.unrealized_pnl;
|
|
349
|
+
const volume = p.volume ?? p.total_volume;
|
|
350
|
+
const positions = p.positions_count ?? p.open_positions;
|
|
351
|
+
const winRate = p.win_rate ?? p.winRate;
|
|
352
|
+
lines.push(`${i + 1}. \`${w}\`` + (name ? ` (${name})` : ''));
|
|
353
|
+
const stats = [];
|
|
354
|
+
if (pnl != null)
|
|
355
|
+
stats.push(`P&L ${formatUsd(pnl)}`);
|
|
356
|
+
if (unrealized != null)
|
|
357
|
+
stats.push(`unrealized ${formatUsd(unrealized)}`);
|
|
358
|
+
if (volume != null)
|
|
359
|
+
stats.push(`vol ${formatUsd(volume)}`);
|
|
360
|
+
if (positions != null)
|
|
361
|
+
stats.push(`${positions} open`);
|
|
362
|
+
if (winRate != null)
|
|
363
|
+
stats.push(`win ${formatPct(winRate, 0)}`);
|
|
364
|
+
if (stats.length > 0)
|
|
365
|
+
lines.push(` ${stats.join(' · ')}`);
|
|
366
|
+
});
|
|
367
|
+
lines.push('', `_$0.005 paid via x402._`);
|
|
368
|
+
return { output: lines.join('\n') };
|
|
369
|
+
}
|
|
370
|
+
case 'smartActivity': {
|
|
371
|
+
// "Discover markets where high-performing wallets are active right now."
|
|
372
|
+
// Replaces the old `smartMoney` action (which hit a non-existent path
|
|
373
|
+
// /v1/pm/polymarket/market/<id>/smart-money — silently 404'd from
|
|
374
|
+
// launch). Verified 2026-05-05 against blockrun.ai/openapi.json.
|
|
375
|
+
const raw = await getWithPayment('/v1/pm/polymarket/markets/smart-activity', {
|
|
376
|
+
limit: cappedLimit,
|
|
377
|
+
search,
|
|
378
|
+
}, ctx);
|
|
379
|
+
const rows = unwrapList(raw);
|
|
380
|
+
if (rows.length === 0) {
|
|
381
|
+
return { output: 'No smart-money activity recorded right now.' };
|
|
382
|
+
}
|
|
383
|
+
const lines = [
|
|
384
|
+
`## Smart-money activity — ${rows.length} market${rows.length === 1 ? '' : 's'}`,
|
|
385
|
+
'_Markets where high-P&L Polymarket wallets are positioning right now._',
|
|
386
|
+
'',
|
|
387
|
+
];
|
|
388
|
+
rows.forEach((r, i) => {
|
|
389
|
+
const title = (r.question || r.title || r.market_slug || 'untitled');
|
|
390
|
+
const cid = (r.condition_id || r.id);
|
|
391
|
+
const cidTag = cid ? ` · \`${String(cid).slice(0, 14)}…\`` : '';
|
|
392
|
+
const smartCount = r.smart_wallets_count ?? r.wallet_count;
|
|
393
|
+
const netFlow = r.net_size ?? r.net_yes_size;
|
|
394
|
+
const stats = [];
|
|
395
|
+
if (smartCount != null)
|
|
396
|
+
stats.push(`${smartCount} smart wallet${smartCount === 1 ? '' : 's'}`);
|
|
397
|
+
if (netFlow != null)
|
|
398
|
+
stats.push(`net ${formatUsd(netFlow)}`);
|
|
399
|
+
lines.push(`${i + 1}. **${title}**${cidTag}` + (stats.length > 0 ? `\n ${stats.join(' · ')}` : ''));
|
|
400
|
+
});
|
|
401
|
+
lines.push('', `_$0.005 paid via x402._`);
|
|
402
|
+
return { output: lines.join('\n') };
|
|
403
|
+
}
|
|
165
404
|
case 'searchPolymarket': {
|
|
166
405
|
const raw = await getWithPayment('/v1/pm/polymarket/markets', {
|
|
167
406
|
search,
|
|
@@ -245,45 +484,9 @@ async function execute(input, ctx) {
|
|
|
245
484
|
lines.push('', `_$0.005 paid via x402._`);
|
|
246
485
|
return { output: lines.join('\n') };
|
|
247
486
|
}
|
|
248
|
-
case 'smartMoney': {
|
|
249
|
-
if (!conditionId) {
|
|
250
|
-
return { output: 'Error: conditionId is required for smartMoney (Polymarket condition_id from a prior searchPolymarket call)', isError: true };
|
|
251
|
-
}
|
|
252
|
-
const path = `/v1/pm/polymarket/market/${encodeURIComponent(conditionId)}/smart-money`;
|
|
253
|
-
const data = await getWithPayment(path, {}, ctx);
|
|
254
|
-
const buyers = (data.buyers ?? []).slice(0, 5);
|
|
255
|
-
const sellers = (data.sellers ?? []).slice(0, 5);
|
|
256
|
-
const lines = [
|
|
257
|
-
`## Smart money — \`${conditionId.slice(0, 14)}…\``,
|
|
258
|
-
];
|
|
259
|
-
if (data.net_yes_size != null || data.net_no_size != null) {
|
|
260
|
-
const yesSize = formatUsd(data.net_yes_size);
|
|
261
|
-
const noSize = formatUsd(data.net_no_size);
|
|
262
|
-
lines.push(`**Net flow:** YES ${yesSize} / NO ${noSize}`);
|
|
263
|
-
}
|
|
264
|
-
if (buyers.length > 0) {
|
|
265
|
-
lines.push('', '**Top buyers**');
|
|
266
|
-
buyers.forEach((b, i) => {
|
|
267
|
-
const w = b.wallet ? `${b.wallet.slice(0, 8)}…${b.wallet.slice(-4)}` : 'unknown';
|
|
268
|
-
lines.push(`${i + 1}. ${w} — ${formatUsd(b.size)} on ${b.outcome ?? 'unknown side'}`);
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
if (sellers.length > 0) {
|
|
272
|
-
lines.push('', '**Top sellers**');
|
|
273
|
-
sellers.forEach((s, i) => {
|
|
274
|
-
const w = s.wallet ? `${s.wallet.slice(0, 8)}…${s.wallet.slice(-4)}` : 'unknown';
|
|
275
|
-
lines.push(`${i + 1}. ${w} — ${formatUsd(s.size)} on ${s.outcome ?? 'unknown side'}`);
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
if (buyers.length === 0 && sellers.length === 0) {
|
|
279
|
-
lines.push('No smart-money flow recorded for this market yet.');
|
|
280
|
-
}
|
|
281
|
-
lines.push('', `_$0.005 paid via x402._`);
|
|
282
|
-
return { output: lines.join('\n') };
|
|
283
|
-
}
|
|
284
487
|
default:
|
|
285
488
|
return {
|
|
286
|
-
output: `Error: unknown action "${action}". Use: searchPolymarket, searchKalshi, crossPlatform,
|
|
489
|
+
output: `Error: unknown action "${action}". Use: searchAll, searchPolymarket, searchKalshi, crossPlatform, leaderboard, walletProfile, smartActivity`,
|
|
287
490
|
isError: true,
|
|
288
491
|
};
|
|
289
492
|
}
|
|
@@ -295,41 +498,56 @@ async function execute(input, ctx) {
|
|
|
295
498
|
export const predictionMarketCapability = {
|
|
296
499
|
spec: {
|
|
297
500
|
name: 'PredictionMarket',
|
|
298
|
-
description: 'Real prediction market data via the BlockRun gateway (powered by Predexon). Use for any "what are the odds of X" / "Polymarket on Y" / "
|
|
501
|
+
description: 'Real prediction market data via the BlockRun gateway (powered by Predexon). Use for any "what are the odds of X" / "Polymarket on Y" / "is there a market on Z" / "follow this trader" question. ' +
|
|
299
502
|
'Actions: ' +
|
|
300
|
-
'`
|
|
301
|
-
'`
|
|
302
|
-
'`
|
|
303
|
-
'`
|
|
304
|
-
'
|
|
305
|
-
'
|
|
503
|
+
'`searchAll` (search markets across Polymarket+Kalshi+Limitless+Opinion+Predict.Fun in one call — $0.005), ' +
|
|
504
|
+
'`searchPolymarket` (Polymarket only, supports sort+status — $0.001), ' +
|
|
505
|
+
'`searchKalshi` (Kalshi only, supports sort+status — $0.001), ' +
|
|
506
|
+
'`crossPlatform` (matched market pairs across Polymarket+Kalshi for arbitrage / consensus — $0.005), ' +
|
|
507
|
+
'`leaderboard` (global top wallets by P&L on Polymarket — $0.001), ' +
|
|
508
|
+
'`walletProfile` (P&L + positions for one or more Polymarket wallets — $0.005), ' +
|
|
509
|
+
'`smartActivity` (markets where high-P&L wallets are positioning right now — $0.005). ' +
|
|
510
|
+
'Default routing: ' +
|
|
511
|
+
'"is there a market on X anywhere" → searchAll. ' +
|
|
512
|
+
'"top wallets / who is profitable / who should I follow on Polymarket" → leaderboard. ' +
|
|
513
|
+
'"how is wallet 0xabc doing / show me their P&L" → walletProfile with that address. ' +
|
|
514
|
+
'"what are smart traders betting on right now" → smartActivity. ' +
|
|
515
|
+
'"should I bet on X" → run searchPolymarket + searchKalshi in parallel and compare implied probabilities — divergence is the signal.',
|
|
306
516
|
input_schema: {
|
|
307
517
|
type: 'object',
|
|
308
518
|
properties: {
|
|
309
519
|
action: {
|
|
310
520
|
type: 'string',
|
|
311
|
-
enum: [
|
|
521
|
+
enum: [
|
|
522
|
+
'searchAll',
|
|
523
|
+
'searchPolymarket',
|
|
524
|
+
'searchKalshi',
|
|
525
|
+
'crossPlatform',
|
|
526
|
+
'leaderboard',
|
|
527
|
+
'walletProfile',
|
|
528
|
+
'smartActivity',
|
|
529
|
+
],
|
|
312
530
|
description: 'Which prediction-market query to run. See tool description for cost per action.',
|
|
313
531
|
},
|
|
314
532
|
search: {
|
|
315
533
|
type: 'string',
|
|
316
|
-
description: 'Search query
|
|
534
|
+
description: 'Search query. Used by searchAll / searchPolymarket / searchKalshi / smartActivity. Optional for crossPlatform/leaderboard/walletProfile.',
|
|
317
535
|
},
|
|
318
536
|
status: {
|
|
319
537
|
type: 'string',
|
|
320
|
-
description: 'Polymarket: active | closed | archived (default active). Kalshi: open | closed (default open).',
|
|
538
|
+
description: 'Polymarket: active | closed | archived (default active). Kalshi: open | closed (default open). Forwarded to searchAll where supported.',
|
|
321
539
|
},
|
|
322
540
|
sort: {
|
|
323
541
|
type: 'string',
|
|
324
|
-
description: 'Polymarket: volume | liquidity | created (default volume). Kalshi: volume | open_interest | price_desc | price_asc | close_time (default volume).',
|
|
542
|
+
description: 'Polymarket: volume | liquidity | created (default volume). Kalshi: volume | open_interest | price_desc | price_asc | close_time (default volume). leaderboard: pnl | volume | win_rate (gateway-defined).',
|
|
325
543
|
},
|
|
326
544
|
limit: {
|
|
327
545
|
type: 'number',
|
|
328
546
|
description: `Max results (default ${DEFAULT_LIMIT}, hard cap ${MAX_LIMIT}).`,
|
|
329
547
|
},
|
|
330
|
-
|
|
548
|
+
wallets: {
|
|
331
549
|
type: 'string',
|
|
332
|
-
description: '
|
|
550
|
+
description: 'For walletProfile: a single Polymarket wallet address, or a comma-separated list of addresses for batch lookup.',
|
|
333
551
|
},
|
|
334
552
|
},
|
|
335
553
|
required: ['action'],
|
package/package.json
CHANGED