@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 +1 -1
- package/dist/index.js +1 -1
- package/dist/share/assets.d.ts +3 -1
- package/dist/share/assets.js +21 -1
- package/dist/share/avatar.d.ts +9 -0
- package/dist/share/avatar.js +167 -0
- package/dist/share/index.d.ts +2 -0
- package/dist/share/index.js +4 -1
- package/dist/share/profile-image.js +457 -161
- package/dist/share/types.d.ts +5 -7
- package/package.json +1 -1
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.
|
|
13
|
+
export declare const VERSION = "1.5.1";
|
package/dist/index.js
CHANGED
package/dist/share/assets.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { SatoriNode } from "./types";
|
|
2
|
-
export declare
|
|
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;
|
package/dist/share/assets.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/dist/share/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/share/index.js
CHANGED
|
@@ -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 =
|
|
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
|
|
45
|
-
BRONZE:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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 =
|
|
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
|
-
|
|
199
|
+
return `${shortenAddress(input.address)} with ${callCopy}`;
|
|
117
200
|
}
|
|
118
201
|
if (input.twitter) {
|
|
119
|
-
|
|
202
|
+
return `@${input.twitter} with ${callCopy}`;
|
|
120
203
|
}
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
|
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:
|
|
146
|
-
max:
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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"
|
|
339
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
},
|
|
850
|
+
}, buildChartArea(theme, input.btcHistory, input.positions)), buildStatRow(input, theme, pnlValue, roiValue, bestMultValue))));
|
|
555
851
|
}
|
package/dist/share/types.d.ts
CHANGED
|
@@ -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
|
|
35
|
-
*
|
|
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)
|
|
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
|
|
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
|
}
|