Files
khadhroony-bobobot/kb_demo_app/frontend/ts/demo_pipeline.ts
2026-05-10 00:33:01 +02:00

1087 lines
40 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// file: kb_demo_app/frontend/ts/demo_pipeline.ts
import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
import { DemoPipelineInspectRequest } from './bindings/DemoPipelineInspectRequest.ts';
import { DemoPipelineInspectPayload } from './bindings/DemoPipelineInspectPayload.ts';
import { DemoPipelineInspectTokenRequest } from './bindings/DemoPipelineInspectTokenRequest.ts';
import { DemoPipelineInspectPairRequest } from './bindings/DemoPipelineInspectPairRequest.ts';
import { DemoPipelineInspectPoolRequest } from './bindings/DemoPipelineInspectPoolRequest.ts';
import { DemoPipelineBackfillTokenRequest } from './bindings/DemoPipelineBackfillTokenRequest.ts';
import { DemoPipelineBackfillTokenPayload } from './bindings/DemoPipelineBackfillTokenPayload.ts';
import { DemoPipelineBackfillPoolRequest } from './bindings/DemoPipelineBackfillPoolRequest.ts';
import { DemoPipelineBackfillPoolPayload } from './bindings/DemoPipelineBackfillPoolPayload.ts';
import * as echarts from "echarts";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
interface DemoPipelinePairCandle {
id: number | null;
pair_id: number;
timeframe_seconds: number;
bucket_start_unix: number;
bucket_end_unix: number;
open_price_quote_per_base: number;
high_price_quote_per_base: number;
low_price_quote_per_base: number;
close_price_quote_per_base: number;
trade_count: number;
buy_count: number;
sell_count: number;
base_volume_raw: string | null;
quote_volume_raw: string | null;
first_trade_signature: string | null;
last_trade_signature: string | null;
created_at: string;
updated_at: string;
}
interface DemoPipelinePairCandleGroup {
pairId: number;
timeframeSeconds: number;
candles: DemoPipelinePairCandle[];
}
function parsePairCandleGroups(rawJson: string): DemoPipelinePairCandleGroup[] {
if (rawJson.trim() === "") {
return [];
}
try {
const parsed = JSON.parse(rawJson) as unknown;
if (!Array.isArray(parsed)) {
return [];
}
const groups: DemoPipelinePairCandleGroup[] = [];
for (const value of parsed) {
if (typeof value !== "object" || value === null) {
continue;
}
const maybeGroup = value as {
pairId?: unknown;
timeframeSeconds?: unknown;
candles?: unknown;
};
if (
typeof maybeGroup.pairId !== "number" ||
typeof maybeGroup.timeframeSeconds !== "number" ||
!Array.isArray(maybeGroup.candles)
) {
continue;
}
groups.push({
pairId: maybeGroup.pairId,
timeframeSeconds: maybeGroup.timeframeSeconds,
candles: maybeGroup.candles as DemoPipelinePairCandle[],
});
}
return groups;
} catch {
return [];
}
}
function formatTimeframeLabel(timeframeSeconds: number): string {
if (timeframeSeconds % 3600 === 0) {
return `${timeframeSeconds / 3600}h`;
}
if (timeframeSeconds % 60 === 0) {
return `${timeframeSeconds / 60}m`;
}
return `${timeframeSeconds}s`;
}
function parseRawVolume(text: string | null, fallback: number): number {
if (text === null || text.trim() === "") {
return fallback;
}
const parsed = Number.parseFloat(text);
if (Number.isNaN(parsed)) {
return fallback;
}
return parsed;
}
function setEmptyCandlesChart(
chart: echarts.ECharts,
chartMeta: HTMLElement,
message: string,
): void {
chartMeta.textContent = message;
chart.setOption({
animation: false,
title: {
text: message,
left: "center",
top: "middle",
textStyle: {
fontSize: 14,
fontWeight: "normal",
},
},
tooltip: {},
xAxis: { show: false, type: "category", data: [] },
yAxis: { show: false, type: "value" },
series: [],
}, true);
}
function refreshCandlesSelectors(
groups: DemoPipelinePairCandleGroup[],
pairSelect: HTMLSelectElement,
timeframeSelect: HTMLSelectElement,
): void {
const currentPairValue = pairSelect.value;
const currentTimeframeValue = timeframeSelect.value;
const uniquePairs = Array.from(new Set(groups.map((group) => group.pairId))).sort((left, right) => left - right);
pairSelect.innerHTML = "";
if (uniquePairs.length === 0) {
const option = document.createElement("option");
option.value = "";
option.textContent = "Aucune";
pairSelect.appendChild(option);
timeframeSelect.innerHTML = "";
const tfOption = document.createElement("option");
tfOption.value = "";
tfOption.textContent = "Aucun";
timeframeSelect.appendChild(tfOption);
return;
}
for (const pairId of uniquePairs) {
const option = document.createElement("option");
option.value = String(pairId);
option.textContent = `Pair #${pairId}`;
if (option.value === currentPairValue) {
option.selected = true;
}
pairSelect.appendChild(option);
}
if (pairSelect.value === "" && uniquePairs.length > 0) {
pairSelect.value = String(uniquePairs[0]);
}
const selectedPairId = Number.parseInt(pairSelect.value, 10);
const pairGroups = groups
.filter((group) => group.pairId === selectedPairId)
.sort((left, right) => left.timeframeSeconds - right.timeframeSeconds);
timeframeSelect.innerHTML = "";
if (pairGroups.length === 0) {
const option = document.createElement("option");
option.value = "";
option.textContent = "Aucun";
timeframeSelect.appendChild(option);
return;
}
for (const group of pairGroups) {
const option = document.createElement("option");
option.value = String(group.timeframeSeconds);
option.textContent = formatTimeframeLabel(group.timeframeSeconds);
if (option.value === currentTimeframeValue) {
option.selected = true;
}
timeframeSelect.appendChild(option);
}
if (timeframeSelect.value === "" && pairGroups.length > 0) {
timeframeSelect.value = String(pairGroups[0].timeframeSeconds);
}
}
function renderSelectedCandlesChart(
chart: echarts.ECharts,
chartMeta: HTMLElement,
groups: DemoPipelinePairCandleGroup[],
pairSelect: HTMLSelectElement,
timeframeSelect: HTMLSelectElement,
): void {
if (groups.length === 0) {
setEmptyCandlesChart(chart, chartMeta, "Aucune candle disponible.");
return;
}
const selectedPairId = Number.parseInt(pairSelect.value, 10);
const selectedTimeframe = Number.parseInt(timeframeSelect.value, 10);
if (Number.isNaN(selectedPairId) || Number.isNaN(selectedTimeframe)) {
setEmptyCandlesChart(chart, chartMeta, "Sélection de paire/timeframe invalide.");
return;
}
const group = groups.find(
(value) =>
value.pairId === selectedPairId &&
value.timeframeSeconds === selectedTimeframe,
);
if (!group || group.candles.length === 0) {
setEmptyCandlesChart(
chart,
chartMeta,
`Aucune candle pour la pair #${selectedPairId} en ${formatTimeframeLabel(selectedTimeframe)}.`,
);
return;
}
const candles = [...group.candles].sort(
(left, right) => left.bucket_start_unix - right.bucket_start_unix,
);
const categoryData = candles.map((candle) =>
new Date(candle.bucket_start_unix * 1000).toLocaleString("fr-CH", {
hour12: false,
year: "2-digit",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
}),
);
const ohlcData = candles.map((candle) => [
candle.open_price_quote_per_base,
candle.close_price_quote_per_base,
candle.low_price_quote_per_base,
candle.high_price_quote_per_base,
]);
const volumeData = candles.map((candle) =>
parseRawVolume(candle.quote_volume_raw, candle.trade_count),
);
chartMeta.textContent =
`Pair #${selectedPairId}${formatTimeframeLabel(selectedTimeframe)}${candles.length} candles`;
chart.setOption(
{
animation: false,
legend: {
data: ["OHLC", "Volume"],
top: 0,
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "cross",
},
},
axisPointer: {
link: [{ xAxisIndex: "all" }],
},
grid: [
{ left: 60, right: 24, top: 40, height: "58%" },
{ left: 60, right: 24, top: "74%", height: "16%" },
],
xAxis: [
{
type: "category",
data: categoryData,
boundaryGap: true,
axisLine: { onZero: false },
splitLine: { show: false },
min: "dataMin",
max: "dataMax",
},
{
type: "category",
gridIndex: 1,
data: categoryData,
boundaryGap: true,
axisLine: { onZero: false },
axisTick: { show: false },
splitLine: { show: false },
axisLabel: { show: false },
min: "dataMin",
max: "dataMax",
},
],
yAxis: [
{
scale: true,
splitArea: { show: false },
},
{
gridIndex: 1,
scale: true,
splitNumber: 2,
},
],
dataZoom: [
{
type: "inside",
xAxisIndex: [0, 1],
start: 0,
end: 100,
},
{
show: true,
type: "slider",
xAxisIndex: [0, 1],
bottom: 6,
start: 0,
end: 100,
},
],
series: [
{
name: "OHLC",
type: "candlestick",
data: ohlcData,
},
{
name: "Volume",
type: "bar",
xAxisIndex: 1,
yAxisIndex: 1,
data: volumeData,
},
],
},
true,
);
}
function applyInspectionPayload(
payload: DemoPipelineInspectPayload,
summaryTextarea: HTMLTextAreaElement,
transactionTextarea: HTMLTextAreaElement,
decodedEventsTextarea: HTMLTextAreaElement,
poolsTextarea: HTMLTextAreaElement,
pairsTextarea: HTMLTextAreaElement,
launchAttributionsTextarea: HTMLTextAreaElement,
poolOriginsTextarea: HTMLTextAreaElement,
walletsTextarea: HTMLTextAreaElement,
tradeEventsTextarea: HTMLTextAreaElement,
pairMetricsTextarea: HTMLTextAreaElement,
pairCandlesTextarea: HTMLTextAreaElement,
pairAnalyticSignalsTextarea: HTMLTextAreaElement,
chart: echarts.ECharts,
chartMeta: HTMLElement,
pairSelect: HTMLSelectElement,
timeframeSelect: HTMLSelectElement,
): void {
summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson;
decodedEventsTextarea.value = payload.decodedEventsJson;
poolsTextarea.value = payload.poolsJson;
pairsTextarea.value = payload.pairsJson;
launchAttributionsTextarea.value = payload.launchAttributionsJson;
poolOriginsTextarea.value = payload.poolOriginsJson;
walletsTextarea.value = payload.walletsJson;
tradeEventsTextarea.value = payload.tradeEventsJson;
pairMetricsTextarea.value = payload.pairMetricsJson;
pairCandlesTextarea.value = payload.pairCandlesJson;
pairAnalyticSignalsTextarea.value = payload.pairAnalyticSignalsJson;
const groups = parsePairCandleGroups(payload.pairCandlesJson);
refreshCandlesSelectors(groups, pairSelect, timeframeSelect);
renderSelectedCandlesChart(chart, chartMeta, groups, pairSelect, timeframeSelect);
}
function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
const now = new Date();
const timestamp = now.toLocaleTimeString("fr-CH", { hour12: false });
const lines = textarea.value === "" ? [] : textarea.value.split("\n");
lines.push(`[${timestamp}] ${line}`);
const maxLines = 300;
textarea.value = lines.slice(-maxLines).join("\n");
textarea.scrollTop = textarea.scrollHeight;
}
function clearInspection(
backfillTextarea: HTMLTextAreaElement,
summaryTextarea: HTMLTextAreaElement,
transactionTextarea: HTMLTextAreaElement,
decodedEventsTextarea: HTMLTextAreaElement,
poolsTextarea: HTMLTextAreaElement,
pairsTextarea: HTMLTextAreaElement,
launchAttributionsTextarea: HTMLTextAreaElement,
poolOriginsTextarea: HTMLTextAreaElement,
walletsTextarea: HTMLTextAreaElement,
tradeEventsTextarea: HTMLTextAreaElement,
pairMetricsTextarea: HTMLTextAreaElement,
pairCandlesTextarea: HTMLTextAreaElement,
pairAnalyticSignalsTextarea: HTMLTextAreaElement,
): void {
backfillTextarea.value = "";
summaryTextarea.value = "";
transactionTextarea.value = "";
decodedEventsTextarea.value = "";
poolsTextarea.value = "";
pairsTextarea.value = "";
launchAttributionsTextarea.value = "";
poolOriginsTextarea.value = "";
walletsTextarea.value = "";
tradeEventsTextarea.value = "";
pairMetricsTextarea.value = "";
pairCandlesTextarea.value = "";
pairAnalyticSignalsTextarea.value = "";
}
function readCustomTimeframeSeconds(
input: HTMLInputElement,
logTextarea: HTMLTextAreaElement,
): bigint | null | undefined {
const customTimeframeText = input.value.trim();
if (customTimeframeText === "") {
return null;
}
const parsed = Number.parseInt(customTimeframeText, 10);
if (Number.isNaN(parsed) || parsed <= 0) {
appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`);
return undefined;
}
return BigInt(parsed);
}
function readPositiveIntegerInput(
input: HTMLInputElement,
logTextarea: HTMLTextAreaElement,
label: string,
): number | undefined {
const text = input.value.trim();
if (text === "") {
appendLogLine(logTextarea, `[ui] ${label} is required`);
return undefined;
}
const parsed = Number.parseInt(text, 10);
if (Number.isNaN(parsed) || parsed <= 0) {
appendLogLine(logTextarea, `[ui] invalid ${label} '${text}'`);
return undefined;
}
return parsed;
}
document.addEventListener("DOMContentLoaded", async () => {
void takeoverConsole();
debug("demo_pipeline window loaded");
const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
if (sidebarToggle) {
// restaurer létat depuis localStorage
if (localStorage.getItem('sidebar-toggle') === 'true') {
document.body.classList.add('sidenav-toggled');
}
sidebarToggle.addEventListener('click', (event) => {
event.preventDefault();
document.body.classList.toggle('sidenav-toggled');
localStorage.setItem('sidebar-toggle', document.body.classList.contains('sidenav-toggled') ? 'true' : 'false');
});
}
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
const toastElList = document.querySelectorAll('.toast');
Array.from(toastElList).map(toastEl => new bootstrap.Toast(toastEl));
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
Array.from(popoverTriggerList).map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl));
const gobackto = location.pathname + location.search;
document.querySelectorAll<HTMLAnchorElement>('a[data-setlang]').forEach((a) => {
const href = a.getAttribute("href");
if (!href) return; // pas de href => on ignore
const url = new URL(href, location.origin);
url.searchParams.set("gobackto", gobackto);
// conserve une URL relative (path + query)
a.setAttribute("href", url.pathname + "?" + url.searchParams.toString());
});
const signatureInput = document.querySelector<HTMLInputElement>("#demoPipelineSignatureInput");
const customTimeframeInput = document.querySelector<HTMLInputElement>("#demoPipelineCustomTimeframeInput");
const inspectButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectButton");
const clearButton = document.querySelector<HTMLButtonElement>("#demoPipelineClearButton");
const clearLogButton = document.querySelector<HTMLButtonElement>("#demoPipelineClearLogButton");
const summaryTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineSummaryTextarea");
const transactionTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineTransactionTextarea");
const decodedEventsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineDecodedEventsTextarea");
const poolsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePoolsTextarea");
const pairsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePairsTextarea");
const launchAttributionsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineLaunchAttributionsTextarea");
const poolOriginsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePoolOriginsTextarea");
const walletsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineWalletsTextarea");
const tradeEventsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineTradeEventsTextarea");
const pairMetricsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePairMetricsTextarea");
const pairCandlesTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePairCandlesTextarea");
const pairAnalyticSignalsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelinePairAnalyticSignalsTextarea");
const logTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineLogTextarea");
const tokenMintInput = document.querySelector<HTMLInputElement>("#demoPipelineTokenMintInput");
const inspectTokenButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectTokenButton");
const pairIdInput = document.querySelector<HTMLInputElement>("#demoPipelinePairIdInput");
const inspectPairButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectPairButton");
const poolAddressInput = document.querySelector<HTMLInputElement>("#demoPipelinePoolAddressInput");
const inspectPoolButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectPoolButton");
const backfillTokenMintInput = document.querySelector<HTMLInputElement>("#demoPipelineBackfillTokenMintInput");
const backfillHttpRoleInput = document.querySelector<HTMLInputElement>("#demoPipelineBackfillHttpRoleInput");
const backfillMintLimitInput = document.querySelector<HTMLInputElement>("#demoPipelineBackfillMintLimitInput");
const backfillPoolLimitInput = document.querySelector<HTMLInputElement>("#demoPipelineBackfillPoolLimitInput");
const backfillTokenButton = document.querySelector<HTMLButtonElement>("#demoPipelineBackfillTokenButton");
const backfillTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineBackfillTextarea");
const chartPairSelect = document.querySelector<HTMLSelectElement>("#demoPipelineChartPairSelect");
const chartTimeframeSelect = document.querySelector<HTMLSelectElement>("#demoPipelineChartTimeframeSelect");
const candlesChartElement = document.querySelector<HTMLDivElement>("#demoPipelineCandlesChart");
const candlesChartMeta = document.querySelector<HTMLDivElement>("#demoPipelineCandlesChartMeta");
const backfillPoolAddressInput = document.querySelector<HTMLInputElement>("#demoPipelineBackfillPoolAddressInput");
const backfillPoolOnlyLimitInput = document.querySelector<HTMLInputElement>("#demoPipelineBackfillPoolOnlyLimitInput");
const backfillPoolButton = document.querySelector<HTMLButtonElement>("#demoPipelineBackfillPoolButton");
if (
!chartPairSelect ||
!chartTimeframeSelect ||
!candlesChartElement ||
!candlesChartMeta ||
!backfillTokenMintInput ||
!backfillHttpRoleInput ||
!backfillMintLimitInput ||
!backfillPoolLimitInput ||
!backfillTokenButton ||
!backfillTextarea ||
!backfillPoolAddressInput ||
!backfillPoolOnlyLimitInput ||
!backfillPoolButton ||
!pairIdInput ||
!inspectPairButton ||
!poolAddressInput ||
!inspectPoolButton ||
!tokenMintInput ||
!inspectTokenButton ||
!signatureInput ||
!customTimeframeInput ||
!inspectButton ||
!clearButton ||
!clearLogButton ||
!summaryTextarea ||
!transactionTextarea ||
!decodedEventsTextarea ||
!poolsTextarea ||
!pairsTextarea ||
!launchAttributionsTextarea ||
!poolOriginsTextarea ||
!walletsTextarea ||
!tradeEventsTextarea ||
!pairMetricsTextarea ||
!pairCandlesTextarea ||
!pairAnalyticSignalsTextarea ||
!logTextarea
) {
console.error("demo_pipeline DOM is incomplete");
return;
}
const candlesChart = echarts.init(candlesChartElement);
setEmptyCandlesChart(candlesChart, candlesChartMeta, "Aucune candle disponible.");
window.addEventListener("resize", () => {
candlesChart.resize();
});
chartPairSelect.addEventListener("change", () => {
const groups = parsePairCandleGroups(pairCandlesTextarea.value);
refreshCandlesSelectors(groups, chartPairSelect, chartTimeframeSelect);
renderSelectedCandlesChart(
candlesChart,
candlesChartMeta,
groups,
chartPairSelect,
chartTimeframeSelect,
);
});
chartTimeframeSelect.addEventListener("change", () => {
const groups = parsePairCandleGroups(pairCandlesTextarea.value);
renderSelectedCandlesChart(
candlesChart,
candlesChartMeta,
groups,
chartPairSelect,
chartTimeframeSelect,
);
});
clearButton.addEventListener("click", () => {
clearInspection(
backfillTextarea,
summaryTextarea,
transactionTextarea,
decodedEventsTextarea,
poolsTextarea,
pairsTextarea,
launchAttributionsTextarea,
poolOriginsTextarea,
walletsTextarea,
tradeEventsTextarea,
pairMetricsTextarea,
pairCandlesTextarea,
pairAnalyticSignalsTextarea,
);
backfillTokenMintInput.value = "";
backfillHttpRoleInput.value = "history_backfill";
backfillMintLimitInput.value = "50";
backfillPoolLimitInput.value = "50";
signatureInput.value = "";
customTimeframeInput.value = "";
tokenMintInput.value = "";
pairIdInput.value = "";
poolAddressInput.value = "";
backfillPoolAddressInput.value = "";
backfillPoolOnlyLimitInput.value = "50";
chartPairSelect.innerHTML = `<option value="">Aucune</option>`;
chartTimeframeSelect.innerHTML = `<option value="">Aucun</option>`;
setEmptyCandlesChart(candlesChart, candlesChartMeta, "Aucune candle disponible.");
appendLogLine(logTextarea, "[ui] inspection state cleared");
});
backfillPoolButton.addEventListener("click", async () => {
const poolAddress = backfillPoolAddressInput.value.trim();
if (poolAddress === "") {
appendLogLine(logTextarea, "[ui] backfill pool address is required");
return;
}
const poolSignatureLimit = readPositiveIntegerInput(
backfillPoolOnlyLimitInput,
logTextarea,
"poolSignatureLimit",
);
if (poolSignatureLimit === undefined) {
return;
}
const httpRoleText = backfillHttpRoleInput.value.trim();
const httpRole = httpRoleText === "" ? null : httpRoleText;
appendLogLine(
logTextarea,
`[ui] launching pool backfill for '${poolAddress}' with role '${httpRole ?? "history_backfill"}' (pool=${poolSignatureLimit})`,
);
const request: DemoPipelineBackfillPoolRequest = {
poolAddress,
httpRole,
poolSignatureLimit,
};
try {
const payload = await invoke<DemoPipelineBackfillPoolPayload>(
"demo_pipeline_backfill_pool_address",
{ request },
);
backfillTextarea.value = payload.backfillJson;
appendLogLine(
logTextarea,
`[ui] pool backfill completed for '${payload.poolAddress}' with role '${payload.httpRole}'`,
);
if (!payload.poolPersistedAfterBackfill) {
appendLogLine(
logTextarea,
`[ui] backfill completed but pool '${payload.poolAddress}' is still absent from persisted pool objects; automatic pool inspection skipped`,
);
return;
}
const inspectRequest: DemoPipelineInspectPoolRequest = {
poolAddress: payload.poolAddress,
customTimeframeSeconds: null,
};
try {
const inspectPayload = await invoke<DemoPipelineInspectPayload>(
"demo_pipeline_inspect_pool_address",
{ request: inspectRequest },
);
applyInspectionPayload(
inspectPayload,
summaryTextarea,
transactionTextarea,
decodedEventsTextarea,
poolsTextarea,
pairsTextarea,
launchAttributionsTextarea,
poolOriginsTextarea,
walletsTextarea,
tradeEventsTextarea,
pairMetricsTextarea,
pairCandlesTextarea,
pairAnalyticSignalsTextarea,
candlesChart,
candlesChartMeta,
chartPairSelect,
chartTimeframeSelect,
);
appendLogLine(
logTextarea,
`[ui] pool inspection refreshed after backfill for '${payload.poolAddress}'`,
);
} catch (error) {
appendLogLine(
logTextarea,
`[ui] backfill completed but automatic pool inspection failed for '${payload.poolAddress}': ${String(error)}`,
);
}
} catch (error) {
appendLogLine(logTextarea, `[ui] pool backfill error: ${String(error)}`);
}
});
clearLogButton.addEventListener("click", () => {
logTextarea.value = "";
});
inspectButton.addEventListener("click", async () => {
const signature = signatureInput.value.trim();
if (signature === "") {
appendLogLine(logTextarea, "[ui] signature is required");
return;
}
let customTimeframeSeconds: bigint | null = null;
const customTimeframeText = customTimeframeInput.value.trim();
if (customTimeframeText !== "") {
const parsed = Number.parseInt(customTimeframeText, 10);
if (Number.isNaN(parsed) || parsed <= 0) {
appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`);
return;
}
customTimeframeSeconds = BigInt(parsed);
}
appendLogLine(
logTextarea,
`[ui] inspecting signature '${signature}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
const request: DemoPipelineInspectRequest = {
signature,
customTimeframeSeconds,
};
try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_signature", { request });
applyInspectionPayload(
payload,
summaryTextarea,
transactionTextarea,
decodedEventsTextarea,
poolsTextarea,
pairsTextarea,
launchAttributionsTextarea,
poolOriginsTextarea,
walletsTextarea,
tradeEventsTextarea,
pairMetricsTextarea,
pairCandlesTextarea,
pairAnalyticSignalsTextarea,
candlesChart,
candlesChartMeta,
chartPairSelect,
chartTimeframeSelect,
);
appendLogLine(logTextarea, `[ui] inspection completed for '${payload.signature}'`);
} catch (error) {
appendLogLine(logTextarea, `[ui] inspect error: ${String(error)}`);
}
});
inspectTokenButton.addEventListener("click", async () => {
const tokenMint = tokenMintInput.value.trim();
if (tokenMint === "") {
appendLogLine(logTextarea, "[ui] token mint is required");
return;
}
let customTimeframeSeconds: bigint | null = null;
const customTimeframeText = customTimeframeInput.value.trim();
if (customTimeframeText !== "") {
const parsed = Number.parseInt(customTimeframeText, 10);
if (Number.isNaN(parsed) || parsed <= 0) {
appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`);
return;
}
customTimeframeSeconds = BigInt(parsed);
}
appendLogLine(
logTextarea,
`[ui] inspecting token mint '${tokenMint}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
const request: DemoPipelineInspectTokenRequest = {
tokenMint,
customTimeframeSeconds,
};
try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_token_mint", { request });
applyInspectionPayload(
payload,
summaryTextarea,
transactionTextarea,
decodedEventsTextarea,
poolsTextarea,
pairsTextarea,
launchAttributionsTextarea,
poolOriginsTextarea,
walletsTextarea,
tradeEventsTextarea,
pairMetricsTextarea,
pairCandlesTextarea,
pairAnalyticSignalsTextarea,
candlesChart,
candlesChartMeta,
chartPairSelect,
chartTimeframeSelect,
);
appendLogLine(logTextarea, `[ui] token inspection completed for '${payload.signature}'`);
} catch (error) {
appendLogLine(logTextarea, `[ui] token inspect error: ${String(error)}`);
}
});
inspectPairButton.addEventListener("click", async () => {
const pairIdText = pairIdInput.value.trim();
if (pairIdText === "") {
appendLogLine(logTextarea, "[ui] pair id is required");
return;
}
const parsedPairId = Number.parseInt(pairIdText, 10);
if (Number.isNaN(parsedPairId) || parsedPairId <= 0) {
appendLogLine(logTextarea, `[ui] invalid pair id '${pairIdText}'`);
return;
}
const customTimeframeSeconds = readCustomTimeframeSeconds(customTimeframeInput, logTextarea);
if (customTimeframeSeconds === undefined) {
return;
}
appendLogLine(
logTextarea,
`[ui] inspecting pair id '${parsedPairId}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
const request: DemoPipelineInspectPairRequest = {
pairId: BigInt(parsedPairId),
customTimeframeSeconds,
};
try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_pair_id", { request });
applyInspectionPayload(
payload,
summaryTextarea,
transactionTextarea,
decodedEventsTextarea,
poolsTextarea,
pairsTextarea,
launchAttributionsTextarea,
poolOriginsTextarea,
walletsTextarea,
tradeEventsTextarea,
pairMetricsTextarea,
pairCandlesTextarea,
pairAnalyticSignalsTextarea,
candlesChart,
candlesChartMeta,
chartPairSelect,
chartTimeframeSelect,
);
appendLogLine(logTextarea, `[ui] pair inspection completed for '${payload.signature}'`);
} catch (error) {
appendLogLine(logTextarea, `[ui] pair inspect error: ${String(error)}`);
}
});
inspectPoolButton.addEventListener("click", async () => {
const poolAddress = poolAddressInput.value.trim();
if (poolAddress === "") {
appendLogLine(logTextarea, "[ui] pool address is required");
return;
}
const customTimeframeSeconds = readCustomTimeframeSeconds(customTimeframeInput, logTextarea);
if (customTimeframeSeconds === undefined) {
return;
}
appendLogLine(
logTextarea,
`[ui] inspecting pool '${poolAddress}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
const request: DemoPipelineInspectPoolRequest = {
poolAddress,
customTimeframeSeconds,
};
try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_pool_address", { request });
applyInspectionPayload(
payload,
summaryTextarea,
transactionTextarea,
decodedEventsTextarea,
poolsTextarea,
pairsTextarea,
launchAttributionsTextarea,
poolOriginsTextarea,
walletsTextarea,
tradeEventsTextarea,
pairMetricsTextarea,
pairCandlesTextarea,
pairAnalyticSignalsTextarea,
candlesChart,
candlesChartMeta,
chartPairSelect,
chartTimeframeSelect,
);
appendLogLine(logTextarea, `[ui] pool inspection completed for '${payload.signature}'`);
} catch (error) {
appendLogLine(logTextarea, `[ui] pool inspect error: ${String(error)}`);
}
});
backfillTokenButton.addEventListener("click", async () => {
const tokenMint = backfillTokenMintInput.value.trim();
if (tokenMint === "") {
appendLogLine(logTextarea, "[ui] backfill token mint is required");
return;
}
const mintSignatureLimit = readPositiveIntegerInput(
backfillMintLimitInput,
logTextarea,
"mintSignatureLimit",
);
if (mintSignatureLimit === undefined) {
return;
}
const poolSignatureLimit = readPositiveIntegerInput(
backfillPoolLimitInput,
logTextarea,
"poolSignatureLimit",
);
if (poolSignatureLimit === undefined) {
return;
}
const httpRoleText = backfillHttpRoleInput.value.trim();
const httpRole = httpRoleText === "" ? null : httpRoleText;
appendLogLine(
logTextarea,
`[ui] launching token backfill for '${tokenMint}' with role '${httpRole ?? "history_backfill"}' (mint=${mintSignatureLimit}, pool=${poolSignatureLimit})`,
);
const request: DemoPipelineBackfillTokenRequest = {
tokenMint,
httpRole,
mintSignatureLimit,
poolSignatureLimit,
};
try {
const payload = await invoke<DemoPipelineBackfillTokenPayload>(
"demo_pipeline_backfill_token_mint",
{ request },
);
backfillTextarea.value = payload.backfillJson;
appendLogLine(
logTextarea,
`[ui] token backfill completed for '${payload.tokenMint}' with role '${payload.httpRole}'`,
);
if (!payload.tokenPersistedAfterBackfill) {
appendLogLine(
logTextarea,
`[ui] backfill completed but token '${payload.tokenMint}' is still absent from persisted token objects; automatic token inspection skipped`,
);
return;
}
const inspectRequest: DemoPipelineInspectTokenRequest = {
tokenMint: payload.tokenMint,
customTimeframeSeconds: null,
};
try {
const inspectPayload = await invoke<DemoPipelineInspectPayload>(
"demo_pipeline_inspect_token_mint",
{ request: inspectRequest },
);
summaryTextarea.value = inspectPayload.summaryJson;
transactionTextarea.value = inspectPayload.transactionJson;
decodedEventsTextarea.value = inspectPayload.decodedEventsJson;
poolsTextarea.value = inspectPayload.poolsJson;
pairsTextarea.value = inspectPayload.pairsJson;
launchAttributionsTextarea.value = inspectPayload.launchAttributionsJson;
poolOriginsTextarea.value = inspectPayload.poolOriginsJson;
walletsTextarea.value = inspectPayload.walletsJson;
tradeEventsTextarea.value = inspectPayload.tradeEventsJson;
pairMetricsTextarea.value = inspectPayload.pairMetricsJson;
pairCandlesTextarea.value = inspectPayload.pairCandlesJson;
pairAnalyticSignalsTextarea.value = inspectPayload.pairAnalyticSignalsJson;
appendLogLine(
logTextarea,
`[ui] token inspection refreshed after backfill for '${payload.tokenMint}'`,
);
} catch (error) {
appendLogLine(
logTextarea,
`[ui] backfill completed but automatic token inspection failed for '${payload.tokenMint}': ${String(error)}`,
);
}
} catch (error) {
appendLogLine(logTextarea, `[ui] token backfill error: ${String(error)}`);
}
});
});