// 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, }) } 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, /// 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 scanned with getSignaturesForAddress. pub resolved_program_id: std::string::String, /// 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 candidate rows returned. #[ts(type = "number")] pub candidate_count: usize, /// Candidate on-chain rows. pub candidates: std::vec::Vec, } /// 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, /// 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()), 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, 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, 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, candidate_count: result.candidate_count, candidates, }; } 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, 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; }