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

@@ -108,6 +108,64 @@
Inspecter pool
</button>
</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" />
@@ -126,6 +184,13 @@
<textarea id="demoPipelineSummaryTextarea" class="form-control font-monospace" rows="10" readonly spellcheck="false"></textarea>
</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-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 { 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<DemoHttpExecutionPayload>("demo_http_execute_request", { request });
const response = await invoke<KbDemoHttpExecutionPayload>("demo_http_execute_request", { request });
lastEndpointText.textContent = `${response.endpointName} (${response.endpointUrl})`;
lastProviderText.textContent = response.provider;

View File

@@ -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<HTMLButtonElement>("#demoPipelineInspectPairButton");
const poolAddressInput = document.querySelector<HTMLInputElement>("#demoPipelinePoolAddressInput");
const inspectPoolButton = document.querySelector<HTMLButtonElement>("#demoPipelineInspectPoolButton");
const backfillTokenMintInput = document.querySelector<HTMLInputElement>("#demoPipelineBackfillTokenMintInput");
const backfillHttpRoleInput = document.querySelector<HTMLInputElement>("#demoPipelineBackfillHttpRoleInput");
const backfillMintLimitInput = document.querySelector<HTMLInputElement>("#demoPipelineBackfillMintLimitInput");
const backfillPoolLimitInput = document.querySelector<HTMLInputElement>("#demoPipelineBackfillPoolLimitInput");
const backfillTokenButton = document.querySelector<HTMLButtonElement>("#demoPipelineBackfillTokenButton");
const backfillTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipelineBackfillTextarea");
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<DemoPipelineInspectPayload>("demo_pipeline_inspect_signature", { request });
const payload = await invoke<KbDemoPipelineInspectPayload>("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<DemoPipelineInspectPayload>("demo_pipeline_inspect_token_mint", { request });
const payload = await invoke<KbDemoPipelineInspectPayload>("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<DemoPipelineInspectPayload>("demo_pipeline_inspect_pair_id", { request });
const payload = await invoke<KbDemoPipelineInspectPayload>("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<DemoPipelineInspectPayload>("demo_pipeline_inspect_pool_address", { request });
const payload = await invoke<KbDemoPipelineInspectPayload>("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<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 { 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<DemoWsStatusPayload>("demo-ws-status", (event) => {
unlistenStatusEvent = await listen<KbDemoWsStatusPayload>("demo-ws-status", (event) => {
applyStatusToUi(
event.payload,
statusBadge,
@@ -346,7 +319,7 @@ document.addEventListener("DOMContentLoaded", async () => {
}
try {
const endpoints = await invoke<DemoWsEndpointSummary[]>("demo_ws_list_endpoints");
const endpoints = await invoke<KbDemoWsEndpointSummary[]>("demo_ws_list_endpoints");
endpointSelect.innerHTML = "";
for (const endpoint of endpoints) {
@@ -401,7 +374,7 @@ document.addEventListener("DOMContentLoaded", async () => {
});
try {
const status = await invoke<DemoWsStatusPayload>("demo_ws_get_status");
const status = await invoke<KbDemoWsStatusPayload>("demo_ws_get_status");
applyStatusToUi(
status,
statusBadge,
@@ -426,7 +399,7 @@ document.addEventListener("DOMContentLoaded", async () => {
connectButton.addEventListener("click", async () => {
try {
const status = await invoke<DemoWsStatusPayload>("demo_ws_connect", {
const status = await invoke<KbDemoWsStatusPayload>("demo_ws_connect", {
endpointName: endpointSelect.value,
});
@@ -453,7 +426,7 @@ document.addEventListener("DOMContentLoaded", async () => {
disconnectButton.addEventListener("click", async () => {
try {
const status = await invoke<DemoWsStatusPayload>("demo_ws_disconnect");
const status = await invoke<KbDemoWsStatusPayload>("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(),

View File

@@ -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<HTMLSpanElement>("#demoWsManagerEndpointCountText");
const startedCountText = document.querySelector<HTMLSpanElement>("#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<void> {
try {
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_get_snapshot");
const snapshot = await invoke<KbDemoWsManagerSnapshotPayload>("demo_ws_manager_get_snapshot");
renderSnapshot(snapshot);
appendLogLine("[ui] refreshed manager snapshot");
} catch (error) {
@@ -107,7 +94,7 @@ async function loadRoles(): Promise<void> {
async function startAll(): Promise<void> {
try {
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_start_all");
const snapshot = await invoke<KbDemoWsManagerSnapshotPayload>("demo_ws_manager_start_all");
renderSnapshot(snapshot);
} catch (error) {
appendLogLine(`[ui] start all error: ${String(error)}`);
@@ -116,7 +103,7 @@ async function startAll(): Promise<void> {
async function stopAll(): Promise<void> {
try {
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_stop_all");
const snapshot = await invoke<KbDemoWsManagerSnapshotPayload>("demo_ws_manager_stop_all");
renderSnapshot(snapshot);
} catch (error) {
appendLogLine(`[ui] stop all error: ${String(error)}`);
@@ -130,7 +117,7 @@ async function startRole(): Promise<void> {
}
try {
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_start_role", {
const snapshot = await invoke<KbDemoWsManagerSnapshotPayload>("demo_ws_manager_start_role", {
role: roleSelect.value,
});
renderSnapshot(snapshot);
@@ -146,7 +133,7 @@ async function stopRole(): Promise<void> {
}
try {
const snapshot = await invoke<DemoWsManagerSnapshotPayload>("demo_ws_manager_stop_role", {
const snapshot = await invoke<KbDemoWsManagerSnapshotPayload>("demo_ws_manager_stop_role", {
role: roleSelect.value,
});
renderSnapshot(snapshot);
@@ -228,7 +215,7 @@ document.addEventListener("DOMContentLoaded", async () => {
appendLogLine(event.payload);
});
await listen<DemoWsManagerSnapshotPayload>("kb-demo-ws-manager-snapshot", (event) => {
await listen<KbDemoWsManagerSnapshotPayload>("kb-demo-ws-manager-snapshot", (event) => {
renderSnapshot(event.payload);
});

View File

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

View File

@@ -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.

View File

@@ -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<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.
#[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;

View File

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

View File

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

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_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::Wry>());
tauri_builder = tauri_builder.setup(|app| {

View File

@@ -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",