11use crate :: models:: {
22 order:: { FillDirection , UserFill } ,
33 transaction_id:: HyperCoreActionId ,
4- user:: { LedgerDelta , LedgerUpdate } ,
4+ user:: { DelegatorHistoryDelta , DelegatorHistoryUpdate , LedgerDelta , LedgerUpdate } ,
55} ;
66use number_formatter:: BigNumberFormatter ;
77use primitives:: {
@@ -11,6 +11,7 @@ use primitives::{
1111
1212pub const ACTION_HISTORY_QUERY_LOOKBACK_MS : u64 = 5_000 ;
1313const ACTION_HISTORY_MATCH_WINDOW_MS : u64 = 5 * 60 * 1_000 ;
14+ const DELEGATOR_WITHDRAWAL_INITIATED : & str = "initiated" ;
1415
1516fn perpetual_fill_type_and_direction ( dir : & FillDirection ) -> Option < ( TransactionType , PerpetualDirection ) > {
1617 match dir {
@@ -69,10 +70,7 @@ pub fn map_transaction_state_order(fills: Vec<UserFill>, oid: u64, request_id: S
6970}
7071
7172pub fn map_transaction_state_order_action ( fills : Vec < UserFill > , nonce : u64 , request_id : String ) -> TransactionUpdate {
72- match order_action_hash ( & fills, nonce) {
73- Some ( hash) => confirmed_hash_change ( request_id, hash) ,
74- None => TransactionUpdate :: new_state ( TransactionState :: Pending ) ,
75- }
73+ transaction_update_from_hash ( order_action_hash ( & fills, nonce) , request_id)
7674}
7775
7876pub fn order_action_hash ( fills : & [ UserFill ] , nonce : u64 ) -> Option < String > {
@@ -84,10 +82,11 @@ pub fn order_action_hash(fills: &[UserFill], nonce: u64) -> Option<String> {
8482}
8583
8684pub fn map_transaction_state_action ( updates : Vec < LedgerUpdate > , action_id : HyperCoreActionId , request_id : String ) -> TransactionUpdate {
87- match ledger_action_hash ( & updates, & action_id) {
88- Some ( hash) => confirmed_hash_change ( request_id, hash) ,
89- None => TransactionUpdate :: new_state ( TransactionState :: Pending ) ,
90- }
85+ transaction_update_from_hash ( ledger_action_hash ( & updates, & action_id) , request_id)
86+ }
87+
88+ pub fn map_transaction_state_staking_action ( updates : Vec < DelegatorHistoryUpdate > , action_id : HyperCoreActionId , request_id : String ) -> TransactionUpdate {
89+ transaction_update_from_hash ( delegator_history_action_hash ( & updates, & action_id) , request_id)
9190}
9291
9392pub fn ledger_action_hash ( updates : & [ LedgerUpdate ] , action_id : & HyperCoreActionId ) -> Option < String > {
@@ -99,6 +98,15 @@ pub fn ledger_action_hash(updates: &[LedgerUpdate], action_id: &HyperCoreActionI
9998 . map ( |( _, update) | update. hash . clone ( ) )
10099}
101100
101+ fn delegator_history_action_hash ( updates : & [ DelegatorHistoryUpdate ] , action_id : & HyperCoreActionId ) -> Option < String > {
102+ let nonce = action_id. nonce ( ) ;
103+ updates
104+ . iter ( )
105+ . filter_map ( |update| delegator_history_match_delta ( update, action_id, nonce) . map ( |delta| ( delta, update) ) )
106+ . min_by_key ( |( delta, _) | * delta)
107+ . map ( |( _, update) | update. hash . clone ( ) )
108+ }
109+
102110fn ledger_match_delta ( update : & LedgerUpdate , action_id : & HyperCoreActionId , nonce : u64 ) -> Option < u64 > {
103111 match & update. delta {
104112 LedgerDelta :: Send { nonce : update_nonce } | LedgerDelta :: SpotTransfer { nonce : update_nonce } if * update_nonce == nonce => Some ( 0 ) ,
@@ -114,23 +122,44 @@ fn ledger_match_delta(update: &LedgerUpdate, action_id: &HyperCoreActionId, nonc
114122 return None ;
115123 }
116124
117- let Ok ( update_wei) = BigNumberFormatter :: value_from_amount ( amount, HYPERCORE_HYPE . decimals as u32 ) else {
118- return None ;
119- } ;
120-
121- action_history_time_delta ( update. time , nonce) . filter ( |_| update_wei == wei. to_string ( ) )
125+ action_history_time_delta ( update. time , nonce) . filter ( |_| amount_matches_wei ( amount, wei) )
122126 }
123127 LedgerDelta :: Send { .. } | LedgerDelta :: SpotTransfer { .. } | LedgerDelta :: Other => None ,
124128 }
125129}
126130
131+ fn delegator_history_match_delta ( update : & DelegatorHistoryUpdate , action_id : & HyperCoreActionId , nonce : u64 ) -> Option < u64 > {
132+ let matches_action = match ( & update. delta , action_id) {
133+ ( DelegatorHistoryDelta { c_deposit : Some ( delta) , .. } , HyperCoreActionId :: CDeposit { wei, .. } ) => amount_matches_wei ( & delta. amount , * wei) ,
134+ ( DelegatorHistoryDelta { delegate : Some ( delta) , .. } , HyperCoreActionId :: TokenDelegate { wei, is_undelegate, .. } ) => {
135+ delta. is_undelegate == * is_undelegate && amount_matches_wei ( & delta. amount , * wei)
136+ }
137+ ( DelegatorHistoryDelta { withdrawal : Some ( delta) , .. } , HyperCoreActionId :: CWithdraw { wei, .. } ) => {
138+ delta. phase == DELEGATOR_WITHDRAWAL_INITIATED && amount_matches_wei ( & delta. amount , * wei)
139+ }
140+ _ => false ,
141+ } ;
142+
143+ if matches_action { action_history_time_delta ( update. time , nonce) } else { None }
144+ }
145+
146+ fn amount_matches_wei ( amount : & str , wei : u64 ) -> bool {
147+ match BigNumberFormatter :: value_from_amount ( amount, HYPERCORE_HYPE . decimals as u32 ) {
148+ Ok ( update_wei) => update_wei == wei. to_string ( ) ,
149+ Err ( _) => false ,
150+ }
151+ }
152+
127153fn action_history_time_delta ( time : u64 , nonce : u64 ) -> Option < u64 > {
128154 let delta = time. checked_sub ( nonce) ?;
129155 if delta <= ACTION_HISTORY_MATCH_WINDOW_MS { Some ( delta) } else { None }
130156}
131157
132- fn confirmed_hash_change ( request_id : String , hash : String ) -> TransactionUpdate {
133- TransactionUpdate :: new ( TransactionState :: Confirmed , vec ! [ TransactionChange :: HashChange { old: request_id, new: hash } ] )
158+ fn transaction_update_from_hash ( hash : Option < String > , request_id : String ) -> TransactionUpdate {
159+ match hash {
160+ Some ( hash) => TransactionUpdate :: new ( TransactionState :: Confirmed , vec ! [ TransactionChange :: HashChange { old: request_id, new: hash } ] ) ,
161+ None => TransactionUpdate :: new_state ( TransactionState :: Pending ) ,
162+ }
134163}
135164
136165#[ cfg( test) ]
@@ -246,8 +275,7 @@ mod tests {
246275 #[ test]
247276 fn test_map_transaction_state_action_without_matching_nonce_stays_pending ( ) {
248277 let updates = serde_json:: from_str ( include_str ! ( "../../testdata/user_non_funding_ledger_updates_action_hash.json" ) ) . unwrap ( ) ;
249- let action_id = HyperCoreActionId :: Nonce ( 1777960893093 ) ;
250- let update = map_transaction_state_action ( updates, action_id, "action:1777960893093" . to_string ( ) ) ;
278+ let update = map_transaction_state_action ( updates, HyperCoreActionId :: Nonce ( 1777960893093 ) , "action:1777960893093" . to_string ( ) ) ;
251279
252280 assert_eq ! ( update. state, TransactionState :: Pending ) ;
253281 assert ! ( update. changes. is_empty( ) ) ;
@@ -334,6 +362,59 @@ mod tests {
334362 ) ;
335363 }
336364
365+ #[ test]
366+ fn test_map_transaction_state_staking_action_confirms_delegator_history_actions ( ) {
367+ let updates: Vec < DelegatorHistoryUpdate > = serde_json:: from_str ( include_str ! ( "../../testdata/delegator_history_staking_actions.json" ) ) . unwrap ( ) ;
368+
369+ for ( request_id, action_id, expected_hash) in [
370+ (
371+ "action:cDeposit:1000000:1780081714468" ,
372+ HyperCoreActionId :: CDeposit {
373+ wei : 1_000_000 ,
374+ nonce : 1780081714468 ,
375+ } ,
376+ "0x945b910697cd885a95d5043c857c0d0201b300ec32c0a72c38243c5956c16245" ,
377+ ) ,
378+ (
379+ "action:tokenDelegate:1000000:stake:1780081715280" ,
380+ HyperCoreActionId :: TokenDelegate {
381+ wei : 1_000_000 ,
382+ is_undelegate : false ,
383+ nonce : 1780081715280 ,
384+ } ,
385+ "0x0cfde0fb239ef8630e77043c857c1502025000e0be921735b0c68c4de292d24d" ,
386+ ) ,
387+ (
388+ "action:cWithdraw:3001423:1780078264489" ,
389+ HyperCoreActionId :: CWithdraw {
390+ wei : 3_001_423 ,
391+ nonce : 1780078264489 ,
392+ } ,
393+ "0x7b435a1210afafef7cbd043c84b8d402064e00f7aba2cec11f0c0564cfa389da" ,
394+ ) ,
395+ (
396+ "action:tokenDelegate:3001423:unstake:1780078264488" ,
397+ HyperCoreActionId :: TokenDelegate {
398+ wei : 3_001_423 ,
399+ is_undelegate : true ,
400+ nonce : 1780078264488 ,
401+ } ,
402+ "0xc24f99bd90d6d68ac3c9043c84b8c90201c000a32bd9f55c661845104fdab075" ,
403+ ) ,
404+ ] {
405+ assert_eq ! (
406+ map_transaction_state_staking_action( updates. clone( ) , action_id, request_id. to_string( ) ) ,
407+ TransactionUpdate :: new(
408+ TransactionState :: Confirmed ,
409+ vec![ TransactionChange :: HashChange {
410+ old: request_id. to_string( ) ,
411+ new: expected_hash. to_string( ) ,
412+ } ]
413+ )
414+ ) ;
415+ }
416+ }
417+
337418 #[ test]
338419 fn test_map_transaction_state_action_without_matching_window_stays_pending ( ) {
339420 let updates = serde_json:: from_str ( include_str ! ( "../../testdata/user_non_funding_ledger_updates_c_staking_transfer.json" ) ) . unwrap ( ) ;
0 commit comments