Skip to content

Commit e751c73

Browse files
authored
Break down BTC and USD charts into individual token/chain components (#159)
* Break down BTC chart into individual token/chain components Split the single "Net BTC Balance" line into 6 stacked area datasets (Onchain, LND Onchain, Lightning, cBTC, WBTC, WBTCe) so balance shifts between chains are visible. Add total sum to chart tooltip. * Break down USD chart into individual token/chain components Split the single "Total USD Holdings" line into 4 stacked area datasets (JUSD, USDC, USDT Ethereum, USDT Polygon) so balance shifts between stablecoins and chains are visible. Add total sum to chart tooltip.
1 parent 298caef commit e751c73

4 files changed

Lines changed: 132 additions & 56 deletions

File tree

e2e/mock-server.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,14 @@ function generateBtcHistory(range) {
6161
const points = [];
6262
for (let i = cfg.count; i >= 0; i--) {
6363
const ts = new Date(now - i * cfg.step).toISOString();
64-
const base = 0.95 + Math.sin(i * 0.3) * 0.05;
65-
points.push({ timestamp: ts, netBalance: parseFloat(base.toFixed(8)) });
64+
const onchain = parseFloat((0.35 + Math.sin(i * 0.2) * 0.02).toFixed(8));
65+
const lndOnchain = parseFloat((0.35 + Math.sin(i * 0.25) * 0.01).toFixed(8));
66+
const lightning = parseFloat((1.28 + Math.sin(i * 0.3) * 0.03).toFixed(8));
67+
const citrea = parseFloat((0.60 + Math.sin(i * 0.15) * 0.01).toFixed(8));
68+
const wbtc = parseFloat((0.004 + Math.sin(i * 0.1) * 0.001).toFixed(8));
69+
const wbtce = parseFloat((0.001 + Math.sin(i * 0.35) * 0.0005).toFixed(8));
70+
const netBalance = parseFloat((onchain + lndOnchain + lightning + citrea + wbtc + wbtce).toFixed(8));
71+
points.push({ timestamp: ts, netBalance, onchain, lndOnchain, lightning, citrea, wbtc, wbtce });
6672
}
6773
return points;
6874
}
@@ -74,8 +80,12 @@ function generateUsdHistory(range) {
7480
const points = [];
7581
for (let i = cfg.count; i >= 0; i--) {
7682
const ts = new Date(now - i * cfg.step).toISOString();
77-
const base = 101000 + Math.sin(i * 0.4) * 2000;
78-
points.push({ timestamp: ts, totalBalance: parseFloat(base.toFixed(2)) });
83+
const jusd = parseFloat((58000 + Math.sin(i * 0.2) * 500).toFixed(2));
84+
const usdc = parseFloat((840 + Math.sin(i * 0.3) * 50).toFixed(2));
85+
const usdtEthereum = parseFloat((2630 + Math.sin(i * 0.25) * 100).toFixed(2));
86+
const usdtPolygon = parseFloat((40091 + Math.sin(i * 0.15) * 200).toFixed(2));
87+
const totalBalance = parseFloat((jusd + usdc + usdtEthereum + usdtPolygon).toFixed(2));
88+
points.push({ timestamp: ts, totalBalance, jusd, usdc, usdtEthereum, usdtPolygon });
7989
}
8090
return points;
8191
}

src/assets/monitoring-btc.js

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -175,29 +175,53 @@ function renderChart(points) {
175175
if (btcChart) btcChart.destroy();
176176

177177
var labels = [];
178-
var netData = [];
178+
var onchainData = [];
179+
var lndOnchainData = [];
180+
var lightningData = [];
181+
var citreaData = [];
182+
var wbtcData = [];
183+
var wbtceData = [];
179184

180185
for (var i = 0; i < points.length; i++) {
181186
labels.push(new Date(points[i].timestamp));
182-
netData.push(points[i].netBalance);
187+
onchainData.push(points[i].onchain);
188+
lndOnchainData.push(points[i].lndOnchain);
189+
lightningData.push(points[i].lightning);
190+
citreaData.push(points[i].citrea);
191+
wbtcData.push(points[i].wbtc);
192+
wbtceData.push(points[i].wbtce);
193+
}
194+
195+
var components = [
196+
{ label: 'Onchain BTC', data: onchainData, color: '#4fc3f7' },
197+
{ label: 'LND Onchain', data: lndOnchainData, color: '#00e5ff' },
198+
{ label: 'Lightning', data: lightningData, color: '#fdd835' },
199+
{ label: 'cBTC (Citrea)', data: citreaData, color: '#ff9800' },
200+
{ label: 'WBTC (Ethereum)', data: wbtcData, color: '#66bb6a' },
201+
{ label: 'WBTCe (Citrea)', data: wbtceData, color: '#ab47bc' },
202+
];
203+
204+
var datasets = [];
205+
for (var i = 0; i < components.length; i++) {
206+
var c = components[i];
207+
datasets.push({
208+
label: c.label,
209+
data: c.data,
210+
borderColor: c.color,
211+
backgroundColor: c.color + '40',
212+
borderWidth: 1.5,
213+
pointRadius: 0,
214+
tension: 0,
215+
fill: true,
216+
stack: 'btc',
217+
});
183218
}
184219

185220
btcChart = new Chart(ctx, {
186221
type: 'line',
187222
data: {
188223
labels: labels,
189-
datasets: [
190-
{
191-
label: 'Net BTC Balance',
192-
data: netData,
193-
borderColor: '#4fc3f7',
194-
backgroundColor: 'rgba(79,195,247,0.1)',
195-
borderWidth: 1.5,
196-
pointRadius: 0,
197-
tension: 0,
198-
fill: true,
199-
},
200-
],
224+
datasets: datasets,
201225
},
202226
options: {
203227
responsive: true,
@@ -208,6 +232,11 @@ function renderChart(points) {
208232
tooltip: {
209233
callbacks: {
210234
label: function (ctx) { return ctx.dataset.label + ': ' + fmtBtc(ctx.parsed.y) + ' BTC'; },
235+
footer: function (items) {
236+
var sum = 0;
237+
for (var i = 0; i < items.length; i++) sum += items[i].parsed.y;
238+
return 'Total: ' + fmtBtc(sum) + ' BTC';
239+
},
211240
},
212241
},
213242
},
@@ -219,6 +248,7 @@ function renderChart(points) {
219248
ticks: { color: '#555', font: { family: "'Courier New', monospace", size: 10 }, maxTicksLimit: 8 },
220249
},
221250
y: {
251+
stacked: true,
222252
grid: { color: '#1a1a1a' },
223253
ticks: {
224254
color: '#555',

src/assets/monitoring-usd.js

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -115,29 +115,47 @@ function renderChart(points) {
115115
if (usdChart) usdChart.destroy();
116116

117117
var labels = [];
118-
var totalData = [];
118+
var jusdData = [];
119+
var usdcData = [];
120+
var usdtEthData = [];
121+
var usdtPolyData = [];
119122

120123
for (var i = 0; i < points.length; i++) {
121124
labels.push(new Date(points[i].timestamp));
122-
totalData.push(points[i].totalBalance);
125+
jusdData.push(points[i].jusd);
126+
usdcData.push(points[i].usdc);
127+
usdtEthData.push(points[i].usdtEthereum);
128+
usdtPolyData.push(points[i].usdtPolygon);
129+
}
130+
131+
var components = [
132+
{ label: 'JUSD (Citrea)', data: jusdData, color: '#ff9800' },
133+
{ label: 'USDC (Ethereum)', data: usdcData, color: '#4fc3f7' },
134+
{ label: 'USDT (Ethereum)', data: usdtEthData, color: '#66bb6a' },
135+
{ label: 'USDT (Polygon)', data: usdtPolyData, color: '#ab47bc' },
136+
];
137+
138+
var datasets = [];
139+
for (var i = 0; i < components.length; i++) {
140+
var c = components[i];
141+
datasets.push({
142+
label: c.label,
143+
data: c.data,
144+
borderColor: c.color,
145+
backgroundColor: c.color + '40',
146+
borderWidth: 1.5,
147+
pointRadius: 0,
148+
tension: 0,
149+
fill: true,
150+
stack: 'usd',
151+
});
123152
}
124153

125154
usdChart = new Chart(ctx, {
126155
type: 'line',
127156
data: {
128157
labels: labels,
129-
datasets: [
130-
{
131-
label: 'Total USD Holdings',
132-
data: totalData,
133-
borderColor: '#66bb6a',
134-
backgroundColor: 'rgba(102,187,106,0.1)',
135-
borderWidth: 1.5,
136-
pointRadius: 0,
137-
tension: 0,
138-
fill: true,
139-
},
140-
],
158+
datasets: datasets,
141159
},
142160
options: {
143161
responsive: true,
@@ -148,6 +166,11 @@ function renderChart(points) {
148166
tooltip: {
149167
callbacks: {
150168
label: function (ctx) { return ctx.dataset.label + ': $' + fmtUsd(ctx.parsed.y); },
169+
footer: function (items) {
170+
var sum = 0;
171+
for (var i = 0; i < items.length; i++) sum += items[i].parsed.y;
172+
return 'Total: $' + fmtUsd(sum);
173+
},
151174
},
152175
},
153176
},
@@ -159,6 +182,7 @@ function renderChart(points) {
159182
ticks: { color: '#555', font: { family: "'Courier New', monospace", size: 10 }, maxTicksLimit: 8 },
160183
},
161184
y: {
185+
stacked: true,
162186
grid: { color: '#1a1a1a' },
163187
ticks: {
164188
color: '#555',

src/subdomains/monitoring/controllers/monitoring.controller.ts

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export class MonitoringController {
151151
@ApiExcludeEndpoint()
152152
async btcHistory(
153153
@Query('range') range: string,
154-
): Promise<{ points: { timestamp: string; netBalance: number }[]; range: string }> {
154+
): Promise<{ points: { timestamp: string; netBalance: number; onchain: number; lndOnchain: number; lightning: number; citrea: number; wbtc: number; wbtce: number }[]; range: string }> {
155155
const { fromDate, grouping } = this.parseRange(range);
156156

157157
const [balanceHistory, evmHistory, seedBalance, seedEvmBalances] = await Promise.all([
@@ -169,7 +169,7 @@ export class MonitoringController {
169169
@ApiExcludeEndpoint()
170170
async usdHistory(
171171
@Query('range') range: string,
172-
): Promise<{ points: { timestamp: string; totalBalance: number }[]; range: string }> {
172+
): Promise<{ points: { timestamp: string; totalBalance: number; jusd: number; usdc: number; usdtEthereum: number; usdtPolygon: number }[]; range: string }> {
173173
const { fromDate, grouping } = this.parseRange(range);
174174

175175
const [evmHistory, seedEvmBalances] = await Promise.all([
@@ -217,7 +217,7 @@ export class MonitoringController {
217217
evmHistory: { timestamp: string; blockchain: string; nativeBalance: number; tokenBalances: string }[],
218218
seedBalance?: { timestamp: string; onchainBalance: number; lndOnchainBalance: number; lightningBalance: number; citreaBalance: number; customerBalance: number },
219219
seedEvmBalances?: { timestamp: string; blockchain: string; nativeBalance: number; tokenBalances: string }[],
220-
): { timestamp: string; netBalance: number }[] {
220+
): { timestamp: string; netBalance: number; onchain: number; lndOnchain: number; lightning: number; citrea: number; wbtc: number; wbtce: number }[] {
221221
const allTimestamps = new Set<string>();
222222
for (const b of balanceHistory) allTimestamps.add(new Date(b.timestamp).toISOString());
223223
for (const e of evmHistory) allTimestamps.add(new Date(e.timestamp).toISOString());
@@ -230,7 +230,7 @@ export class MonitoringController {
230230
let balIdx = 0;
231231
let evmIdx = 0;
232232

233-
const points: { timestamp: string; netBalance: number }[] = [];
233+
const points: { timestamp: string; netBalance: number; onchain: number; lndOnchain: number; lightning: number; citrea: number; wbtc: number; wbtce: number }[] = [];
234234

235235
for (const ts of sorted) {
236236
while (balIdx < balanceHistory.length && new Date(balanceHistory[balIdx].timestamp).toISOString() <= ts) {
@@ -243,7 +243,10 @@ export class MonitoringController {
243243
evmIdx++;
244244
}
245245

246-
const onchain = lastBalance ? (Number(lastBalance.onchainBalance) + Number(lastBalance.lndOnchainBalance) + Number(lastBalance.lightningBalance) + Number(lastBalance.citreaBalance)) / 1e8 : 0;
246+
const onchain = lastBalance ? Number(lastBalance.onchainBalance) / 1e8 : 0;
247+
const lndOnchain = lastBalance ? Number(lastBalance.lndOnchainBalance) / 1e8 : 0;
248+
const lightning = lastBalance ? Number(lastBalance.lightningBalance) / 1e8 : 0;
249+
const citrea = lastBalance ? Number(lastBalance.citreaBalance) / 1e8 : 0;
247250
const customer = lastBalance ? Number(lastBalance.customerBalance) / 1e8 : 0;
248251

249252
let wbtc = 0;
@@ -263,7 +266,13 @@ export class MonitoringController {
263266

264267
points.push({
265268
timestamp: ts,
266-
netBalance: onchain + wbtc + wbtce - customer,
269+
netBalance: onchain + lndOnchain + lightning + citrea + wbtc + wbtce - customer,
270+
onchain,
271+
lndOnchain,
272+
lightning,
273+
citrea,
274+
wbtc,
275+
wbtce,
267276
});
268277
}
269278

@@ -273,14 +282,7 @@ export class MonitoringController {
273282
private buildUsdHistory(
274283
evmHistory: { timestamp: string; blockchain: string; nativeBalance: number; tokenBalances: string }[],
275284
seedEvmBalances?: { timestamp: string; blockchain: string; nativeBalance: number; tokenBalances: string }[],
276-
): { timestamp: string; totalBalance: number }[] {
277-
const usdTokens = [
278-
{ symbol: 'JUSD', chain: 'citrea' },
279-
{ symbol: 'USDC', chain: 'ethereum' },
280-
{ symbol: 'USDT', chain: 'ethereum' },
281-
{ symbol: 'USDT', chain: 'polygon' },
282-
];
283-
285+
): { timestamp: string; totalBalance: number; jusd: number; usdc: number; usdtEthereum: number; usdtPolygon: number }[] {
284286
const allTimestamps = new Set<string>();
285287
for (const e of evmHistory) allTimestamps.add(new Date(e.timestamp).toISOString());
286288

@@ -290,24 +292,34 @@ export class MonitoringController {
290292
if (seedEvmBalances) for (const e of seedEvmBalances) lastEvm[e.blockchain] = e;
291293
let evmIdx = 0;
292294

293-
const points: { timestamp: string; totalBalance: number }[] = [];
295+
const points: { timestamp: string; totalBalance: number; jusd: number; usdc: number; usdtEthereum: number; usdtPolygon: number }[] = [];
294296

295297
for (const ts of sorted) {
296298
while (evmIdx < evmHistory.length && new Date(evmHistory[evmIdx].timestamp).toISOString() <= ts) {
297299
lastEvm[evmHistory[evmIdx].blockchain] = evmHistory[evmIdx];
298300
evmIdx++;
299301
}
300302

301-
let total = 0;
302-
for (const def of usdTokens) {
303-
if (lastEvm[def.chain]) {
304-
const tokens = this.parseTokenBalances(lastEvm[def.chain].tokenBalances);
305-
const t = tokens.find((tk) => tk.symbol === def.symbol);
306-
if (t) total += t.balance;
307-
}
308-
}
303+
const findToken = (chain: string, symbol: string): number => {
304+
if (!lastEvm[chain]) return 0;
305+
const tokens = this.parseTokenBalances(lastEvm[chain].tokenBalances);
306+
const t = tokens.find((tk) => tk.symbol === symbol);
307+
return t ? t.balance : 0;
308+
};
309+
310+
const jusd = findToken('citrea', 'JUSD');
311+
const usdc = findToken('ethereum', 'USDC');
312+
const usdtEthereum = findToken('ethereum', 'USDT');
313+
const usdtPolygon = findToken('polygon', 'USDT');
309314

310-
points.push({ timestamp: ts, totalBalance: total });
315+
points.push({
316+
timestamp: ts,
317+
totalBalance: jusd + usdc + usdtEthereum + usdtPolygon,
318+
jusd,
319+
usdc,
320+
usdtEthereum,
321+
usdtPolygon,
322+
});
311323
}
312324

313325
return points;

0 commit comments

Comments
 (0)