@signals-protocol/v1-sdk 1.4.1 → 1.5.1

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/index.d.ts CHANGED
@@ -10,4 +10,4 @@ export * as MathUtils from "./utils/math";
10
10
  export { toWAD, toMicroUSDC } from "./clmsr-sdk";
11
11
  export { createCLMSRSDK, createSignalsSDK } from "./clmsr-sdk";
12
12
  export * from "./share";
13
- export declare const VERSION = "1.4.1";
13
+ export declare const VERSION = "1.5.1";
package/dist/index.js CHANGED
@@ -67,4 +67,4 @@ Object.defineProperty(exports, "createSignalsSDK", { enumerable: true, get: func
67
67
  // Share image VNode templates
68
68
  __exportStar(require("./share"), exports);
69
69
  // Version (keep in sync with package.json)
70
- exports.VERSION = "1.4.1";
70
+ exports.VERSION = "1.5.1";
@@ -1,3 +1,5 @@
1
1
  import type { SatoriNode } from "./types";
2
- export declare const SIGNALS_LOGO_URI = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTciIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNyAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSI4LjUwMDI0IiB5PSIwIiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSIjMTQ0NGMyIi8+PHJlY3QgeD0iOC41MDAyNCIgeT0iNC4wMDAwNiIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iIzE0NDRjMiIvPjxyZWN0IHg9IjAuNSIgeT0iMTIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9IiMxNDQ0YzIiLz48cmVjdCB4PSIxMi41IiB5PSI4LjAwMDA2IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSIjMTQ0NGMyIi8+PHJlY3QgeD0iMTIuNSIgeT0iMCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iIzE0NDRjMiIvPjxyZWN0IHg9IjQuNTAwMjQiIHk9IjAiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9IiMxNDQ0YzIiLz48cmVjdCB4PSIxMi41IiB5PSI0LjAwMDA2IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSIjMTQ0NGMyIi8+PHJlY3QgeD0iNC41MDAyNCIgeT0iOC4wMDAwNiIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iIzE0NDRjMiIvPjwvc3ZnPg==";
2
+ export declare function buildLogoSquaresSVG(theme: "light" | "dark"): string;
3
+ /** @deprecated Use buildLogoSquaresSVG("light") for theme-aware rendering. */
4
+ export declare const SIGNALS_LOGO_URI: string;
3
5
  export declare function buildLogoStrokeSVG(size: number, theme: "light" | "dark"): SatoriNode;
@@ -1,10 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SIGNALS_LOGO_URI = void 0;
4
+ exports.buildLogoSquaresSVG = buildLogoSquaresSVG;
4
5
  exports.buildLogoStrokeSVG = buildLogoStrokeSVG;
5
6
  const h_1 = require("./h");
7
+ const LOGO_SQUARES = [
8
+ { x: "8.50024", y: "0" },
9
+ { x: "8.50024", y: "4.00006" },
10
+ { x: "0.5", y: "12" },
11
+ { x: "12.5", y: "8.00006" },
12
+ { x: "12.5", y: "0" },
13
+ { x: "4.50024", y: "0" },
14
+ { x: "12.5", y: "4.00006" },
15
+ { x: "4.50024", y: "8.00006" },
16
+ ];
17
+ function buildLogoSquaresSVG(theme) {
18
+ const fill = theme === "dark" ? "#FFFFFF" : "#1444c2";
19
+ const rects = LOGO_SQUARES.map(({ x, y }) => `<rect x="${x}" y="${y}" width="4" height="4" fill="${fill}"/>`).join("");
20
+ const svg = `<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">${rects}</svg>`;
21
+ // Use btoa for cross-platform compatibility (browser + Node 16+).
22
+ // SVG is ASCII only so direct btoa is safe (no UTF-8 escape needed).
23
+ return `data:image/svg+xml;base64,${btoa(svg)}`;
24
+ }
6
25
  // Signals logo SVG (pixel block pattern) as base64 data URI
7
- exports.SIGNALS_LOGO_URI = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTciIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNyAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSI4LjUwMDI0IiB5PSIwIiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSIjMTQ0NGMyIi8+PHJlY3QgeD0iOC41MDAyNCIgeT0iNC4wMDAwNiIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iIzE0NDRjMiIvPjxyZWN0IHg9IjAuNSIgeT0iMTIiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9IiMxNDQ0YzIiLz48cmVjdCB4PSIxMi41IiB5PSI4LjAwMDA2IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSIjMTQ0NGMyIi8+PHJlY3QgeD0iMTIuNSIgeT0iMCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iIzE0NDRjMiIvPjxyZWN0IHg9IjQuNTAwMjQiIHk9IjAiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9IiMxNDQ0YzIiLz48cmVjdCB4PSIxMi41IiB5PSI0LjAwMDA2IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSIjMTQ0NGMyIi8+PHJlY3QgeD0iNC41MDAyNCIgeT0iOC4wMDAwNiIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iIzE0NDRjMiIvPjwvc3ZnPg==";
26
+ /** @deprecated Use buildLogoSquaresSVG("light") for theme-aware rendering. */
27
+ exports.SIGNALS_LOGO_URI = buildLogoSquaresSVG("light");
8
28
  function buildLogoStrokeSVG(size, theme) {
9
29
  const stroke = theme === "dark" ? "white" : "#1444c2";
10
30
  return (0, h_1.h)("svg", {
@@ -0,0 +1,9 @@
1
+ export type AvatarTheme = "light" | "dark";
2
+ export interface AvatarData {
3
+ pattern: boolean[][];
4
+ primary: string;
5
+ secondary: string;
6
+ background: string;
7
+ }
8
+ export declare const AVATAR_COLOR_PALETTE: readonly ["#1444c2", "#2d88ff", "#fdd979", "#8bcaff", "#00BF40", "#F7931A", "#FF4343", "#6366f1", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#fb7185", "#fc97f3", "#5eead4", "#c4b5fd"];
9
+ export declare function getAvatarData(address: string, theme: AvatarTheme): AvatarData;
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AVATAR_COLOR_PALETTE = void 0;
4
+ exports.getAvatarData = getAvatarData;
5
+ exports.AVATAR_COLOR_PALETTE = [
6
+ "#1444c2",
7
+ "#2d88ff",
8
+ "#fdd979",
9
+ "#8bcaff",
10
+ "#00BF40",
11
+ "#F7931A",
12
+ "#FF4343",
13
+ "#6366f1",
14
+ "#8b5cf6",
15
+ "#ec4899",
16
+ "#f59e0b",
17
+ "#10b981",
18
+ "#fb7185",
19
+ "#fc97f3",
20
+ "#5eead4",
21
+ "#c4b5fd",
22
+ ];
23
+ const AVATAR_BACKGROUNDS = {
24
+ light: "#f8fafc",
25
+ dark: "#1e1e22",
26
+ };
27
+ function simpleHash(str) {
28
+ let hash = 5381;
29
+ for (let i = 0; i < str.length; i += 1) {
30
+ const char = str.charCodeAt(i);
31
+ hash = (hash << 5) + hash + char;
32
+ }
33
+ return Math.abs(hash);
34
+ }
35
+ function getSecondaryHash(address) {
36
+ let hash = 0;
37
+ const suffix = address.slice(-8);
38
+ for (let i = 0; i < suffix.length; i += 1) {
39
+ hash = ((hash << 3) + hash + suffix.charCodeAt(i)) & 0xffffffff;
40
+ }
41
+ return Math.abs(hash);
42
+ }
43
+ function generateSeedValues(address) {
44
+ const hash1 = simpleHash(address);
45
+ const hash2 = getSecondaryHash(address);
46
+ const values = [];
47
+ let seed = hash1;
48
+ for (let i = 0; i < 10; i += 1) {
49
+ seed = (seed * 1103015245 + 12345 + hash2) & 0x7fffffff;
50
+ values.push(seed);
51
+ }
52
+ return values;
53
+ }
54
+ function generatePixelPattern(address) {
55
+ const seeds = generateSeedValues(address);
56
+ const pattern = Array(5)
57
+ .fill(null)
58
+ .map(() => Array(5).fill(false));
59
+ for (let y = 0; y < 5; y += 1) {
60
+ for (let x = 0; x < 2; x += 1) {
61
+ const index = y * 2 + x;
62
+ const seedIndex = index % seeds.length;
63
+ const shouldFill = seeds[seedIndex] % 100 > 40;
64
+ pattern[y][x] = shouldFill;
65
+ pattern[y][4 - x] = shouldFill;
66
+ }
67
+ const centerIndex = y + 10;
68
+ const centerSeedIndex = centerIndex % seeds.length;
69
+ pattern[y][2] = seeds[centerSeedIndex] % 100 > 40;
70
+ }
71
+ const countFilled = () => pattern.flat().filter(Boolean).length;
72
+ const ensureMinPixels = (minPixels) => {
73
+ let filledCount = countFilled();
74
+ if (filledCount >= minPixels)
75
+ return;
76
+ const candidates = [];
77
+ for (let y = 0; y < 5; y += 1) {
78
+ for (let x = 0; x < 3; x += 1) {
79
+ if (!pattern[y][x]) {
80
+ candidates.push([y, x]);
81
+ }
82
+ }
83
+ }
84
+ let i = 0;
85
+ while (filledCount < minPixels && candidates.length > 0) {
86
+ const idx = (seeds[i % seeds.length] + i) % candidates.length;
87
+ const [y, x] = candidates.splice(idx, 1)[0];
88
+ if (!pattern[y][x]) {
89
+ pattern[y][x] = true;
90
+ if (x !== 2) {
91
+ if (!pattern[y][4 - x]) {
92
+ pattern[y][4 - x] = true;
93
+ filledCount += 2;
94
+ }
95
+ else {
96
+ filledCount += 1;
97
+ }
98
+ }
99
+ else {
100
+ filledCount += 1;
101
+ }
102
+ }
103
+ i += 1;
104
+ }
105
+ };
106
+ ensureMinPixels(12);
107
+ const parityCounts = { even: 0, odd: 0 };
108
+ for (let y = 0; y < 5; y += 1) {
109
+ for (let x = 0; x < 5; x += 1) {
110
+ if (pattern[y][x]) {
111
+ if ((y + x) % 2 === 0) {
112
+ parityCounts.even += 1;
113
+ }
114
+ else {
115
+ parityCounts.odd += 1;
116
+ }
117
+ }
118
+ }
119
+ }
120
+ if (parityCounts.even === 0 || parityCounts.odd === 0) {
121
+ const missingParity = parityCounts.even === 0 ? 0 : 1;
122
+ const candidates = [];
123
+ for (let y = 0; y < 5; y += 1) {
124
+ for (let x = 0; x < 3; x += 1) {
125
+ if (!pattern[y][x] && (y + x) % 2 === missingParity) {
126
+ candidates.push([y, x]);
127
+ }
128
+ }
129
+ }
130
+ if (candidates.length > 0) {
131
+ const idx = (seeds[(seeds.length - 1 + missingParity) % seeds.length] +
132
+ parityCounts.even +
133
+ parityCounts.odd) %
134
+ candidates.length;
135
+ const [y, x] = candidates[idx];
136
+ pattern[y][x] = true;
137
+ if (x !== 2) {
138
+ pattern[y][4 - x] = true;
139
+ }
140
+ }
141
+ }
142
+ return pattern;
143
+ }
144
+ function generateColors(address) {
145
+ const seeds = generateSeedValues(address);
146
+ const hash1 = simpleHash(address);
147
+ const hash2 = getSecondaryHash(address);
148
+ const primaryColorIndex = Math.abs(hash1 ^ seeds[3]) % exports.AVATAR_COLOR_PALETTE.length;
149
+ let secondaryColorIndex = Math.abs(hash2 ^ seeds[7]) % exports.AVATAR_COLOR_PALETTE.length;
150
+ if (secondaryColorIndex === primaryColorIndex) {
151
+ secondaryColorIndex =
152
+ (secondaryColorIndex + 1) % exports.AVATAR_COLOR_PALETTE.length;
153
+ }
154
+ return {
155
+ primary: exports.AVATAR_COLOR_PALETTE[primaryColorIndex],
156
+ secondary: exports.AVATAR_COLOR_PALETTE[secondaryColorIndex],
157
+ };
158
+ }
159
+ function getAvatarData(address, theme) {
160
+ const colors = generateColors(address);
161
+ return {
162
+ pattern: generatePixelPattern(address),
163
+ primary: colors.primary,
164
+ secondary: colors.secondary,
165
+ background: AVATAR_BACKGROUNDS[theme],
166
+ };
167
+ }
@@ -3,6 +3,8 @@ export { buildPositionVNode } from "./position-image";
3
3
  export { buildProfileVNode } from "./profile-image";
4
4
  export { buildDistributionVNode } from "./distribution-image";
5
5
  export { buildBrandVNode } from "./brand-image";
6
+ export { AVATAR_COLOR_PALETTE, getAvatarData } from "./avatar";
7
+ export type { AvatarData, AvatarTheme } from "./avatar";
6
8
  export { h } from "./h";
7
9
  export { SIGNALS_LOGO_URI, buildLogoStrokeSVG } from "./assets";
8
10
  export { formatUSDC, formatPrice, formatPriceRange, percForm, formatUsdPnl, getPnlColor, } from "./format";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatDistributionPriceRange = exports.getPeakRange = exports.formatXTick = exports.generateXAxisTicks = exports.getSqrtHeight = exports.getBarCategory = exports.getPnlColor = exports.formatUsdPnl = exports.percForm = exports.formatPriceRange = exports.formatPrice = exports.formatUSDC = exports.buildLogoStrokeSVG = exports.SIGNALS_LOGO_URI = exports.h = exports.buildBrandVNode = exports.buildDistributionVNode = exports.buildProfileVNode = exports.buildPositionVNode = void 0;
3
+ exports.formatDistributionPriceRange = exports.getPeakRange = exports.formatXTick = exports.generateXAxisTicks = exports.getSqrtHeight = exports.getBarCategory = exports.getPnlColor = exports.formatUsdPnl = exports.percForm = exports.formatPriceRange = exports.formatPrice = exports.formatUSDC = exports.buildLogoStrokeSVG = exports.SIGNALS_LOGO_URI = exports.h = exports.getAvatarData = exports.AVATAR_COLOR_PALETTE = exports.buildBrandVNode = exports.buildDistributionVNode = exports.buildProfileVNode = exports.buildPositionVNode = void 0;
4
4
  // VNode builders
5
5
  var position_image_1 = require("./position-image");
6
6
  Object.defineProperty(exports, "buildPositionVNode", { enumerable: true, get: function () { return position_image_1.buildPositionVNode; } });
@@ -10,6 +10,9 @@ var distribution_image_1 = require("./distribution-image");
10
10
  Object.defineProperty(exports, "buildDistributionVNode", { enumerable: true, get: function () { return distribution_image_1.buildDistributionVNode; } });
11
11
  var brand_image_1 = require("./brand-image");
12
12
  Object.defineProperty(exports, "buildBrandVNode", { enumerable: true, get: function () { return brand_image_1.buildBrandVNode; } });
13
+ var avatar_1 = require("./avatar");
14
+ Object.defineProperty(exports, "AVATAR_COLOR_PALETTE", { enumerable: true, get: function () { return avatar_1.AVATAR_COLOR_PALETTE; } });
15
+ Object.defineProperty(exports, "getAvatarData", { enumerable: true, get: function () { return avatar_1.getAvatarData; } });
13
16
  // Helpers (used by v1-server for its own distribution rendering)
14
17
  var h_1 = require("./h");
15
18
  Object.defineProperty(exports, "h", { enumerable: true, get: function () { return h_1.h; } });
@@ -3,18 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildProfileVNode = buildProfileVNode;
4
4
  const h_1 = require("./h");
5
5
  const assets_1 = require("./assets");
6
+ const avatar_1 = require("./avatar");
6
7
  const CARD_WIDTH = 1200;
7
8
  const CARD_HEIGHT = 630;
8
9
  const OUTER_PADDING = 24;
9
10
  const CARD_PADDING_X = 32;
10
11
  const CARD_PADDING_Y = 28;
11
12
  const CHART_WIDTH = CARD_WIDTH - OUTER_PADDING * 2 - CARD_PADDING_X * 2;
12
- const CHART_HEIGHT = 228;
13
+ const CHART_HEIGHT = 308;
13
14
  const PLOT_LEFT = 56;
14
15
  const PLOT_RIGHT = 18;
15
16
  const PLOT_TOP = 14;
16
17
  const PLOT_BOTTOM = 24;
17
18
  const BAND_WIDTH = 18;
19
+ const DAY_MS = 86400000;
20
+ const AVATAR_SIZE = 96;
21
+ const MAX_RENDERED_POSITIONS = 1000;
18
22
  const OG_THEMES = {
19
23
  light: {
20
24
  pageBg: "#eef7ff", // bg-surface-soft
@@ -41,13 +45,83 @@ const OG_THEMES = {
41
45
  neutralBg: "#27272A", // surface-muted
42
46
  },
43
47
  };
44
- const TIER_ACCENT_COLORS = {
45
- BRONZE: "#C7742A",
46
- SILVER: "#BFC6CE",
47
- GOLD: "#F3BC3C",
48
- PLATINUM: "#63CBB2",
49
- DIAMOND: "#5EC0F2",
48
+ const TIER_CHIP_THEMES = {
49
+ BRONZE: {
50
+ light: {
51
+ from: "#FFE6CC",
52
+ to: "#FFC999",
53
+ border: "#C97E3D",
54
+ icon: "#8B4513",
55
+ },
56
+ dark: {
57
+ from: "#3D2B1A",
58
+ to: "#5C3A1E",
59
+ border: "#8B5E2B",
60
+ icon: "#F5E5D4",
61
+ },
62
+ },
63
+ SILVER: {
64
+ light: {
65
+ from: "#F2F5F8",
66
+ to: "#E2E7EE",
67
+ border: "#BFC6CE",
68
+ icon: "#6B7280",
69
+ },
70
+ dark: {
71
+ from: "#2A2D31",
72
+ to: "#363A40",
73
+ border: "#6B7280",
74
+ icon: "#F3F4F6",
75
+ },
76
+ },
77
+ GOLD: {
78
+ light: {
79
+ from: "#FFF0B8",
80
+ to: "#FFD875",
81
+ border: "#D9A441",
82
+ icon: "#DA9100",
83
+ },
84
+ dark: {
85
+ from: "#3D3520",
86
+ to: "#4A3D1A",
87
+ border: "#B8892E",
88
+ icon: "#FFE9B3",
89
+ },
90
+ },
91
+ PLATINUM: {
92
+ light: {
93
+ from: "#E4FFF7",
94
+ to: "#CDEFE5",
95
+ border: "#8FD7C6",
96
+ icon: "#059669",
97
+ },
98
+ dark: {
99
+ from: "#1A3D33",
100
+ to: "#1F3B31",
101
+ border: "#4DAE96",
102
+ icon: "#BAEBD9",
103
+ },
104
+ },
105
+ DIAMOND: {
106
+ light: {
107
+ from: "#E2F4FF",
108
+ to: "#C9E9FF",
109
+ border: "#89D0FF",
110
+ icon: "#0284C7",
111
+ },
112
+ dark: {
113
+ from: "#1A2F3D",
114
+ to: "#1E3545",
115
+ border: "#5AAFE0",
116
+ icon: "#5EC0F2",
117
+ },
118
+ },
50
119
  };
120
+ const PRICE_TICK_STEPS = [
121
+ 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000,
122
+ 250000, 500000, 1000000,
123
+ ];
124
+ const MAX_MATERIALIZED_PRICE_TICKS = 20;
51
125
  function shortenAddress(address) {
52
126
  return `${address.slice(0, 6)}…${address.slice(-4)}`;
53
127
  }
@@ -99,68 +173,126 @@ function normalizePositions(positions) {
99
173
  });
100
174
  return normalized;
101
175
  }
176
+ function capRenderablePositions(positions) {
177
+ if (positions.length <= MAX_RENDERED_POSITIONS) {
178
+ return positions;
179
+ }
180
+ return positions
181
+ .map((position, index) => ({ position, index }))
182
+ .sort((a, b) => b.position.marketEndTimestampMs - a.position.marketEndTimestampMs ||
183
+ a.index - b.index)
184
+ .slice(0, MAX_RENDERED_POSITIONS)
185
+ .map(({ position }) => position);
186
+ }
102
187
  function buildMainText(input) {
103
188
  const handle = resolveDisplayName(input.realHandle ?? null);
104
189
  if (handle)
105
190
  return handle;
106
- if (input.hideWallet)
107
- return "A Signals trader";
108
191
  return resolveDisplayName(input.displayName) ?? shortenAddress(input.address);
109
192
  }
193
+ function formatCallCopy(totalCalls) {
194
+ return `${totalCalls} BTC range ${totalCalls === 1 ? "call" : "calls"}`;
195
+ }
110
196
  function buildSubline(input) {
111
- const callCopy = `${input.totalCalls} BTC range calls`;
112
- if (input.hideWallet)
113
- return callCopy;
114
- const parts = [];
197
+ const callCopy = formatCallCopy(input.totalCalls);
115
198
  if (resolveDisplayName(input.realHandle ?? null) !== null) {
116
- parts.push(shortenAddress(input.address));
199
+ return `${shortenAddress(input.address)} with ${callCopy}`;
117
200
  }
118
201
  if (input.twitter) {
119
- parts.push(`@${input.twitter}`);
202
+ return `@${input.twitter} with ${callCopy}`;
120
203
  }
121
- parts.push(callCopy);
122
- return parts.join(" · ");
204
+ return `${callCopy} on Signals`;
205
+ }
206
+ function buildPriceTickCandidate(min, max, step) {
207
+ const alignedMin = Math.floor(min / step) * step;
208
+ const alignedMax = Math.ceil(max / step) * step;
209
+ const count = Math.floor((alignedMax - alignedMin) / step) + 1;
210
+ const ticks = [];
211
+ if (count > MAX_MATERIALIZED_PRICE_TICKS) {
212
+ return {
213
+ min: alignedMin,
214
+ max: alignedMax,
215
+ step,
216
+ count,
217
+ ticks,
218
+ };
219
+ }
220
+ for (let tick = alignedMin; tick <= alignedMax + step / 2; tick += step) {
221
+ ticks.push(tick);
222
+ }
223
+ return {
224
+ min: alignedMin,
225
+ max: alignedMax,
226
+ step,
227
+ count,
228
+ ticks,
229
+ };
123
230
  }
124
231
  function getPriceTickStep(span) {
125
232
  const target = span / 4;
126
- const candidates = [
127
- 500,
128
- 1000,
129
- 2500,
130
- 5000,
131
- 10000,
132
- 25000,
133
- 50000,
134
- 100000,
135
- 250000,
136
- 500000,
137
- 1000000,
138
- ];
139
- return candidates.find((step) => step >= target) ?? 1000000;
233
+ return (PRICE_TICK_STEPS.find((step) => step >= target) ??
234
+ PRICE_TICK_STEPS[PRICE_TICK_STEPS.length - 1]);
140
235
  }
141
236
  function getExpandedPriceBounds(min, max) {
142
237
  const span = max - min;
143
- const step = getPriceTickStep(span);
238
+ const desiredStep = getPriceTickStep(span);
239
+ const candidates = PRICE_TICK_STEPS.map((step) => buildPriceTickCandidate(min, max, step));
240
+ const validCandidates = candidates.filter((candidate) => candidate.ticks.length > 0 &&
241
+ candidate.count >= 4 &&
242
+ candidate.count <= 5);
243
+ const fallbackCandidates = candidates.filter((candidate) => candidate.ticks.length > 0);
244
+ const [selected] = (validCandidates.length > 0
245
+ ? validCandidates
246
+ : fallbackCandidates.length > 0
247
+ ? fallbackCandidates
248
+ : candidates)
249
+ .slice()
250
+ .sort((a, b) => {
251
+ const aCountPenalty = a.count >= 4 && a.count <= 5
252
+ ? 0
253
+ : Math.abs(a.count - 5) * 1000000;
254
+ const bCountPenalty = b.count >= 4 && b.count <= 5
255
+ ? 0
256
+ : Math.abs(b.count - 5) * 1000000;
257
+ return (aCountPenalty +
258
+ Math.abs(a.step - desiredStep) -
259
+ (bCountPenalty + Math.abs(b.step - desiredStep)));
260
+ });
144
261
  return {
145
- min: Math.floor(min / step) * step,
146
- max: Math.ceil(max / step) * step,
147
- step,
262
+ min: selected.min,
263
+ max: selected.max,
264
+ step: selected.step,
148
265
  };
149
266
  }
150
267
  function getPriceTicks(min, max, step) {
268
+ const count = Math.floor((max - min) / step) + 1;
151
269
  const ticks = [];
152
- for (let tick = min; tick <= max + step / 2; tick += step) {
153
- ticks.push(tick);
154
- if (ticks.length >= 6)
155
- break;
270
+ if (count <= MAX_MATERIALIZED_PRICE_TICKS) {
271
+ for (let tick = min; tick <= max + step / 2; tick += step) {
272
+ ticks.push(tick);
273
+ }
274
+ }
275
+ if (count >= 4 && count <= 5 && ticks.length > 0) {
276
+ return { ticks, step };
156
277
  }
157
- return ticks.length > 0 ? ticks : [min, max];
278
+ const fallbackCount = count < 4 ? 4 : 5;
279
+ const fallbackStep = (max - min) / (fallbackCount - 1);
280
+ return {
281
+ ticks: Array.from({ length: fallbackCount }, (_, index) => Math.round(min + fallbackStep * index)),
282
+ step: fallbackStep,
283
+ };
158
284
  }
159
- function formatAxisLabel(value) {
285
+ function formatAxisLabel(value, step) {
286
+ if (step < 100) {
287
+ return `$${Math.round(value).toLocaleString("en-US")}`;
288
+ }
160
289
  if (Math.abs(value) >= 1000) {
290
+ if (step >= 100 && step < 1000) {
291
+ return `$${(value / 1000).toFixed(1)}k`;
292
+ }
161
293
  return `$${Math.round(value / 1000)}k`;
162
294
  }
163
- return `$${Math.round(value)}`;
295
+ return `$${Math.round(value).toLocaleString("en-US")}`;
164
296
  }
165
297
  function computePriceBounds(btcHistory, positions) {
166
298
  let rawMin = Infinity;
@@ -232,9 +364,205 @@ function buildHistoryPath(btcHistory, scaleX, scaleY) {
232
364
  })
233
365
  .join(" ");
234
366
  }
367
+ function clamp(value, min, max) {
368
+ return Math.min(Math.max(value, min), max);
369
+ }
370
+ function formatTierName(tier) {
371
+ return `${tier[0]}${tier.slice(1).toLowerCase()}`;
372
+ }
373
+ function buildTierChip(tier, themeName, theme) {
374
+ const tierTheme = TIER_CHIP_THEMES[tier][themeName];
375
+ return (0, h_1.h)("div", {
376
+ style: {
377
+ display: "flex",
378
+ alignItems: "center",
379
+ gap: 8,
380
+ padding: "8px 14px",
381
+ borderRadius: 999,
382
+ border: `1px solid ${tierTheme.border}`,
383
+ background: `linear-gradient(135deg, ${tierTheme.from}, ${tierTheme.to})`,
384
+ color: theme.ink,
385
+ fontSize: 18,
386
+ fontWeight: 700,
387
+ lineHeight: 1,
388
+ flexShrink: 0,
389
+ },
390
+ }, (0, h_1.h)("svg", {
391
+ width: 18,
392
+ height: 18,
393
+ viewBox: "0 0 16 16",
394
+ style: { display: "flex", flexShrink: 0 },
395
+ }, (0, h_1.h)("polygon", {
396
+ points: "8 1 15 8 8 15 1 8",
397
+ fill: tierTheme.icon,
398
+ })), (0, h_1.h)("div", { style: { display: "flex" } }, formatTierName(tier)));
399
+ }
400
+ function buildAddressAvatar(address, themeName, theme) {
401
+ const avatar = (0, avatar_1.getAvatarData)(address, themeName);
402
+ return (0, h_1.h)("div", {
403
+ style: {
404
+ width: AVATAR_SIZE,
405
+ height: AVATAR_SIZE,
406
+ display: "flex",
407
+ flexDirection: "column",
408
+ overflow: "hidden",
409
+ borderRadius: 22,
410
+ border: `1px solid ${theme.border}`,
411
+ background: avatar.background,
412
+ flexShrink: 0,
413
+ },
414
+ }, ...avatar.pattern.map((row, y) => (0, h_1.h)("div", {
415
+ style: {
416
+ display: "flex",
417
+ width: "100%",
418
+ height: "20%",
419
+ },
420
+ }, ...row.map((isFilled, x) => (0, h_1.h)("div", {
421
+ style: {
422
+ width: "20%",
423
+ height: "100%",
424
+ backgroundColor: isFilled
425
+ ? (y + x) % 2 === 0
426
+ ? avatar.primary
427
+ : avatar.secondary
428
+ : "transparent",
429
+ },
430
+ })))));
431
+ }
432
+ function buildAvatar(input, theme) {
433
+ return buildAddressAvatar(input.address, input.theme, theme);
434
+ }
435
+ function buildBrandWordmark(themeName, theme) {
436
+ return (0, h_1.h)("div", {
437
+ style: {
438
+ display: "flex",
439
+ alignItems: "center",
440
+ gap: 10,
441
+ flexShrink: 0,
442
+ },
443
+ }, (0, h_1.h)("img", {
444
+ src: (0, assets_1.buildLogoSquaresSVG)(themeName),
445
+ width: 34,
446
+ height: 32,
447
+ style: { display: "flex" },
448
+ }), (0, h_1.h)("div", {
449
+ style: {
450
+ display: "flex",
451
+ fontSize: 28,
452
+ fontWeight: 700,
453
+ color: theme.primary,
454
+ },
455
+ }, "Signals"));
456
+ }
457
+ function buildHeader(input, theme, mainText, subline) {
458
+ return (0, h_1.h)("div", {
459
+ style: {
460
+ display: "flex",
461
+ alignItems: "flex-start",
462
+ justifyContent: "space-between",
463
+ gap: 24,
464
+ minHeight: AVATAR_SIZE,
465
+ },
466
+ }, (0, h_1.h)("div", {
467
+ style: {
468
+ flex: 1,
469
+ minWidth: 0,
470
+ display: "flex",
471
+ alignItems: "center",
472
+ gap: 18,
473
+ },
474
+ }, buildAvatar(input, theme), (0, h_1.h)("div", {
475
+ style: {
476
+ flex: 1,
477
+ minWidth: 0,
478
+ display: "flex",
479
+ flexDirection: "column",
480
+ },
481
+ }, (0, h_1.h)("div", {
482
+ style: {
483
+ display: "flex",
484
+ alignItems: "center",
485
+ gap: 14,
486
+ minWidth: 0,
487
+ },
488
+ }, (0, h_1.h)("div", {
489
+ style: {
490
+ display: "flex",
491
+ fontSize: 52,
492
+ lineHeight: 1.05,
493
+ fontWeight: 700,
494
+ color: theme.ink,
495
+ maxWidth: 620,
496
+ overflow: "hidden",
497
+ textOverflow: "ellipsis",
498
+ whiteSpace: "nowrap",
499
+ },
500
+ }, mainText), buildTierChip(input.tier, input.theme, theme)), (0, h_1.h)("div", {
501
+ style: {
502
+ display: "flex",
503
+ fontSize: 20,
504
+ fontWeight: 500,
505
+ color: theme.inkMuted,
506
+ marginTop: 8,
507
+ overflow: "hidden",
508
+ textOverflow: "ellipsis",
509
+ whiteSpace: "nowrap",
510
+ },
511
+ }, subline))), buildBrandWordmark(input.theme, theme));
512
+ }
513
+ function buildStatItem(label, value, valueColor, theme) {
514
+ return (0, h_1.h)("div", {
515
+ style: {
516
+ flex: 1,
517
+ minWidth: 0,
518
+ display: "flex",
519
+ flexDirection: "column",
520
+ },
521
+ }, (0, h_1.h)("div", {
522
+ style: {
523
+ display: "flex",
524
+ fontSize: 14,
525
+ fontWeight: 500,
526
+ color: theme.inkMuted,
527
+ marginBottom: 8,
528
+ },
529
+ }, label), (0, h_1.h)("div", {
530
+ style: {
531
+ display: "flex",
532
+ fontSize: 30,
533
+ lineHeight: 1.1,
534
+ fontWeight: 700,
535
+ color: valueColor,
536
+ },
537
+ }, value));
538
+ }
539
+ function buildStatSeparator(theme) {
540
+ return (0, h_1.h)("div", {
541
+ style: {
542
+ width: 1,
543
+ height: 28,
544
+ display: "flex",
545
+ background: theme.border,
546
+ margin: "0 18px",
547
+ flexShrink: 0,
548
+ },
549
+ });
550
+ }
551
+ function buildStatRow(input, theme, pnlValue, roiValue, bestMultValue) {
552
+ return (0, h_1.h)("div", {
553
+ style: {
554
+ display: "flex",
555
+ alignItems: "center",
556
+ marginTop: 20,
557
+ padding: "0 4px",
558
+ },
559
+ }, buildStatItem("P&L", pnlValue, input.hideAmounts
560
+ ? theme.inkMuted
561
+ : getSignedValueColor(input.totalPnl, theme), theme), buildStatSeparator(theme), buildStatItem("ROI", roiValue, getSignedValueColor(input.roi, theme), theme), buildStatSeparator(theme), buildStatItem("Best call", bestMultValue, theme.primary, theme));
562
+ }
235
563
  function buildChartArea(theme, btcHistory, positions) {
236
564
  const normalizedPositions = normalizePositions(positions);
237
- const renderablePositions = normalizedPositions.filter((position) => position.status !== "CLOSED");
565
+ const renderablePositions = capRenderablePositions(normalizedPositions);
238
566
  const sortedHistory = [...btcHistory].sort((a, b) => a.t - b.t);
239
567
  if (btcHistory.length === 0 && renderablePositions.length === 0) {
240
568
  return (0, h_1.h)("div", {
@@ -284,12 +612,40 @@ function buildChartArea(theme, btcHistory, positions) {
284
612
  return PLOT_TOP + plotHeight / 2;
285
613
  }
286
614
  return (PLOT_TOP +
287
- (1 -
288
- (value - priceBounds.min) / (priceBounds.max - priceBounds.min)) *
615
+ (1 - (value - priceBounds.min) / (priceBounds.max - priceBounds.min)) *
289
616
  plotHeight);
290
617
  };
291
618
  const priceTicks = getPriceTicks(priceBounds.min, priceBounds.max, priceBounds.step);
292
619
  const hasOpenPositions = renderablePositions.some((position) => position.status === "OPEN");
620
+ const latestSettlement = renderablePositions
621
+ .filter((position) => (position.status === "WIN" || position.status === "LOSS") &&
622
+ position.settlementPrice != null)
623
+ .sort((a, b) => b.marketEndTimestampMs - a.marketEndTimestampMs)[0];
624
+ const groupSizes = new Map();
625
+ const groupOrdinals = new Map();
626
+ renderablePositions.forEach((position) => {
627
+ const timestamp = position.marketEndTimestampMs;
628
+ const ordinal = groupSizes.get(timestamp) ?? 0;
629
+ groupOrdinals.set(position, ordinal);
630
+ groupSizes.set(timestamp, ordinal + 1);
631
+ });
632
+ const dayWidth = timeMin === timeMax ? 0 : (DAY_MS / (timeMax - timeMin)) * plotWidth;
633
+ const getBandX = (position) => {
634
+ const groupSize = groupSizes.get(position.marketEndTimestampMs) ?? 1;
635
+ const ordinal = groupOrdinals.get(position) ?? 0;
636
+ const center = scaleX(position.marketEndTimestampMs);
637
+ const spreadWidth = groupSize <= 1
638
+ ? 0
639
+ : timeMin === timeMax
640
+ ? plotWidth / 4
641
+ : Math.max(dayWidth, BAND_WIDTH * (groupSize + 1));
642
+ const offset = groupSize <= 1
643
+ ? 0
644
+ : (ordinal - (groupSize - 1) / 2) * (spreadWidth / groupSize);
645
+ const rawX = center + offset - BAND_WIDTH / 2;
646
+ return clamp(rawX, PLOT_LEFT, PLOT_LEFT + plotWidth - BAND_WIDTH);
647
+ };
648
+ const getBandCenterX = (position) => getBandX(position) + BAND_WIDTH / 2;
293
649
  const svgChildren = [];
294
650
  if (hasOpenPositions) {
295
651
  svgChildren.push((0, h_1.h)("defs", null, (0, h_1.h)("pattern", {
@@ -303,7 +659,7 @@ function buildChartArea(theme, btcHistory, positions) {
303
659
  strokeWidth: 2,
304
660
  }))));
305
661
  }
306
- priceTicks.forEach((tick) => {
662
+ priceTicks.ticks.forEach((tick) => {
307
663
  const y = scaleY(tick);
308
664
  svgChildren.push((0, h_1.h)("line", {
309
665
  x1: PLOT_LEFT,
@@ -315,10 +671,23 @@ function buildChartArea(theme, btcHistory, positions) {
315
671
  strokeWidth: 1,
316
672
  }));
317
673
  });
674
+ if (latestSettlement) {
675
+ const y = scaleY(latestSettlement.settlementPrice);
676
+ svgChildren.push((0, h_1.h)("line", {
677
+ x1: PLOT_LEFT,
678
+ x2: PLOT_LEFT + plotWidth,
679
+ y1: y,
680
+ y2: y,
681
+ stroke: theme.bitcoin,
682
+ strokeDasharray: "4 5",
683
+ strokeWidth: 1.5,
684
+ strokeOpacity: 0.75,
685
+ }));
686
+ }
318
687
  renderablePositions.forEach((position) => {
319
688
  const topPrice = Math.max(position.lowerPrice, position.upperPrice);
320
689
  const bottomPrice = Math.min(position.lowerPrice, position.upperPrice);
321
- const x = scaleX(position.marketEndTimestampMs) - BAND_WIDTH / 2;
690
+ const x = getBandX(position);
322
691
  const top = scaleY(topPrice);
323
692
  const bottom = scaleY(bottomPrice);
324
693
  const height = Math.max(bottom - top, 6);
@@ -330,13 +699,22 @@ function buildChartArea(theme, btcHistory, positions) {
330
699
  height,
331
700
  rx: 6,
332
701
  fill: "url(#profile-open-band)",
702
+ opacity: 0.4,
333
703
  stroke: theme.bitcoin,
334
704
  strokeWidth: 2,
335
705
  }));
336
706
  return;
337
707
  }
338
- const fill = position.status === "WIN" ? theme.primary : theme.negative;
339
- const fillOpacity = position.status === "WIN" ? 0.55 : 0.28;
708
+ const fill = position.status === "WIN"
709
+ ? theme.primary
710
+ : position.status === "LOSS"
711
+ ? theme.negative
712
+ : theme.inkMuted;
713
+ const fillOpacity = position.status === "WIN"
714
+ ? 0.45
715
+ : position.status === "LOSS"
716
+ ? 0.28
717
+ : 0.3;
340
718
  svgChildren.push((0, h_1.h)("rect", {
341
719
  x,
342
720
  y: top,
@@ -346,7 +724,7 @@ function buildChartArea(theme, btcHistory, positions) {
346
724
  fill,
347
725
  fillOpacity,
348
726
  stroke: fill,
349
- strokeOpacity: 0.9,
727
+ strokeOpacity: position.status === "CLOSED" ? 0.55 : 0.9,
350
728
  }));
351
729
  });
352
730
  if (sortedHistory.length === 1) {
@@ -371,14 +749,14 @@ function buildChartArea(theme, btcHistory, positions) {
371
749
  if ((position.status === "WIN" || position.status === "LOSS") &&
372
750
  position.settlementPrice != null) {
373
751
  svgChildren.push((0, h_1.h)("circle", {
374
- cx: scaleX(position.marketEndTimestampMs),
752
+ cx: getBandCenterX(position),
375
753
  cy: scaleY(position.settlementPrice),
376
754
  r: 4.5,
377
755
  fill: position.status === "WIN" ? theme.primary : theme.negative,
378
756
  }));
379
757
  }
380
758
  });
381
- const labelNodes = priceTicks.map((tick) => (0, h_1.h)("div", {
759
+ const labelNodes = priceTicks.ticks.map((tick) => (0, h_1.h)("div", {
382
760
  style: {
383
761
  position: "absolute",
384
762
  left: 12,
@@ -390,7 +768,26 @@ function buildChartArea(theme, btcHistory, positions) {
390
768
  background: theme.card,
391
769
  padding: "0 4px",
392
770
  },
393
- }, formatAxisLabel(tick)));
771
+ }, formatAxisLabel(tick, priceTicks.step)));
772
+ const settlementLabel = latestSettlement == null
773
+ ? []
774
+ : [
775
+ (0, h_1.h)("div", {
776
+ style: {
777
+ position: "absolute",
778
+ left: PLOT_LEFT + plotWidth - 64,
779
+ top: scaleY(latestSettlement.settlementPrice) - 14,
780
+ display: "flex",
781
+ fontSize: 14,
782
+ fontWeight: 700,
783
+ color: theme.bitcoin,
784
+ background: theme.card,
785
+ border: `1px solid ${theme.bitcoin}`,
786
+ borderRadius: 999,
787
+ padding: "4px 10px",
788
+ },
789
+ }, formatAxisLabel(latestSettlement.settlementPrice, priceTicks.step)),
790
+ ];
394
791
  return (0, h_1.h)("div", {
395
792
  style: {
396
793
  width: CHART_WIDTH,
@@ -398,9 +795,6 @@ function buildChartArea(theme, btcHistory, positions) {
398
795
  display: "flex",
399
796
  position: "relative",
400
797
  overflow: "hidden",
401
- background: theme.neutralBg,
402
- border: `1px solid ${theme.border}`,
403
- borderRadius: 20,
404
798
  },
405
799
  }, (0, h_1.h)("svg", {
406
800
  width: CHART_WIDTH,
@@ -412,43 +806,15 @@ function buildChartArea(theme, btcHistory, positions) {
412
806
  display: "flex",
413
807
  pointerEvents: "none",
414
808
  },
415
- }, ...svgChildren), ...labelNodes);
416
- }
417
- function buildStatCard(label, value, valueColor, theme) {
418
- return (0, h_1.h)("div", {
419
- style: {
420
- flex: 1,
421
- display: "flex",
422
- flexDirection: "column",
423
- padding: "16px 18px",
424
- borderRadius: 18,
425
- border: `1px solid ${theme.border}`,
426
- background: theme.neutralBg,
427
- },
428
- }, (0, h_1.h)("div", {
429
- style: {
430
- display: "flex",
431
- fontSize: 14,
432
- fontWeight: 500,
433
- color: theme.inkMuted,
434
- marginBottom: 10,
435
- },
436
- }, label), (0, h_1.h)("div", {
437
- style: {
438
- display: "flex",
439
- fontSize: 28,
440
- lineHeight: 1.1,
441
- fontWeight: 700,
442
- color: valueColor,
443
- },
444
- }, value));
809
+ }, ...svgChildren), ...labelNodes, ...settlementLabel);
445
810
  }
446
811
  function buildProfileVNode(input) {
447
812
  const theme = OG_THEMES[input.theme];
448
- const accent = TIER_ACCENT_COLORS[input.tier];
449
813
  const mainText = buildMainText(input);
450
814
  const subline = buildSubline(input);
451
- const pnlValue = input.hideAmounts ? "••••" : formatProfilePnl(input.totalPnl);
815
+ const pnlValue = input.hideAmounts
816
+ ? "••••"
817
+ : formatProfilePnl(input.totalPnl);
452
818
  const roiValue = formatProfileRoi(input.roi);
453
819
  const bestMultValue = formatBestMult(input.bestMult);
454
820
  return (0, h_1.h)("div", {
@@ -470,86 +836,16 @@ function buildProfileVNode(input) {
470
836
  border: `1px solid ${theme.border}`,
471
837
  borderRadius: 24,
472
838
  },
473
- }, (0, h_1.h)("div", {
474
- style: {
475
- display: "flex",
476
- alignItems: "center",
477
- justifyContent: "space-between",
478
- },
479
- }, (0, h_1.h)("div", {
480
- style: {
481
- display: "flex",
482
- alignItems: "center",
483
- gap: 10,
484
- },
485
- }, (0, h_1.h)("img", {
486
- src: assets_1.SIGNALS_LOGO_URI,
487
- width: 34,
488
- height: 32,
489
- style: { display: "flex" },
490
- }), (0, h_1.h)("div", {
491
- style: {
492
- display: "flex",
493
- fontSize: 28,
494
- fontWeight: 700,
495
- color: theme.primary,
496
- },
497
- }, "Signals")), (0, h_1.h)("div", {
498
- style: {
499
- display: "flex",
500
- fontSize: 18,
501
- fontWeight: 700,
502
- letterSpacing: "0.04em",
503
- textTransform: "uppercase",
504
- color: accent,
505
- },
506
- }, input.tier)), (0, h_1.h)("div", {
839
+ }, buildHeader(input, theme, mainText, subline), (0, h_1.h)("div", {
507
840
  style: {
508
841
  flex: 1,
509
842
  display: "flex",
510
843
  flexDirection: "column",
511
- marginTop: 20,
512
- },
513
- }, (0, h_1.h)("div", {
514
- style: {
515
- display: "flex",
516
- flexDirection: "column",
844
+ marginTop: 22,
517
845
  },
518
846
  }, (0, h_1.h)("div", {
519
847
  style: {
520
848
  display: "flex",
521
- fontSize: 52,
522
- lineHeight: 1.05,
523
- fontWeight: 700,
524
- color: theme.ink,
525
- marginBottom: 10,
526
- },
527
- }, mainText), (0, h_1.h)("div", {
528
- style: {
529
- display: "flex",
530
- fontSize: 20,
531
- fontWeight: 500,
532
- color: theme.inkMuted,
533
- },
534
- }, subline)), (0, h_1.h)("div", {
535
- style: {
536
- display: "flex",
537
- marginTop: 24,
538
- },
539
- }, buildChartArea(theme, input.btcHistory, input.positions)), (0, h_1.h)("div", {
540
- style: {
541
- display: "flex",
542
- gap: 16,
543
- marginTop: 20,
544
- },
545
- }, buildStatCard("P&L", pnlValue, getSignedValueColor(input.totalPnl, theme), theme), buildStatCard("ROI", roiValue, getSignedValueColor(input.roi, theme), theme), buildStatCard("Best call", bestMultValue, theme.primary, theme)), (0, h_1.h)("div", {
546
- style: {
547
- display: "flex",
548
- justifyContent: "flex-end",
549
- marginTop: 16,
550
- fontSize: 13,
551
- fontWeight: 500,
552
- color: theme.inkMuted,
553
849
  },
554
- }, "signals.wtf · Pick your range"))));
850
+ }, buildChartArea(theme, input.btcHistory, input.positions)), buildStatRow(input, theme, pnlValue, roiValue, bestMultValue))));
555
851
  }
@@ -31,19 +31,16 @@ export interface ProfileChartPosition {
31
31
  }
32
32
  export interface ProfileImageInput {
33
33
  /**
34
- * User-facing display fallback used when `realHandle` is empty and
35
- * `hideWallet` is false. This may include an address fallback supplied by
36
- * legacy callers; it is never shown while `hideWallet` is true.
34
+ * User-facing display fallback used when `realHandle` is empty. This may
35
+ * include an address fallback supplied by legacy callers.
37
36
  */
38
37
  displayName: string | null;
39
38
  /**
40
- * Real, human-friendly handle (e.g. Namoshi name) that is safe to show
41
- * even when `hideWallet` is true.
39
+ * Real, human-friendly handle (e.g. Namoshi name).
42
40
  *
43
41
  * Pass null when no real handle exists. Do not pass a truncated address
44
42
  * fallback here; that belongs in `displayName`. Optional for backward
45
- * compatibility, so callers that omit it get the existing hideWallet
46
- * fallback semantics.
43
+ * compatibility.
47
44
  */
48
45
  realHandle?: string | null;
49
46
  address: string;
@@ -67,6 +64,7 @@ export interface ProfileImageInput {
67
64
  }>;
68
65
  positions: ProfileChartPosition[];
69
66
  theme: "light" | "dark";
67
+ /** @deprecated Has no effect since 1.5.1. */
70
68
  hideWallet: boolean;
71
69
  hideAmounts: boolean;
72
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signals-protocol/v1-sdk",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "Signals v1 SDK for CLMSR market calculations and utilities",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",