@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.
@@ -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. Four actions:
340
- - \`searchPolymarket\` (\$0.001) and \`searchKalshi\` (\$0.001) search markets by keyword. Run them **in parallel** when the user wants the current odds; comparing implied probability across two venues is the high-value answer.
341
- - \`crossPlatform\` (\$0.005) pre-matched pairs of equivalent markets across Polymarket and Kalshi. Use when the user wants arbitrage candidates or wants to know "where does the consensus disagree".
342
- - \`smartMoney\` (\$0.005) top-wallet flow on a specific Polymarket \`condition_id\`. Get the \`condition_id\` from a prior \`searchPolymarket\` call.
343
-
344
- NEVER answer "what are the odds of X" from training-data memory these are live markets that move every minute. NEVER claim "Polymarket doesn't have a market on this" without running \`searchPolymarket\` first. If both Polymarket and Kalshi return zero markets, say so explicitly with the searches you tried, then offer to broaden the query.
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.
@@ -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 / cross-platform
3
- * matching / smart-money endpoints via the BlockRun gateway. Each call
4
- * settles via x402 against the user's USDC wallet.
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
- * smartMoney $0.005 smart-money positioning on one Polymarket
17
- * condition_id (top wallet flow + side bias)
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
@@ -1,7 +1,8 @@
1
1
  /**
2
- * PredictionMarket — unified access to Polymarket / Kalshi / cross-platform
3
- * matching / smart-money endpoints via the BlockRun gateway. Each call
4
- * settles via x402 against the user's USDC wallet.
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
- * smartMoney $0.005 smart-money positioning on one Polymarket
17
- * condition_id (top wallet flow + side bias)
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, conditionId } = input;
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 { output: 'Error: action is required (searchPolymarket | searchKalshi | crossPlatform | smartMoney)', isError: true };
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, smartMoney`,
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" / "Kalshi market for Z" question. ' +
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
- '`searchPolymarket` (search Polymarket markets — $0.001), ' +
301
- '`searchKalshi` (search Kalshi markets — $0.001), ' +
302
- '`crossPlatform` (matched pairs across Polymarket+Kalshi for arbitrage / consensus — $0.005), ' +
303
- '`smartMoney` (smart-money positioning on a Polymarket condition_id — $0.005). ' +
304
- 'Polymarket and Kalshi are the two largest legit prediction markets; cross-platform matching is unique to BlockRun. ' +
305
- 'For "should I bet on X" / "what does the market price imply": run searchPolymarket AND searchKalshi in parallel and compare implied probabilities divergence is the signal.',
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: ['searchPolymarket', 'searchKalshi', 'crossPlatform', 'smartMoney'],
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 (3-100 chars). Used by searchPolymarket / searchKalshi. Skip for crossPlatform/smartMoney.',
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
- conditionId: {
548
+ wallets: {
331
549
  type: 'string',
332
- description: 'Polymarket condition_id. Required for smartMoney. Get one from a prior searchPolymarket call.',
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.69",
3
+ "version": "3.15.70",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {