@oracle-agent/oracle 0.25.1 → 0.25.2
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/assets/graphics/fonts/CormorantGaramond-Bold.ttf +0 -0
- package/dist/assets/graphics/fonts/RobotoMono-Bold.ttf +0 -0
- package/dist/assets/graphics/fonts/dmmono-Medium.ttf +0 -0
- package/dist/assets/graphics/oracle_card.py +242 -39
- package/dist/assets/skills/oracle-desk-product/SKILL.md +1 -1
- package/dist/bin/desk-server.mjs +44 -44
- package/dist/bin/oracle-data-mcp.mjs +1 -1
- package/dist/bin/oracle-public-server.mjs +15 -15
- package/dist/bin/oracle-route.mjs +1 -1
- package/dist/cli/commands/chat.mjs +94 -94
- package/dist/cli/commands/doctor.mjs +3 -3
- package/dist/cli/commands/eval.mjs +27 -27
- package/dist/cli/commands/farm.mjs +57 -57
- package/dist/cli/commands/model.mjs +98 -98
- package/dist/cli/commands/receipt.mjs +2 -2
- package/dist/cli/commands/sign.mjs +13 -13
- package/dist/cli/commands/signer.mjs +13 -13
- package/dist/cli/commands/strategy.mjs +1 -1
- package/dist/cli/commands/swap.mjs +1 -1
- package/dist/cli/commands/venues.mjs +2 -2
- package/dist/data/desk-data.mjs +42 -42
- package/dist/index.mjs +51 -51
- package/dist/local-signer/index.mjs +3 -3
- package/dist/nft/index.mjs +1 -1
- package/dist/prepare-envelope.mjs +1 -1
- package/dist/public-api/agent-grants.mjs +3 -3
- package/dist/router/index.mjs +10 -10
- package/dist/router/prepare-bridge.mjs +1 -1
- package/dist/strategy/index.mjs +1 -1
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Oracle-branded alert cards.
|
|
3
3
|
|
|
4
|
-
RH AGENT
|
|
4
|
+
RH AGENT brand is dead. This module is the Oracle desk surface:
|
|
5
5
|
same lime/red candles, Oracle splash kit, official chain lockup (logo, not
|
|
6
6
|
a typeset fake), and price-only chrome for Polymarket / HIP-4.
|
|
7
7
|
|
|
@@ -66,6 +66,7 @@ def _init_fonts():
|
|
|
66
66
|
roots = [
|
|
67
67
|
Path.home() / ".local/share/fonts",
|
|
68
68
|
Path.home() / ".local/share/fonts/dmmono",
|
|
69
|
+
Path(__file__).resolve().parent / "fonts",
|
|
69
70
|
]
|
|
70
71
|
for root in roots:
|
|
71
72
|
if not root.exists():
|
|
@@ -93,8 +94,48 @@ def _init_fonts():
|
|
|
93
94
|
return _FONTS
|
|
94
95
|
|
|
95
96
|
|
|
97
|
+
def _oracle_display_fp(size: float):
|
|
98
|
+
"""Oracle display face at a real weight. Variable Cormorant has no bold
|
|
99
|
+
instance in matplotlib — we ship a 700 static cut next to this file."""
|
|
100
|
+
from matplotlib import font_manager as fm
|
|
101
|
+
|
|
102
|
+
here = Path(__file__).resolve().parent / "fonts"
|
|
103
|
+
for name in ("CormorantGaramond-Bold.ttf", "CormorantGaramond-SemiBold.ttf"):
|
|
104
|
+
p = here / name
|
|
105
|
+
if p.exists():
|
|
106
|
+
try:
|
|
107
|
+
fm.fontManager.addfont(str(p))
|
|
108
|
+
except Exception:
|
|
109
|
+
pass
|
|
110
|
+
return fm.FontProperties(fname=str(p), size=size)
|
|
111
|
+
return fm.FontProperties(family=_FONTS.get("display") or "DejaVu Serif", size=size, weight="bold")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _oracle_num_fp(size: float):
|
|
115
|
+
"""Oracle data face for figures. Cormorant is for names; numbers use
|
|
116
|
+
DM Mono / Roboto Mono from a real file so they stay bold and tabular."""
|
|
117
|
+
from matplotlib import font_manager as fm
|
|
118
|
+
|
|
119
|
+
cands = [
|
|
120
|
+
Path(__file__).resolve().parent / "fonts" / "RobotoMono-Bold.ttf",
|
|
121
|
+
Path.home() / ".local/share/fonts/keyitdev/RobotoMono-Bold.ttf",
|
|
122
|
+
Path(__file__).resolve().parent / "fonts" / "dmmono-Medium.ttf",
|
|
123
|
+
Path.home() / ".local/share/fonts/dmmono/dmmono-Medium.ttf",
|
|
124
|
+
Path.home() / ".local/share/fonts/MapleMono/MapleMono-Bold.ttf",
|
|
125
|
+
]
|
|
126
|
+
for p in cands:
|
|
127
|
+
if not p.exists():
|
|
128
|
+
continue
|
|
129
|
+
try:
|
|
130
|
+
fm.fontManager.addfont(str(p))
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
return fm.FontProperties(fname=str(p), size=size)
|
|
134
|
+
return fm.FontProperties(family=_FONTS.get("mono") or "DejaVu Sans Mono", size=size, weight="bold")
|
|
135
|
+
|
|
136
|
+
|
|
96
137
|
def _esc(s: str) -> str:
|
|
97
|
-
return str(s or "")
|
|
138
|
+
return str(s or "")
|
|
98
139
|
|
|
99
140
|
|
|
100
141
|
def _fmt_px(n: float) -> str:
|
|
@@ -208,7 +249,7 @@ def _load_logo(path: Path | None):
|
|
|
208
249
|
return None
|
|
209
250
|
|
|
210
251
|
|
|
211
|
-
def _chrome(fig, *, venue: str, eyebrow: str, hero: str, hero_color: str, chip: str, chip_bg: str, footer: str):
|
|
252
|
+
def _chrome(fig, *, venue: str, eyebrow: str, hero: str, hero_color: str, chip: str, chip_bg: str, footer: str, pair_label: str = ""):
|
|
212
253
|
from matplotlib import patheffects as pe
|
|
213
254
|
from matplotlib.lines import Line2D
|
|
214
255
|
|
|
@@ -228,7 +269,6 @@ def _chrome(fig, *, venue: str, eyebrow: str, hero: str, hero_color: str, chip:
|
|
|
228
269
|
text_x = 0.048
|
|
229
270
|
wm = fig.text(text_x, 0.922, "ORACLE", color=B["blue2"], fontsize=16, fontfamily=fonts["display"], fontweight="medium", va="center")
|
|
230
271
|
wm.set_path_effects([pe.withStroke(linewidth=3.0, foreground=B["glow"], alpha=0.65)])
|
|
231
|
-
# Chain tag: "CHAIN:" + the venue's official lockup (its real logo + font).
|
|
232
272
|
tag_x = text_x + 0.118
|
|
233
273
|
fig.text(tag_x, 0.922, "CHAIN:", color=B["mute"], fontsize=8.0, fontfamily=fonts["mono"], va="center")
|
|
234
274
|
lockup = _load_logo(meta.get("logo"))
|
|
@@ -249,11 +289,43 @@ def _chrome(fig, *, venue: str, eyebrow: str, hero: str, hero_color: str, chip:
|
|
|
249
289
|
fig.text(tag_x + 0.052, 0.922, meta["label"], color=meta["accent"], fontsize=8.0, fontfamily=fonts["mono"], va="center")
|
|
250
290
|
fig.text(0.048, 0.815, eyebrow, color=B["blue"], fontsize=7.2, fontfamily=fonts["mono"], va="top")
|
|
251
291
|
if hero:
|
|
252
|
-
|
|
292
|
+
hp = None
|
|
293
|
+
try:
|
|
294
|
+
hp = _oracle_num_fp(26)
|
|
295
|
+
except Exception:
|
|
296
|
+
hp = None
|
|
297
|
+
ht = fig.text(
|
|
298
|
+
0.952, 0.932, _esc(hero), color=hero_color or B["blue2"],
|
|
299
|
+
fontsize=26, fontfamily=fonts["mono"], fontweight="bold",
|
|
300
|
+
fontproperties=hp, ha="right", va="center", zorder=9,
|
|
301
|
+
parse_math=False,
|
|
302
|
+
)
|
|
303
|
+
if pair_label:
|
|
304
|
+
tp = None
|
|
305
|
+
try:
|
|
306
|
+
tp = _oracle_display_fp(20)
|
|
307
|
+
except Exception:
|
|
308
|
+
tp = None
|
|
309
|
+
# Sit flush left of the MC using the real glyph box.
|
|
310
|
+
gap = 0.014
|
|
311
|
+
x = 0.74
|
|
312
|
+
try:
|
|
313
|
+
fig.canvas.draw()
|
|
314
|
+
bb = ht.get_window_extent(renderer=fig.canvas.get_renderer())
|
|
315
|
+
x0, _ = fig.transFigure.inverted().transform((bb.x0, bb.y0))
|
|
316
|
+
x = x0 - gap
|
|
317
|
+
except Exception:
|
|
318
|
+
x = 0.952 - max(0.18, len(_esc(hero)) * 0.014)
|
|
319
|
+
fig.text(
|
|
320
|
+
x, 0.932, _esc(pair_label),
|
|
321
|
+
color=B["ink"], fontsize=20, fontfamily=fonts["display"],
|
|
322
|
+
fontweight="bold", fontproperties=tp, ha="right", va="center",
|
|
323
|
+
zorder=9,
|
|
324
|
+
)
|
|
253
325
|
if chip:
|
|
254
326
|
fig.text(0.952, 0.868, chip, color=B["blue2"], fontsize=8.2, fontfamily=fonts["mono"], ha="right", va="center")
|
|
255
327
|
fig.add_artist(Line2D([0.045, 0.955], [0.072, 0.072], transform=fig.transFigure, color=B["line"], linewidth=1.0))
|
|
256
|
-
fig.text(0.048, 0.040, os.environ.get("ORACLE_CARD_FOOTER", "oracle
|
|
328
|
+
fig.text(0.048, 0.040, os.environ.get("ORACLE_CARD_FOOTER", "oracle.demi.la"), color=B["blue2"], fontsize=6.8, fontfamily=fonts["mono"], fontweight="bold", va="center")
|
|
257
329
|
tag = fig.text(0.500, 0.040, "T H E F U T U R E I S A G E N T I C", color=B["blue2"], fontsize=6.8, fontfamily=fonts["display"], ha="center", va="center")
|
|
258
330
|
tag.set_path_effects([pe.withStroke(linewidth=2.2, foreground=B["glow"], alpha=0.6)])
|
|
259
331
|
right = f"{_esc(footer)} · " if footer else ""
|
|
@@ -285,6 +357,59 @@ def _bg(fig):
|
|
|
285
357
|
pass
|
|
286
358
|
|
|
287
359
|
|
|
360
|
+
def render_spot_card(
|
|
361
|
+
*,
|
|
362
|
+
symbol: str,
|
|
363
|
+
venue: str = "robinhood",
|
|
364
|
+
hero: str | None = None,
|
|
365
|
+
chip: str | None = None,
|
|
366
|
+
mid: str | None = None,
|
|
367
|
+
sub: str | None = None,
|
|
368
|
+
reason: str = "OHLCV UNAVAILABLE",
|
|
369
|
+
out_path: str | None = None,
|
|
370
|
+
) -> str | None:
|
|
371
|
+
"""Oracle chrome with live stats but no fake candles.
|
|
372
|
+
|
|
373
|
+
Used when Gecko OHLCV 429s. Honest: no data, no fake lines.
|
|
374
|
+
"""
|
|
375
|
+
import matplotlib
|
|
376
|
+
|
|
377
|
+
matplotlib.use("Agg")
|
|
378
|
+
import matplotlib.pyplot as plt
|
|
379
|
+
|
|
380
|
+
fonts = _init_fonts()
|
|
381
|
+
B = ORACLE
|
|
382
|
+
fig = plt.figure(figsize=(11.2, 6.3), dpi=160, facecolor=B["bg"])
|
|
383
|
+
_bg(fig)
|
|
384
|
+
ax = fig.add_axes([0.075, 0.135, 0.875, 0.68])
|
|
385
|
+
ax.set_facecolor(B["panel"])
|
|
386
|
+
ax.set_xticks([])
|
|
387
|
+
ax.set_yticks([])
|
|
388
|
+
for sp in ax.spines.values():
|
|
389
|
+
sp.set_visible(False)
|
|
390
|
+
sym = str(symbol or "TOKEN").lstrip("$").upper()
|
|
391
|
+
ax.text(0.06, 0.72, f"${sym}", color=B["ink"], fontsize=28, fontfamily=fonts["display"], fontweight="medium", transform=ax.transAxes)
|
|
392
|
+
if mid:
|
|
393
|
+
ax.text(0.06, 0.48, mid, color=B["blue2"], fontsize=18, fontfamily=fonts["display"], transform=ax.transAxes)
|
|
394
|
+
if sub:
|
|
395
|
+
ax.text(0.06, 0.30, sub, color=B["mute"], fontsize=12, fontfamily=fonts["mono"], transform=ax.transAxes)
|
|
396
|
+
ax.text(0.06, 0.12, reason, color=B["mute"], fontsize=8.5, fontfamily=fonts["mono"], transform=ax.transAxes)
|
|
397
|
+
_chrome(
|
|
398
|
+
fig,
|
|
399
|
+
venue=venue,
|
|
400
|
+
eyebrow=f"{sym} · SPOT",
|
|
401
|
+
hero=hero or "",
|
|
402
|
+
hero_color=B["blue2"],
|
|
403
|
+
chip=chip or "",
|
|
404
|
+
chip_bg=B["blue"],
|
|
405
|
+
footer=reason,
|
|
406
|
+
)
|
|
407
|
+
dest = out_path or str(Path(tempfile.gettempdir()) / f"oracle-spot-{sym[:12]}.png")
|
|
408
|
+
fig.savefig(dest, facecolor=fig.get_facecolor())
|
|
409
|
+
plt.close(fig)
|
|
410
|
+
return dest if Path(dest).exists() else None
|
|
411
|
+
|
|
412
|
+
|
|
288
413
|
def render_ohlc_card(
|
|
289
414
|
rows: list[dict],
|
|
290
415
|
*,
|
|
@@ -302,6 +427,9 @@ def render_ohlc_card(
|
|
|
302
427
|
hero: str | None = None,
|
|
303
428
|
chip: str | None = None,
|
|
304
429
|
chip_bg: str | None = None,
|
|
430
|
+
edge_px: float | None = None,
|
|
431
|
+
holders_delta_24h: int | None = None,
|
|
432
|
+
holders_now: int | None = None,
|
|
305
433
|
) -> str | None:
|
|
306
434
|
"""OHLC card with optional position overlays.
|
|
307
435
|
|
|
@@ -333,6 +461,17 @@ def render_ohlc_card(
|
|
|
333
461
|
lows = [float(r["l"]) for r in rows]
|
|
334
462
|
closes = [float(r["c"]) for r in rows]
|
|
335
463
|
vols = [float(r.get("v") or 0) for r in rows]
|
|
464
|
+
last_c = closes[-1] if closes else 0.0
|
|
465
|
+
if last_c > 0:
|
|
466
|
+
keep = [i for i in range(len(closes)) if 0.4 * last_c <= closes[i] <= 2.5 * last_c]
|
|
467
|
+
if 8 <= len(keep) < len(closes):
|
|
468
|
+
xs = [xs[i] for i in keep]
|
|
469
|
+
opens = [opens[i] for i in keep]
|
|
470
|
+
highs = [highs[i] for i in keep]
|
|
471
|
+
lows = [lows[i] for i in keep]
|
|
472
|
+
closes = [closes[i] for i in keep]
|
|
473
|
+
vols = [vols[i] for i in keep]
|
|
474
|
+
rows = [rows[i] for i in keep]
|
|
336
475
|
fig = plt.figure(figsize=(11.2, 6.3), dpi=160, facecolor=B["bg"])
|
|
337
476
|
_bg(fig)
|
|
338
477
|
ax = fig.add_axes([0.075, 0.195, 0.875, 0.575])
|
|
@@ -348,15 +487,25 @@ def render_ohlc_card(
|
|
|
348
487
|
a.set_axisbelow(True)
|
|
349
488
|
plt.setp(ax.get_xticklabels(), visible=False)
|
|
350
489
|
xnum = mdates.date2num(xs)
|
|
351
|
-
|
|
490
|
+
deltas = [xnum[i] - xnum[i - 1] for i in range(1, len(xnum)) if xnum[i] > xnum[i - 1]]
|
|
491
|
+
med = sorted(deltas)[len(deltas) // 2] if deltas else (15.0 / 1440.0)
|
|
492
|
+
wide = min(max(med * 0.55, 4.0 / 1440.0), 18.0 / 1440.0)
|
|
493
|
+
last_px = closes[-1] if closes else 0.0
|
|
494
|
+
clip_wicks = last_px >= 20
|
|
352
495
|
for i in range(len(rows)):
|
|
353
|
-
|
|
496
|
+
o, h, l, c = opens[i], highs[i], lows[i], closes[i]
|
|
497
|
+
if clip_wicks:
|
|
498
|
+
body_hi, body_lo = max(o, c), min(o, c)
|
|
499
|
+
cap = max(abs(c) * 0.012, (body_hi - body_lo) * 2.5, 0.15)
|
|
500
|
+
h = min(h, body_hi + cap)
|
|
501
|
+
l = max(l, body_lo - cap)
|
|
502
|
+
up = c >= o
|
|
354
503
|
body_c = B["up"] if up else B["down"]
|
|
355
504
|
wick_c = B["up_wick"] if up else B["down_wick"]
|
|
356
|
-
ax.plot([xnum[i], xnum[i]], [
|
|
357
|
-
body_lo = min(
|
|
358
|
-
body_h = abs(
|
|
359
|
-
ax.add_patch(Rectangle((xnum[i] - wide / 2, body_lo), wide, body_h, facecolor=body_c, edgecolor=body_c, linewidth=0.
|
|
505
|
+
ax.plot([xnum[i], xnum[i]], [l, h], color=wick_c, linewidth=0.85, zorder=2, solid_capstyle="round")
|
|
506
|
+
body_lo = min(o, c)
|
|
507
|
+
body_h = abs(c - o) or (h - l) * 0.04 or abs(c) * 1e-4
|
|
508
|
+
ax.add_patch(Rectangle((xnum[i] - wide / 2, body_lo), wide, body_h, facecolor=body_c, edgecolor=body_c, linewidth=0.4, zorder=3))
|
|
360
509
|
axv.bar(xnum[i], vols[i], width=wide, color=body_c, alpha=0.42, zorder=2)
|
|
361
510
|
last = closes[-1]
|
|
362
511
|
ax.axhline(last, color=B["blue"], linewidth=0.8, alpha=0.35, linestyle=(0, (4, 3)), zorder=4)
|
|
@@ -421,7 +570,7 @@ def render_ohlc_card(
|
|
|
421
570
|
except Exception:
|
|
422
571
|
continue
|
|
423
572
|
ax.annotate(
|
|
424
|
-
_esc(_fmt_px(last)), xy=(1.0, last), xycoords=("axes fraction", "data"),
|
|
573
|
+
_esc(_fmt_px(edge_px if edge_px is not None else last)), xy=(1.0, last), xycoords=("axes fraction", "data"),
|
|
425
574
|
xytext=(4, 0), textcoords="offset points", va="center", ha="left",
|
|
426
575
|
fontsize=7.0, fontfamily=fonts["mono"], fontweight="bold", color=B["chip_ink"],
|
|
427
576
|
bbox={"boxstyle": "round,pad=0.30", "facecolor": B["blue2"], "edgecolor": "none"}, zorder=6, annotation_clip=False,
|
|
@@ -438,6 +587,8 @@ def render_ohlc_card(
|
|
|
438
587
|
axv.xaxis_date()
|
|
439
588
|
axv.xaxis.set_major_formatter(mdates.DateFormatter("%m-%d %H:%M"))
|
|
440
589
|
axv.set_yticks([])
|
|
590
|
+
if last >= 1:
|
|
591
|
+
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda v, _p: f"{v:,.2f}"))
|
|
441
592
|
# 24h % gain: change vs the close nearest 24h before the last bar.
|
|
442
593
|
last_ts = xs[-1].timestamp()
|
|
443
594
|
base = None
|
|
@@ -447,15 +598,29 @@ def render_ohlc_card(
|
|
|
447
598
|
if base is None:
|
|
448
599
|
base = closes[0]
|
|
449
600
|
chg = (closes[-1] / base - 1) * 100 if base > 0 else 0
|
|
601
|
+
if last >= 20 and abs(chg) > 80:
|
|
602
|
+
chg = 0.0
|
|
603
|
+
if chip is None:
|
|
604
|
+
bits = []
|
|
605
|
+
if not (last >= 20 and abs((closes[-1] / base - 1) * 100 if base > 0 else 0) > 80):
|
|
606
|
+
bits.append(f"{chg:+.2f}% 24H")
|
|
607
|
+
elif chg != 0:
|
|
608
|
+
bits.append(f"{chg:+.2f}% 24H")
|
|
609
|
+
else:
|
|
610
|
+
bits.append("24H n/a")
|
|
611
|
+
if holders_delta_24h is not None:
|
|
612
|
+
bits.append(f"HLD {int(holders_delta_24h):+d}/24H")
|
|
613
|
+
elif holders_now is not None:
|
|
614
|
+
bits.append(f"HLD {int(holders_now)}")
|
|
615
|
+
chip = " · ".join(bits)
|
|
450
616
|
_chip_bg = chip_bg or (B["up"] if chg >= 0 else B["down"])
|
|
451
|
-
eyebrow_bits = [str(pair or symbol).upper()]
|
|
452
|
-
if market:
|
|
453
|
-
eyebrow_bits.append(str(market).upper())
|
|
454
617
|
_chrome(
|
|
455
|
-
fig, venue=venue, eyebrow="
|
|
618
|
+
fig, venue=venue, eyebrow="",
|
|
456
619
|
hero=hero if hero is not None else f"{_fmt_px(last)}", hero_color=B["blue2"],
|
|
457
|
-
chip=chip
|
|
620
|
+
chip=chip,
|
|
621
|
+
chip_bg=_chip_bg,
|
|
458
622
|
footer=f"{source} · CANDLES · {chain_meta(venue)['label']}",
|
|
623
|
+
pair_label=str(pair or "").upper(),
|
|
459
624
|
)
|
|
460
625
|
out = out_path or str(Path(tempfile.gettempdir()) / f"oracle-card-{venue}-{symbol}.png")
|
|
461
626
|
fig.savefig(out, facecolor=fig.get_facecolor(), bbox_inches="tight", pad_inches=0.08)
|
|
@@ -598,7 +763,16 @@ def render_stat_card(
|
|
|
598
763
|
cx = (i % per_row) * tile_w + tile_w / 2
|
|
599
764
|
cy = 0.985 - (i // per_row) * tile_h
|
|
600
765
|
ax.text(cx, cy - 0.055, str(lab).upper(), ha="center", va="top", fontsize=7.0, fontfamily=fonts["mono"], color=B["mute"])
|
|
601
|
-
|
|
766
|
+
np_ = None
|
|
767
|
+
try:
|
|
768
|
+
np_ = _oracle_num_fp(15)
|
|
769
|
+
except Exception:
|
|
770
|
+
np_ = None
|
|
771
|
+
ax.text(
|
|
772
|
+
cx, cy - 0.115, _esc(str(val)), ha="center", va="top",
|
|
773
|
+
fontsize=15, fontfamily=fonts["mono"], fontweight="bold",
|
|
774
|
+
fontproperties=np_, color=B["ink"], parse_math=False,
|
|
775
|
+
)
|
|
602
776
|
table_top = 0.985 - n_tile_rows * tile_h - 0.03
|
|
603
777
|
else:
|
|
604
778
|
table_top = 0.95
|
|
@@ -669,6 +843,7 @@ def render_nft_card(
|
|
|
669
843
|
volume_24h: float | None = None,
|
|
670
844
|
sales_24h: int | None = None,
|
|
671
845
|
listed: int | None = None,
|
|
846
|
+
listed_plus: bool = False,
|
|
672
847
|
supply: int | None = None,
|
|
673
848
|
owners: int | None = None,
|
|
674
849
|
top_offer: float | None = None,
|
|
@@ -736,23 +911,33 @@ def render_nft_card(
|
|
|
736
911
|
src = str(tmp_img)
|
|
737
912
|
from PIL import Image
|
|
738
913
|
|
|
914
|
+
raw = Path(src).read_bytes()[:200]
|
|
915
|
+
if src.lower().endswith(".svg") or raw.lstrip().startswith(b"<svg") or b"<svg" in raw[:80]:
|
|
916
|
+
png = Path(src).with_suffix(".png")
|
|
917
|
+
import subprocess
|
|
918
|
+
subprocess.run(
|
|
919
|
+
["rsvg-convert", "-w", "720", "-h", "720", "-o", str(png), src],
|
|
920
|
+
check=True, timeout=12, capture_output=True,
|
|
921
|
+
)
|
|
922
|
+
src = str(png)
|
|
739
923
|
img_obj = Image.open(src).convert("RGB")
|
|
740
924
|
except Exception:
|
|
741
925
|
img_obj = None
|
|
742
926
|
|
|
743
|
-
# Left: NFT image
|
|
927
|
+
# Left: NFT image, flush under the collection name. Right: floor chart.
|
|
744
928
|
chart_left = 0.075
|
|
929
|
+
panel_y, panel_h = 0.228, 0.538
|
|
745
930
|
if img_obj is not None:
|
|
746
|
-
iax = fig.add_axes([0.048,
|
|
931
|
+
iax = fig.add_axes([0.048, panel_y, 0.248, panel_h], zorder=5)
|
|
747
932
|
iax.imshow(img_obj)
|
|
748
933
|
iax.set_xticks([])
|
|
749
934
|
iax.set_yticks([])
|
|
750
935
|
for sp in iax.spines.values():
|
|
751
936
|
sp.set_color(B["blue"])
|
|
752
|
-
sp.set_linewidth(1.
|
|
937
|
+
sp.set_linewidth(1.15)
|
|
753
938
|
sp.set_alpha(0.55)
|
|
754
939
|
chart_left = 0.318
|
|
755
|
-
ax = fig.add_axes([chart_left,
|
|
940
|
+
ax = fig.add_axes([chart_left, panel_y, 0.952 - chart_left, panel_h])
|
|
756
941
|
ax.set_facecolor(B["panel"])
|
|
757
942
|
ax.tick_params(colors=B["mute"], labelsize=6.8)
|
|
758
943
|
for lbl in ax.get_xticklabels() + ax.get_yticklabels():
|
|
@@ -805,13 +990,23 @@ def render_nft_card(
|
|
|
805
990
|
ax.text(0.5, 0.5, "NO FLOOR HISTORY", ha="center", va="center", fontsize=9, fontfamily=fonts["mono"], color=B["mute"], transform=ax.transAxes)
|
|
806
991
|
# Bottom stat strip.
|
|
807
992
|
chg_txt = f"{floor_chg_24h:+.1f}%" if floor_chg_24h is not None else "UNKNOWN"
|
|
993
|
+
if listed is None:
|
|
994
|
+
listed_txt = "UNKNOWN"
|
|
995
|
+
elif listed_plus and supply:
|
|
996
|
+
listed_txt = f"{listed:,}+/{supply:,}"
|
|
997
|
+
elif listed_plus:
|
|
998
|
+
listed_txt = f"{listed:,}+"
|
|
999
|
+
elif supply:
|
|
1000
|
+
listed_txt = f"{listed:,}/{supply:,}"
|
|
1001
|
+
else:
|
|
1002
|
+
listed_txt = f"{listed:,}"
|
|
808
1003
|
strip = [
|
|
809
1004
|
("FLOOR", fmt(floor if floor is not None else (fy[-1] if fy else None))),
|
|
810
1005
|
("24H %", chg_txt),
|
|
811
1006
|
("TOP OFFER", fmt(top_offer)),
|
|
812
1007
|
("24H VOL", fmt(volume_24h)),
|
|
813
1008
|
("24H SALES", f"{sales_24h:,}" if sales_24h is not None else "UNKNOWN"),
|
|
814
|
-
("LISTED",
|
|
1009
|
+
("LISTED", listed_txt),
|
|
815
1010
|
("HOLDERS", f"{owners:,}" if owners is not None else "UNKNOWN"),
|
|
816
1011
|
]
|
|
817
1012
|
n = len(strip)
|
|
@@ -821,7 +1016,16 @@ def render_nft_card(
|
|
|
821
1016
|
if lab == "24H %" and floor_chg_24h is not None:
|
|
822
1017
|
col = B["up"] if floor_chg_24h >= 0 else B["down"]
|
|
823
1018
|
fig.text(cx, 0.185, lab, ha="center", va="center", fontsize=6.2, fontfamily=fonts["mono"], color=B["mute"])
|
|
824
|
-
|
|
1019
|
+
np_ = None
|
|
1020
|
+
try:
|
|
1021
|
+
np_ = _oracle_num_fp(12.5)
|
|
1022
|
+
except Exception:
|
|
1023
|
+
np_ = None
|
|
1024
|
+
fig.text(
|
|
1025
|
+
cx, 0.135, _esc(str(val)), ha="center", va="center",
|
|
1026
|
+
fontsize=12.5, fontfamily=fonts["mono"], fontweight="bold",
|
|
1027
|
+
fontproperties=np_, color=col, parse_math=False,
|
|
1028
|
+
)
|
|
825
1029
|
chip_bits = []
|
|
826
1030
|
if floor_chg_24h is not None:
|
|
827
1031
|
chip_bits.append(f"{floor_chg_24h:+.1f}% 24H")
|
|
@@ -835,24 +1039,23 @@ def render_nft_card(
|
|
|
835
1039
|
chip_color = B["up"] if floor_chg_24h >= 0 else B["down"]
|
|
836
1040
|
hero_color = ORACLE["blue2"]
|
|
837
1041
|
_chrome(
|
|
838
|
-
fig, venue=venue, eyebrow=""
|
|
1042
|
+
fig, venue=venue, eyebrow="",
|
|
839
1043
|
hero=fmt(floor if floor is not None else (fy[-1] if fy else None)),
|
|
840
1044
|
hero_color=hero_color, chip=chip, chip_bg=chip_color,
|
|
841
1045
|
footer=source,
|
|
842
1046
|
)
|
|
843
|
-
# Collection
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
fig.text(0.048, 0.812, collection[:60], color=B["blue"], fontsize=7.2, fontfamily=fonts["mono"], va="center")
|
|
1047
|
+
# Collection name: Oracle display (Cormorant 700), ice-blue like IF/USD.
|
|
1048
|
+
name = collection[:44].upper()
|
|
1049
|
+
t = fig.text(
|
|
1050
|
+
0.048, 0.800, name, color=B["blue2"],
|
|
1051
|
+
fontproperties=_oracle_display_fp(22.5),
|
|
1052
|
+
va="center", ha="left", zorder=9,
|
|
1053
|
+
)
|
|
1054
|
+
try:
|
|
1055
|
+
from matplotlib import patheffects as pe
|
|
1056
|
+
t.set_path_effects([pe.withStroke(linewidth=2.4, foreground=B["glow"], alpha=0.55)])
|
|
1057
|
+
except Exception:
|
|
1058
|
+
pass
|
|
856
1059
|
out = out_path or str(Path(tempfile.gettempdir()) / f"oracle-nft-{venue}.png")
|
|
857
1060
|
fig.savefig(out, facecolor=fig.get_facecolor(), bbox_inches="tight", pad_inches=0.08)
|
|
858
1061
|
plt.close(fig)
|
|
@@ -97,7 +97,7 @@ does not prove npm users receive it. A locally hosted model/data/policy loop doe
|
|
|
97
97
|
a bundled signer. A merged feature is not public until the npm artifact containing it is
|
|
98
98
|
published and inspected.
|
|
99
99
|
|
|
100
|
-
**Candidate release in this source tree:** public package `@oracle-agent/oracle@0.25.
|
|
100
|
+
**Candidate release in this source tree:** public package `@oracle-agent/oracle@0.25.2`
|
|
101
101
|
ships the encrypted local vault + short-lived loopback signer. Hosted/keyless
|
|
102
102
|
Oracle cannot hold keys or move funds. Self-host after `oracle sign init|import` is **auto-armed**
|
|
103
103
|
for user-initiated actions. Public still charges the disclosed fee card; Administrator
|