This commit is contained in:
2026-05-01 12:01:13 +02:00
parent c542aa9d32
commit 60db521a88
29 changed files with 639 additions and 155 deletions

View File

@@ -53,3 +53,4 @@
0.7.20 - Ajout dune 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.20 - Ajout dune 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 dune 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.21 - Ajout dune 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 dune première fenêtre `Demo Pipeline` dans `kb_app` pour linspection 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 dune instance partagée de la base SQLite pour éviter la réouverture et la réinitialisation du schéma à chaque commande UI 0.7.22 - Ajout dune première fenêtre `Demo Pipeline` dans `kb_app` pour linspection 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 dune 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

View File

@@ -8,7 +8,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.7.22" version = "0.7.23"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot" 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 = { version = "^0.1", features = [] }
tracing-appender = { version = "^0.2", features = [] } tracing-appender = { version = "^0.2", features = [] }
tracing-subscriber = { version = "^0.3", features = ["ansi", "env-filter", "chrono", "serde", "json"] } 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-client = { version = "^13.0", features = [] }
yellowstone-grpc-proto = { version = "^12.2", features = [] } yellowstone-grpc-proto = { version = "^12.2", features = [] }
uuid = { version = "^1.23", features = ["v4", "serde"] } uuid = { version = "^1.23", features = ["v4", "serde"] }

View File

