0.7.26
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Local decoded-event diagnostics summary for the UI.
|
||||
*/
|
||||
export type KbDemoPipeline2LocalDecodedEventDiagnosticSummary = {
|
||||
/**
|
||||
* Protocol name.
|
||||
*/
|
||||
protocolName: string,
|
||||
/**
|
||||
* Event kind.
|
||||
*/
|
||||
eventKind: string,
|
||||
/**
|
||||
* Event category.
|
||||
*/
|
||||
eventCategory: string | null,
|
||||
/**
|
||||
* Trade candidate flag.
|
||||
*/
|
||||
tradeCandidate: boolean | null,
|
||||
/**
|
||||
* Candle candidate flag.
|
||||
*/
|
||||
candleCandidate: boolean | null,
|
||||
/**
|
||||
* Event count.
|
||||
*/
|
||||
eventCount: number,
|
||||
/**
|
||||
* Linked trade-event count.
|
||||
*/
|
||||
tradeEventCount: number, };
|
||||
@@ -0,0 +1,38 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Local DEX diagnostics summary for the UI.
|
||||
*/
|
||||
export type KbDemoPipeline2LocalDexDiagnosticSummary = {
|
||||
/**
|
||||
* DEX code.
|
||||
*/
|
||||
dexCode: string,
|
||||
/**
|
||||
* Pool count.
|
||||
*/
|
||||
poolCount: number,
|
||||
/**
|
||||
* Pair count.
|
||||
*/
|
||||
pairCount: number,
|
||||
/**
|
||||
* Decoded event count.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Decoded trade candidate count.
|
||||
*/
|
||||
decodedTradeCandidateCount: number,
|
||||
/**
|
||||
* Decoded candle candidate count.
|
||||
*/
|
||||
decodedCandleCandidateCount: number,
|
||||
/**
|
||||
* Trade event count.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Pair candle count.
|
||||
*/
|
||||
pairCandleCount: number, };
|
||||
@@ -0,0 +1,19 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { KbDemoPipeline2LocalPipelineDiagnosticSummary } from "./KbDemoPipeline2LocalPipelineDiagnosticSummary";
|
||||
|
||||
/**
|
||||
* Local diagnostics payload returned to the UI.
|
||||
*/
|
||||
export type KbDemoPipeline2LocalDiagnosticsPayload = {
|
||||
/**
|
||||
* Open database URL.
|
||||
*/
|
||||
databaseUrl: string,
|
||||
/**
|
||||
* Pretty JSON diagnostics summary.
|
||||
*/
|
||||
summaryJson: string,
|
||||
/**
|
||||
* Structured diagnostics summary.
|
||||
*/
|
||||
summary: KbDemoPipeline2LocalPipelineDiagnosticSummary, };
|
||||
@@ -0,0 +1,34 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Local duplicate decoded-event trade diagnostic sample for the UI.
|
||||
*/
|
||||
export type KbDemoPipeline2LocalDuplicateDecodedEventTradeDiagnosticSample = {
|
||||
/**
|
||||
* Decoded event id.
|
||||
*/
|
||||
decodedEventId: number,
|
||||
/**
|
||||
* Protocol name.
|
||||
*/
|
||||
protocolName: string | null,
|
||||
/**
|
||||
* Event kind.
|
||||
*/
|
||||
eventKind: string | null,
|
||||
/**
|
||||
* Pool account.
|
||||
*/
|
||||
poolAccount: string | null,
|
||||
/**
|
||||
* Trade event count.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Trade event ids.
|
||||
*/
|
||||
tradeEventIds: string | null,
|
||||
/**
|
||||
* Signatures.
|
||||
*/
|
||||
signatures: string | null, };
|
||||
@@ -0,0 +1,50 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Local missing-trade-event diagnostic sample for the UI.
|
||||
*/
|
||||
export type KbDemoPipeline2LocalMissingTradeEventDiagnosticSample = {
|
||||
/**
|
||||
* Decoded event id.
|
||||
*/
|
||||
decodedEventId: number,
|
||||
/**
|
||||
* Chain transaction id.
|
||||
*/
|
||||
transactionId: number | null,
|
||||
/**
|
||||
* Transaction signature.
|
||||
*/
|
||||
signature: string | null,
|
||||
/**
|
||||
* Protocol name.
|
||||
*/
|
||||
protocolName: string,
|
||||
/**
|
||||
* Event kind.
|
||||
*/
|
||||
eventKind: string,
|
||||
/**
|
||||
* Pool account.
|
||||
*/
|
||||
poolAccount: string | null,
|
||||
/**
|
||||
* Whether the source transaction failed.
|
||||
*/
|
||||
transactionFailed: boolean,
|
||||
/**
|
||||
* Diagnostic reason explaining why no trade event was linked.
|
||||
*/
|
||||
reason: string,
|
||||
/**
|
||||
* Whether payload has an explicit base amount.
|
||||
*/
|
||||
hasBaseAmountPayload: boolean,
|
||||
/**
|
||||
* Whether payload has an explicit quote amount.
|
||||
*/
|
||||
hasQuoteAmountPayload: boolean,
|
||||
/**
|
||||
* Whether payload has an explicit price.
|
||||
*/
|
||||
hasPricePayload: boolean, };
|
||||
@@ -0,0 +1,38 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Local multi-trade signature/pair diagnostic sample for the UI.
|
||||
*/
|
||||
export type KbDemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample = {
|
||||
/**
|
||||
* Transaction signature.
|
||||
*/
|
||||
signature: string,
|
||||
/**
|
||||
* Pair id.
|
||||
*/
|
||||
pairId: number,
|
||||
/**
|
||||
* Pool address.
|
||||
*/
|
||||
poolAddress: string | null,
|
||||
/**
|
||||
* DEX code.
|
||||
*/
|
||||
dexCode: string | null,
|
||||
/**
|
||||
* Trade event count.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Distinct decoded event count.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Trade event ids.
|
||||
*/
|
||||
tradeEventIds: string | null,
|
||||
/**
|
||||
* Decoded event ids.
|
||||
*/
|
||||
decodedEventIds: string | null, };
|
||||
@@ -0,0 +1,66 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Local pair diagnostics summary for the UI.
|
||||
*/
|
||||
export type KbDemoPipeline2LocalPairDiagnosticSummary = {
|
||||
/**
|
||||
* Pair id.
|
||||
*/
|
||||
pairId: number,
|
||||
/**
|
||||
* Pool address.
|
||||
*/
|
||||
poolAddress: string,
|
||||
/**
|
||||
* DEX code.
|
||||
*/
|
||||
dexCode: string,
|
||||
/**
|
||||
* Base mint.
|
||||
*/
|
||||
baseMint: string,
|
||||
/**
|
||||
* Base symbol.
|
||||
*/
|
||||
baseSymbol: string | null,
|
||||
/**
|
||||
* Quote mint.
|
||||
*/
|
||||
quoteMint: string,
|
||||
/**
|
||||
* Quote symbol.
|
||||
*/
|
||||
quoteSymbol: string | null,
|
||||
/**
|
||||
* Pair symbol.
|
||||
*/
|
||||
pairSymbol: string | null,
|
||||
/**
|
||||
* Decoded event count.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Decoded trade candidate count.
|
||||
*/
|
||||
decodedTradeCandidateCount: number,
|
||||
/**
|
||||
* Decoded candle candidate count.
|
||||
*/
|
||||
decodedCandleCandidateCount: number,
|
||||
/**
|
||||
* Trade event count.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Invalid trade event count.
|
||||
*/
|
||||
invalidTradeEventCount: number,
|
||||
/**
|
||||
* Pair candle count.
|
||||
*/
|
||||
pairCandleCount: number,
|
||||
/**
|
||||
* Last known price.
|
||||
*/
|
||||
lastPriceQuotePerBase: number | null, };
|
||||
@@ -0,0 +1,54 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Local pair gap diagnostic sample for the UI.
|
||||
*/
|
||||
export type KbDemoPipeline2LocalPairGapDiagnosticSample = {
|
||||
/**
|
||||
* Pair id.
|
||||
*/
|
||||
pairId: number,
|
||||
/**
|
||||
* Pool address.
|
||||
*/
|
||||
poolAddress: string,
|
||||
/**
|
||||
* DEX code.
|
||||
*/
|
||||
dexCode: string,
|
||||
/**
|
||||
* Base mint.
|
||||
*/
|
||||
baseMint: string,
|
||||
/**
|
||||
* Base symbol.
|
||||
*/
|
||||
baseSymbol: string | null,
|
||||
/**
|
||||
* Quote mint.
|
||||
*/
|
||||
quoteMint: string,
|
||||
/**
|
||||
* Quote symbol.
|
||||
*/
|
||||
quoteSymbol: string | null,
|
||||
/**
|
||||
* Pair symbol.
|
||||
*/
|
||||
pairSymbol: string | null,
|
||||
/**
|
||||
* Decoded event count.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Decoded trade candidate count.
|
||||
*/
|
||||
decodedTradeCandidateCount: number,
|
||||
/**
|
||||
* Trade event count.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Pair candle count.
|
||||
*/
|
||||
pairCandleCount: number, };
|
||||
@@ -0,0 +1,150 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { KbDemoPipeline2LocalDecodedEventDiagnosticSummary } from "./KbDemoPipeline2LocalDecodedEventDiagnosticSummary";
|
||||
import type { KbDemoPipeline2LocalDexDiagnosticSummary } from "./KbDemoPipeline2LocalDexDiagnosticSummary";
|
||||
import type { KbDemoPipeline2LocalDuplicateDecodedEventTradeDiagnosticSample } from "./KbDemoPipeline2LocalDuplicateDecodedEventTradeDiagnosticSample";
|
||||
import type { KbDemoPipeline2LocalMissingTradeEventDiagnosticSample } from "./KbDemoPipeline2LocalMissingTradeEventDiagnosticSample";
|
||||
import type { KbDemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample } from "./KbDemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample";
|
||||
import type { KbDemoPipeline2LocalPairDiagnosticSummary } from "./KbDemoPipeline2LocalPairDiagnosticSummary";
|
||||
import type { KbDemoPipeline2LocalPairGapDiagnosticSample } from "./KbDemoPipeline2LocalPairGapDiagnosticSample";
|
||||
|
||||
/**
|
||||
* Local pipeline diagnostics summary for the UI.
|
||||
*/
|
||||
export type KbDemoPipeline2LocalPipelineDiagnosticSummary = {
|
||||
/**
|
||||
* Total persisted chain transactions.
|
||||
*/
|
||||
transactionCount: number,
|
||||
/**
|
||||
* Total successful chain transactions.
|
||||
*/
|
||||
okTransactionCount: number,
|
||||
/**
|
||||
* Total failed chain transactions.
|
||||
*/
|
||||
failedTransactionCount: number,
|
||||
/**
|
||||
* Total decoded DEX events.
|
||||
*/
|
||||
decodedEventCount: number,
|
||||
/**
|
||||
* Total decoded DEX trade candidates.
|
||||
*/
|
||||
decodedTradeCandidateCount: number,
|
||||
/**
|
||||
* Total decoded DEX candle candidates.
|
||||
*/
|
||||
decodedCandleCandidateCount: number,
|
||||
/**
|
||||
* Whether the local persisted pipeline has no blocking diagnostic issue.
|
||||
*/
|
||||
diagnosticsClean: boolean,
|
||||
/**
|
||||
* Number of blocking diagnostic issues.
|
||||
*/
|
||||
blockingIssueCount: number,
|
||||
/**
|
||||
* Total trade candidates without trade event, including ignored failed transactions.
|
||||
*/
|
||||
missingTradeEventCount: number,
|
||||
/**
|
||||
* Explicit alias for decoded trade candidates without linked trade event.
|
||||
*/
|
||||
decodedTradeCandidateWithoutTradeEventCount: number,
|
||||
/**
|
||||
* Trade candidates without linked trade event on successful transactions.
|
||||
*/
|
||||
decodedTradeCandidateWithoutTradeEventOnOkTransactionCount: number,
|
||||
/**
|
||||
* Trade candidates without linked trade event on failed transactions.
|
||||
*/
|
||||
decodedTradeCandidateWithoutTradeEventOnFailedTransactionCount: number,
|
||||
/**
|
||||
* Trade candidates without linked trade event and without explicit base/quote payload amounts.
|
||||
* Actionable missing trade events on successful transactions.
|
||||
*/
|
||||
actionableMissingTradeEventCount: number,
|
||||
/**
|
||||
* Ignored missing trade events caused by failed transactions.
|
||||
*/
|
||||
ignoredFailedTransactionTradeCandidateCount: number, decodedTradeCandidateWithoutAmountPayloadCount: number,
|
||||
/**
|
||||
* Total persisted trade events.
|
||||
*/
|
||||
tradeEventCount: number,
|
||||
/**
|
||||
* Total invalid trade events.
|
||||
*/
|
||||
invalidTradeEventCount: number,
|
||||
/**
|
||||
* Total persisted pair candles.
|
||||
*/
|
||||
pairCandleCount: number,
|
||||
/**
|
||||
* Real duplicate trade rows grouped by decoded event id.
|
||||
*/
|
||||
duplicateDecodedEventTradeCount: number,
|
||||
/**
|
||||
* Multi-trade groups sharing the same signature and pair id.
|
||||
*/
|
||||
multiTradeSignaturePairCount: number,
|
||||
/**
|
||||
* Total duplicate candle buckets.
|
||||
*/
|
||||
duplicateCandleBucketCount: number,
|
||||
/**
|
||||
* Total known tokens.
|
||||
*/
|
||||
tokenCount: number,
|
||||
/**
|
||||
* Total tokens missing symbol or name.
|
||||
*/
|
||||
tokenMetadataMissingCount: number,
|
||||
/**
|
||||
* Total known pools.
|
||||
*/
|
||||
poolCount: number,
|
||||
/**
|
||||
* Total known pairs.
|
||||
*/
|
||||
pairCount: number,
|
||||
/**
|
||||
* Total pairs without trade.
|
||||
*/
|
||||
pairWithoutTradeCount: number,
|
||||
/**
|
||||
* Total pairs without candle.
|
||||
*/
|
||||
pairWithoutCandleCount: number,
|
||||
/**
|
||||
* Diagnostics grouped by DEX.
|
||||
*/
|
||||
dexSummaries: Array<KbDemoPipeline2LocalDexDiagnosticSummary>,
|
||||
/**
|
||||
* Diagnostics grouped by pair.
|
||||
*/
|
||||
pairSummaries: Array<KbDemoPipeline2LocalPairDiagnosticSummary>,
|
||||
/**
|
||||
* Diagnostics grouped by decoded event kind.
|
||||
*/
|
||||
decodedEventSummaries: Array<KbDemoPipeline2LocalDecodedEventDiagnosticSummary>,
|
||||
/**
|
||||
* Samples of decoded trade candidates without linked trade event.
|
||||
*/
|
||||
missingTradeEventSamples: Array<KbDemoPipeline2LocalMissingTradeEventDiagnosticSample>,
|
||||
/**
|
||||
* Samples of duplicated trade rows by decoded event id.
|
||||
*/
|
||||
duplicateDecodedEventTradeSamples: Array<KbDemoPipeline2LocalDuplicateDecodedEventTradeDiagnosticSample>,
|
||||
/**
|
||||
* Samples of multi-trade signature/pair groups.
|
||||
*/
|
||||
multiTradeSignaturePairSamples: Array<KbDemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample>,
|
||||
/**
|
||||
* Samples of pairs without trade.
|
||||
*/
|
||||
pairWithoutTradeSamples: Array<KbDemoPipeline2LocalPairGapDiagnosticSample>,
|
||||
/**
|
||||
* Samples of pairs without candle.
|
||||
*/
|
||||
pairWithoutCandleSamples: Array<KbDemoPipeline2LocalPairGapDiagnosticSample>, };
|
||||
@@ -13,6 +13,7 @@ import type { KbDemoPipeline2BackfillPoolRequest } from "./bindings/KbDemoPipeli
|
||||
import type { KbDemoPipeline2BackfillPayload } from "./bindings/KbDemoPipeline2BackfillPayload.ts";
|
||||
import type { KbDemoPipeline2PairCandlesRequest } from "./bindings/KbDemoPipeline2PairCandlesRequest.ts";
|
||||
import type { KbDemoPipeline2PairCandlesPayload } from "./bindings/KbDemoPipeline2PairCandlesPayload.ts";
|
||||
import type { KbDemoPipeline2LocalDiagnosticsPayload } from "./bindings/KbDemoPipeline2LocalDiagnosticsPayload.ts";
|
||||
|
||||
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
|
||||
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
@@ -50,8 +51,8 @@ interface KbLocalPipelineReplayResult {
|
||||
decodedEventCount: number;
|
||||
detectionCount: number;
|
||||
tradeEventCount: number;
|
||||
pairCandleCount: number;
|
||||
analyticSignalCount: number;
|
||||
pairCandleUpsertCount: number;
|
||||
analyticSignalUpsertCount: number;
|
||||
tokenMetadataUpdatedCount: number;
|
||||
pairSymbolUpdatedCount: number;
|
||||
globalErrorCount: number;
|
||||
@@ -349,6 +350,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
const replayMetadataCheckbox = document.querySelector<HTMLInputElement>("#demoPipeline2ReplayMetadataCheckbox");
|
||||
const replayMetadataLimitInput = document.querySelector<HTMLInputElement>("#demoPipeline2ReplayMetadataLimitInput");
|
||||
const replayLocalPipelineButton = document.querySelector<HTMLButtonElement>("#demoPipeline2ReplayLocalPipelineButton");
|
||||
const diagnoseLocalPipelineButton = document.querySelector<HTMLButtonElement>("#demoPipeline2DiagnoseLocalPipelineButton");
|
||||
|
||||
const pairSelect = document.querySelector<HTMLSelectElement>("#demoPipeline2PairSelect");
|
||||
const timeframeSelect = document.querySelector<HTMLSelectElement>("#demoPipeline2TimeframeSelect");
|
||||
@@ -359,6 +361,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
const backfillSummaryTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipeline2BackfillSummaryTextarea");
|
||||
const chartElement = document.querySelector<HTMLDivElement>("#demoPipeline2Chart");
|
||||
const chartMeta = document.querySelector<HTMLDivElement>("#demoPipeline2ChartMeta");
|
||||
const localDiagnosticsTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipeline2LocalDiagnosticsTextarea");
|
||||
|
||||
const clearLogButton = document.querySelector<HTMLButtonElement>("#demoPipeline2ClearLogButton");
|
||||
const logTextarea = document.querySelector<HTMLTextAreaElement>("#demoPipeline2LogTextarea");
|
||||
@@ -380,12 +383,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
!replayMetadataCheckbox ||
|
||||
!replayMetadataLimitInput ||
|
||||
!replayLocalPipelineButton ||
|
||||
!diagnoseLocalPipelineButton ||
|
||||
!pairSelect ||
|
||||
!timeframeSelect ||
|
||||
!customTimeframeInput ||
|
||||
!preferMaterializedInput ||
|
||||
!loadCandlesButton ||
|
||||
!backfillSummaryTextarea ||
|
||||
!localDiagnosticsTextarea ||
|
||||
!chartElement ||
|
||||
!chartMeta ||
|
||||
!clearLogButton ||
|
||||
@@ -405,6 +410,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
const safeChartMeta = chartMeta;
|
||||
|
||||
const safeLogTextarea = logTextarea;
|
||||
const safeLocalDiagnosticsTextarea = localDiagnosticsTextarea;
|
||||
|
||||
const chart = echarts.init(safeChartElement);
|
||||
setEmptyChart(chart, safeChartMeta, "Aucune candle disponible.");
|
||||
@@ -581,7 +587,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
|
||||
appendLogLine(
|
||||
logTextarea,
|
||||
`[ui] local pipeline replay completed: ${result.replayedTransactionCount.toString()} replayed, ${result.tradeEventCount.toString()} trades, ${result.pairCandleCount.toString()} candles`,
|
||||
`[ui] local pipeline replay completed: ${result.replayedTransactionCount.toString()} replayed, ${result.tradeEventCount.toString()} trades, ${result.pairCandleUpsertCount.toString()} candle upserts`,
|
||||
);
|
||||
|
||||
await refreshCatalog();
|
||||
@@ -590,6 +596,29 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
diagnoseLocalPipelineButton.addEventListener("click", async () => {
|
||||
appendLogLine(logTextarea, "[ui] diagnosing local pipeline");
|
||||
|
||||
diagnoseLocalPipelineButton.disabled = true;
|
||||
|
||||
try {
|
||||
const payload = await invoke<KbDemoPipeline2LocalDiagnosticsPayload>(
|
||||
"demo_pipeline2_diagnose_local_pipeline",
|
||||
);
|
||||
|
||||
safeLocalDiagnosticsTextarea.value = payload.summaryJson;
|
||||
|
||||
appendLogLine(
|
||||
logTextarea,
|
||||
`[ui] local pipeline diagnostics completed: ${payload.summary.decodedEventCount.toString()} decoded, ${payload.summary.tradeEventCount.toString()} trades, ${payload.summary.pairCandleCount.toString()} candles`,
|
||||
);
|
||||
} catch (error) {
|
||||
appendLogLine(logTextarea, `[ui] local pipeline diagnostics error: ${String(error)}`);
|
||||
} finally {
|
||||
diagnoseLocalPipelineButton.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
loadCandlesButton.addEventListener("click", async () => {
|
||||
const pairIdText = pairSelect.value.trim();
|
||||
if (pairIdText === "") {
|
||||
|
||||
Reference in New Issue
Block a user