diff --git a/CHANGELOG.md b/CHANGELOG.md
index b4eb98b..b71e049 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -53,3 +53,4 @@
0.7.20 - Ajout d’une première couche candles / OHLCV avec matérialisation en base des timeframes usuels et régénération à la demande pour un timeframe arbitraire depuis les trade events
0.7.21 - Ajout d’une première couche de signaux analytiques enrichis par paire avec persistance dédiée et détection de first trade, trade burst, buy/sell imbalance, price jump et volume spike
0.7.22 - Ajout d’une première fenêtre `Demo Pipeline` dans `kb_app` pour l’inspection en lecture seule du pipeline `0.7.x`, avec recherche par signature, token mint, pair id ou pool address, affichage structuré des transactions résolues, événements DEX décodés, pools, paires, listings, launch origins, pool origins, wallets et holdings observés, trade events, pair metrics, candles et signaux analytiques déjà persistés, ainsi que conservation d’une instance partagée de la base SQLite pour éviter la réouverture et la réinitialisation du schéma à chaque commande UI
+0.7.23 - Ajout du pilotage UI du backfill historique ciblé par `token mint` dans `kb_app`, avec saisie du rôle HTTP et des limites de signatures, affichage du résumé de backfill, réinspection automatique du token dans `Demo Pipeline` lorsque des objets persistés sont effectivement reconstruits, et gestion explicite du cas où le backfill réussit sans matérialiser de token exploitable dans la base locale
diff --git a/Cargo.toml b/Cargo.toml
index 43ae483..1d5993a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
-version = "0.7.22"
+version = "0.7.23"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"
@@ -54,7 +54,7 @@ tokio-tungstenite = { version = "^0.29", default-features = false, features = ["
tracing = { version = "^0.1", features = [] }
tracing-appender = { version = "^0.2", features = [] }
tracing-subscriber = { version = "^0.3", features = ["ansi", "env-filter", "chrono", "serde", "json"] }
-ts-rs = { version = "^12.0", features = [] }
+ts-rs = { version = "^12.0", features = ["bigdecimal", "bson", "bytes", "chrono", "indexmap", "ordered-float", "serde_json", "tokio", "url", "uuid"] }
yellowstone-grpc-client = { version = "^13.0", features = [] }
yellowstone-grpc-proto = { version = "^12.2", features = [] }
uuid = { version = "^1.23", features = ["v4", "serde"] }
diff --git a/ROADMAP.md b/ROADMAP.md
index 22430a7..6b89a9d 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -675,16 +675,15 @@ Réalisé :
- conservation d’une instance partagée de `KbDatabase` dans `kb_app` afin d’éviter la réouverture de la base et la réinitialisation du schéma à chaque commande UI,
- validation pratique de l’inspection du pipeline `0.7.x` sans dépendre uniquement des logs bruts ou de la consultation manuelle de SQLite.
-### 6.055. Version `0.7.23` — `kb_app` : backfill token et inspection ciblée
-Objectif : piloter le backfill historique depuis l’interface desktop et afficher le résultat de façon exploitable.
+### 6.055. Version `0.7.23` — `kb_app` : backfill token ciblé
+Réalisé :
-À faire :
-
-- ajouter une vue de saisie d’un `token_mint`,
-- permettre le déclenchement manuel du `KbTokenBackfillService`,
-- afficher le résumé du backfill : signatures mint, pools retrouvés, signatures de pools, transactions résolues, decoded events, trade events, candles et analytic signals,
-- permettre une navigation simple entre token, pools, paires et événements liés,
-- préparer la réexécution ciblée de backfills sans casser l’idempotence du modèle.
+- ajout d’un pilotage UI du backfill historique ciblé par `token mint` dans `kb_app`,
+- sélection du `token mint`, du rôle HTTP et des limites de signatures `mint / pool`,
+- exécution de `KbTokenBackfillService` depuis une commande Tauri dédiée,
+- affichage du résumé de backfill dans `Demo Pipeline`,
+- réinspection automatique du token après backfill lorsque des objets persistés exploitables sont effectivement reconstruits,
+- gestion explicite du cas où le backfill réussit sans matérialiser de token exploitable dans la base locale.
### 6.056. Version `0.7.24` — `kb_app` : visualisation candles / OHLCV
Objectif : fournir une vue graphique exploitable des candles via `echarts`.
diff --git a/kb_app/frontend/demo_pipeline.html b/kb_app/frontend/demo_pipeline.html
index 4f26d27..df9ce4c 100644
--- a/kb_app/frontend/demo_pipeline.html
+++ b/kb_app/frontend/demo_pipeline.html
@@ -108,6 +108,64 @@
Inspecter pool
+
+
diff --git a/kb_app/frontend/ts/bindings/KbDemoHttpExecutionPayload.ts b/kb_app/frontend/ts/bindings/KbDemoHttpExecutionPayload.ts
new file mode 100644
index 0000000..f78670a
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoHttpExecutionPayload.ts
@@ -0,0 +1,34 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Response payload for one demo HTTP execution.
+ */
+export type KbDemoHttpExecutionPayload = {
+/**
+ * Selected endpoint name.
+ */
+endpointName: string,
+/**
+ * Selected endpoint provider.
+ */
+provider: string,
+/**
+ * Selected endpoint URL.
+ */
+endpointUrl: string,
+/**
+ * Requested role.
+ */
+role: string,
+/**
+ * Executed method name.
+ */
+method: string,
+/**
+ * Classified method family.
+ */
+methodClass: string,
+/**
+ * Pretty-printed JSON response payload.
+ */
+responseJson: string, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoHttpRequest.ts b/kb_app/frontend/ts/bindings/KbDemoHttpRequest.ts
new file mode 100644
index 0000000..c671aff
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoHttpRequest.ts
@@ -0,0 +1,24 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Request payload for one demo HTTP execution.
+ */
+export type KbDemoHttpRequest = {
+/**
+ * Logical role used to select one endpoint from the pool.
+ */
+role: string,
+/**
+ * JSON-RPC HTTP method name.
+ */
+method: string,
+/**
+ * Optional first string argument, used by methods such as
+ * `getBalance`, `getAccountInfo`, `getProgramAccounts`,
+ * `getSignaturesForAddress`, `getTransaction`, `sendTransaction`.
+ */
+firstArg: string | null,
+/**
+ * Optional JSON config payload encoded as a string.
+ */
+configJson: string | null, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoPipelineBackfillTokenPayload.ts b/kb_app/frontend/ts/bindings/KbDemoPipelineBackfillTokenPayload.ts
new file mode 100644
index 0000000..646e9b4
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoPipelineBackfillTokenPayload.ts
@@ -0,0 +1,22 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Response payload for one token backfill launched from `kb_app`.
+ */
+export type KbDemoPipelineBackfillTokenPayload = {
+/**
+ * Backfilled token mint.
+ */
+tokenMint: string,
+/**
+ * HTTP role used during backfill.
+ */
+httpRole: string,
+/**
+ * Pretty JSON summary returned by `KbTokenBackfillService`.
+ */
+backfillJson: string,
+/**
+ * Whether the token exists in persisted token objects after backfill.
+ */
+tokenPersistedAfterBackfill: boolean, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoPipelineBackfillTokenRequest.ts b/kb_app/frontend/ts/bindings/KbDemoPipelineBackfillTokenRequest.ts
new file mode 100644
index 0000000..b1d11ff
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoPipelineBackfillTokenRequest.ts
@@ -0,0 +1,22 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Request payload for one token backfill launched from `kb_app`.
+ */
+export type KbDemoPipelineBackfillTokenRequest = {
+/**
+ * Token mint to backfill.
+ */
+tokenMint: string,
+/**
+ * HTTP role used to select one endpoint in the pool.
+ */
+httpRole: string | null,
+/**
+ * Maximum number of signatures fetched directly from the mint.
+ */
+mintSignatureLimit: number,
+/**
+ * Maximum number of signatures fetched from each discovered pool.
+ */
+poolSignatureLimit: number, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoPipelineInspectPairRequest.ts b/kb_app/frontend/ts/bindings/KbDemoPipelineInspectPairRequest.ts
new file mode 100644
index 0000000..412f4d8
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoPipelineInspectPairRequest.ts
@@ -0,0 +1,14 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Request payload for one pipeline inspection by pair id.
+ */
+export type KbDemoPipelineInspectPairRequest = {
+/**
+ * Pair id to inspect.
+ */
+pairId: bigint,
+/**
+ * Optional custom timeframe in seconds for on-demand candle rebuild.
+ */
+customTimeframeSeconds: bigint | null, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoPipelineInspectPayload.ts b/kb_app/frontend/ts/bindings/KbDemoPipelineInspectPayload.ts
new file mode 100644
index 0000000..b8e76ed
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoPipelineInspectPayload.ts
@@ -0,0 +1,58 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Response payload for one pipeline inspection.
+ */
+export type KbDemoPipelineInspectPayload = {
+/**
+ * Inspected signature.
+ */
+signature: string,
+/**
+ * Summary JSON block.
+ */
+summaryJson: string,
+/**
+ * Resolved transaction JSON block.
+ */
+transactionJson: string,
+/**
+ * Decoded events JSON block.
+ */
+decodedEventsJson: string,
+/**
+ * Pools JSON block.
+ */
+poolsJson: string,
+/**
+ * Pairs JSON block.
+ */
+pairsJson: string,
+/**
+ * Launch attributions JSON block.
+ */
+launchAttributionsJson: string,
+/**
+ * Pool origins JSON block.
+ */
+poolOriginsJson: string,
+/**
+ * Wallet inspection JSON block.
+ */
+walletsJson: string,
+/**
+ * Trade events JSON block.
+ */
+tradeEventsJson: string,
+/**
+ * Pair metrics JSON block.
+ */
+pairMetricsJson: string,
+/**
+ * Pair candles JSON block.
+ */
+pairCandlesJson: string,
+/**
+ * Pair analytic signals JSON block.
+ */
+pairAnalyticSignalsJson: string, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoPipelineInspectPoolRequest.ts b/kb_app/frontend/ts/bindings/KbDemoPipelineInspectPoolRequest.ts
new file mode 100644
index 0000000..f9fcc4b
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoPipelineInspectPoolRequest.ts
@@ -0,0 +1,14 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Request payload for one pipeline inspection by pool address.
+ */
+export type KbDemoPipelineInspectPoolRequest = {
+/**
+ * Pool address to inspect.
+ */
+poolAddress: string,
+/**
+ * Optional custom timeframe in seconds for on-demand candle rebuild.
+ */
+customTimeframeSeconds: bigint | null, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoPipelineInspectRequest.ts b/kb_app/frontend/ts/bindings/KbDemoPipelineInspectRequest.ts
new file mode 100644
index 0000000..a3b136c
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoPipelineInspectRequest.ts
@@ -0,0 +1,14 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Request payload for one pipeline inspection by signature.
+ */
+export type KbDemoPipelineInspectRequest = {
+/**
+ * Transaction signature to inspect.
+ */
+signature: string,
+/**
+ * Optional custom timeframe in seconds for on-demand candle rebuild.
+ */
+customTimeframeSeconds: bigint | null, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoPipelineInspectTokenRequest.ts b/kb_app/frontend/ts/bindings/KbDemoPipelineInspectTokenRequest.ts
new file mode 100644
index 0000000..961c33c
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoPipelineInspectTokenRequest.ts
@@ -0,0 +1,14 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Request payload for one pipeline inspection by token mint.
+ */
+export type KbDemoPipelineInspectTokenRequest = {
+/**
+ * Token mint to inspect.
+ */
+tokenMint: string,
+/**
+ * Optional custom timeframe in seconds for on-demand candle rebuild.
+ */
+customTimeframeSeconds: bigint | null, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoWsEndpointSummary.ts b/kb_app/frontend/ts/bindings/KbDemoWsEndpointSummary.ts
new file mode 100644
index 0000000..5d12705
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoWsEndpointSummary.ts
@@ -0,0 +1,6 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Endpoint summary sent to the demo frontend.
+ */
+export type KbDemoWsEndpointSummary = { name: string, resolvedUrl: string, provider: string, enabled: boolean, roles: Array, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoWsManagerEndpointSummary.ts b/kb_app/frontend/ts/bindings/KbDemoWsManagerEndpointSummary.ts
new file mode 100644
index 0000000..29b750c
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoWsManagerEndpointSummary.ts
@@ -0,0 +1,6 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Static endpoint summary enriched with current manager state.
+ */
+export type KbDemoWsManagerEndpointSummary = { name: string, resolvedUrl: string, provider: string, roles: Array, connectionState: string, activeSubscriptionCount: number, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoWsManagerSnapshotPayload.ts b/kb_app/frontend/ts/bindings/KbDemoWsManagerSnapshotPayload.ts
new file mode 100644
index 0000000..a81d133
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoWsManagerSnapshotPayload.ts
@@ -0,0 +1,7 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { KbDemoWsManagerEndpointSummary } from "./KbDemoWsManagerEndpointSummary";
+
+/**
+ * Global demo manager snapshot payload.
+ */
+export type KbDemoWsManagerSnapshotPayload = { endpointCount: number, startedCount: number, endpoints: Array, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoWsStatusPayload.ts b/kb_app/frontend/ts/bindings/KbDemoWsStatusPayload.ts
new file mode 100644
index 0000000..bbd6fcd
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoWsStatusPayload.ts
@@ -0,0 +1,6 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Current demo window runtime status.
+ */
+export type KbDemoWsStatusPayload = { connectionState: string, endpointName: string | null, endpointUrl: string | null, currentSubscriptionId: bigint | null, currentSubscribeMethod: string | null, currentUnsubscribeMethod: string | null, currentNotificationMethod: string | null, eventCountTotal: bigint, notificationCountTotal: bigint, uiLogCount: bigint, suppressedLogCount: bigint, lastEventKind: string | null, };
diff --git a/kb_app/frontend/ts/bindings/KbDemoWsSubscribeRequest.ts b/kb_app/frontend/ts/bindings/KbDemoWsSubscribeRequest.ts
new file mode 100644
index 0000000..f693d4f
--- /dev/null
+++ b/kb_app/frontend/ts/bindings/KbDemoWsSubscribeRequest.ts
@@ -0,0 +1,6 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Subscribe request sent by the demo frontend.
+ */
+export type KbDemoWsSubscribeRequest = { method: string, mode: string, target: string | null, filterJson: string | null, configJson: string | null, };
diff --git a/kb_app/frontend/ts/demo_http.ts b/kb_app/frontend/ts/demo_http.ts
index 0b20105..777232d 100644
--- a/kb_app/frontend/ts/demo_http.ts
+++ b/kb_app/frontend/ts/demo_http.ts
@@ -3,6 +3,8 @@ import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
+import { KbDemoHttpRequest } from './bindings/KbDemoHttpRequest.ts';
+import { KbDemoHttpExecutionPayload } from './bindings/KbDemoHttpExecutionPayload.ts';
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
@@ -17,23 +19,6 @@ interface DemoHttpPoolClientSnapshot {
availableConcurrencySlots: number;
}
-interface DemoHttpRequest {
- role: string;
- method: string;
- firstArg: string | null;
- configJson: string | null;
-}
-
-interface DemoHttpExecutionPayload {
- endpointName: string;
- provider: string;
- endpointUrl: string;
- role: string;
- method: string;
- methodClass: string;
- responseJson: string;
-}
-
let demoHttpLastResponseRawText = "";
function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
@@ -400,7 +385,7 @@ document.addEventListener("DOMContentLoaded", async () => {
});
executeButton.addEventListener("click", async () => {
- const request: DemoHttpRequest = {
+ const request: KbDemoHttpRequest = {
role: roleSelect.value,
method: methodSelect.value,
firstArg: firstArgInput.value.trim() === "" ? null : firstArgInput.value.trim(),
@@ -408,7 +393,7 @@ document.addEventListener("DOMContentLoaded", async () => {
};
try {
- const response = await invoke("demo_http_execute_request", { request });
+ const response = await invoke("demo_http_execute_request", { request });
lastEndpointText.textContent = `${response.endpointName} (${response.endpointUrl})`;
lastProviderText.textContent = response.provider;
diff --git a/kb_app/frontend/ts/demo_pipeline.ts b/kb_app/frontend/ts/demo_pipeline.ts
index 065762f..8a5e27b 100644
--- a/kb_app/frontend/ts/demo_pipeline.ts
+++ b/kb_app/frontend/ts/demo_pipeline.ts
@@ -5,45 +5,17 @@ import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
-
+import { KbDemoPipelineInspectRequest } from './bindings/KbDemoPipelineInspectRequest.ts';
+import { KbDemoPipelineInspectPayload } from './bindings/KbDemoPipelineInspectPayload.ts';
+import { KbDemoPipelineInspectTokenRequest } from './bindings/KbDemoPipelineInspectTokenRequest.ts';
+import { KbDemoPipelineInspectPairRequest } from './bindings/KbDemoPipelineInspectPairRequest.ts';
+import { KbDemoPipelineInspectPoolRequest } from './bindings/KbDemoPipelineInspectPoolRequest.ts';
+import { KbDemoPipelineBackfillTokenRequest } from './bindings/KbDemoPipelineBackfillTokenRequest.ts';
+import { KbDemoPipelineBackfillTokenPayload } from './bindings/KbDemoPipelineBackfillTokenPayload.ts';
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
-interface DemoPipelineInspectRequest {
- signature: string;
- customTimeframeSeconds: number | null;
-}
-interface DemoPipelineInspectPayload {
- signature: string;
- summaryJson: string;
- transactionJson: string;
- decodedEventsJson: string;
- poolsJson: string;
- pairsJson: string;
- launchAttributionsJson: string;
- poolOriginsJson: string;
- walletsJson: string;
- tradeEventsJson: string;
- pairMetricsJson: string;
- pairCandlesJson: string;
- pairAnalyticSignalsJson: string;
-}
-
-interface DemoPipelineInspectTokenRequest {
- tokenMint: string;
- customTimeframeSeconds: number | null;
-}
-
-interface DemoPipelineInspectPairRequest {
- pairId: number;
- customTimeframeSeconds: number | null;
-}
-
-interface DemoPipelineInspectPoolRequest {
- poolAddress: string;
- customTimeframeSeconds: number | null;
-}
function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
const now = new Date();
@@ -58,6 +30,7 @@ function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
}
function clearInspection(
+ backfillTextarea: HTMLTextAreaElement,
summaryTextarea: HTMLTextAreaElement,
transactionTextarea: HTMLTextAreaElement,
decodedEventsTextarea: HTMLTextAreaElement,
@@ -71,6 +44,7 @@ function clearInspection(
pairCandlesTextarea: HTMLTextAreaElement,
pairAnalyticSignalsTextarea: HTMLTextAreaElement,
): void {
+ backfillTextarea.value = "";
summaryTextarea.value = "";
transactionTextarea.value = "";
decodedEventsTextarea.value = "";
@@ -88,7 +62,7 @@ function clearInspection(
function readCustomTimeframeSeconds(
input: HTMLInputElement,
logTextarea: HTMLTextAreaElement,
-): number | null | undefined {
+): bigint | null | undefined {
const customTimeframeText = input.value.trim();
if (customTimeframeText === "") {
return null;
@@ -100,6 +74,26 @@ function readCustomTimeframeSeconds(
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;
}
@@ -166,8 +160,21 @@ document.addEventListener("DOMContentLoaded", async () => {
const inspectPairButton = document.querySelector("#demoPipelineInspectPairButton");
const poolAddressInput = document.querySelector("#demoPipelinePoolAddressInput");
const inspectPoolButton = document.querySelector("#demoPipelineInspectPoolButton");
+ const backfillTokenMintInput = document.querySelector("#demoPipelineBackfillTokenMintInput");
+ const backfillHttpRoleInput = document.querySelector("#demoPipelineBackfillHttpRoleInput");
+ const backfillMintLimitInput = document.querySelector("#demoPipelineBackfillMintLimitInput");
+ const backfillPoolLimitInput = document.querySelector("#demoPipelineBackfillPoolLimitInput");
+ const backfillTokenButton = document.querySelector("#demoPipelineBackfillTokenButton");
+ const backfillTextarea = document.querySelector("#demoPipelineBackfillTextarea");
+
if (
+ !backfillTokenMintInput ||
+ !backfillHttpRoleInput ||
+ !backfillMintLimitInput ||
+ !backfillPoolLimitInput ||
+ !backfillTokenButton ||
+ !backfillTextarea ||
!pairIdInput ||
!inspectPairButton ||
!poolAddressInput ||
@@ -199,6 +206,7 @@ document.addEventListener("DOMContentLoaded", async () => {
clearButton.addEventListener("click", () => {
clearInspection(
+ backfillTextarea,
summaryTextarea,
transactionTextarea,
decodedEventsTextarea,
@@ -212,6 +220,10 @@ document.addEventListener("DOMContentLoaded", async () => {
pairCandlesTextarea,
pairAnalyticSignalsTextarea,
);
+ backfillTokenMintInput.value = "";
+ backfillHttpRoleInput.value = "history_backfill";
+ backfillMintLimitInput.value = "50";
+ backfillPoolLimitInput.value = "50";
signatureInput.value = "";
customTimeframeInput.value = "";
tokenMintInput.value = "";
@@ -231,7 +243,7 @@ document.addEventListener("DOMContentLoaded", async () => {
return;
}
- let customTimeframeSeconds: number | null = null;
+ let customTimeframeSeconds: bigint | null = null;
const customTimeframeText = customTimeframeInput.value.trim();
if (customTimeframeText !== "") {
const parsed = Number.parseInt(customTimeframeText, 10);
@@ -239,7 +251,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`);
return;
}
- customTimeframeSeconds = parsed;
+ customTimeframeSeconds = BigInt(parsed);
}
appendLogLine(
@@ -247,13 +259,13 @@ document.addEventListener("DOMContentLoaded", async () => {
`[ui] inspecting signature '${signature}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
- const request: DemoPipelineInspectRequest = {
+ const request: KbDemoPipelineInspectRequest = {
signature,
customTimeframeSeconds,
};
try {
- const payload = await invoke("demo_pipeline_inspect_signature", { request });
+ const payload = await invoke("demo_pipeline_inspect_signature", { request });
summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson;
@@ -281,7 +293,7 @@ document.addEventListener("DOMContentLoaded", async () => {
return;
}
- let customTimeframeSeconds: number | null = null;
+ let customTimeframeSeconds: bigint | null = null;
const customTimeframeText = customTimeframeInput.value.trim();
if (customTimeframeText !== "") {
const parsed = Number.parseInt(customTimeframeText, 10);
@@ -289,7 +301,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`);
return;
}
- customTimeframeSeconds = parsed;
+ customTimeframeSeconds = BigInt(parsed);
}
appendLogLine(
@@ -297,13 +309,13 @@ document.addEventListener("DOMContentLoaded", async () => {
`[ui] inspecting token mint '${tokenMint}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
- const request: DemoPipelineInspectTokenRequest = {
+ const request: KbDemoPipelineInspectTokenRequest = {
tokenMint,
customTimeframeSeconds,
};
try {
- const payload = await invoke("demo_pipeline_inspect_token_mint", { request });
+ const payload = await invoke("demo_pipeline_inspect_token_mint", { request });
summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson;
@@ -347,13 +359,13 @@ document.addEventListener("DOMContentLoaded", async () => {
`[ui] inspecting pair id '${parsedPairId}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
- const request: DemoPipelineInspectPairRequest = {
- pairId: parsedPairId,
+ const request: KbDemoPipelineInspectPairRequest = {
+ pairId: BigInt(parsedPairId),
customTimeframeSeconds,
};
try {
- const payload = await invoke("demo_pipeline_inspect_pair_id", { request });
+ const payload = await invoke("demo_pipeline_inspect_pair_id", { request });
summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson;
@@ -391,13 +403,13 @@ document.addEventListener("DOMContentLoaded", async () => {
`[ui] inspecting pool '${poolAddress}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
);
- const request: DemoPipelineInspectPoolRequest = {
+ const request: KbDemoPipelineInspectPoolRequest = {
poolAddress,
customTimeframeSeconds,
};
try {
- const payload = await invoke("demo_pipeline_inspect_pool_address", { request });
+ const payload = await invoke("demo_pipeline_inspect_pool_address", { request });
summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson;
@@ -417,4 +429,103 @@ document.addEventListener("DOMContentLoaded", async () => {
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: KbDemoPipelineBackfillTokenRequest = {
+ tokenMint,
+ httpRole,
+ mintSignatureLimit,
+ poolSignatureLimit,
+ };
+
+ try {
+ const payload = await invoke(
+ "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: KbDemoPipelineInspectTokenRequest = {
+ tokenMint: payload.tokenMint,
+ customTimeframeSeconds: null,
+ };
+
+ try {
+ const inspectPayload = await invoke(
+ "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)}`);
+ }
+ });
});
\ No newline at end of file
diff --git a/kb_app/frontend/ts/demo_ws.ts b/kb_app/frontend/ts/demo_ws.ts
index 459a799..b791ac1 100644
--- a/kb_app/frontend/ts/demo_ws.ts
+++ b/kb_app/frontend/ts/demo_ws.ts
@@ -6,40 +6,13 @@ import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
+import { KbDemoWsEndpointSummary } from './bindings/KbDemoWsEndpointSummary.ts';
+import { KbDemoWsStatusPayload } from './bindings/KbDemoWsStatusPayload.ts';
+import { KbDemoWsSubscribeRequest } from './bindings/KbDemoWsSubscribeRequest.ts';
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
-interface DemoWsEndpointSummary {
- name: string;
- resolvedUrl: string;
- provider: string;
- enabled: boolean;
- roles: string[];
-}
-
-interface DemoWsStatusPayload {
- connectionState: string;
- endpointName: string | null;
- endpointUrl: string | null;
- currentSubscriptionId: number | null;
- currentSubscribeMethod: string | null;
- currentUnsubscribeMethod: string | null;
- currentNotificationMethod: string | null;
- eventCountTotal: number;
- notificationCountTotal: number;
- uiLogCount: number;
- suppressedLogCount: number;
- lastEventKind: string | null;
-}
-
-interface DemoWsSubscribeRequest {
- method: string;
- mode: string;
- target: string | null;
- filterJson: string | null;
- configJson: string | null;
-}
function shortenLine(line: string, maxChars = 3000): string {
if (line.length <= maxChars) {
@@ -195,7 +168,7 @@ function updateFormVisibility(
}
function applyStatusToUi(
- status: DemoWsStatusPayload,
+ status: KbDemoWsStatusPayload,
statusBadge: HTMLSpanElement,
stateText: HTMLSpanElement,
endpointText: HTMLSpanElement,
@@ -323,7 +296,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(logTextarea, event.payload);
});
- unlistenStatusEvent = await listen("demo-ws-status", (event) => {
+ unlistenStatusEvent = await listen("demo-ws-status", (event) => {
applyStatusToUi(
event.payload,
statusBadge,
@@ -346,7 +319,7 @@ document.addEventListener("DOMContentLoaded", async () => {
}
try {
- const endpoints = await invoke("demo_ws_list_endpoints");
+ const endpoints = await invoke("demo_ws_list_endpoints");
endpointSelect.innerHTML = "";
for (const endpoint of endpoints) {
@@ -401,7 +374,7 @@ document.addEventListener("DOMContentLoaded", async () => {
});
try {
- const status = await invoke("demo_ws_get_status");
+ const status = await invoke("demo_ws_get_status");
applyStatusToUi(
status,
statusBadge,
@@ -426,7 +399,7 @@ document.addEventListener("DOMContentLoaded", async () => {
connectButton.addEventListener("click", async () => {
try {
- const status = await invoke("demo_ws_connect", {
+ const status = await invoke("demo_ws_connect", {
endpointName: endpointSelect.value,
});
@@ -453,7 +426,7 @@ document.addEventListener("DOMContentLoaded", async () => {
disconnectButton.addEventListener("click", async () => {
try {
- const status = await invoke("demo_ws_disconnect");
+ const status = await invoke("demo_ws_disconnect");
applyStatusToUi(
status,
@@ -477,7 +450,7 @@ document.addEventListener("DOMContentLoaded", async () => {
});
subscribeButton.addEventListener("click", async () => {
- const request: DemoWsSubscribeRequest = {
+ const request: KbDemoWsSubscribeRequest = {
method: methodSelect.value,
mode: modeSelect.value,
target: targetInput.value.trim() === "" ? null : targetInput.value.trim(),
diff --git a/kb_app/frontend/ts/demo_ws_manager.ts b/kb_app/frontend/ts/demo_ws_manager.ts
index 4c03587..3d86455 100644
--- a/kb_app/frontend/ts/demo_ws_manager.ts
+++ b/kb_app/frontend/ts/demo_ws_manager.ts
@@ -6,24 +6,11 @@ import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
+import { KbDemoWsManagerSnapshotPayload } from './bindings/KbDemoWsManagerSnapshotPayload.ts';
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
-type DemoWsManagerEndpointSummary = {
- name: string;
- resolvedUrl: string;
- provider: string;
- roles: string[];
- connectionState: string;
- activeSubscriptionCount: number;
-};
-
-type DemoWsManagerSnapshotPayload = {
- endpointCount: number;
- startedCount: number;
- endpoints: DemoWsManagerEndpointSummary[];
-};
const endpointCountText = document.querySelector("#demoWsManagerEndpointCountText");
const startedCountText = document.querySelector("#demoWsManagerStartedCountText");
@@ -46,7 +33,7 @@ function appendLogLine(line: string): void {
logTextarea.scrollTop = logTextarea.scrollHeight;
}
-function renderSnapshot(snapshot: DemoWsManagerSnapshotPayload): void {
+function renderSnapshot(snapshot: KbDemoWsManagerSnapshotPayload): void {
if (endpointCountText) {
endpointCountText.textContent = String(snapshot.endpointCount);
}
@@ -77,7 +64,7 @@ function renderSnapshot(snapshot: DemoWsManagerSnapshotPayload): void {
async function refreshSnapshot(): Promise {
try {
- const snapshot = await invoke("demo_ws_manager_get_snapshot");
+ const snapshot = await invoke("demo_ws_manager_get_snapshot");
renderSnapshot(snapshot);
appendLogLine("[ui] refreshed manager snapshot");
} catch (error) {
@@ -107,7 +94,7 @@ async function loadRoles(): Promise {
async function startAll(): Promise {
try {
- const snapshot = await invoke("demo_ws_manager_start_all");
+ const snapshot = await invoke("demo_ws_manager_start_all");
renderSnapshot(snapshot);
} catch (error) {
appendLogLine(`[ui] start all error: ${String(error)}`);
@@ -116,7 +103,7 @@ async function startAll(): Promise {
async function stopAll(): Promise {
try {
- const snapshot = await invoke("demo_ws_manager_stop_all");
+ const snapshot = await invoke("demo_ws_manager_stop_all");
renderSnapshot(snapshot);
} catch (error) {
appendLogLine(`[ui] stop all error: ${String(error)}`);
@@ -130,7 +117,7 @@ async function startRole(): Promise {
}
try {
- const snapshot = await invoke("demo_ws_manager_start_role", {
+ const snapshot = await invoke("demo_ws_manager_start_role", {
role: roleSelect.value,
});
renderSnapshot(snapshot);
@@ -146,7 +133,7 @@ async function stopRole(): Promise {
}
try {
- const snapshot = await invoke("demo_ws_manager_stop_role", {
+ const snapshot = await invoke("demo_ws_manager_stop_role", {
role: roleSelect.value,
});
renderSnapshot(snapshot);
@@ -228,7 +215,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(event.payload);
});
- await listen("kb-demo-ws-manager-snapshot", (event) => {
+ await listen("kb-demo-ws-manager-snapshot", (event) => {
renderSnapshot(event.payload);
});
diff --git a/kb_app/package.json b/kb_app/package.json
index c923029..d2e4f2f 100644
--- a/kb_app/package.json
+++ b/kb_app/package.json
@@ -1,7 +1,7 @@
{
"name": "kb-app",
"private": true,
- "version": "0.7.22",
+ "version": "0.7.23",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/kb_app/src/demo_http.rs b/kb_app/src/demo_http.rs
index 8f5ca91..ac8a97d 100644
--- a/kb_app/src/demo_http.rs
+++ b/kb_app/src/demo_http.rs
@@ -7,7 +7,8 @@
use tauri::Manager;
/// Request payload for one demo HTTP execution.
-#[derive(Clone, Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoHttpRequest.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoHttpRequest {
/// Logical role used to select one endpoint from the pool.
@@ -23,7 +24,8 @@ pub(crate) struct KbDemoHttpRequest {
}
/// Response payload for one demo HTTP execution.
-#[derive(Clone, Debug, serde::Serialize)]
+#[derive(Clone, Debug, serde::Serialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoHttpExecutionPayload.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoHttpExecutionPayload {
/// Selected endpoint name.
diff --git a/kb_app/src/demo_pipeline.rs b/kb_app/src/demo_pipeline.rs
index 281e049..3a68170 100644
--- a/kb_app/src/demo_pipeline.rs
+++ b/kb_app/src/demo_pipeline.rs
@@ -5,7 +5,8 @@
use tauri::Manager;
/// Request payload for one pipeline inspection by signature.
-#[derive(Clone, Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoPipelineInspectRequest.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoPipelineInspectRequest {
/// Transaction signature to inspect.
@@ -15,7 +16,8 @@ pub(crate) struct KbDemoPipelineInspectRequest {
}
/// Response payload for one pipeline inspection.
-#[derive(Clone, Debug, serde::Serialize)]
+#[derive(Clone, Debug, serde::Serialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoPipelineInspectPayload.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoPipelineInspectPayload {
/// Inspected signature.
@@ -47,7 +49,8 @@ pub(crate) struct KbDemoPipelineInspectPayload {
}
/// Request payload for one pipeline inspection by token mint.
-#[derive(Clone, Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoPipelineInspectTokenRequest.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoPipelineInspectTokenRequest {
/// Token mint to inspect.
@@ -57,7 +60,8 @@ pub(crate) struct KbDemoPipelineInspectTokenRequest {
}
/// Request payload for one pipeline inspection by pair id.
-#[derive(Clone, Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoPipelineInspectPairRequest.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoPipelineInspectPairRequest {
/// Pair id to inspect.
@@ -67,7 +71,8 @@ pub(crate) struct KbDemoPipelineInspectPairRequest {
}
/// Request payload for one pipeline inspection by pool address.
-#[derive(Clone, Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoPipelineInspectPoolRequest.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoPipelineInspectPoolRequest {
/// Pool address to inspect.
@@ -76,6 +81,112 @@ pub(crate) struct KbDemoPipelineInspectPoolRequest {
pub custom_timeframe_seconds: std::option::Option,
}
+/// Request payload for one token backfill launched from `kb_app`.
+#[derive(Clone, Debug, serde::Deserialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoPipelineBackfillTokenRequest.ts")]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct KbDemoPipelineBackfillTokenRequest {
+ /// Token mint to backfill.
+ pub token_mint: std::string::String,
+ /// HTTP role used to select one endpoint in the pool.
+ pub http_role: std::option::Option,
+ /// Maximum number of signatures fetched directly from the mint.
+ pub mint_signature_limit: usize,
+ /// Maximum number of signatures fetched from each discovered pool.
+ pub pool_signature_limit: usize,
+}
+
+/// Response payload for one token backfill launched from `kb_app`.
+#[derive(Clone, Debug, serde::Serialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoPipelineBackfillTokenPayload.ts")]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct KbDemoPipelineBackfillTokenPayload {
+ /// Backfilled token mint.
+ pub token_mint: std::string::String,
+ /// HTTP role used during backfill.
+ pub http_role: std::string::String,
+ /// Pretty JSON summary returned by `KbTokenBackfillService`.
+ pub backfill_json: std::string::String,
+ /// Whether the token exists in persisted token objects after backfill.
+ pub token_persisted_after_backfill: bool,
+}
+
+/// Launches one token backfill through the persisted `kb_lib` services.
+#[tauri::command]
+pub(crate) async fn demo_pipeline_backfill_token_mint(
+ state: tauri::State<'_, crate::KbAppState>,
+ request: KbDemoPipelineBackfillTokenRequest,
+) -> Result {
+ let token_mint = request.token_mint.trim().to_string();
+ if token_mint.is_empty() {
+ return Err("demo pipeline backfill token mint must not be empty".to_string());
+ }
+ let http_role = match request.http_role.clone() {
+ Some(http_role) => {
+ let trimmed = http_role.trim().to_string();
+ if trimmed.is_empty() {
+ "history_backfill".to_string()
+ } else {
+ trimmed
+ }
+ }
+ None => "history_backfill".to_string(),
+ };
+ if request.mint_signature_limit == 0 {
+ return Err("demo pipeline mintSignatureLimit must be > 0".to_string());
+ }
+ if request.pool_signature_limit == 0 {
+ return Err("demo pipeline poolSignatureLimit must be > 0".to_string());
+ }
+ let database = state.database.clone();
+ let http_pool = std::sync::Arc::new(state.http_pool.clone());
+ let service =
+ kb_lib::KbTokenBackfillService::new(http_pool, database.clone(), http_role.clone());
+ let backfill_result = service
+ .backfill_token_by_mint(
+ token_mint.as_str(),
+ request.mint_signature_limit,
+ request.pool_signature_limit,
+ )
+ .await;
+ let backfill = match backfill_result {
+ Ok(backfill) => backfill,
+ Err(error) => {
+ return Err(format!(
+ "cannot backfill token mint '{}' with role '{}': {}",
+ token_mint, http_role, error
+ ));
+ }
+ };
+ let backfill_json_result = serde_json::to_string_pretty(&backfill);
+ let backfill_json = match backfill_json_result {
+ Ok(backfill_json) => backfill_json,
+ Err(error) => {
+ return Err(format!(
+ "cannot serialize token backfill result for '{}': {}",
+ token_mint, error
+ ));
+ }
+ };
+ let token_result = kb_lib::get_token_by_mint(database.as_ref(), token_mint.as_str()).await;
+ let token_option = match token_result {
+ Ok(token_option) => token_option,
+ Err(error) => {
+ return Err(format!(
+ "cannot verify persisted token mint '{}' after backfill with role '{}': {}",
+ token_mint, http_role, error
+ ));
+ }
+ };
+ let token_persisted_after_backfill = token_option.is_some();
+ Ok(KbDemoPipelineBackfillTokenPayload {
+ token_mint,
+ http_role,
+ backfill_json,
+ token_persisted_after_backfill,
+ })
+}
+
/// Inspects one pair id through the persisted `kb_lib` pipeline state.
#[tauri::command]
pub(crate) async fn demo_pipeline_inspect_pair_id(
@@ -159,10 +270,7 @@ pub(crate) async fn demo_pipeline_inspect_pool_address(
let pool_id = match pool.id {
Some(pool_id) => pool_id,
None => {
- return Err(format!(
- "pool '{}' has no internal id",
- pool.address
- ));
+ return Err(format!("pool '{}' has no internal id", pool.address));
}
};
let pair_result = kb_lib::get_pair_by_pool_id(database.as_ref(), pool_id).await;
diff --git a/kb_app/src/demo_ws.rs b/kb_app/src/demo_ws.rs
index cbe72ff..95f0c88 100644
--- a/kb_app/src/demo_ws.rs
+++ b/kb_app/src/demo_ws.rs
@@ -9,7 +9,8 @@ use tauri::Emitter;
use tauri::Manager;
/// Endpoint summary sent to the demo frontend.
-#[derive(Clone, Debug, serde::Serialize)]
+#[derive(Clone, Debug, serde::Serialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoWsEndpointSummary.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoWsEndpointSummary {
name: std::string::String,
@@ -20,7 +21,8 @@ pub(crate) struct KbDemoWsEndpointSummary {
}
/// Current demo window runtime status.
-#[derive(Clone, Debug, serde::Serialize)]
+#[derive(Clone, Debug, serde::Serialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoWsStatusPayload.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoWsStatusPayload {
connection_state: std::string::String,
@@ -38,7 +40,8 @@ pub(crate) struct KbDemoWsStatusPayload {
}
/// Subscribe request sent by the demo frontend.
-#[derive(Clone, Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoWsSubscribeRequest.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoWsSubscribeRequest {
method: std::string::String,
diff --git a/kb_app/src/demo_ws_manager.rs b/kb_app/src/demo_ws_manager.rs
index 974ee4a..b81ee8f 100644
--- a/kb_app/src/demo_ws_manager.rs
+++ b/kb_app/src/demo_ws_manager.rs
@@ -8,7 +8,8 @@ use tauri::Emitter;
use tauri::Manager;
/// Static endpoint summary enriched with current manager state.
-#[derive(Clone, Debug, serde::Serialize)]
+#[derive(Clone, Debug, serde::Serialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoWsManagerEndpointSummary.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoWsManagerEndpointSummary {
name: std::string::String,
@@ -20,7 +21,8 @@ pub(crate) struct KbDemoWsManagerEndpointSummary {
}
/// Global demo manager snapshot payload.
-#[derive(Clone, Debug, serde::Serialize)]
+#[derive(Clone, Debug, serde::Serialize, ts_rs::TS)]
+#[ts(export, export_to = "../frontend/ts/bindings/KbDemoWsManagerSnapshotPayload.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoWsManagerSnapshotPayload {
endpoint_count: usize,
diff --git a/kb_app/src/lib.rs b/kb_app/src/lib.rs
index c02d4bb..23b8ad3 100644
--- a/kb_app/src/lib.rs
+++ b/kb_app/src/lib.rs
@@ -142,6 +142,7 @@ pub async fn run() -> Result<(), kb_lib::KbError> {
crate::demo_pipeline::demo_pipeline_inspect_token_mint,
crate::demo_pipeline::demo_pipeline_inspect_pair_id,
crate::demo_pipeline::demo_pipeline_inspect_pool_address,
+ crate::demo_pipeline::demo_pipeline_backfill_token_mint,
]);
tauri_builder = tauri_builder.plugin(tracing_builder.build::());
tauri_builder = tauri_builder.setup(|app| {
diff --git a/kb_app/tauri.conf.json b/kb_app/tauri.conf.json
index 6209cd2..17e3858 100644
--- a/kb_app/tauri.conf.json
+++ b/kb_app/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "kb-bapp",
- "version": "0.7.22",
+ "version": "0.7.23",
"identifier": "com.sasedev.kb-app",
"build": {
"beforeDevCommand": "npm run dev",