@blockrun/franklin 3.15.69 → 3.15.71
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 +10 -6
- package/dist/agent/loop.js +12 -2
- package/dist/panel/html.js +1 -1
- package/dist/stats/audit.js +12 -1
- package/dist/tools/prediction.d.ts +12 -4
- package/dist/tools/prediction.js +336 -65
- package/package.json +1 -1
package/dist/agent/context.js
CHANGED
|
@@ -336,12 +336,16 @@ 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. Eight 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 across markets" → \`smartActivity\` (\$0.005) — markets where high-P&L wallets are positioning.
|
|
346
|
+
- "show smart money on this specific Polymarket market / this condition_id" → \`smartMoney\` (\$0.005) with \`conditionId="<condition_id>"\`.
|
|
347
|
+
|
|
348
|
+
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
349
|
|
|
346
350
|
**Trading verdicts (TradingSignal).** When the user asks "how does $TICKER look" / "should I buy X" / "is BTC overbought":
|
|
347
351
|
- Run **TradingSignal** with default lookback (90d). Lower values leave MACD undefined.
|
package/dist/agent/loop.js
CHANGED
|
@@ -939,10 +939,20 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
939
939
|
// never approached its 1M-token compaction threshold. Compact here
|
|
940
940
|
// when the turn has accumulated lots of tool calls AND real spend,
|
|
941
941
|
// even though the context window isn't close to full.
|
|
942
|
+
//
|
|
943
|
+
// Thresholds tightened in 3.15.71. Original 3.15.69 used
|
|
944
|
+
// (>30 calls AND >$0.05) — verified too loose against a real
|
|
945
|
+
// franklin-shorts edit session: 16 deepseek-v4-pro calls for
|
|
946
|
+
// $0.055 ended naturally before the trigger fired, even though
|
|
947
|
+
// by call #4 the per-call input was already 13K tokens (worth
|
|
948
|
+
// compacting). Lowering to (>15 AND >$0.03) catches sessions
|
|
949
|
+
// where input-replay tax has clearly started biting; the
|
|
950
|
+
// fire-once-per-turn flag still bounds the worst case at one
|
|
951
|
+
// extra summary call (~$0.005).
|
|
942
952
|
if (!bloatCompactedThisTurn &&
|
|
943
953
|
compactFailures < 3 &&
|
|
944
|
-
turnToolCalls >
|
|
945
|
-
turnCostUsd > 0.
|
|
954
|
+
turnToolCalls > 15 &&
|
|
955
|
+
turnCostUsd > 0.03) {
|
|
946
956
|
try {
|
|
947
957
|
const beforeTokens = estimateHistoryTokens(history);
|
|
948
958
|
const { history: compacted, compacted: didCompact } = await forceCompact(history, config.model, client, config.debug);
|
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">
|
package/dist/stats/audit.js
CHANGED
|
@@ -114,7 +114,18 @@ export function extractLastUserPrompt(history) {
|
|
|
114
114
|
const text = flattenContent(msg.content);
|
|
115
115
|
if (!text)
|
|
116
116
|
continue;
|
|
117
|
-
|
|
117
|
+
const cleaned = text.replace(/\s+/g, ' ').trim();
|
|
118
|
+
// Anthropic's message format puts harness-injected context, guardrail
|
|
119
|
+
// warnings, and grounding-retry feedback under role:"user" too. Walking
|
|
120
|
+
// back blindly returns those synthetic strings instead of the real user
|
|
121
|
+
// intent — verified 2026-05-06 in the audit log: 403 entries showed
|
|
122
|
+
// "[FRANKLIN HARNESS PREFETCH] CRCL price..." and 18 showed
|
|
123
|
+
// "[GROUNDING CHECK FAILED] ..." instead of the user's actual question.
|
|
124
|
+
// Skip any message whose first non-whitespace block is a SCREAMING-CASE
|
|
125
|
+
// bracketed label and keep walking back to the real user turn.
|
|
126
|
+
if (/^\[[A-Z][A-Z _-]+\]/.test(cleaned))
|
|
127
|
+
continue;
|
|
128
|
+
return cleaned;
|
|
118
129
|
}
|
|
119
130
|
return undefined;
|
|
120
131
|
}
|
|
@@ -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,19 @@
|
|
|
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)
|
|
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
|
|
16
24
|
* smartMoney $0.005 smart-money positioning on one Polymarket
|
|
17
|
-
* condition_id (
|
|
25
|
+
* condition_id (per-market drill-down)
|
|
18
26
|
*
|
|
19
27
|
* Output is filtered + truncated on the way back so a single call never
|
|
20
28
|
* 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,19 @@
|
|
|
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)
|
|
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
|
|
16
24
|
* smartMoney $0.005 smart-money positioning on one Polymarket
|
|
17
|
-
* condition_id (
|
|
25
|
+
* condition_id (per-market drill-down)
|
|
18
26
|
*
|
|
19
27
|
* Output is filtered + truncated on the way back so a single call never
|
|
20
28
|
* dumps 100 markets into the agent's context. Default 20 rows; agents that
|
|
@@ -23,9 +31,31 @@
|
|
|
23
31
|
import { getOrCreateWallet, getOrCreateSolanaWallet, createPaymentPayload, createSolanaPaymentPayload, parsePaymentRequired, extractPaymentDetails, solanaKeyToBytes, SOLANA_NETWORK, } from '@blockrun/llm';
|
|
24
32
|
import { loadChain, API_URLS, VERSION } from '../config.js';
|
|
25
33
|
import { logger } from '../logger.js';
|
|
34
|
+
import { recordFetch } from '../trading/providers/telemetry.js';
|
|
26
35
|
const TIMEOUT_MS = 30_000;
|
|
27
36
|
const DEFAULT_LIMIT = 20;
|
|
28
37
|
const MAX_LIMIT = 50;
|
|
38
|
+
// Per-action price table — mirrors the Predexon openapi.json. Used to feed
|
|
39
|
+
// the Markets-tab telemetry ring buffer so prediction-market spend appears
|
|
40
|
+
// in "Calls today / Spend today / Recent paid calls" alongside trading calls.
|
|
41
|
+
// If a path isn't here we don't record cost — we still record the fetch
|
|
42
|
+
// (success/latency) so panel health stays accurate.
|
|
43
|
+
const PATH_PRICES = [
|
|
44
|
+
{ pattern: /\/v1\/pm\/markets\/search$/, usd: 0.005 },
|
|
45
|
+
{ pattern: /\/v1\/pm\/matching-markets/, usd: 0.005 },
|
|
46
|
+
{ pattern: /\/v1\/pm\/polymarket\/wallets\//, usd: 0.005 },
|
|
47
|
+
{ pattern: /\/v1\/pm\/polymarket\/wallet\//, usd: 0.005 },
|
|
48
|
+
{ pattern: /\/v1\/pm\/polymarket\/market\/[^/]+\/smart-money$/, usd: 0.005 },
|
|
49
|
+
{ pattern: /\/v1\/pm\/polymarket\/markets\/smart-activity$/, usd: 0.005 },
|
|
50
|
+
{ pattern: /\/v1\/pm\/.+/, usd: 0.001 },
|
|
51
|
+
];
|
|
52
|
+
function priceForPath(path) {
|
|
53
|
+
for (const { pattern, usd } of PATH_PRICES) {
|
|
54
|
+
if (pattern.test(path))
|
|
55
|
+
return usd;
|
|
56
|
+
}
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
29
59
|
// ─── Shared GET-with-x402 flow ────────────────────────────────────────────
|
|
30
60
|
async function getWithPayment(path, query, ctx) {
|
|
31
61
|
const chain = loadChain();
|
|
@@ -46,6 +76,8 @@ async function getWithPayment(path, query, ctx) {
|
|
|
46
76
|
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
47
77
|
const onAbort = () => controller.abort();
|
|
48
78
|
ctx.abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
79
|
+
const startedAt = Date.now();
|
|
80
|
+
let costRecorded = 0;
|
|
49
81
|
try {
|
|
50
82
|
let response = await fetch(endpoint, { method: 'GET', signal: controller.signal, headers });
|
|
51
83
|
if (response.status === 402) {
|
|
@@ -58,11 +90,23 @@ async function getWithPayment(path, query, ctx) {
|
|
|
58
90
|
signal: controller.signal,
|
|
59
91
|
headers: { ...headers, ...paymentHeaders },
|
|
60
92
|
});
|
|
93
|
+
// Only record cost on the post-402 settlement; the initial 402
|
|
94
|
+
// response is free and counting it would double-charge the panel.
|
|
95
|
+
costRecorded = priceForPath(path);
|
|
61
96
|
}
|
|
62
97
|
if (!response.ok) {
|
|
63
98
|
const errText = await response.text().catch(() => '');
|
|
99
|
+
// Surface failed paid calls in the Markets-tab health summary.
|
|
100
|
+
recordFetch({ provider: 'blockrun', endpoint: path, ok: false, latencyMs: Date.now() - startedAt });
|
|
64
101
|
throw new Error(`PredictionMarket ${path} failed (${response.status}): ${errText.slice(0, 200)}`);
|
|
65
102
|
}
|
|
103
|
+
recordFetch({
|
|
104
|
+
provider: 'blockrun',
|
|
105
|
+
endpoint: path,
|
|
106
|
+
ok: true,
|
|
107
|
+
latencyMs: Date.now() - startedAt,
|
|
108
|
+
costUsd: costRecorded > 0 ? costRecorded : undefined,
|
|
109
|
+
});
|
|
66
110
|
return (await response.json());
|
|
67
111
|
}
|
|
68
112
|
finally {
|
|
@@ -120,21 +164,33 @@ async function extractPaymentReq(response) {
|
|
|
120
164
|
return header;
|
|
121
165
|
}
|
|
122
166
|
// ─── Formatting helpers ────────────────────────────────────────────────────
|
|
167
|
+
function asNumber(value) {
|
|
168
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
169
|
+
return value;
|
|
170
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
171
|
+
const n = Number(value);
|
|
172
|
+
if (Number.isFinite(n))
|
|
173
|
+
return n;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
123
177
|
function formatUsd(value) {
|
|
124
|
-
|
|
178
|
+
const n = asNumber(value);
|
|
179
|
+
if (n == null)
|
|
125
180
|
return 'n/a';
|
|
126
|
-
if (
|
|
127
|
-
return `$${(
|
|
128
|
-
if (
|
|
129
|
-
return `$${(
|
|
130
|
-
if (
|
|
131
|
-
return `$${(
|
|
132
|
-
return `$${
|
|
181
|
+
if (n >= 1e9)
|
|
182
|
+
return `$${(n / 1e9).toFixed(2)}B`;
|
|
183
|
+
if (n >= 1e6)
|
|
184
|
+
return `$${(n / 1e6).toFixed(2)}M`;
|
|
185
|
+
if (n >= 1e3)
|
|
186
|
+
return `$${(n / 1e3).toFixed(1)}K`;
|
|
187
|
+
return `$${n.toFixed(2)}`;
|
|
133
188
|
}
|
|
134
189
|
function formatPct(value, digits = 1) {
|
|
135
|
-
|
|
190
|
+
const n = asNumber(value);
|
|
191
|
+
if (n == null)
|
|
136
192
|
return 'n/a';
|
|
137
|
-
return `${(
|
|
193
|
+
return `${(n * 100).toFixed(digits)}%`;
|
|
138
194
|
}
|
|
139
195
|
// API responses sometimes come wrapped as `{data: [...], pagination: ...}`,
|
|
140
196
|
// other times as a bare array. Normalise to an array.
|
|
@@ -155,13 +211,242 @@ function unwrapList(raw) {
|
|
|
155
211
|
return [];
|
|
156
212
|
}
|
|
157
213
|
async function execute(input, ctx) {
|
|
158
|
-
const { action, search, status, sort, limit, conditionId } = input;
|
|
214
|
+
const { action, search, status, sort, limit, conditionId, wallets } = input;
|
|
159
215
|
const cappedLimit = Math.min(Math.max(1, limit ?? DEFAULT_LIMIT), MAX_LIMIT);
|
|
160
216
|
if (!action) {
|
|
161
|
-
return {
|
|
217
|
+
return {
|
|
218
|
+
output: 'Error: action is required (searchAll | searchPolymarket | searchKalshi | crossPlatform | leaderboard | walletProfile | smartActivity | smartMoney)',
|
|
219
|
+
isError: true,
|
|
220
|
+
};
|
|
162
221
|
}
|
|
163
222
|
try {
|
|
164
223
|
switch (action) {
|
|
224
|
+
case 'searchAll': {
|
|
225
|
+
// One $0.005 call across 5 platforms — Polymarket, Kalshi, Limitless,
|
|
226
|
+
// Opinion, Predict.Fun. The right entry point for "is there a market
|
|
227
|
+
// on X anywhere?" — beats firing per-platform searches in parallel.
|
|
228
|
+
const raw = await getWithPayment('/v1/pm/markets/search', {
|
|
229
|
+
search,
|
|
230
|
+
status,
|
|
231
|
+
sort,
|
|
232
|
+
limit: cappedLimit,
|
|
233
|
+
}, ctx);
|
|
234
|
+
// Predexon returns either a flat list or per-platform buckets.
|
|
235
|
+
// Try the bucket shape first; fall back to a flat list.
|
|
236
|
+
const lines = [
|
|
237
|
+
`## Cross-platform market search` + (search ? ` · "${search}"` : ''),
|
|
238
|
+
'_Searched Polymarket, Kalshi, Limitless, Opinion, Predict.Fun in one call._',
|
|
239
|
+
'',
|
|
240
|
+
];
|
|
241
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
242
|
+
const obj = raw;
|
|
243
|
+
const platforms = ['polymarket', 'kalshi', 'limitless', 'opinion', 'predictfun', 'predict_fun'];
|
|
244
|
+
let totalShown = 0;
|
|
245
|
+
for (const p of platforms) {
|
|
246
|
+
const list = unwrapList(obj[p]);
|
|
247
|
+
if (list.length === 0)
|
|
248
|
+
continue;
|
|
249
|
+
const remaining = cappedLimit - totalShown;
|
|
250
|
+
if (remaining <= 0)
|
|
251
|
+
break;
|
|
252
|
+
const shown = list.slice(0, Math.min(5, remaining));
|
|
253
|
+
lines.push(`### ${p}`);
|
|
254
|
+
shown.forEach((m, i) => {
|
|
255
|
+
const title = (m.title || m.question || m.market_slug || m.ticker || 'untitled');
|
|
256
|
+
const id = (m.condition_id || m.ticker || m.id);
|
|
257
|
+
const idTag = id ? ` · \`${String(id).slice(0, 18)}…\`` : '';
|
|
258
|
+
const vol = m.volume != null ? ` · vol ${formatUsd(m.volume)}` : '';
|
|
259
|
+
lines.push(`${i + 1}. ${title}${idTag}${vol}`);
|
|
260
|
+
totalShown++;
|
|
261
|
+
});
|
|
262
|
+
lines.push('');
|
|
263
|
+
}
|
|
264
|
+
if (totalShown === 0) {
|
|
265
|
+
// Bucket shape but empty — fall back to flat-list interpretation.
|
|
266
|
+
const flat = unwrapList(raw);
|
|
267
|
+
if (flat.length === 0) {
|
|
268
|
+
return { output: 'No markets matched across any platform.' };
|
|
269
|
+
}
|
|
270
|
+
flat.slice(0, cappedLimit).forEach((m, i) => {
|
|
271
|
+
const title = (m.title || m.question || m.market_slug || m.ticker || 'untitled');
|
|
272
|
+
const platform = (m.platform || m.source || 'unknown');
|
|
273
|
+
lines.push(`${i + 1}. **[${platform}]** ${title}`);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const flat = unwrapList(raw);
|
|
279
|
+
if (flat.length === 0) {
|
|
280
|
+
return { output: 'No markets matched across any platform.' };
|
|
281
|
+
}
|
|
282
|
+
flat.slice(0, cappedLimit).forEach((m, i) => {
|
|
283
|
+
const title = (m.title || m.question || m.market_slug || m.ticker || 'untitled');
|
|
284
|
+
const platform = (m.platform || m.source || 'unknown');
|
|
285
|
+
lines.push(`${i + 1}. **[${platform}]** ${title}`);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
lines.push(`_$0.005 paid via x402._`);
|
|
289
|
+
return { output: lines.join('\n') };
|
|
290
|
+
}
|
|
291
|
+
case 'leaderboard': {
|
|
292
|
+
// Global top-wallet ranking. Cheap ($0.001) — the right answer to
|
|
293
|
+
// "who's making money on Polymarket" / "who should I follow".
|
|
294
|
+
const raw = await getWithPayment('/v1/pm/polymarket/leaderboard', {
|
|
295
|
+
limit: cappedLimit,
|
|
296
|
+
sort,
|
|
297
|
+
}, ctx);
|
|
298
|
+
const rows = unwrapList(raw);
|
|
299
|
+
if (rows.length === 0) {
|
|
300
|
+
return { output: 'No leaderboard data returned.' };
|
|
301
|
+
}
|
|
302
|
+
const lines = [
|
|
303
|
+
`## Polymarket leaderboard — top ${rows.length} wallet${rows.length === 1 ? '' : 's'}`,
|
|
304
|
+
'',
|
|
305
|
+
];
|
|
306
|
+
rows.forEach((r, i) => {
|
|
307
|
+
const wallet = (r.wallet || r.address || r.proxy_wallet || 'unknown');
|
|
308
|
+
const w = wallet.length > 12
|
|
309
|
+
? `${wallet.slice(0, 8)}…${wallet.slice(-4)}`
|
|
310
|
+
: wallet;
|
|
311
|
+
const pnl = r.pnl ?? r.realized_pnl ?? r.total_pnl;
|
|
312
|
+
const volume = r.volume ?? r.total_volume;
|
|
313
|
+
const winRate = r.win_rate ?? r.winRate;
|
|
314
|
+
const name = (r.name || r.handle || r.username);
|
|
315
|
+
const handle = name ? ` (${name})` : '';
|
|
316
|
+
const parts = [];
|
|
317
|
+
if (pnl != null)
|
|
318
|
+
parts.push(`P&L ${formatUsd(pnl)}`);
|
|
319
|
+
if (volume != null)
|
|
320
|
+
parts.push(`vol ${formatUsd(volume)}`);
|
|
321
|
+
if (winRate != null)
|
|
322
|
+
parts.push(`win ${formatPct(winRate, 0)}`);
|
|
323
|
+
lines.push(`${i + 1}. \`${w}\`${handle}` + (parts.length > 0 ? ` — ${parts.join(' · ')}` : ''));
|
|
324
|
+
});
|
|
325
|
+
lines.push('', `_$0.001 paid via x402._`);
|
|
326
|
+
return { output: lines.join('\n') };
|
|
327
|
+
}
|
|
328
|
+
case 'walletProfile': {
|
|
329
|
+
if (!wallets || !wallets.trim()) {
|
|
330
|
+
return {
|
|
331
|
+
output: 'Error: `wallets` is required for walletProfile (single address or comma-separated list of Polymarket wallet addresses)',
|
|
332
|
+
isError: true,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
// Predexon's batch endpoint accepts multiple wallets; we forward
|
|
336
|
+
// verbatim. Single wallet works too — caller passes one address.
|
|
337
|
+
const raw = await getWithPayment('/v1/pm/polymarket/wallets/profiles', {
|
|
338
|
+
wallets: wallets.trim(),
|
|
339
|
+
}, ctx);
|
|
340
|
+
const profiles = unwrapList(raw);
|
|
341
|
+
if (profiles.length === 0) {
|
|
342
|
+
return { output: `No profile data returned for: ${wallets}` };
|
|
343
|
+
}
|
|
344
|
+
const lines = [
|
|
345
|
+
`## Polymarket wallet profile${profiles.length === 1 ? '' : 's'} — ${profiles.length}`,
|
|
346
|
+
'',
|
|
347
|
+
];
|
|
348
|
+
profiles.forEach((p, i) => {
|
|
349
|
+
const wallet = (p.wallet || p.address || p.proxy_wallet || 'unknown');
|
|
350
|
+
const w = wallet.length > 12
|
|
351
|
+
? `${wallet.slice(0, 8)}…${wallet.slice(-4)}`
|
|
352
|
+
: wallet;
|
|
353
|
+
const name = (p.name || p.handle || p.username);
|
|
354
|
+
const pnl = p.pnl ?? p.realized_pnl ?? p.total_pnl;
|
|
355
|
+
const unrealized = p.unrealized_pnl;
|
|
356
|
+
const volume = p.volume ?? p.total_volume;
|
|
357
|
+
const positions = p.positions_count ?? p.open_positions;
|
|
358
|
+
const winRate = p.win_rate ?? p.winRate;
|
|
359
|
+
lines.push(`${i + 1}. \`${w}\`` + (name ? ` (${name})` : ''));
|
|
360
|
+
const stats = [];
|
|
361
|
+
if (pnl != null)
|
|
362
|
+
stats.push(`P&L ${formatUsd(pnl)}`);
|
|
363
|
+
if (unrealized != null)
|
|
364
|
+
stats.push(`unrealized ${formatUsd(unrealized)}`);
|
|
365
|
+
if (volume != null)
|
|
366
|
+
stats.push(`vol ${formatUsd(volume)}`);
|
|
367
|
+
if (positions != null)
|
|
368
|
+
stats.push(`${positions} open`);
|
|
369
|
+
if (winRate != null)
|
|
370
|
+
stats.push(`win ${formatPct(winRate, 0)}`);
|
|
371
|
+
if (stats.length > 0)
|
|
372
|
+
lines.push(` ${stats.join(' · ')}`);
|
|
373
|
+
});
|
|
374
|
+
lines.push('', `_$0.005 paid via x402._`);
|
|
375
|
+
return { output: lines.join('\n') };
|
|
376
|
+
}
|
|
377
|
+
case 'smartActivity': {
|
|
378
|
+
// "Discover markets where high-performing wallets are active right now."
|
|
379
|
+
// Replaces the old `smartMoney` action (which hit a non-existent path
|
|
380
|
+
// /v1/pm/polymarket/market/<id>/smart-money — silently 404'd from
|
|
381
|
+
// launch). Verified 2026-05-05 against blockrun.ai/openapi.json.
|
|
382
|
+
const raw = await getWithPayment('/v1/pm/polymarket/markets/smart-activity', {
|
|
383
|
+
limit: cappedLimit,
|
|
384
|
+
search,
|
|
385
|
+
}, ctx);
|
|
386
|
+
const rows = unwrapList(raw);
|
|
387
|
+
if (rows.length === 0) {
|
|
388
|
+
return { output: 'No smart-money activity recorded right now.' };
|
|
389
|
+
}
|
|
390
|
+
const lines = [
|
|
391
|
+
`## Smart-money activity — ${rows.length} market${rows.length === 1 ? '' : 's'}`,
|
|
392
|
+
'_Markets where high-P&L Polymarket wallets are positioning right now._',
|
|
393
|
+
'',
|
|
394
|
+
];
|
|
395
|
+
rows.forEach((r, i) => {
|
|
396
|
+
const title = (r.question || r.title || r.market_slug || 'untitled');
|
|
397
|
+
const cid = (r.condition_id || r.id);
|
|
398
|
+
const cidTag = cid ? ` · \`${String(cid).slice(0, 14)}…\`` : '';
|
|
399
|
+
const smartCount = r.smart_wallets_count ?? r.wallet_count;
|
|
400
|
+
const netFlow = r.net_size ?? r.net_yes_size;
|
|
401
|
+
const stats = [];
|
|
402
|
+
if (smartCount != null)
|
|
403
|
+
stats.push(`${smartCount} smart wallet${smartCount === 1 ? '' : 's'}`);
|
|
404
|
+
if (netFlow != null)
|
|
405
|
+
stats.push(`net ${formatUsd(netFlow)}`);
|
|
406
|
+
lines.push(`${i + 1}. **${title}**${cidTag}` + (stats.length > 0 ? `\n ${stats.join(' · ')}` : ''));
|
|
407
|
+
});
|
|
408
|
+
lines.push('', `_$0.005 paid via x402._`);
|
|
409
|
+
return { output: lines.join('\n') };
|
|
410
|
+
}
|
|
411
|
+
case 'smartMoney': {
|
|
412
|
+
if (!conditionId) {
|
|
413
|
+
return {
|
|
414
|
+
output: 'Error: conditionId is required for smartMoney (Polymarket condition_id from a prior searchPolymarket or smartActivity call)',
|
|
415
|
+
isError: true,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
// Per-market drill-down. Official live registry:
|
|
419
|
+
// /api/v1/pm/polymarket/market/:condition_id/smart-money
|
|
420
|
+
const path = `/v1/pm/polymarket/market/${encodeURIComponent(conditionId)}/smart-money`;
|
|
421
|
+
const data = await getWithPayment(path, {}, ctx);
|
|
422
|
+
const buyers = (data.buyers ?? []).slice(0, 5);
|
|
423
|
+
const sellers = (data.sellers ?? []).slice(0, 5);
|
|
424
|
+
const lines = [
|
|
425
|
+
`## Smart money — \`${conditionId.slice(0, 14)}…\``,
|
|
426
|
+
];
|
|
427
|
+
if (data.net_yes_size != null || data.net_no_size != null) {
|
|
428
|
+
lines.push(`**Net flow:** YES ${formatUsd(data.net_yes_size)} / NO ${formatUsd(data.net_no_size)}`);
|
|
429
|
+
}
|
|
430
|
+
if (buyers.length > 0) {
|
|
431
|
+
lines.push('', '**Top buyers**');
|
|
432
|
+
buyers.forEach((b, i) => {
|
|
433
|
+
const w = b.wallet ? `${b.wallet.slice(0, 8)}…${b.wallet.slice(-4)}` : 'unknown';
|
|
434
|
+
lines.push(`${i + 1}. ${w} — ${formatUsd(b.size)} on ${b.outcome ?? 'unknown side'}`);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (sellers.length > 0) {
|
|
438
|
+
lines.push('', '**Top sellers**');
|
|
439
|
+
sellers.forEach((s, i) => {
|
|
440
|
+
const w = s.wallet ? `${s.wallet.slice(0, 8)}…${s.wallet.slice(-4)}` : 'unknown';
|
|
441
|
+
lines.push(`${i + 1}. ${w} — ${formatUsd(s.size)} on ${s.outcome ?? 'unknown side'}`);
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
if (buyers.length === 0 && sellers.length === 0) {
|
|
445
|
+
lines.push('No smart-money flow recorded for this market yet.');
|
|
446
|
+
}
|
|
447
|
+
lines.push('', `_$0.005 paid via x402._`);
|
|
448
|
+
return { output: lines.join('\n') };
|
|
449
|
+
}
|
|
165
450
|
case 'searchPolymarket': {
|
|
166
451
|
const raw = await getWithPayment('/v1/pm/polymarket/markets', {
|
|
167
452
|
search,
|
|
@@ -245,45 +530,9 @@ async function execute(input, ctx) {
|
|
|
245
530
|
lines.push('', `_$0.005 paid via x402._`);
|
|
246
531
|
return { output: lines.join('\n') };
|
|
247
532
|
}
|
|
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
533
|
default:
|
|
285
534
|
return {
|
|
286
|
-
output: `Error: unknown action "${action}". Use: searchPolymarket, searchKalshi, crossPlatform, smartMoney`,
|
|
535
|
+
output: `Error: unknown action "${action}". Use: searchAll, searchPolymarket, searchKalshi, crossPlatform, leaderboard, walletProfile, smartActivity, smartMoney`,
|
|
287
536
|
isError: true,
|
|
288
537
|
};
|
|
289
538
|
}
|
|
@@ -295,41 +544,63 @@ async function execute(input, ctx) {
|
|
|
295
544
|
export const predictionMarketCapability = {
|
|
296
545
|
spec: {
|
|
297
546
|
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" / "
|
|
547
|
+
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
548
|
'Actions: ' +
|
|
300
|
-
'`
|
|
301
|
-
'`
|
|
302
|
-
'`
|
|
303
|
-
'`
|
|
304
|
-
'
|
|
305
|
-
'
|
|
549
|
+
'`searchAll` (search markets across Polymarket+Kalshi+Limitless+Opinion+Predict.Fun in one call — $0.005), ' +
|
|
550
|
+
'`searchPolymarket` (Polymarket only, supports sort+status — $0.001), ' +
|
|
551
|
+
'`searchKalshi` (Kalshi only, supports sort+status — $0.001), ' +
|
|
552
|
+
'`crossPlatform` (matched market pairs across Polymarket+Kalshi for arbitrage / consensus — $0.005), ' +
|
|
553
|
+
'`leaderboard` (global top wallets by P&L on Polymarket — $0.001), ' +
|
|
554
|
+
'`walletProfile` (P&L + positions for one or more Polymarket wallets — $0.005), ' +
|
|
555
|
+
'`smartActivity` (markets where high-P&L wallets are positioning right now — $0.005), ' +
|
|
556
|
+
'`smartMoney` (smart-money positioning on one Polymarket condition_id — $0.005). ' +
|
|
557
|
+
'Default routing: ' +
|
|
558
|
+
'"is there a market on X anywhere" → searchAll. ' +
|
|
559
|
+
'"top wallets / who is profitable / who should I follow on Polymarket" → leaderboard. ' +
|
|
560
|
+
'"how is wallet 0xabc doing / show me their P&L" → walletProfile with that address. ' +
|
|
561
|
+
'"what are smart traders betting on right now" → smartActivity. ' +
|
|
562
|
+
'"show smart money on this specific Polymarket market" → smartMoney with conditionId. ' +
|
|
563
|
+
'"should I bet on X" → run searchPolymarket + searchKalshi in parallel and compare implied probabilities — divergence is the signal.',
|
|
306
564
|
input_schema: {
|
|
307
565
|
type: 'object',
|
|
308
566
|
properties: {
|
|
309
567
|
action: {
|
|
310
568
|
type: 'string',
|
|
311
|
-
enum: [
|
|
569
|
+
enum: [
|
|
570
|
+
'searchAll',
|
|
571
|
+
'searchPolymarket',
|
|
572
|
+
'searchKalshi',
|
|
573
|
+
'crossPlatform',
|
|
574
|
+
'leaderboard',
|
|
575
|
+
'walletProfile',
|
|
576
|
+
'smartActivity',
|
|
577
|
+
'smartMoney',
|
|
578
|
+
],
|
|
312
579
|
description: 'Which prediction-market query to run. See tool description for cost per action.',
|
|
313
580
|
},
|
|
314
581
|
search: {
|
|
315
582
|
type: 'string',
|
|
316
|
-
description: 'Search query
|
|
583
|
+
description: 'Search query. Used by searchAll / searchPolymarket / searchKalshi / smartActivity. Optional for crossPlatform/leaderboard/walletProfile/smartMoney.',
|
|
317
584
|
},
|
|
318
585
|
status: {
|
|
319
586
|
type: 'string',
|
|
320
|
-
description: 'Polymarket: active | closed | archived (default active). Kalshi: open | closed (default open).',
|
|
587
|
+
description: 'Polymarket: active | closed | archived (default active). Kalshi: open | closed (default open). Forwarded to searchAll where supported.',
|
|
321
588
|
},
|
|
322
589
|
sort: {
|
|
323
590
|
type: 'string',
|
|
324
|
-
description: 'Polymarket: volume | liquidity | created (default volume). Kalshi: volume | open_interest | price_desc | price_asc | close_time (default volume).',
|
|
591
|
+
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
592
|
},
|
|
326
593
|
limit: {
|
|
327
594
|
type: 'number',
|
|
328
595
|
description: `Max results (default ${DEFAULT_LIMIT}, hard cap ${MAX_LIMIT}).`,
|
|
329
596
|
},
|
|
597
|
+
wallets: {
|
|
598
|
+
type: 'string',
|
|
599
|
+
description: 'For walletProfile: a single Polymarket wallet address, or a comma-separated list of addresses for batch lookup.',
|
|
600
|
+
},
|
|
330
601
|
conditionId: {
|
|
331
602
|
type: 'string',
|
|
332
|
-
description: '
|
|
603
|
+
description: 'For smartMoney: Polymarket condition_id from searchPolymarket or smartActivity.',
|
|
333
604
|
},
|
|
334
605
|
},
|
|
335
606
|
required: ['action'],
|
package/package.json
CHANGED