@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.
@@ -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
- - "how is wallet 0xabc doing / show this trader's P&L / are they profitable" → \`walletProfile\` (\$0.005) with \`wallets="<address>"\` (comma-separated for batch).
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 batch profile lookup for one or more Polymarket
21
- * wallets P&L, positions, identity
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
@@ -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 batch profile lookup for one or more Polymarket
21
- * wallets P&L, positions, identity
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
- return `${(n * 100).toFixed(digits)}%`;
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
- // 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);
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
- // 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.
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` (P&L + positions for one or more Polymarket wallets — $0.005), ' +
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
- '"how is wallet 0xabc doing / show me their P&L" → walletProfile with that address. ' +
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.71",
3
+ "version": "3.15.73",
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": {