66//
77// Open claim accounting: `storage::OpenClaimCount(holder, policy_id)` must be
88// incremented when a claim enters `Processing` and decremented when it reaches
9- // a terminal status (`Approved` / `Rejected`), so policy termination can block
9+ // a terminal status (`Approved` / `Rejected` / `Withdrawn` ), so policy termination can block
1010// or audit in-flight claims. Until `file_claim` ships, admins may use
1111// `admin_set_open_claim_count` in tests or break-glass ops only.
1212//
@@ -143,6 +143,19 @@ struct ClaimFiled {
143143 pub image_hash : u64 ,
144144}
145145
146+ /// Emitted when the claimant withdraws before any vote is cast.
147+ ///
148+ /// Topic layout: ["niffyinsure", "claim_withdrawn", claim_id]
149+ #[ contractevent( topics = [ "niffyinsure" , "claim_withdrawn" ] ) ]
150+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
151+ pub struct ClaimWithdrawn {
152+ #[ topic]
153+ pub claim_id : u64 ,
154+ pub policy_id : u32 ,
155+ pub claimant : Address ,
156+ pub at_ledger : u32 ,
157+ }
158+
146159/// Emitted as the authoritative rejection signal. Indexers must consume this
147160/// event (not poll storage) to drive user-facing messaging. The vote tallies
148161/// are included so the UI can explain the outcome (e.g., "rejected 4–1").
@@ -254,8 +267,11 @@ pub fn file_claim(
254267 return Err ( Error :: DuplicateOpenClaim ) ;
255268 }
256269
270+ // Anchor for restoring per-holder rate limit if claimant later withdraws (see `withdraw_claim`).
271+ let rate_limit_anchor_before_filing = storage:: get_last_claim_ledger ( env, holder) ;
272+
257273 // Rate-limit check.
258- if let Some ( last) = storage :: get_last_claim_ledger ( env , holder ) {
274+ if let Some ( last) = rate_limit_anchor_before_filing {
259275 if !ledger:: is_rate_limit_elapsed ( now, last, ledger:: RATE_LIMIT_WINDOW_LEDGERS ) {
260276 return Err ( Error :: RateLimitExceeded ) ;
261277 }
@@ -297,6 +313,7 @@ pub fn file_claim(
297313 storage:: snapshot_claim_voters ( env, claim_id) ;
298314 storage:: set_claim_quorum_bps ( env, claim_id, storage:: get_quorum_bps ( env) ) ;
299315 storage:: set_last_claim_ledger ( env, holder, now) ;
316+ storage:: set_claim_rate_limit_prev ( env, claim_id, rate_limit_anchor_before_filing) ;
300317
301318 ClaimFiled {
302319 claim_id,
@@ -308,6 +325,60 @@ pub fn file_claim(
308325 Ok ( claim_id)
309326}
310327
328+ // ── withdraw_claim ────────────────────────────────────────────────────────────
329+
330+ /// Claimant-only: withdraw a claim before any ballot is cast.
331+ ///
332+ /// Allowed only while `status == Processing` and `approve_votes + reject_votes == 0`.
333+ /// Sets status to [`ClaimStatus::Withdrawn`], clears the open-claim flag, restores the
334+ /// holder's claim **rate-limit anchor** to its value before this claim was filed (see
335+ /// `storage::ClaimRateLimitPrev`), and emits [`ClaimWithdrawn`].
336+ ///
337+ /// **Rate limit vs open-claim cap:** Withdrawal does **not** consume the per-policy
338+ /// "one open claim" slot once complete (open flag cleared). The per-holder time spacing
339+ /// between **successful** `file_claim` calls is reverted to the pre-filing anchor so a
340+ /// mistaken filing does not force the holder to wait another full window before refiling.
341+ pub fn withdraw_claim ( env : & Env , claimant : & Address , claim_id : u64 ) -> Result < ( ) , Error > {
342+ storage:: assert_claims_not_paused ( env) ;
343+
344+ let mut claim = storage:: get_claim ( env, claim_id) . ok_or ( Error :: ClaimNotFound ) ?;
345+
346+ if claimant != & claim. claimant {
347+ return Err ( Error :: NotEligibleVoter ) ;
348+ }
349+
350+ if claim. status != ClaimStatus :: Processing {
351+ return Err ( Error :: ClaimAlreadyTerminal ) ;
352+ }
353+
354+ if claim. approve_votes != 0 || claim. reject_votes != 0 {
355+ return Err ( Error :: ClaimAlreadyTerminal ) ;
356+ }
357+
358+ let now = env. ledger ( ) . sequence ( ) ;
359+ claim. status = ClaimStatus :: Withdrawn ;
360+ push_status_transition ( & mut claim. status_history , ClaimStatus :: Withdrawn , now) ;
361+
362+ storage:: set_open_claim ( env, & claim. claimant , claim. policy_id , false ) ;
363+
364+ match storage:: take_claim_rate_limit_prev ( env, claim_id) {
365+ Some ( ledger) => storage:: set_last_claim_ledger ( env, & claim. claimant , ledger) ,
366+ None => storage:: remove_last_claim_ledger ( env, & claim. claimant ) ,
367+ }
368+
369+ storage:: set_claim ( env, & claim) ;
370+
371+ ClaimWithdrawn {
372+ claim_id,
373+ policy_id : claim. policy_id ,
374+ claimant : claimant. clone ( ) ,
375+ at_ledger : now,
376+ }
377+ . publish ( env) ;
378+
379+ Ok ( ( ) )
380+ }
381+
311382// ── vote_on_claim ─────────────────────────────────────────────────────────────
312383
313384/// Cast a vote on a pending claim.
@@ -383,6 +454,10 @@ pub fn vote_on_claim(
383454 storage:: set_open_claim ( env, & claim. claimant , claim. policy_id , false ) ;
384455 }
385456
457+ if status_before == ClaimStatus :: Processing && claim. status != ClaimStatus :: Processing {
458+ storage:: remove_claim_rate_limit_prev ( env, claim_id) ;
459+ }
460+
386461 let status = claim. status . clone ( ) ;
387462 storage:: set_claim ( env, & claim) ;
388463
@@ -445,6 +520,11 @@ pub fn finalize_claim(env: &Env, claim_id: u64) -> Result<ClaimStatus, Error> {
445520 let newly_rejected = claim. status == ClaimStatus :: Rejected ;
446521
447522 storage:: set_open_claim ( env, & claim. claimant , claim. policy_id , false ) ;
523+
524+ if status_before == ClaimStatus :: Processing && claim. status != ClaimStatus :: Processing {
525+ storage:: remove_claim_rate_limit_prev ( env, claim_id) ;
526+ }
527+
448528 let status = claim. status . clone ( ) ;
449529 storage:: set_claim ( env, & claim) ;
450530
@@ -485,6 +565,7 @@ pub fn process_claim(env: &Env, claim_id: u64) -> Result<(), Error> {
485565 claim. status = ClaimStatus :: Paid ;
486566 push_status_transition ( & mut claim. status_history , ClaimStatus :: Paid , now) ;
487567 storage:: set_open_claim ( env, & claim. claimant , claim. policy_id , false ) ;
568+ storage:: remove_claim_rate_limit_prev ( env, claim_id) ;
488569 storage:: set_claim ( env, & claim) ;
489570 Ok ( ( ) )
490571}
0 commit comments