Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion src/app/actions.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use starknet::core::types::Felt;

use crate::app::state::SourceStatus;
use crate::app::views::address_info::UnfilledGap;
use crate::app::views::address_info::{UnfilledCallGap, UnfilledGap};
use crate::data::pathfinder::ClassHashEntry;
use crate::data::types::{
AddressTxSummary, ClassContractEntry, ClassDeclareInfo, ContractCallSummary,
Expand Down Expand Up @@ -77,6 +77,15 @@ pub enum Action {
known_txs: Vec<AddressTxSummary>,
gap: UnfilledGap,
},
/// On-demand Calls-tab block-range gap fill: dispatched when the user
/// presses Enter on a call-gap row. Backfills the `[lo_block, hi_block]`
/// window (contract → Dune `starknet.calls`; account → pf event window)
/// and merges the result, shrinking the gap from its newer edge.
FillAddressCallGap {
address: Felt,
gap: UnfilledCallGap,
is_contract: bool,
},
/// Enrich WS-streamed call stubs (missing sender/function/fee/timestamp).
EnrichAddressCalls {
address: Felt,
Expand Down Expand Up @@ -271,6 +280,30 @@ pub enum Action {
address: Felt,
calls: Vec<ContractCallSummary>,
},
/// An on-demand Calls-tab gap fill covered the whole `[lo_block, hi_block]`
/// range (the windowed query returned below its limit). The reducer records
/// the range as fully scanned so `detect_unfilled_call_gaps` stops
/// re-surfacing a genuinely-sparse hole as a fillable gap.
AddressCallRangeScanned {
address: Felt,
lo_block: u64,
hi_block: u64,
},
/// An on-demand Calls-tab gap fill finished (success, no-op, or error). The
/// reducer clears the matching gap's `fill_dispatched` so the row leaves the
/// "loading" state and Enter can re-dispatch — without this a failed fill
/// would leave the gap stuck forever.
AddressCallGapFillFinished {
address: Felt,
lo_block: u64,
},
/// Persisted fully-scanned call ranges for an address, loaded from cache at
/// address entry. Seeds `scanned_call_ranges` so a previously-closed
/// (genuinely-sparse) gap stays suppressed across re-navigation/restart.
AddressCallScannedRangesLoaded {
address: Felt,
ranges: Vec<(u64, u64)>,
},
/// Older blocks loaded (appended to block list).
OlderBlocksLoaded(Vec<SnBlock>),
/// More address transactions loaded (appended to address tx list).
Expand Down
4 changes: 4 additions & 0 deletions src/app/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,10 @@ fn handle_enter(app: &mut App) -> Option<Action> {
return app.navigate_to(NavTarget::Transaction(hash));
}
crate::app::AddressTab::Calls => {
if app.address.call_gap_selected.is_some() {
app.dispatch_address_call_gap_fill();
return None;
}
let hash = app.address.calls.selected_item()?.tx_hash;
return app.navigate_to(NavTarget::Transaction(hash));
}
Expand Down
100 changes: 95 additions & 5 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ impl App {
self.maybe_enrich_visible_address_txs();
}
AddressTab::Calls => {
self.address.calls.scroll_by(delta);
self.address.call_list_scroll_by(delta);
self.maybe_fetch_more_address_txs();
}
AddressTab::MetaTxs => {
Expand Down Expand Up @@ -622,7 +622,7 @@ impl App {
self.maybe_enrich_visible_address_txs();
}
AddressTab::Calls => {
self.address.calls.next();
self.address.call_list_next();
self.maybe_fetch_more_address_txs();
}
AddressTab::MetaTxs => {
Expand Down Expand Up @@ -658,7 +658,7 @@ impl App {
self.address.tx_list_previous();
self.maybe_enrich_visible_address_txs();
}
AddressTab::Calls => self.address.calls.previous(),
AddressTab::Calls => self.address.call_list_previous(),
AddressTab::MetaTxs => self.address.meta_txs.previous(),
AddressTab::Events => self.address.events.previous(),
AddressTab::ClassHistory => {
Expand All @@ -685,7 +685,7 @@ impl App {
self.address.tx_list_select_first();
self.maybe_enrich_visible_address_txs();
}
AddressTab::Calls => self.address.calls.select_first(),
AddressTab::Calls => self.address.call_list_select_first(),
AddressTab::MetaTxs => self.address.meta_txs.select_first(),
AddressTab::Events => self.address.events.select_first(),
AddressTab::ClassHistory => {
Expand Down Expand Up @@ -723,7 +723,7 @@ impl App {
self.maybe_enrich_visible_address_txs();
}
AddressTab::Calls => {
self.address.calls.select_last();
self.address.call_list_select_last();
self.maybe_fetch_more_address_txs();
}
AddressTab::MetaTxs => {
Expand Down Expand Up @@ -829,6 +829,45 @@ impl App {
true
}

/// Dispatch an on-demand backfill for the currently selected call-gap row
/// in the address Calls tab. Returns `true` if a fill was sent. No-op if no
/// call gap is selected, its fill is already in flight, or there is no
/// current address. Mirrors `dispatch_address_gap_fill`.
pub fn dispatch_address_call_gap_fill(&mut self) -> bool {
let Some(address) = self.address.context else {
return false;
};
let Some(sel_lo) = self.address.call_gap_selected else {
return false;
};
let Some(gap) = self
.address
.call_gaps
.iter_mut()
.find(|g| g.lo_block == sel_lo)
else {
return false;
};
if gap.fill_dispatched {
return false;
}
let gap_clone = gap.clone();
gap.fill_dispatched = true;
let _ = self.action_tx.send(Action::FillAddressCallGap {
address,
gap: gap_clone,
is_contract: self.address.is_contract,
});
true
}

/// Re-detect Calls-tab block-range gaps after a calls merge. Unlike the
/// nonce path there is no auto-fill — a block-range hole doesn't prove
/// missing calls, so every call gap waits for an explicit Enter.
fn refresh_address_call_gaps(&mut self) {
self.address.refresh_unfilled_call_gaps();
}

/// Refresh the address's nonce-gap list, then auto-dispatch fills for any
/// "tiny" gaps (≤ `AUTO_FILL_MAX_MISSING` missing nonces). Tiny gaps
/// shouldn't require user action — a 1-nonce hole in a sparse account is
Expand Down Expand Up @@ -1437,6 +1476,10 @@ impl App {
self.address.dune_has_more = true;
self.address.rpc_has_more = true;
}

// Surface any block-range hole between the freshly-fetched
// recent window and older cached calls as a fillable gap.
self.refresh_address_call_gaps();
}

// Set default tab based on contract type
Expand Down Expand Up @@ -1518,6 +1561,7 @@ impl App {
self.address.rpc_cursor_block = Some(oldest_block);
self.address.dune_has_more = has_more;
self.address.rpc_has_more = has_more;
self.refresh_address_call_gaps();
}
self.address.fetching_more_txs = false;
}
Expand Down Expand Up @@ -2083,12 +2127,58 @@ impl App {
{
self.address.calls.select_first();
}
// A merge can both close gaps (new calls bridge a hole) and
// surface new ones (a fresh top-of-list batch lands above the
// cached set). `refresh_unfilled_call_gaps` preserves any
// in-flight `fill_dispatched` whose range is unchanged.
self.refresh_address_call_gaps();
// Persist the merged set so it survives restarts.
let _ = self.action_tx.send(Action::PersistAddressCalls {
address,
calls: self.address.calls.items.clone(),
});
}
Action::AddressCallRangeScanned {
address,
lo_block,
hi_block,
} => {
if self.address.context == Some(address) {
// The fill covered this whole range. Record it so a
// genuinely-sparse hole inside it stops resurfacing, then
// re-detect (this pass is the one that drops the gap).
self.address.note_scanned_call_range(lo_block, hi_block);
self.refresh_address_call_gaps();
}
}
Action::AddressCallGapFillFinished { address, lo_block } => {
// The fill task ended (success, no-op, or error). Clear the
// in-flight flag so the row stops showing "loading" and Enter
// can re-dispatch. On a successful fill the gap may have already
// shrunk/closed via the merge above, in which case this is a
// harmless no-op.
if self.address.context == Some(address)
&& let Some(gap) = self
.address
.call_gaps
.iter_mut()
.find(|g| g.lo_block == lo_block)
{
gap.fill_dispatched = false;
}
}
Action::AddressCallScannedRangesLoaded { address, ranges } => {
if self.address.context == Some(address) {
// Merge the persisted closed ranges into whatever's in
// memory (empty on fresh entry; superset-safe on refresh),
// then re-detect so any hole we already scanned-and-found-
// sparse stays suppressed from first paint.
for (lo, hi) in ranges {
self.address.note_scanned_call_range(lo, hi);
}
self.refresh_address_call_gaps();
}
}
Action::AddressBalancesLoaded { address, balances } => {
if self.address.context == Some(address) {
// Balances can beat the nonce/class_hash fetch to the UI —
Expand Down
Loading
Loading