@blockrun/franklin 3.15.71 → 3.15.73
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 +1 -1
- package/dist/tools/prediction.d.ts +6 -2
- package/dist/tools/prediction.js +171 -17
- package/package.json +1 -1
package/dist/agent/context.js
CHANGED
|
@@ -341,7 +341,7 @@ If you find yourself about to emit one of these, stop and call the tool instead.
|
|
|
341
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
342
|
- "where do Polymarket and Kalshi disagree / arbitrage" → \`crossPlatform\` (\$0.005) returns pre-matched pairs.
|
|
343
343
|
- "who's profitable / top traders / who should I follow on Polymarket" → \`leaderboard\` (\$0.001) — global top wallets by P&L.
|
|
344
|
-
- "
|
|
344
|
+
- "analyze this wallet / can I copy this trader / 复制交易 / show me their P&L AND positions" → run \`walletProfile\` + \`walletPnl\` + \`walletPositions\` IN PARALLEL with the same address. Three \$0.005 calls = full picture for \$0.015. Do NOT \`Bash\`-curl \`data-api.polymarket.com\` directly — those are paid Predexon endpoints and going around them defeats the wallet-attached architecture. If just the profile is needed: \`walletProfile\` alone (single address → /wallet/{addr}, comma-list → batch).
|
|
345
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
346
|
- "show smart money on this specific Polymarket market / this condition_id" → \`smartMoney\` (\$0.005) with \`conditionId="<condition_id>"\`.
|
|
347
347
|
|
|
@@ -17,8 +17,12 @@
|
|
|
17
17
|
* crossPlatform $0.005 matching market pairs across Polymarket+Kalshi
|
|
18
18
|
* (the arbitrage / consensus signal)
|
|
19
19
|
* leaderboard $0.001 global Polymarket leaderboard — top wallets by P&L
|
|
20
|
-
* walletProfile $0.005
|
|
21
|
-
*
|
|
20
|
+
* walletProfile $0.005 full Polymarket wallet profile (single wallet)
|
|
21
|
+
* or batch profiles (comma-separated wallets)
|
|
22
|
+
* walletPnl $0.005 P&L summary + realized P&L time series for one
|
|
23
|
+
* Polymarket wallet
|
|
24
|
+
* walletPositions $0.005 open + historical positions for one Polymarket
|
|
25
|
+
* wallet
|
|
22
26
|
* smartActivity $0.005 discover markets where high-performing wallets
|
|
23
27
|
* are active right now
|
|
24
28
|
* smartMoney $0.005 smart-money positioning on one Polymarket
|
package/dist/tools/prediction.js
CHANGED
|
@@ -17,8 +17,12 @@
|
|
|
17
17
|
* crossPlatform $0.005 matching market pairs across Polymarket+Kalshi
|
|
18
18
|
* (the arbitrage / consensus signal)
|
|
19
19
|
* leaderboard $0.001 global Polymarket leaderboard — top wallets by P&L
|
|
20
|
-
* walletProfile $0.005
|
|
21
|
-
*
|
|
20
|
+
* walletProfile $0.005 full Polymarket wallet profile (single wallet)
|
|
21
|
+
* or batch profiles (comma-separated wallets)
|
|
22
|
+
* walletPnl $0.005 P&L summary + realized P&L time series for one
|
|
23
|
+
* Polymarket wallet
|
|
24
|
+
* walletPositions $0.005 open + historical positions for one Polymarket
|
|
25
|
+
* wallet
|
|
22
26
|
* smartActivity $0.005 discover markets where high-performing wallets
|
|
23
27
|
* are active right now
|
|
24
28
|
* smartMoney $0.005 smart-money positioning on one Polymarket
|
|
@@ -186,11 +190,18 @@ function formatUsd(value) {
|
|
|
186
190
|
return `$${(n / 1e3).toFixed(1)}K`;
|
|
187
191
|
return `$${n.toFixed(2)}`;
|
|
188
192
|
}
|
|
193
|
+
function formatQuantity(value) {
|
|
194
|
+
const n = asNumber(value);
|
|
195
|
+
if (n == null)
|
|
196
|
+
return String(value ?? 'n/a');
|
|
197
|
+
return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(undefined, { maximumFractionDigits: 4 });
|
|
198
|
+
}
|
|
189
199
|
function formatPct(value, digits = 1) {
|
|
190
200
|
const n = asNumber(value);
|
|
191
201
|
if (n == null)
|
|
192
202
|
return 'n/a';
|
|
193
|
-
|
|
203
|
+
const pct = Math.abs(n) > 1 ? n : n * 100;
|
|
204
|
+
return `${pct.toFixed(digits)}%`;
|
|
194
205
|
}
|
|
195
206
|
// API responses sometimes come wrapped as `{data: [...], pagination: ...}`,
|
|
196
207
|
// other times as a bare array. Normalise to an array.
|
|
@@ -207,15 +218,23 @@ function unwrapList(raw) {
|
|
|
207
218
|
return obj.pairs;
|
|
208
219
|
if (Array.isArray(obj.results))
|
|
209
220
|
return obj.results;
|
|
221
|
+
if (Array.isArray(obj.positions))
|
|
222
|
+
return obj.positions;
|
|
210
223
|
}
|
|
211
224
|
return [];
|
|
212
225
|
}
|
|
226
|
+
function parseWalletsInput(value) {
|
|
227
|
+
return value
|
|
228
|
+
.split(',')
|
|
229
|
+
.map(w => w.trim())
|
|
230
|
+
.filter(Boolean);
|
|
231
|
+
}
|
|
213
232
|
async function execute(input, ctx) {
|
|
214
233
|
const { action, search, status, sort, limit, conditionId, wallets } = input;
|
|
215
234
|
const cappedLimit = Math.min(Math.max(1, limit ?? DEFAULT_LIMIT), MAX_LIMIT);
|
|
216
235
|
if (!action) {
|
|
217
236
|
return {
|
|
218
|
-
output: 'Error: action is required (searchAll | searchPolymarket | searchKalshi | crossPlatform | leaderboard | walletProfile | smartActivity | smartMoney)',
|
|
237
|
+
output: 'Error: action is required (searchAll | searchPolymarket | searchKalshi | crossPlatform | leaderboard | walletProfile | walletPnl | walletPositions | smartActivity | smartMoney)',
|
|
219
238
|
isError: true,
|
|
220
239
|
};
|
|
221
240
|
}
|
|
@@ -332,12 +351,33 @@ async function execute(input, ctx) {
|
|
|
332
351
|
isError: true,
|
|
333
352
|
};
|
|
334
353
|
}
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
354
|
+
// Smart dispatch: a single wallet → /wallet/{addr} (full profile,
|
|
355
|
+
// labels, scores, stats); a comma-list → /wallets/profiles (batch).
|
|
356
|
+
// The 3.15.70 ship hit the BATCH endpoint for everything and got 422
|
|
357
|
+
// for the single-wallet case; the gateway team confirmed 2026-05-06
|
|
358
|
+
// the right surface for "analyze this trader" is the path-parameter
|
|
359
|
+
// single-wallet endpoint, not the batch query-param one.
|
|
360
|
+
const parsedWallets = parseWalletsInput(wallets);
|
|
361
|
+
if (parsedWallets.length === 0) {
|
|
362
|
+
return {
|
|
363
|
+
output: 'Error: `wallets` must include at least one Polymarket wallet address',
|
|
364
|
+
isError: true,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
const list = parsedWallets.join(',');
|
|
368
|
+
const isBatch = parsedWallets.length > 1;
|
|
369
|
+
const raw = isBatch
|
|
370
|
+
? await getWithPayment('/v1/pm/polymarket/wallets/profiles', {
|
|
371
|
+
addresses: list,
|
|
372
|
+
}, ctx)
|
|
373
|
+
: await getWithPayment(`/v1/pm/polymarket/wallet/${encodeURIComponent(list)}`, {}, ctx);
|
|
374
|
+
// Single-wallet path returns a single profile object; batch returns
|
|
375
|
+
// an array (or {data:[]}). unwrapList handles the batch shape but
|
|
376
|
+
// returns [] for a bare object — wrap explicitly so the formatter
|
|
377
|
+
// below sees the single profile.
|
|
378
|
+
const profiles = isBatch
|
|
379
|
+
? unwrapList(raw)
|
|
380
|
+
: (raw && typeof raw === 'object' ? [raw] : []);
|
|
341
381
|
if (profiles.length === 0) {
|
|
342
382
|
return { output: `No profile data returned for: ${wallets}` };
|
|
343
383
|
}
|
|
@@ -374,11 +414,121 @@ async function execute(input, ctx) {
|
|
|
374
414
|
lines.push('', `_$0.005 paid via x402._`);
|
|
375
415
|
return { output: lines.join('\n') };
|
|
376
416
|
}
|
|
417
|
+
case 'walletPnl': {
|
|
418
|
+
// Single-wallet P&L summary + time series.
|
|
419
|
+
// Predexon path: /v1/pm/polymarket/wallet/pnl/{wallet} — Tier 2 ($0.005).
|
|
420
|
+
if (!wallets || !wallets.trim()) {
|
|
421
|
+
return {
|
|
422
|
+
output: 'Error: `wallets` is required for walletPnl (single Polymarket wallet address)',
|
|
423
|
+
isError: true,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
const parsedWallets = parseWalletsInput(wallets);
|
|
427
|
+
if (parsedWallets.length !== 1) {
|
|
428
|
+
return {
|
|
429
|
+
output: 'Error: walletPnl accepts exactly one wallet address. For multiple wallets, call walletPnl once per address in parallel.',
|
|
430
|
+
isError: true,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const wallet = parsedWallets[0];
|
|
434
|
+
const raw = await getWithPayment(`/v1/pm/polymarket/wallet/pnl/${encodeURIComponent(wallet)}`, {}, ctx);
|
|
435
|
+
if (!raw || typeof raw !== 'object') {
|
|
436
|
+
return { output: `No P&L data returned for ${wallet}` };
|
|
437
|
+
}
|
|
438
|
+
const data = raw;
|
|
439
|
+
const realized = data.realized_pnl ?? data.realizedPnl ?? data.total_pnl ?? data.pnl;
|
|
440
|
+
const unrealized = data.unrealized_pnl ?? data.unrealizedPnl;
|
|
441
|
+
const total = data.total_value ?? data.totalValue ?? data.equity;
|
|
442
|
+
const volume = data.volume ?? data.total_volume;
|
|
443
|
+
const winRate = data.win_rate ?? data.winRate;
|
|
444
|
+
const w = wallet.length > 12 ? `${wallet.slice(0, 8)}…${wallet.slice(-4)}` : wallet;
|
|
445
|
+
const lines = [`## Polymarket wallet P&L — \`${w}\``, ''];
|
|
446
|
+
const summary = [];
|
|
447
|
+
if (realized != null)
|
|
448
|
+
summary.push(`realized ${formatUsd(realized)}`);
|
|
449
|
+
if (unrealized != null)
|
|
450
|
+
summary.push(`unrealized ${formatUsd(unrealized)}`);
|
|
451
|
+
if (total != null)
|
|
452
|
+
summary.push(`equity ${formatUsd(total)}`);
|
|
453
|
+
if (volume != null)
|
|
454
|
+
summary.push(`vol ${formatUsd(volume)}`);
|
|
455
|
+
if (winRate != null)
|
|
456
|
+
summary.push(`win ${formatPct(winRate, 0)}`);
|
|
457
|
+
if (summary.length > 0)
|
|
458
|
+
lines.push(summary.join(' · '));
|
|
459
|
+
// Optional time series — show recent points compactly if present.
|
|
460
|
+
const series = (data.series ?? data.history ?? data.daily);
|
|
461
|
+
if (Array.isArray(series) && series.length > 0) {
|
|
462
|
+
lines.push('', `**Recent points** (latest ${Math.min(7, series.length)}):`);
|
|
463
|
+
series.slice(-7).forEach(pt => {
|
|
464
|
+
const t = (pt.date ?? pt.ts ?? pt.timestamp);
|
|
465
|
+
const v = (pt.pnl ?? pt.value ?? pt.cumulative_pnl);
|
|
466
|
+
if (t != null && v != null) {
|
|
467
|
+
const tStr = typeof t === 'number' ? new Date(t).toISOString().slice(0, 10) : String(t).slice(0, 10);
|
|
468
|
+
lines.push(`- ${tStr} · ${formatUsd(v)}`);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
lines.push('', `_$0.005 paid via x402._`);
|
|
473
|
+
return { output: lines.join('\n') };
|
|
474
|
+
}
|
|
475
|
+
case 'walletPositions': {
|
|
476
|
+
// Single-wallet positions (open + historical).
|
|
477
|
+
// Predexon path: /v1/pm/polymarket/wallet/positions/{wallet} — Tier 2 ($0.005).
|
|
478
|
+
if (!wallets || !wallets.trim()) {
|
|
479
|
+
return {
|
|
480
|
+
output: 'Error: `wallets` is required for walletPositions (single Polymarket wallet address)',
|
|
481
|
+
isError: true,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
const parsedWallets = parseWalletsInput(wallets);
|
|
485
|
+
if (parsedWallets.length !== 1) {
|
|
486
|
+
return {
|
|
487
|
+
output: 'Error: walletPositions accepts exactly one wallet address. For multiple wallets, call walletPositions once per address in parallel.',
|
|
488
|
+
isError: true,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
const wallet = parsedWallets[0];
|
|
492
|
+
const raw = await getWithPayment(`/v1/pm/polymarket/wallet/positions/${encodeURIComponent(wallet)}`, { limit: cappedLimit }, ctx);
|
|
493
|
+
const positions = unwrapList(raw);
|
|
494
|
+
if (positions.length === 0) {
|
|
495
|
+
return { output: `No positions returned for ${wallet}` };
|
|
496
|
+
}
|
|
497
|
+
const w = wallet.length > 12 ? `${wallet.slice(0, 8)}…${wallet.slice(-4)}` : wallet;
|
|
498
|
+
const lines = [
|
|
499
|
+
`## Polymarket positions — \`${w}\` — ${positions.length} position${positions.length === 1 ? '' : 's'}`,
|
|
500
|
+
'',
|
|
501
|
+
];
|
|
502
|
+
positions.slice(0, cappedLimit).forEach((p, i) => {
|
|
503
|
+
const title = (p.title || p.market || p.question || p.market_slug || 'untitled');
|
|
504
|
+
const outcome = (p.outcome || p.side);
|
|
505
|
+
const size = p.size ?? p.shares ?? p.quantity;
|
|
506
|
+
const avgPrice = p.avg_price ?? p.avgPrice ?? p.average_price;
|
|
507
|
+
const currentValue = p.current_value ?? p.currentValue ?? p.value;
|
|
508
|
+
const pnl = p.pnl ?? p.unrealized_pnl ?? p.realized_pnl;
|
|
509
|
+
const pnlPct = p.pnl_pct ?? p.pnlPct ?? p.percent_pnl;
|
|
510
|
+
const parts = [];
|
|
511
|
+
if (outcome)
|
|
512
|
+
parts.push(outcome);
|
|
513
|
+
if (size != null)
|
|
514
|
+
parts.push(`size ${formatQuantity(size)}`);
|
|
515
|
+
if (avgPrice != null)
|
|
516
|
+
parts.push(`avg ${formatPct(avgPrice)}`);
|
|
517
|
+
if (currentValue != null)
|
|
518
|
+
parts.push(`now ${formatUsd(currentValue)}`);
|
|
519
|
+
if (pnl != null) {
|
|
520
|
+
const pctStr = pnlPct != null ? ` (${formatPct(pnlPct, 1)})` : '';
|
|
521
|
+
parts.push(`P&L ${formatUsd(pnl)}${pctStr}`);
|
|
522
|
+
}
|
|
523
|
+
lines.push(`${i + 1}. **${title}** — ${parts.join(' · ')}`);
|
|
524
|
+
});
|
|
525
|
+
lines.push('', `_$0.005 paid via x402._`);
|
|
526
|
+
return { output: lines.join('\n') };
|
|
527
|
+
}
|
|
377
528
|
case 'smartActivity': {
|
|
378
529
|
// "Discover markets where high-performing wallets are active right now."
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
// launch). Verified 2026-05-05 against blockrun.ai/openapi.json.
|
|
530
|
+
// Complements `smartMoney`: this discovers interesting markets across
|
|
531
|
+
// the venue; smartMoney drills into one condition_id.
|
|
382
532
|
const raw = await getWithPayment('/v1/pm/polymarket/markets/smart-activity', {
|
|
383
533
|
limit: cappedLimit,
|
|
384
534
|
search,
|
|
@@ -532,7 +682,7 @@ async function execute(input, ctx) {
|
|
|
532
682
|
}
|
|
533
683
|
default:
|
|
534
684
|
return {
|
|
535
|
-
output: `Error: unknown action "${action}". Use: searchAll, searchPolymarket, searchKalshi, crossPlatform, leaderboard, walletProfile, smartActivity, smartMoney`,
|
|
685
|
+
output: `Error: unknown action "${action}". Use: searchAll, searchPolymarket, searchKalshi, crossPlatform, leaderboard, walletProfile, walletPnl, walletPositions, smartActivity, smartMoney`,
|
|
536
686
|
isError: true,
|
|
537
687
|
};
|
|
538
688
|
}
|
|
@@ -551,13 +701,15 @@ export const predictionMarketCapability = {
|
|
|
551
701
|
'`searchKalshi` (Kalshi only, supports sort+status — $0.001), ' +
|
|
552
702
|
'`crossPlatform` (matched market pairs across Polymarket+Kalshi for arbitrage / consensus — $0.005), ' +
|
|
553
703
|
'`leaderboard` (global top wallets by P&L on Polymarket — $0.001), ' +
|
|
554
|
-
'`walletProfile` (
|
|
704
|
+
'`walletProfile` (full Polymarket wallet profile — labels, scores, stats. Single address → /wallet/{addr}; comma-list → batch /wallets/profiles — $0.005), ' +
|
|
705
|
+
'`walletPnl` (single Polymarket wallet P&L summary + time series — $0.005), ' +
|
|
706
|
+
'`walletPositions` (single Polymarket wallet positions — open + historical with P&L per position — $0.005), ' +
|
|
555
707
|
'`smartActivity` (markets where high-P&L wallets are positioning right now — $0.005), ' +
|
|
556
708
|
'`smartMoney` (smart-money positioning on one Polymarket condition_id — $0.005). ' +
|
|
557
709
|
'Default routing: ' +
|
|
558
710
|
'"is there a market on X anywhere" → searchAll. ' +
|
|
559
711
|
'"top wallets / who is profitable / who should I follow on Polymarket" → leaderboard. ' +
|
|
560
|
-
'"
|
|
712
|
+
'"analyze this wallet / can I copy this trader / 复制交易 / show me their P&L AND positions" → run walletProfile + walletPnl + walletPositions IN PARALLEL with the same address — three $0.005 calls give the full picture for $0.015. Do not Bash-curl Polymarket directly; the agent has paid tools for this. ' +
|
|
561
713
|
'"what are smart traders betting on right now" → smartActivity. ' +
|
|
562
714
|
'"show smart money on this specific Polymarket market" → smartMoney with conditionId. ' +
|
|
563
715
|
'"should I bet on X" → run searchPolymarket + searchKalshi in parallel and compare implied probabilities — divergence is the signal.',
|
|
@@ -573,6 +725,8 @@ export const predictionMarketCapability = {
|
|
|
573
725
|
'crossPlatform',
|
|
574
726
|
'leaderboard',
|
|
575
727
|
'walletProfile',
|
|
728
|
+
'walletPnl',
|
|
729
|
+
'walletPositions',
|
|
576
730
|
'smartActivity',
|
|
577
731
|
'smartMoney',
|
|
578
732
|
],
|
|
@@ -580,7 +734,7 @@ export const predictionMarketCapability = {
|
|
|
580
734
|
},
|
|
581
735
|
search: {
|
|
582
736
|
type: 'string',
|
|
583
|
-
description: 'Search query. Used by searchAll / searchPolymarket / searchKalshi / smartActivity. Optional for crossPlatform/leaderboard/walletProfile/smartMoney.',
|
|
737
|
+
description: 'Search query. Used by searchAll / searchPolymarket / searchKalshi / smartActivity. Optional for crossPlatform/leaderboard/walletProfile/walletPnl/walletPositions/smartMoney.',
|
|
584
738
|
},
|
|
585
739
|
status: {
|
|
586
740
|
type: 'string',
|
package/package.json
CHANGED