@@ -675,16 +675,15 @@ Réalisé :
- conservation dune 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, - conservation dune 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 linspection du pipeline `0.7.x` sans dépendre uniquement des logs bruts ou de la consultation manuelle de SQLite. - validation pratique de linspection 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 ### 6.055. Version `0.7.23` — `kb_app` : backfill token ciblé
Objectif : piloter le backfill historique depuis linterface desktop et afficher le résultat de façon exploitable. Réalisé :
À faire : - ajout dun 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`,
- ajouter une vue de saisie dun `token_mint`, - exécution de `KbTokenBackfillService` depuis une commande Tauri dédiée,
- permettre le déclenchement manuel du `KbTokenBackfillService`, - affichage du résumé de backfill dans `Demo Pipeline`,
- 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, - réinspection automatique du token après backfill lorsque des objets persistés exploitables sont effectivement reconstruits,
- permettre une navigation simple entre token, pools, paires et événements liés, - gestion explicite du cas où le backfill réussit sans matérialiser de token exploitable dans la base locale.
- préparer la réexécution ciblée de backfills sans casser lidempotence du modèle.
### 6.056. Version `0.7.24` — `kb_app` : visualisation candles / OHLCV ### 6.056. Version `0.7.24` — `kb_app` : visualisation candles / OHLCV
Objectif : fournir une vue graphique exploitable des candles via `echarts`. Objectif : fournir une vue graphique exploitable des candles via `echarts`.

View File

@@ -108,6 +108,64 @@
Inspecter pool Inspecter pool
</button> </button>
</div> </div>
<hr class="my-4" />
<h2 class="h5 mb-3">Backfill token</h2>
<div class="mb-3">
<label for="demoPipelineBackfillTokenMintInput" class="form-label">Token mint à backfill</label>
<input
id="demoPipelineBackfillTokenMintInput"
type="text"
class="form-control font-monospace"
spellcheck="false"
placeholder="Mint SPL à reconstruire depuis le RPC HTTP"
/>
</div>
<div class="mb-3">
<label for="demoPipelineBackfillHttpRoleInput" class="form-label">HTTP role</label>
<input
id="demoPipelineBackfillHttpRoleInput"
type="text"
class="form-control"
spellcheck="false"
value="history_backfill"
placeholder="Ex: history_backfill"
/>
</div>
<div class="row g-2">
<div class="col-6">
<label for="demoPipelineBackfillMintLimitInput" class="form-label">Mint signatures</label>
<input
id="demoPipelineBackfillMintLimitInput"
type="number"
min="1"
step="1"
class="form-control"
value="50"
/>
</div>
<div class="col-6">
<label for="demoPipelineBackfillPoolLimitInput" class="form-label">Pool signatures</label>
<input
id="demoPipelineBackfillPoolLimitInput"
type="number"
min="1"
step="1"
class="form-control"
value="50"
/>
</div>
</div>
<div class="d-flex flex-wrap gap-2 mt-3 mb-4">
<button id="demoPipelineBackfillTokenButton" type="button" class="btn btn-outline-primary">
Backfill token
</button>
</div>
<hr class="my-4" /> <hr class="my-4" />
@@ -126,6 +184,13 @@
<textarea id="demoPipelineSummaryTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea> <textarea id="demoPipelineSummaryTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</div> </div>
</div> </div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h2 class="h5 mb-3">Dernier backfill token</h2>
<textarea id="demoPipelineBackfillTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</div>
</div>
<div class="accordion" id="demoPipelineAccordion"> <div class="accordion" id="demoPipelineAccordion">
<div class="accordion-item"> <div class="accordion-item">

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -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<string>, };

View File

@@ -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<string>, connectionState: string, activeSubscriptionCount: number, };

View File

@@ -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<KbDemoWsManagerEndpointSummary>, };

View File

@@ -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, };

View File

@@ -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, };

View File

@@ -3,6 +3,8 @@ import "simplebar";
import ResizeObserver from "resize-observer-polyfill"; import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing"; 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 & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; (window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
@@ -17,23 +19,6 @@ interface DemoHttpPoolClientSnapshot {
availableConcurrencySlots: number; 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 = ""; let demoHttpLastResponseRawText = "";
function appendLogLine(textarea: HTMLTextAreaElement, line: string): void { function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
@@ -400,7 +385,7 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
executeButton.addEventListener("click", async () => { executeButton.addEventListener("click", async () => {
const request: DemoHttpRequest = { const request: KbDemoHttpRequest = {
role: roleSelect.value, role: roleSelect.value,
method: methodSelect.value, method: methodSelect.value,
firstArg: firstArgInput.value.trim() === "" ? null : firstArgInput.value.trim(), firstArg: firstArgInput.value.trim() === "" ? null : firstArgInput.value.trim(),
@@ -408,7 +393,7 @@ document.addEventListener("DOMContentLoaded", async () => {
}; };
try { try {
const response = await invoke<DemoHttpExecutionPayload>("demo_http_execute_request", { request }); const response = await invoke<KbDemoHttpExecutionPayload>("demo_http_execute_request", { request });
lastEndpointText.textContent = `${response.endpointName} (${response.endpointUrl})`; lastEndpointText.textContent = `${response.endpointName} (${response.endpointUrl})`;
lastProviderText.textContent = response.provider; lastProviderText.textContent = response.provider;

View File

@@ -5,45 +5,17 @@ import "simplebar";
import ResizeObserver from "resize-observer-polyfill"; import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing"; 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 & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; (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 { function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
const now = new Date(); const now = new Date();
@@ -58,6 +30,7 @@ function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
} }
function clearInspection( function clearInspection(
backfillTextarea: HTMLTextAreaElement,
summaryTextarea: HTMLTextAreaElement, summaryTextarea: HTMLTextAreaElement,
transactionTextarea: HTMLTextAreaElement, transactionTextarea: HTMLTextAreaElement,
decodedEventsTextarea: HTMLTextAreaElement, decodedEventsTextarea: HTMLTextAreaElement,
@@ -71,6 +44,7 @@ function clearInspection(
pairCandlesTextarea: HTMLTextAreaElement, pairCandlesTextarea: HTMLTextAreaElement,
pairAnalyticSignalsTextarea: HTMLTextAreaElement, pairAnalyticSignalsTextarea: HTMLTextAreaElement,
): void { ): void {
backfillTextarea.value = "";
summaryTextarea.value = ""; summaryTextarea.value = "";
transactionTextarea.value = ""; transactionTextarea.value = "";
decodedEventsTextarea.value = ""; decodedEventsTextarea.value = "";
@@ -88,7 +62,7 @@ function clearInspection(
function readCustomTimeframeSeconds( function readCustomTimeframeSeconds(
input: HTMLInputElement, input: HTMLInputElement,
logTextarea: HTMLTextAreaElement, logTextarea: HTMLTextAreaElement,
): number | null | undefined { ): bigint | null | undefined {
const customTimeframeText = input.value.trim(); const customTimeframeText = input.value.trim();
if (customTimeframeText === "") { if (customTimeframeText === "") {
return null; return null;
@@ -100,6 +74,26 @@ function readCustomTimeframeSeconds(
return undefined; 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; return parsed;
} }
@@ -166,8 +160,21 @@ document.addEventListener("DOMContentLoaded", async () => {
const inspectPairButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectPairButton"); const inspectPairButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectPairButton");
const poolAddressInput = document.querySelector<HTMLInputElement>("#demoPipelinePoolAddressInput"); const poolAddressInput = document.querySelector<HTMLInputElement>("#demoPipelinePoolAddressInput");
const inspectPoolButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectPoolButton"); 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");
if ( if (
!backfillTokenMintInput ||
!backfillHttpRoleInput ||
!backfillMintLimitInput ||
!backfillPoolLimitInput ||
!backfillTokenButton ||
!backfillTextarea ||
!pairIdInput || !pairIdInput ||
!inspectPairButton || !inspectPairButton ||
!poolAddressInput || !poolAddressInput ||
@@ -199,6 +206,7 @@ document.addEventListener("DOMContentLoaded", async () => {
clearButton.addEventListener("click", () => { clearButton.addEventListener("click", () => {
clearInspection( clearInspection(
backfillTextarea,
summaryTextarea, summaryTextarea,
transactionTextarea, transactionTextarea,
decodedEventsTextarea, decodedEventsTextarea,
@@ -212,6 +220,10 @@ document.addEventListener("DOMContentLoaded", async () => {
pairCandlesTextarea, pairCandlesTextarea,
pairAnalyticSignalsTextarea, pairAnalyticSignalsTextarea,
); );
backfillTokenMintInput.value = "";
backfillHttpRoleInput.value = "history_backfill";
backfillMintLimitInput.value = "50";
backfillPoolLimitInput.value = "50";
signatureInput.value = ""; signatureInput.value = "";
customTimeframeInput.value = ""; customTimeframeInput.value = "";
tokenMintInput.value = ""; tokenMintInput.value = "";
@@ -231,7 +243,7 @@ document.addEventListener("DOMContentLoaded", async () => {
return; return;
} }
let customTimeframeSeconds: number | null = null; let customTimeframeSeconds: bigint | null = null;
const customTimeframeText = customTimeframeInput.value.trim(); const customTimeframeText = customTimeframeInput.value.trim();
if (customTimeframeText !== "") { if (customTimeframeText !== "") {
const parsed = Number.parseInt(customTimeframeText, 10); const parsed = Number.parseInt(customTimeframeText, 10);
@@ -239,7 +251,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`); appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`);
return; return;
} }
customTimeframeSeconds = parsed; customTimeframeSeconds = BigInt(parsed);
} }
appendLogLine( appendLogLine(
@@ -247,13 +259,13 @@ document.addEventListener("DOMContentLoaded", async () => {
`[ui] inspecting signature '${signature}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`, `[ui] inspecting signature '${signature}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
); );
const request: DemoPipelineInspectRequest = { const request: KbDemoPipelineInspectRequest = {
signature, signature,
customTimeframeSeconds, customTimeframeSeconds,
}; };
try { try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_signature", { request }); const payload = await invoke<KbDemoPipelineInspectPayload>("demo_pipeline_inspect_signature", { request });
summaryTextarea.value = payload.summaryJson; summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson; transactionTextarea.value = payload.transactionJson;
@@ -281,7 +293,7 @@ document.addEventListener("DOMContentLoaded", async () => {
return; return;
} }
let customTimeframeSeconds: number | null = null; let customTimeframeSeconds: bigint | null = null;
const customTimeframeText = customTimeframeInput.value.trim(); const customTimeframeText = customTimeframeInput.value.trim();
if (customTimeframeText !== "") { if (customTimeframeText !== "") {
const parsed = Number.parseInt(customTimeframeText, 10); const parsed = Number.parseInt(customTimeframeText, 10);
@@ -289,7 +301,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`); appendLogLine(logTextarea, `[ui] invalid custom timeframe '${customTimeframeText}'`);
return; return;
} }
customTimeframeSeconds = parsed; customTimeframeSeconds = BigInt(parsed);
} }
appendLogLine( appendLogLine(
@@ -297,13 +309,13 @@ document.addEventListener("DOMContentLoaded", async () => {
`[ui] inspecting token mint '${tokenMint}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`, `[ui] inspecting token mint '${tokenMint}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
); );
const request: DemoPipelineInspectTokenRequest = { const request: KbDemoPipelineInspectTokenRequest = {
tokenMint, tokenMint,
customTimeframeSeconds, customTimeframeSeconds,
}; };
try { try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_token_mint", { request }); const payload = await invoke<KbDemoPipelineInspectPayload>("demo_pipeline_inspect_token_mint", { request });
summaryTextarea.value = payload.summaryJson; summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson; transactionTextarea.value = payload.transactionJson;
@@ -347,13 +359,13 @@ document.addEventListener("DOMContentLoaded", async () => {
`[ui] inspecting pair id '${parsedPairId}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`, `[ui] inspecting pair id '${parsedPairId}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
); );
const request: DemoPipelineInspectPairRequest = { const request: KbDemoPipelineInspectPairRequest = {
pairId: parsedPairId, pairId: BigInt(parsedPairId),
customTimeframeSeconds, customTimeframeSeconds,
}; };
try { try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_pair_id", { request }); const payload = await invoke<KbDemoPipelineInspectPayload>("demo_pipeline_inspect_pair_id", { request });
summaryTextarea.value = payload.summaryJson; summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson; transactionTextarea.value = payload.transactionJson;
@@ -391,13 +403,13 @@ document.addEventListener("DOMContentLoaded", async () => {
`[ui] inspecting pool '${poolAddress}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`, `[ui] inspecting pool '${poolAddress}'${customTimeframeSeconds === null ? "" : ` with custom timeframe ${customTimeframeSeconds}s`}`,
); );
const request: DemoPipelineInspectPoolRequest = { const request: KbDemoPipelineInspectPoolRequest = {
poolAddress, poolAddress,
customTimeframeSeconds, customTimeframeSeconds,
}; };
try { try {
const payload = await invoke<DemoPipelineInspectPayload>("demo_pipeline_inspect_pool_address", { request }); const payload = await invoke<KbDemoPipelineInspectPayload>("demo_pipeline_inspect_pool_address", { request });
summaryTextarea.value = payload.summaryJson; summaryTextarea.value = payload.summaryJson;
transactionTextarea.value = payload.transactionJson; transactionTextarea.value = payload.transactionJson;
@@ -417,4 +429,103 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(logTextarea, `[ui] pool inspect error: ${String(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: KbDemoPipelineBackfillTokenRequest = {
tokenMint,
httpRole,
mintSignatureLimit,
poolSignatureLimit,
};
try {
const payload = await invoke<KbDemoPipelineBackfillTokenPayload>(
"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<KbDemoPipelineInspectPayload>(
"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)}`);
}
});
}); });

