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//
@@ -148,6 +148,19 @@ struct ClaimFiled {
148148 pub image_hash : u64 ,
149149}
150150
151+ /// Emitted when the claimant withdraws before any vote is cast.
152+ ///
153+ /// Topic layout: ["niffyinsure", "claim_withdrawn", claim_id]
154+ #[ contractevent( topics = [ "niffyinsure" , "claim_withdrawn" ] ) ]
155+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
156+ pub struct ClaimWithdrawn {
157+ #[ topic]
158+ pub claim_id : u64 ,
159+ pub policy_id : u32 ,
160+ pub claimant : Address ,
161+ pub at_ledger : u32 ,
162+ }
163+
151164/// Emitted as the authoritative rejection signal. Indexers must consume this
152165/// event (not poll storage) to drive user-facing messaging. The vote tallies
153166/// are included so the UI can explain the outcome (e.g., "rejected 4–1").
@@ -259,8 +272,11 @@ pub fn file_claim(
259272 return Err ( Error :: DuplicateOpenClaim ) ;
260273 }
261274
275+ // Anchor for restoring per-holder rate limit if claimant later withdraws (see `withdraw_claim`).
276+ let rate_limit_anchor_before_filing = storage:: get_last_claim_ledger ( env, holder) ;
277+
262278 // Rate-limit check.
263- if let Some ( last) = storage :: get_last_claim_ledger ( env , holder ) {
279+ if let Some ( last) = rate_limit_anchor_before_filing {
264280 if !ledger:: is_rate_limit_elapsed ( now, last, ledger:: RATE_LIMIT_WINDOW_LEDGERS ) {
265281 return Err ( Error :: RateLimitExceeded ) ;
266282 }
@@ -305,6 +321,7 @@ pub fn file_claim(
305321 storage:: snapshot_claim_voters ( env, claim_id) ;
306322 storage:: set_claim_quorum_bps ( env, claim_id, storage:: get_quorum_bps ( env) ) ;
307323 storage:: set_last_claim_ledger ( env, holder, now) ;
324+ storage:: set_claim_rate_limit_prev ( env, claim_id, rate_limit_anchor_before_filing) ;
308325
309326 let mut evidence_hashes: Vec < BytesN < 32 > > = Vec :: new ( env) ;
310327 for e in evidence. iter ( ) {
@@ -324,6 +341,60 @@ pub fn file_claim(
324341 Ok ( claim_id)
325342}
326343
344+ // ── withdraw_claim ────────────────────────────────────────────────────────────
345+
346+ /// Claimant-only: withdraw a claim before any ballot is cast.
347+ ///
348+ /// Allowed only while `status == Processing` and `approve_votes + reject_votes == 0`.
349+ /// Sets status to [`ClaimStatus::Withdrawn`], clears the open-claim flag, restores the
350+ /// holder's claim **rate-limit anchor** to its value before this claim was filed (see
351+ /// `storage::ClaimRateLimitPrev`), and emits [`ClaimWithdrawn`].
352+ ///
353+ /// **Rate limit vs open-claim cap:** Withdrawal does **not** consume the per-policy
354+ /// "one open claim" slot once complete (open flag cleared). The per-holder time spacing
355+ /// between **successful** `file_claim` calls is reverted to the pre-filing anchor so a
356+ /// mistaken filing does not force the holder to wait another full window before refiling.
357+ pub fn withdraw_claim ( env : & Env , claimant : & Address , claim_id : u64 ) -> Result < ( ) , Error > {
358+ storage:: assert_claims_not_paused ( env) ;
359+
360+ let mut claim = storage:: get_claim ( env, claim_id) . ok_or ( Error :: ClaimNotFound ) ?;
361+
362+ if claimant != & claim. claimant {
363+ return Err ( Error :: NotEligibleVoter ) ;
364+ }
365+
366+ if claim. status != ClaimStatus :: Processing {
367+ return Err ( Error :: ClaimAlreadyTerminal ) ;
368+ }
369+
370+ if claim. approve_votes != 0 || claim. reject_votes != 0 {
371+ return Err ( Error :: ClaimAlreadyTerminal ) ;
372+ }
373+
374+ let now = env. ledger ( ) . sequence ( ) ;
375+ claim. status = ClaimStatus :: Withdrawn ;
376+ push_status_transition ( & mut claim. status_history , ClaimStatus :: Withdrawn , now) ;
377+
378+ storage:: set_open_claim ( env, & claim. claimant , claim. policy_id , false ) ;
379+
380+ match storage:: take_claim_rate_limit_prev ( env, claim_id) {
381+ Some ( ledger) => storage:: set_last_claim_ledger ( env, & claim. claimant , ledger) ,
382+ None => storage:: remove_last_claim_ledger ( env, & claim. claimant ) ,
383+ }
384+
385+ storage:: set_claim ( env, & claim) ;
386+
387+ ClaimWithdrawn {
388+ claim_id,
389+ policy_id : claim. policy_id ,
390+ claimant : claimant. clone ( ) ,
391+ at_ledger : now,
392+ }
393+ . publish ( env) ;
394+
395+ Ok ( ( ) )
396+ }
397+
327398// ── vote_on_claim ─────────────────────────────────────────────────────────────
328399
329400/// Cast a vote on a pending claim.
@@ -403,6 +474,10 @@ pub fn vote_on_claim(
403474 storage:: set_open_claim ( env, & claim. claimant , claim. policy_id , false ) ;
404475 }
405476
477+ if status_before == ClaimStatus :: Processing && claim. status != ClaimStatus :: Processing {
478+ storage:: remove_claim_rate_limit_prev ( env, claim_id) ;
479+ }
480+
406481 let status = claim. status . clone ( ) ;
407482 storage:: set_claim ( env, & claim) ;
408483
@@ -476,6 +551,11 @@ pub fn finalize_claim(env: &Env, claim_id: u64) -> Result<ClaimStatus, Error> {
476551 let newly_rejected = claim. status == ClaimStatus :: Rejected ;
477552
478553 storage:: set_open_claim ( env, & claim. claimant , claim. policy_id , false ) ;
554+
555+ if status_before == ClaimStatus :: Processing && claim. status != ClaimStatus :: Processing {
556+ storage:: remove_claim_rate_limit_prev ( env, claim_id) ;
557+ }
558+
479559 let status = claim. status . clone ( ) ;
480560 storage:: set_claim ( env, & claim) ;
481561
@@ -516,6 +596,7 @@ pub fn process_claim(env: &Env, claim_id: u64) -> Result<(), Error> {
516596 claim. status = ClaimStatus :: Paid ;
517597 push_status_transition ( & mut claim. status_history , ClaimStatus :: Paid , now) ;
518598 storage:: set_open_claim ( env, & claim. claimant , claim. policy_id , false ) ;
599+ storage:: remove_claim_rate_limit_prev ( env, claim_id) ;
519600 storage:: set_claim ( env, & claim) ;
520601 Ok ( ( ) )
521602}
0 commit comments