// file: kb_demo_app/src/demo3.rs //! Tauri commands for Demo3 local DEX corpus search. //! //! Demo3 is intentionally a thin UI-facing wrapper around `kb_lib` local //! corpus search services. It does not contain DEX decoding or protocol logic. use tauri::Manager; use ts_rs::TS; /// Request payload for a local DEX corpus search. #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS)] #[ts( export, export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchRequest.ts" )] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3LocalDexCorpusSearchRequest { /// Optional DEX code or decoded protocol name. pub dex_code: std::option::Option, /// Optional Solana program id. pub program_id: std::option::Option, /// Optional local pair id. #[ts(type = "number | null")] pub pair_id: std::option::Option, /// Optional pool account/address. pub pool_address: std::option::Option, /// Optional token mint to match as base, quote or decoded mint. pub token_mint: std::option::Option, /// Optional transaction signature. pub signature: std::option::Option, /// Maximum number of rows to return per sample category. pub limit: u32, } /// Response payload returned by Demo3 local DEX corpus search. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts( export, export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchPayload.ts" )] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3LocalDexCorpusSearchPayload { /// Open database URL. pub database_url: std::string::String, /// Pretty JSON representation of the search result. pub result_json: std::string::String, /// Structured local DEX corpus search result. pub result: Demo3LocalDexCorpusSearchResult, } /// Structured local DEX corpus search result. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchResult.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3LocalDexCorpusSearchResult { /// Normalized search request applied by the backend service. pub request: Demo3LocalDexCorpusSearchRequest, /// Aggregate counts for the matching local data. pub summary: Demo3LocalDexCorpusSearchSummary, /// Matching transaction samples. pub transaction_samples: std::vec::Vec, /// Matching pool/pair samples. pub pool_pair_samples: std::vec::Vec, /// Matching decoded event samples. pub decoded_event_samples: std::vec::Vec, } /// Aggregate counts for one local DEX corpus search. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts( export, export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchSummary.ts" )] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3LocalDexCorpusSearchSummary { /// Number of distinct matching transactions. #[ts(type = "number")] pub transaction_count: i64, /// Number of distinct matching instructions. #[ts(type = "number")] pub instruction_count: i64, /// Number of distinct matching decoded DEX events. #[ts(type = "number")] pub decoded_event_count: i64, /// Number of distinct matching pools. #[ts(type = "number")] pub pool_count: i64, /// Number of distinct matching pairs. #[ts(type = "number")] pub pair_count: i64, /// Number of distinct matching trade events. #[ts(type = "number")] pub trade_event_count: i64, /// Number of distinct matching candle rows. #[ts(type = "number")] pub pair_candle_count: i64, /// Number of distinct matching protocol candidate rows. #[ts(type = "number")] pub protocol_candidate_count: i64, } /// Matching transaction sample for corpus discovery. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts( export, export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusTransactionSample.ts" )] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3LocalDexCorpusTransactionSample { /// Transaction id. #[ts(type = "number")] pub transaction_id: i64, /// Transaction signature. pub signature: std::string::String, /// Optional Solana slot. #[ts(type = "number | null")] pub slot: std::option::Option, /// Whether the transaction has a non-null error payload. pub failed: bool, /// Number of persisted instructions for the transaction. #[ts(type = "number")] pub instruction_count: i64, /// Number of persisted decoded DEX events for the transaction. #[ts(type = "number")] pub decoded_event_count: i64, /// Number of persisted trade events for the transaction. #[ts(type = "number")] pub trade_event_count: i64, /// Comma-separated distinct program ids seen in the transaction. pub program_ids_csv: std::string::String, } /// Matching pool/pair sample for corpus discovery. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts( export, export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusPoolPairSample.ts" )] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3LocalDexCorpusPoolPairSample { /// Optional pool id. #[ts(type = "number | null")] pub pool_id: std::option::Option, /// Optional pool address. pub pool_address: std::option::Option, /// Optional pair id. #[ts(type = "number | null")] pub pair_id: std::option::Option, /// Optional DEX code. pub dex_code: std::option::Option, /// Optional pair symbol. pub pair_symbol: std::option::Option, /// Optional base token mint. pub base_mint: std::option::Option, /// Optional base token symbol. pub base_symbol: std::option::Option, /// Optional quote token mint. pub quote_mint: std::option::Option, /// Optional quote token symbol. pub quote_symbol: std::option::Option, /// Number of decoded events attached to the pool. #[ts(type = "number")] pub decoded_event_count: i64, /// Number of trade events attached to the pair. #[ts(type = "number")] pub trade_event_count: i64, /// Number of candle rows attached to the pair. #[ts(type = "number")] pub pair_candle_count: i64, /// Latest known founding or activity signature for the pool/pair. pub latest_signature: std::option::Option, } /// Matching decoded event sample for corpus discovery. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts( export, export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusDecodedEventSample.ts" )] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3LocalDexCorpusDecodedEventSample { /// Decoded event id. #[ts(type = "number")] pub decoded_event_id: i64, /// Transaction id. #[ts(type = "number")] pub transaction_id: i64, /// Transaction signature. pub signature: std::string::String, /// Optional Solana slot. #[ts(type = "number | null")] pub slot: std::option::Option, /// Protocol name stored on the decoded event. pub protocol_name: std::string::String, /// Program id stored on the decoded event. pub program_id: std::string::String, /// Event kind. pub event_kind: std::string::String, /// Optional pool account. pub pool_account: std::option::Option, /// Optional token A mint. pub token_a_mint: std::option::Option, /// Optional token B mint. pub token_b_mint: std::option::Option, /// Decoded event category extracted from payload JSON. pub event_category: std::option::Option, /// Decoded event lifecycle kind extracted from payload JSON. pub event_lifecycle_kind: std::option::Option, /// Decoded event actionability extracted from payload JSON. pub event_actionability: std::option::Option, /// Whether the decoded event is a trade candidate. pub trade_candidate: bool, /// Whether the decoded event is a candle candidate. pub candle_candidate: bool, } /// Opens the `Demo3` local DEX corpus search window. #[tauri::command] pub(crate) fn open_demo3_window(app_handle: tauri::AppHandle) -> Result<(), std::string::String> { let existing_window_option = app_handle.get_webview_window("demo3"); let demo_window = match existing_window_option { Some(demo_window) => demo_window, None => { let builder = tauri::WebviewWindowBuilder::new( &app_handle, "demo3", tauri::WebviewUrl::App("demo3.html".into()), ) .title("Demo3 Local DEX Corpus") .inner_size(1480.0, 920.0) .min_inner_size(1100.0, 720.0) .center() .visible(true) .transparent(false) .decorations(true); let build_result = builder.build(); match build_result { Ok(window) => window, Err(error) => { return Err(format!("cannot create demo3 window: {error:?}")); }, } }, }; let show_result = demo_window.show(); if let Err(error) = show_result { return Err(format!("cannot show demo3 window: {error:?}")); } let focus_result = demo_window.set_focus(); if let Err(error) = focus_result { return Err(format!("cannot focus demo3 window: {error:?}")); } Ok(()) } /// Searches local DEX corpus candidates from the persisted SQLite pipeline. #[tauri::command] pub(crate) async fn demo3_search_local_dex_corpus( state: tauri::State<'_, crate::AppState>, request: Demo3LocalDexCorpusSearchRequest, ) -> Result { let database = state.database.clone(); let service = kb_lib::LocalDexCorpusSearchService::new(database.clone()); let search_result = service.search(to_lib_search_request(&request)).await; let lib_result = match search_result { Ok(result) => result, Err(error) => { return Err(format!("local DEX corpus search failed: {}", error)); }, }; let ui_result = from_lib_search_result(lib_result); let result_json_result = serde_json::to_string_pretty(&ui_result); let result_json = match result_json_result { Ok(result_json) => result_json, Err(error) => { return Err(format!("cannot serialize local DEX corpus search result: {}", error)); }, }; Ok(Demo3LocalDexCorpusSearchPayload { database_url: database.database_url().to_string(), result_json, result: ui_result, }) } /// Search request for the static upstream registry exposed through Demo3. #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS)] #[ts( export, export_to = "../frontend/ts/bindings/Demo3UpstreamRegistrySearchRequest.ts" )] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3UpstreamRegistrySearchRequest { /// Optional decoder-code filter. pub decoder_code: std::option::Option, /// Optional program-id filter. pub program_id: std::option::Option, /// Optional program-family filter. pub program_family: std::option::Option, /// Optional surface-kind filter. pub surface_kind: std::option::Option, /// Optional entry-kind filter. pub entry_kind: std::option::Option, /// Optional proof-status filter. pub proof_status: std::option::Option, /// Optional maximum number of entries to return. pub limit: std::option::Option, } /// Payload returned by the Demo3 upstream registry command. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3UpstreamRegistryPayload.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3UpstreamRegistryPayload { /// Pretty JSON representation of the registry result. pub result_json: std::string::String, /// Structured registry result. pub result: Demo3UpstreamRegistryResult, } /// Structured upstream registry result exposed through Demo3. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3UpstreamRegistryResult.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3UpstreamRegistryResult { /// Normalized request used by kb_lib. pub request: Demo3UpstreamRegistrySearchRequest, /// Registry summary. pub summary: Demo3UpstreamRegistrySummary, /// Matching entries. pub entries: std::vec::Vec, } /// Summary of the upstream registry snapshot exposed through Demo3. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3UpstreamRegistrySummary.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3UpstreamRegistrySummary { /// Total static registry entry count before filtering. #[ts(type = "number")] pub total_entry_count: usize, /// Returned entry count after filtering. #[ts(type = "number")] pub returned_entry_count: usize, /// Number of entries that have a program id. #[ts(type = "number")] pub entries_with_program_id_count: usize, /// Number of entries that have a discriminator. #[ts(type = "number")] pub entries_with_discriminator_count: usize, /// Number of program-level seed entries. #[ts(type = "number")] pub program_entry_count: usize, /// Number of instruction entries. #[ts(type = "number")] pub instruction_entry_count: usize, /// Number of event entries. #[ts(type = "number")] pub event_entry_count: usize, /// Number of account entries. #[ts(type = "number")] pub account_entry_count: usize, /// Number of entries still unverified from upstream Git or seed data. #[ts(type = "number")] pub upstream_git_unverified_count: usize, /// Number of entries mapped into decoders but not locally observed. #[ts(type = "number")] pub upstream_git_mapped_unverified_count: usize, /// Number of entries observed in the local corpus. #[ts(type = "number")] pub upstream_git_local_corpus_observed_count: usize, /// Number of entries materialized in local business tables. #[ts(type = "number")] pub upstream_git_local_corpus_materialized_count: usize, /// Number of layout entries still unverified locally. #[ts(type = "number")] pub upstream_git_layout_unverified_count: usize, } /// One upstream registry entry exposed through Demo3. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3UpstreamRegistryEntry.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3UpstreamRegistryEntry { /// Repository name or bootstrap locator that produced the entry. pub source_repo: std::option::Option, /// Repository-relative path or bootstrap path that produced the entry. pub source_path: std::option::Option, /// Stable decoder code used by the registry. pub decoder_code: std::string::String, /// Optional Solana program id when already known by the source entry. pub program_id: std::option::Option, /// Program family used to group related programs. pub program_family: std::string::String, /// Surface kind such as AMM, CLMM, launch, aggregator or core Solana. pub surface_kind: std::string::String, /// Entry kind: instruction, event, account or program. pub entry_kind: std::string::String, /// Source-level entry name. pub entry_name: std::string::String, /// Optional discriminator bytes encoded as lowercase hexadecimal. pub discriminator_hex: std::option::Option, /// Optional discriminator byte length. pub discriminator_len: std::option::Option, /// Current proof status. pub proof_status: std::string::String, /// Notes that preserve uncertainty and validation requirements. pub notes: std::string::String, } /// Searches the static upstream registry from Demo3. #[tauri::command] pub(crate) fn demo3_search_upstream_registry( request: Demo3UpstreamRegistrySearchRequest, ) -> Result { let service = kb_lib::UpstreamRegistryService::new(); let lib_request = to_lib_upstream_registry_request(&request); let lib_result = service.search(&lib_request); let ui_result = from_lib_upstream_registry_result(lib_result); let result_json_result = serde_json::to_string_pretty(&ui_result); let result_json = match result_json_result { Ok(result_json) => result_json, Err(error) => { return Err(format!("cannot serialize upstream registry result: {}", error)); }, }; return Ok(Demo3UpstreamRegistryPayload { result_json, result: ui_result }); } fn to_lib_upstream_registry_request( request: &Demo3UpstreamRegistrySearchRequest, ) -> kb_lib::UpstreamRegistrySearchRequestDto { return kb_lib::UpstreamRegistrySearchRequestDto { decoder_code: normalize_optional_text(request.decoder_code.clone()), program_id: normalize_optional_text(request.program_id.clone()), program_family: normalize_optional_text(request.program_family.clone()), surface_kind: normalize_optional_text(request.surface_kind.clone()), entry_kind: normalize_optional_text(request.entry_kind.clone()), proof_status: normalize_optional_text(request.proof_status.clone()), limit: match request.limit { Some(limit) => Some(limit as usize), None => None, }, }; } fn from_lib_upstream_registry_result( result: kb_lib::UpstreamRegistrySearchResultDto, ) -> Demo3UpstreamRegistryResult { let mut entries = std::vec::Vec::new(); for entry in result.entries { entries.push(from_lib_upstream_registry_entry(entry)); } return Demo3UpstreamRegistryResult { request: from_lib_upstream_registry_request(result.request), summary: from_lib_upstream_registry_summary(result.summary), entries, }; } fn from_lib_upstream_registry_request( request: kb_lib::UpstreamRegistrySearchRequestDto, ) -> Demo3UpstreamRegistrySearchRequest { return Demo3UpstreamRegistrySearchRequest { decoder_code: request.decoder_code, program_id: request.program_id, program_family: request.program_family, surface_kind: request.surface_kind, entry_kind: request.entry_kind, proof_status: request.proof_status, limit: match request.limit { Some(limit) => Some(limit as u32), None => None, }, }; } fn from_lib_upstream_registry_summary( summary: kb_lib::UpstreamRegistrySummaryDto, ) -> Demo3UpstreamRegistrySummary { return Demo3UpstreamRegistrySummary { total_entry_count: summary.total_entry_count, returned_entry_count: summary.returned_entry_count, entries_with_program_id_count: summary.entries_with_program_id_count, entries_with_discriminator_count: summary.entries_with_discriminator_count, program_entry_count: summary.program_entry_count, instruction_entry_count: summary.instruction_entry_count, event_entry_count: summary.event_entry_count, account_entry_count: summary.account_entry_count, upstream_git_unverified_count: summary.upstream_git_unverified_count, upstream_git_mapped_unverified_count: summary.upstream_git_mapped_unverified_count, upstream_git_local_corpus_observed_count: summary.upstream_git_local_corpus_observed_count, upstream_git_local_corpus_materialized_count: summary .upstream_git_local_corpus_materialized_count, upstream_git_layout_unverified_count: summary.upstream_git_layout_unverified_count, }; } fn from_lib_upstream_registry_entry( entry: kb_lib::UpstreamRegistryEntryDto, ) -> Demo3UpstreamRegistryEntry { return Demo3UpstreamRegistryEntry { source_repo: entry.source_repo, source_path: entry.source_path, decoder_code: entry.decoder_code, program_id: entry.program_id, program_family: entry.program_family, surface_kind: entry.surface_kind, entry_kind: entry.entry_kind, entry_name: entry.entry_name, discriminator_hex: entry.discriminator_hex, discriminator_len: entry.discriminator_len, proof_status: entry.proof_status, notes: entry.notes, }; } fn to_lib_search_request( request: &Demo3LocalDexCorpusSearchRequest, ) -> kb_lib::LocalDexCorpusSearchRequestDto { return kb_lib::LocalDexCorpusSearchRequestDto { dex_code: normalize_optional_text(request.dex_code.clone()), program_id: normalize_optional_text(request.program_id.clone()), pair_id: request.pair_id, pool_address: normalize_optional_text(request.pool_address.clone()), token_mint: normalize_optional_text(request.token_mint.clone()), signature: normalize_optional_text(request.signature.clone()), limit: request.limit, }; } fn normalize_optional_text( value: std::option::Option, ) -> std::option::Option { let value = match value { Some(value) => value.trim().to_string(), None => return None, }; if value.is_empty() { return None; } return Some(value); } fn from_lib_search_result( result: kb_lib::LocalDexCorpusSearchResultDto, ) -> Demo3LocalDexCorpusSearchResult { let mut transaction_samples = std::vec::Vec::new(); for sample in result.transaction_samples { transaction_samples.push(from_lib_transaction_sample(sample)); } let mut pool_pair_samples = std::vec::Vec::new(); for sample in result.pool_pair_samples { pool_pair_samples.push(from_lib_pool_pair_sample(sample)); } let mut decoded_event_samples = std::vec::Vec::new(); for sample in result.decoded_event_samples { decoded_event_samples.push(from_lib_decoded_event_sample(sample)); } return Demo3LocalDexCorpusSearchResult { request: from_lib_request(result.request), summary: from_lib_summary(result.summary), transaction_samples, pool_pair_samples, decoded_event_samples, }; } fn from_lib_request( request: kb_lib::LocalDexCorpusSearchRequestDto, ) -> Demo3LocalDexCorpusSearchRequest { return Demo3LocalDexCorpusSearchRequest { dex_code: request.dex_code, program_id: request.program_id, pair_id: request.pair_id, pool_address: request.pool_address, token_mint: request.token_mint, signature: request.signature, limit: request.limit, }; } fn from_lib_summary( summary: kb_lib::LocalDexCorpusSearchSummaryDto, ) -> Demo3LocalDexCorpusSearchSummary { return Demo3LocalDexCorpusSearchSummary { transaction_count: summary.transaction_count, instruction_count: summary.instruction_count, decoded_event_count: summary.decoded_event_count, pool_count: summary.pool_count, pair_count: summary.pair_count, trade_event_count: summary.trade_event_count, pair_candle_count: summary.pair_candle_count, protocol_candidate_count: summary.protocol_candidate_count, }; } fn from_lib_transaction_sample( sample: kb_lib::LocalDexCorpusTransactionSampleDto, ) -> Demo3LocalDexCorpusTransactionSample { return Demo3LocalDexCorpusTransactionSample { transaction_id: sample.transaction_id, signature: sample.signature, slot: sample.slot, failed: sample.failed, instruction_count: sample.instruction_count, decoded_event_count: sample.decoded_event_count, trade_event_count: sample.trade_event_count, program_ids_csv: sample.program_ids_csv, }; } fn from_lib_pool_pair_sample( sample: kb_lib::LocalDexCorpusPoolPairSampleDto, ) -> Demo3LocalDexCorpusPoolPairSample { return Demo3LocalDexCorpusPoolPairSample { pool_id: sample.pool_id, pool_address: sample.pool_address, pair_id: sample.pair_id, dex_code: sample.dex_code, pair_symbol: sample.pair_symbol, base_mint: sample.base_mint, base_symbol: sample.base_symbol, quote_mint: sample.quote_mint, quote_symbol: sample.quote_symbol, decoded_event_count: sample.decoded_event_count, trade_event_count: sample.trade_event_count, pair_candle_count: sample.pair_candle_count, latest_signature: sample.latest_signature, }; } fn from_lib_decoded_event_sample( sample: kb_lib::LocalDexCorpusDecodedEventSampleDto, ) -> Demo3LocalDexCorpusDecodedEventSample { return Demo3LocalDexCorpusDecodedEventSample { decoded_event_id: sample.decoded_event_id, transaction_id: sample.transaction_id, signature: sample.signature, slot: sample.slot, protocol_name: sample.protocol_name, program_id: sample.program_id, event_kind: sample.event_kind, pool_account: sample.pool_account, token_a_mint: sample.token_a_mint, token_b_mint: sample.token_b_mint, event_category: sample.event_category, event_lifecycle_kind: sample.event_lifecycle_kind, event_actionability: sample.event_actionability, trade_candidate: sample.trade_candidate, candle_candidate: sample.candle_candidate, }; } /// Request payload for on-chain DEX pair/pool discovery. #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexDiscoveryRequest.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3OnchainDexDiscoveryRequest { /// Optional DEX code from the support matrix. pub dex_code: std::option::Option, /// Optional Solana program id. When absent, dex_code must resolve to a verified program id. pub program_id: std::option::Option, /// Optional signature source: `program_id` or `address`. pub signature_source: std::option::Option, /// Optional source address used when signature_source is `address`. pub source_address: std::option::Option, /// Optional extra source addresses used for multi-pool discovery. #[serde(default)] pub source_addresses: std::vec::Vec, /// Optional `before` cursor passed to Solana getSignaturesForAddress. #[serde(default)] pub before_signature: std::option::Option, /// Optional `until` cursor passed to Solana getSignaturesForAddress. #[serde(default)] pub until_signature: std::option::Option, /// Maximum number of signature pages to fetch per source address. #[serde(default)] pub max_pages: u32, /// Signature processing order: newest_first or oldest_first. #[serde(default)] pub scan_order: std::option::Option, /// Optional target event family used to find non-swap signatures. pub target_event: std::option::Option, /// Optional instruction name filter, e.g. `withdraw` or `raydium_cpmm.withdraw`. #[serde(default)] pub target_instruction_name: std::option::Option, /// Optional instruction discriminator filter as 16-char lower hex, comma-separated when needed. #[serde(default)] pub target_discriminator_hex: std::option::Option, /// Whether transactions containing swap-like logs should be skipped. pub exclude_swaps: bool, /// Whether failed transactions should be returned as candidates. pub include_failed: bool, /// HTTP role used to query Solana RPC. pub http_role: std::string::String, /// Maximum number of signatures to inspect. pub signature_limit: u32, /// Maximum number of transactions to fetch from the signature list. pub transaction_limit: u32, /// Maximum number of candidate rows to return. pub candidate_limit: u32, } /// Response payload returned by Demo3 on-chain DEX discovery. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexDiscoveryPayload.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3OnchainDexDiscoveryPayload { /// HTTP role used by the request. pub http_role: std::string::String, /// Pretty JSON representation of the discovery result. pub result_json: std::string::String, /// Structured discovery result. pub result: Demo3OnchainDexDiscoveryResult, } /// Structured on-chain DEX discovery result. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexDiscoveryResult.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3OnchainDexDiscoveryResult { /// Normalized request used by kb_lib. pub request: Demo3OnchainDexDiscoveryRequest, /// DEX code resolved from the support matrix when available. pub resolved_dex_code: std::option::Option, /// Program id used to filter matched instructions. pub resolved_program_id: std::string::String, /// Signature source actually used by getSignaturesForAddress. pub resolved_signature_source: std::string::String, /// Address scanned with getSignaturesForAddress. pub resolved_signature_address: std::string::String, /// All addresses scanned with getSignaturesForAddress. pub resolved_signature_addresses: std::vec::Vec, /// Cursor hints by scanned address. pub next_before_by_address: std::vec::Vec, /// Number of signature pages fetched. #[ts(type = "number")] pub fetched_signature_page_count: usize, /// Number of unique fetched signatures after de-duplication. #[ts(type = "number")] pub unique_fetched_signature_count: usize, /// Number of unique candidate signatures. #[ts(type = "number")] pub unique_signature_count: usize, /// Unique signatures ready for signature backfill. pub unique_backfill_signatures: std::vec::Vec, /// Rejected candidate summary. pub rejected_candidate_summary: std::vec::Vec, /// Number of signatures returned by Solana RPC. #[ts(type = "number")] pub fetched_signature_count: usize, /// Number of fetched transactions. #[ts(type = "number")] pub fetched_transaction_count: usize, /// Number of getTransaction calls returning null. #[ts(type = "number")] pub missing_transaction_count: usize, /// Number of failed transactions encountered. #[ts(type = "number")] pub failed_transaction_count: usize, /// Number of failed transactions skipped because include_failed is false. #[ts(type = "number")] pub skipped_failed_transaction_count: usize, /// Number of swap-log transactions skipped by the transaction-level swap guard. #[ts(type = "number")] pub skipped_swap_log_transaction_count: usize, /// Number of candidate rows extracted before target-event filtering. #[ts(type = "number")] pub extracted_candidate_count: usize, /// Number of candidate rows rejected by target-event filtering. #[ts(type = "number")] pub target_rejected_candidate_count: usize, /// Number of candidate rows returned. #[ts(type = "number")] pub candidate_count: usize, /// Candidate on-chain rows. pub candidates: std::vec::Vec, } /// Pagination cursor hint for one scanned source address. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexPaginationCursor.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3OnchainDexPaginationCursor { /// Scanned source address. pub address: std::string::String, /// Signature usable as beforeSignature for the next page window. pub next_before_signature: std::option::Option, /// Raw signature count fetched for this address. #[ts(type = "number")] pub fetched_signature_count: usize, /// Page count fetched for this address. #[ts(type = "number")] pub fetched_page_count: usize, } /// Rejected on-chain discovery candidate summary. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts( export, export_to = "../frontend/ts/bindings/Demo3OnchainDexRejectedCandidateSummary.ts" )] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3OnchainDexRejectedCandidateSummary { /// Candidate kind rejected by target filtering. pub candidate_kind: std::string::String, /// Optional instruction data prefix. pub instruction_data_prefix: std::option::Option, /// Optional instruction name. pub instruction_name: std::option::Option, /// Rejection reason. pub rejection_reason: std::string::String, /// Count of matching rejected candidates. #[ts(type = "number")] pub count: usize, } /// Candidate on-chain transaction/instruction for a DEX program id. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexPairCandidate.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3OnchainDexPairCandidate { /// Transaction signature. pub signature: std::string::String, /// Slot when available. #[ts(type = "number | null")] pub slot: std::option::Option, /// Block time when available. #[ts(type = "number | null")] pub block_time: std::option::Option, /// Whether the transaction failed. pub failed: bool, /// Program id matched by the candidate. pub program_id: std::string::String, /// DEX code when known. pub dex_code: std::option::Option, /// Candidate kind inferred from data/logs. pub candidate_kind: std::string::String, /// Extraction confidence. pub confidence: std::string::String, /// Top-level instruction index. #[ts(type = "number | null")] pub instruction_index: std::option::Option, /// Inner instruction index. #[ts(type = "number | null")] pub inner_instruction_index: std::option::Option, /// Instruction name inferred from data/logs. pub instruction_name: std::option::Option, /// Prefix of the raw base58 instruction data, useful for audit grouping. pub instruction_data_prefix: std::option::Option, /// First eight instruction-data bytes as lower hex. pub instruction_discriminator_hex: std::option::Option, /// Candidate pool address. pub pool_address: std::option::Option, /// Candidate token A mint. pub token_a_mint: std::option::Option, /// Candidate token B mint. pub token_b_mint: std::option::Option, /// Verified pool address when a DEX decoder or stable layout proves it. pub verified_pool_address: std::option::Option, /// Token mints observed generically from transaction token balances. pub observed_token_mints: std::vec::Vec, /// Token balance deltas observed through transaction metadata. pub token_balance_deltas: std::vec::Vec, /// Program-owned or writable accounts that may be pool/config/state accounts. pub candidate_pool_accounts: std::vec::Vec, /// Token accounts that may be pool vaults. pub candidate_token_vault_accounts: std::vec::Vec, /// Other candidate accounts attached to the matched instruction. pub candidate_program_accounts: std::vec::Vec, /// Short account sample. pub account_samples: std::vec::Vec, /// Short log sample. pub log_samples: std::vec::Vec, /// Suggested next action. pub backfill_hint: std::string::String, } /// Token-balance delta observed in one on-chain candidate transaction. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts( export, export_to = "../frontend/ts/bindings/Demo3OnchainDexTokenBalanceDelta.ts" )] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3OnchainDexTokenBalanceDelta { /// Token account index in the transaction message when available. #[ts(type = "number | null")] pub account_index: std::option::Option, /// Token account address resolved from the transaction account keys. pub account_address: std::option::Option, /// SPL Token or Token-2022 mint address. pub mint: std::string::String, /// Token account owner when Solana RPC exposes it. pub owner: std::option::Option, /// Token program id when Solana RPC exposes it. pub token_program: std::option::Option, /// Raw token amount before the transaction. pub pre_amount_raw: std::option::Option, /// Raw token amount after the transaction. pub post_amount_raw: std::option::Option, /// Signed raw delta when calculable. pub delta_raw: std::option::Option, } /// Candidate account inferred from generic on-chain transaction evidence. #[derive(Clone, Debug, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexCandidateAccount.ts")] #[serde(rename_all = "camelCase")] pub(crate) struct Demo3OnchainDexCandidateAccount { /// Account address. pub address: std::string::String, /// Account index in the transaction message when known. #[ts(type = "number | null")] pub account_index: std::option::Option, /// Whether the account is writable in the transaction message when known. pub writable: std::option::Option, /// Whether the account is a signer in the transaction message when known. pub signer: std::option::Option, /// Generic role inferred by Demo3. pub inferred_role: std::string::String, /// Confidence of the generic account inference. pub confidence: std::string::String, /// Short reason explaining why the account is listed. pub reason: std::string::String, } /// Discovers candidate DEX pairs/pools directly from Solana RPC. #[tauri::command] pub(crate) async fn demo3_discover_onchain_dex_pairs( state: tauri::State<'_, crate::AppState>, request: Demo3OnchainDexDiscoveryRequest, ) -> Result { let http_pool = std::sync::Arc::new(state.http_pool.clone()); let service = kb_lib::OnchainDexPairDiscoveryService::new(http_pool); let lib_request = to_lib_onchain_request(&request); let discover_result = service.discover(lib_request).await; let lib_result = match discover_result { Ok(result) => result, Err(error) => { return Err(format!("on-chain DEX discovery failed: {}", error)); }, }; let ui_result = from_lib_onchain_result(lib_result); let result_json_result = serde_json::to_string_pretty(&ui_result); let result_json = match result_json_result { Ok(result_json) => result_json, Err(error) => { return Err(format!("cannot serialize on-chain DEX discovery result: {}", error)); }, }; Ok(Demo3OnchainDexDiscoveryPayload { http_role: ui_result.request.http_role.clone(), result_json, result: ui_result, }) } fn to_lib_onchain_request( request: &Demo3OnchainDexDiscoveryRequest, ) -> kb_lib::OnchainDexPairDiscoveryRequestDto { return kb_lib::OnchainDexPairDiscoveryRequestDto { dex_code: normalize_optional_text(request.dex_code.clone()), program_id: normalize_optional_text(request.program_id.clone()), signature_source: normalize_optional_text(request.signature_source.clone()), source_address: normalize_optional_text(request.source_address.clone()), source_addresses: request.source_addresses.clone(), before_signature: normalize_optional_text(request.before_signature.clone()), until_signature: normalize_optional_text(request.until_signature.clone()), max_pages: request.max_pages, scan_order: normalize_optional_text(request.scan_order.clone()), target_event: normalize_optional_text(request.target_event.clone()), target_instruction_name: normalize_optional_text(request.target_instruction_name.clone()), target_discriminator_hex: normalize_optional_text(request.target_discriminator_hex.clone()), exclude_swaps: request.exclude_swaps, include_failed: request.include_failed, http_role: request.http_role.trim().to_string(), signature_limit: request.signature_limit, transaction_limit: request.transaction_limit, candidate_limit: request.candidate_limit, }; } fn from_lib_onchain_result( result: kb_lib::OnchainDexPairDiscoveryResultDto, ) -> Demo3OnchainDexDiscoveryResult { let mut candidates = std::vec::Vec::new(); for candidate in result.candidates { candidates.push(from_lib_onchain_candidate(candidate)); } return Demo3OnchainDexDiscoveryResult { request: Demo3OnchainDexDiscoveryRequest { dex_code: result.request.dex_code, program_id: result.request.program_id, signature_source: result.request.signature_source, source_address: result.request.source_address, source_addresses: result.request.source_addresses, before_signature: result.request.before_signature, until_signature: result.request.until_signature, max_pages: result.request.max_pages, scan_order: result.request.scan_order, target_event: result.request.target_event, target_instruction_name: result.request.target_instruction_name, target_discriminator_hex: result.request.target_discriminator_hex, exclude_swaps: result.request.exclude_swaps, include_failed: result.request.include_failed, http_role: result.request.http_role, signature_limit: result.request.signature_limit, transaction_limit: result.request.transaction_limit, candidate_limit: result.request.candidate_limit, }, resolved_dex_code: result.resolved_dex_code, resolved_program_id: result.resolved_program_id, resolved_signature_source: result.resolved_signature_source, resolved_signature_address: result.resolved_signature_address, resolved_signature_addresses: result.resolved_signature_addresses, next_before_by_address: from_lib_onchain_pagination_cursors(result.next_before_by_address), fetched_signature_page_count: result.fetched_signature_page_count, unique_fetched_signature_count: result.unique_fetched_signature_count, unique_signature_count: result.unique_signature_count, unique_backfill_signatures: result.unique_backfill_signatures, rejected_candidate_summary: from_lib_rejected_candidate_summary( result.rejected_candidate_summary, ), fetched_signature_count: result.fetched_signature_count, fetched_transaction_count: result.fetched_transaction_count, missing_transaction_count: result.missing_transaction_count, failed_transaction_count: result.failed_transaction_count, skipped_failed_transaction_count: result.skipped_failed_transaction_count, skipped_swap_log_transaction_count: result.skipped_swap_log_transaction_count, extracted_candidate_count: result.extracted_candidate_count, target_rejected_candidate_count: result.target_rejected_candidate_count, candidate_count: result.candidate_count, candidates, }; } fn from_lib_onchain_pagination_cursors( values: std::vec::Vec, ) -> std::vec::Vec { let mut mapped = std::vec::Vec::new(); for value in values { mapped.push(Demo3OnchainDexPaginationCursor { address: value.address, next_before_signature: value.next_before_signature, fetched_signature_count: value.fetched_signature_count, fetched_page_count: value.fetched_page_count, }); } return mapped; } fn from_lib_rejected_candidate_summary( values: std::vec::Vec, ) -> std::vec::Vec { let mut mapped = std::vec::Vec::new(); for value in values { mapped.push(Demo3OnchainDexRejectedCandidateSummary { candidate_kind: value.candidate_kind, instruction_data_prefix: value.instruction_data_prefix, instruction_name: value.instruction_name, rejection_reason: value.rejection_reason, count: value.count, }); } return mapped; } fn from_lib_onchain_candidate( candidate: kb_lib::OnchainDexPairCandidateDto, ) -> Demo3OnchainDexPairCandidate { return Demo3OnchainDexPairCandidate { signature: candidate.signature, slot: candidate.slot, block_time: candidate.block_time, failed: candidate.failed, program_id: candidate.program_id, dex_code: candidate.dex_code, candidate_kind: candidate.candidate_kind, confidence: candidate.confidence, instruction_index: candidate.instruction_index, inner_instruction_index: candidate.inner_instruction_index, instruction_name: candidate.instruction_name, instruction_data_prefix: candidate.instruction_data_prefix, instruction_discriminator_hex: candidate.instruction_discriminator_hex, pool_address: candidate.pool_address, token_a_mint: candidate.token_a_mint, token_b_mint: candidate.token_b_mint, verified_pool_address: candidate.verified_pool_address, observed_token_mints: candidate.observed_token_mints, token_balance_deltas: from_lib_onchain_token_balance_deltas(candidate.token_balance_deltas), candidate_pool_accounts: from_lib_onchain_candidate_accounts( candidate.candidate_pool_accounts, ), candidate_token_vault_accounts: from_lib_onchain_candidate_accounts( candidate.candidate_token_vault_accounts, ), candidate_program_accounts: from_lib_onchain_candidate_accounts( candidate.candidate_program_accounts, ), account_samples: candidate.account_samples, log_samples: candidate.log_samples, backfill_hint: candidate.backfill_hint, }; } fn from_lib_onchain_token_balance_deltas( values: std::vec::Vec, ) -> std::vec::Vec { let mut mapped = std::vec::Vec::new(); for value in values { mapped.push(Demo3OnchainDexTokenBalanceDelta { account_index: value.account_index, account_address: value.account_address, mint: value.mint, owner: value.owner, token_program: value.token_program, pre_amount_raw: value.pre_amount_raw, post_amount_raw: value.post_amount_raw, delta_raw: value.delta_raw, }); } return mapped; } fn from_lib_onchain_candidate_accounts( values: std::vec::Vec, ) -> std::vec::Vec { let mut mapped = std::vec::Vec::new(); for value in values { mapped.push(Demo3OnchainDexCandidateAccount { address: value.address, account_index: value.account_index, writable: value.writable, signer: value.signer, inferred_role: value.inferred_role, confidence: value.confidence, reason: value.reason, }); } return mapped; }