View File

@@ -6,40 +6,13 @@ import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing"; 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 & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; (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 { function shortenLine(line: string, maxChars = 3000): string {
if (line.length <= maxChars) { if (line.length <= maxChars) {
@@ -195,7 +168,7 @@ function updateFormVisibility(
} }
function applyStatusToUi( function applyStatusToUi(
status: DemoWsStatusPayload, status: KbDemoWsStatusPayload,
statusBadge: HTMLSpanElement, statusBadge: HTMLSpanElement,
stateText: HTMLSpanElement, stateText: HTMLSpanElement,
endpointText: HTMLSpanElement, endpointText: HTMLSpanElement,
@@ -323,7 +296,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(logTextarea, event.payload); appendLogLine(logTextarea, event.payload);
}); });
unlistenStatusEvent = await listen<DemoWsStatusPayload>("demo-ws-status", (event) => { unlistenStatusEvent = await listen<KbDemoWsStatusPayload>("demo-ws-status", (event) => {
applyStatusToUi( applyStatusToUi(
event.payload, event.payload,
statusBadge, statusBadge,
@@ -346,7 +319,7 @@ document.addEventListener("DOMContentLoaded", async () => {
} }
try { try {
const endpoints = await invoke<DemoWsEndpointSummary[]>("demo_ws_list_endpoints"); const endpoints = await invoke<KbDemoWsEndpointSummary[]>("demo_ws_list_endpoints");
endpointSelect.innerHTML = ""; endpointSelect.innerHTML = "";
for (const endpoint of endpoints) { for (const endpoint of endpoints) {
@@ -401,7 +374,7 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
try { try {
const status = await invoke<DemoWsStatusPayload>("demo_ws_get_status"); const status = await invoke<KbDemoWsStatusPayload>("demo_ws_get_status");
applyStatusToUi( applyStatusToUi(
status, status,
statusBadge, statusBadge,
@@ -426,7 +399,7 @@ document.addEventListener("DOMContentLoaded", async () => {
connectButton.addEventListener("click", async () => { connectButton.addEventListener("click", async () => {
try { try {
const status = await invoke<DemoWsStatusPayload>("demo_ws_connect", { const status = await invoke<KbDemoWsStatusPayload>("demo_ws_connect", {
endpointName: endpointSelect.value, endpointName: endpointSelect.value,
}); });
@@ -453,7 +426,7 @@ document.addEventListener("DOMContentLoaded", async () => {
disconnectButton.addEventListener("click", async () => { disconnectButton.addEventListener("click", async () => {
try { try {
const status = await invoke<DemoWsStatusPayload>("demo_ws_disconnect"); const status = await invoke<KbDemoWsStatusPayload>("demo_ws_disconnect");
applyStatusToUi( applyStatusToUi(
status, status,
@@ -477,7 +450,7 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
subscribeButton.addEventListener("click", async () => { subscribeButton.addEventListener("click", async () => {
const request: DemoWsSubscribeRequest = { const request: KbDemoWsSubscribeRequest = {
method: methodSelect.value, method: methodSelect.value,
mode: modeSelect.value, mode: modeSelect.value,
target: targetInput.value.trim() === "" ? null : targetInput.value.trim(), target: targetInput.value.trim() === "" ? null : targetInput.value.trim(),

View File

@@ -6,24 +6,11 @@ import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing"; 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 & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver; (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<HTMLSpanElement>("#demoWsManagerEndpointCountText"); const endpointCountText = document.querySelector<HTMLSpanElement>("#demoWsManagerEndpointCountText");
const startedCountText = document.querySelector<HTMLSpanElement>("#demoWsManagerStartedCountText"); const startedCountText = document.querySelector<HTMLSpanElement>("#demoWsManagerStartedCountText");
@@ -46,7 +33,7 @@ function appendLogLine(line: string): void {
logTextarea.scrollTop = logTextarea.scrollHeight; logTextarea.scrollTop = logTextarea.scrollHeight;
} }
function renderSnapshot(snapshot: DemoWsManagerSnapshotPayload): void { function renderSnapshot(snapshot: KbDemoWsManagerSnapshotPayload): void {
if (endpointCountText) { if (endpointCountText) {
endpointCountText.textContent = String(snapshot.endpointCount); endpointCountText.textContent = String(snapshot.endpointCount);
} }
@@ -77,7 +64,7 @@ function renderSnapshot(snapshot: DemoWsManagerSnapshotPayload): void {
async function refreshSnapshot(): Promise<void> { async function refreshSnapshot(): Promise<void> {
try { try {
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_get_snapshot"); const snapshot = await invoke<KbDemoWsManagerSnapshotPayload>("demo_ws_manager_get_snapshot");
renderSnapshot(snapshot); renderSnapshot(snapshot);
appendLogLine("[ui] refreshed manager snapshot"); appendLogLine("[ui] refreshed manager snapshot");
} catch (error) { } catch (error) {
@@ -107,7 +94,7 @@ async function loadRoles(): Promise<void> {
async function startAll(): Promise<void> { async function startAll(): Promise<void> {
try { try {
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_start_all"); const snapshot = await invoke<KbDemoWsManagerSnapshotPayload>("demo_ws_manager_start_all");
renderSnapshot(snapshot); renderSnapshot(snapshot);
} catch (error) { } catch (error) {
appendLogLine(`[ui] start all error: ${String(error)}`); appendLogLine(`[ui] start all error: ${String(error)}`);
@@ -116,7 +103,7 @@ async function startAll(): Promise<void> {
async function stopAll(): Promise<void> { async function stopAll(): Promise<void> {
try { try {
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_stop_all"); const snapshot = await invoke<KbDemoWsManagerSnapshotPayload>("demo_ws_manager_stop_all");
renderSnapshot(snapshot); renderSnapshot(snapshot);
} catch (error) { } catch (error) {
appendLogLine(`[ui] stop all error: ${String(error)}`); appendLogLine(`[ui] stop all error: ${String(error)}`);
@@ -130,7 +117,7 @@ async function startRole(): Promise<void> {
} }
try { try {
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_start_role", { const snapshot = await invoke<KbDemoWsManagerSnapshotPayload>("demo_ws_manager_start_role", {
role: roleSelect.value, role: roleSelect.value,
}); });
renderSnapshot(snapshot); renderSnapshot(snapshot);
@@ -146,7 +133,7 @@ async function stopRole(): Promise<void> {
} }
try { try {
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_stop_role", { const snapshot = await invoke<KbDemoWsManagerSnapshotPayload>("demo_ws_manager_stop_role", {
role: roleSelect.value, role: roleSelect.value,
}); });
renderSnapshot(snapshot); renderSnapshot(snapshot);
@@ -228,7 +215,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(event.payload); appendLogLine(event.payload);
}); });
await listen<DemoWsManagerSnapshotPayload>("kb-demo-ws-manager-snapshot", (event) => { await listen<KbDemoWsManagerSnapshotPayload>("kb-demo-ws-manager-snapshot", (event) => {
renderSnapshot(event.payload); renderSnapshot(event.payload);
}); });

View File

@@ -1,7 +1,7 @@
{ {
"name": "kb-app", "name": "kb-app",
"private": true, "private": true,
"version": "0.7.22", "version": "0.7.23",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -7,7 +7,8 @@
use tauri::Manager; use tauri::Manager;
/// Request payload for one demo HTTP execution. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoHttpRequest { pub(crate) struct KbDemoHttpRequest {
/// Logical role used to select one endpoint from the pool. /// 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. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoHttpExecutionPayload { pub(crate) struct KbDemoHttpExecutionPayload {
/// Selected endpoint name. /// Selected endpoint name.

View File

@@ -5,7 +5,8 @@
use tauri::Manager; use tauri::Manager;
/// Request payload for one pipeline inspection by signature. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoPipelineInspectRequest { pub(crate) struct KbDemoPipelineInspectRequest {
/// Transaction signature to inspect. /// Transaction signature to inspect.
@@ -15,7 +16,8 @@ pub(crate) struct KbDemoPipelineInspectRequest {
} }
/// Response payload for one pipeline inspection. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoPipelineInspectPayload { pub(crate) struct KbDemoPipelineInspectPayload {
/// Inspected signature. /// Inspected signature.
@@ -47,7 +49,8 @@ pub(crate) struct KbDemoPipelineInspectPayload {
} }
/// Request payload for one pipeline inspection by token mint. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoPipelineInspectTokenRequest { pub(crate) struct KbDemoPipelineInspectTokenRequest {
/// Token mint to inspect. /// Token mint to inspect.
@@ -57,7 +60,8 @@ pub(crate) struct KbDemoPipelineInspectTokenRequest {
} }
/// Request payload for one pipeline inspection by pair id. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoPipelineInspectPairRequest { pub(crate) struct KbDemoPipelineInspectPairRequest {
/// Pair id to inspect. /// Pair id to inspect.
@@ -67,7 +71,8 @@ pub(crate) struct KbDemoPipelineInspectPairRequest {
} }
/// Request payload for one pipeline inspection by pool address. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoPipelineInspectPoolRequest { pub(crate) struct KbDemoPipelineInspectPoolRequest {
/// Pool address to inspect. /// Pool address to inspect.
@@ -76,6 +81,112 @@ pub(crate) struct KbDemoPipelineInspectPoolRequest {
pub custom_timeframe_seconds: std::option::Option<i64>, pub custom_timeframe_seconds: std::option::Option<i64>,
} }
/// 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<std::string::String>,
/// 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<KbDemoPipelineBackfillTokenPayload, std::string::String> {
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. /// Inspects one pair id through the persisted `kb_lib` pipeline state.
#[tauri::command] #[tauri::command]
pub(crate) async fn demo_pipeline_inspect_pair_id( 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 { let pool_id = match pool.id {
Some(pool_id) => pool_id, Some(pool_id) => pool_id,
None => { None => {
return Err(format!( return Err(format!("pool '{}' has no internal id", pool.address));
"pool '{}' has no internal id",
pool.address
));
} }
}; };
let pair_result = kb_lib::get_pair_by_pool_id(database.as_ref(), pool_id).await; let pair_result = kb_lib::get_pair_by_pool_id(database.as_ref(), pool_id).await;

View File

@@ -9,7 +9,8 @@ use tauri::Emitter;
use tauri::Manager; use tauri::Manager;
/// Endpoint summary sent to the demo frontend. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoWsEndpointSummary { pub(crate) struct KbDemoWsEndpointSummary {
name: std::string::String, name: std::string::String,
@@ -20,7 +21,8 @@ pub(crate) struct KbDemoWsEndpointSummary {
} }
/// Current demo window runtime status. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoWsStatusPayload { pub(crate) struct KbDemoWsStatusPayload {
connection_state: std::string::String, connection_state: std::string::String,
@@ -38,7 +40,8 @@ pub(crate) struct KbDemoWsStatusPayload {
} }
/// Subscribe request sent by the demo frontend. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoWsSubscribeRequest { pub(crate) struct KbDemoWsSubscribeRequest {
method: std::string::String, method: std::string::String,

View File

@@ -8,7 +8,8 @@ use tauri::Emitter;
use tauri::Manager; use tauri::Manager;
/// Static endpoint summary enriched with current manager state. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoWsManagerEndpointSummary { pub(crate) struct KbDemoWsManagerEndpointSummary {
name: std::string::String, name: std::string::String,
@@ -20,7 +21,8 @@ pub(crate) struct KbDemoWsManagerEndpointSummary {
} }
/// Global demo manager snapshot payload. /// 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")] #[serde(rename_all = "camelCase")]
pub(crate) struct KbDemoWsManagerSnapshotPayload { pub(crate) struct KbDemoWsManagerSnapshotPayload {
endpoint_count: usize, endpoint_count: usize,

View File

@@ -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_token_mint,
crate::demo_pipeline::demo_pipeline_inspect_pair_id, crate::demo_pipeline::demo_pipeline_inspect_pair_id,
crate::demo_pipeline::demo_pipeline_inspect_pool_address, 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::Wry>()); tauri_builder = tauri_builder.plugin(tracing_builder.build::<tauri::Wry>());
tauri_builder = tauri_builder.setup(|app| { tauri_builder = tauri_builder.setup(|app| {

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "kb-bapp", "productName": "kb-bapp",
"version": "0.7.22", "version": "0.7.23",
"identifier": "com.sasedev.kb-app", "identifier": "com.sasedev.kb-app",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",