@@ -139,7 +139,13 @@ pub struct AmountWrapper {
139139 pub symbol : Option < String > ,
140140}
141141
142- /// Deserialize amount from either string or number (for JS BigInt compatibility)
142+ /// Deserialize amount from either string or number (for JS BigInt compatibility).
143+ ///
144+ /// Negative values are clamped to 0 rather than rejected. The staking service can
145+ /// send negative `remainingStakingAmount` when the unstake amount exceeds the current
146+ /// balance. Callers already guard with `> 0` checks (e.g. build.rs partial unstake),
147+ /// so clamping is safe and avoids a deserialization error that would prevent the
148+ /// intent from being processed at all.
143149fn deserialize_amount < ' de , D > ( deserializer : D ) -> Result < u64 , D :: Error >
144150where
145151 D : serde:: Deserializer < ' de > ,
@@ -166,14 +172,19 @@ where
166172 where
167173 E : de:: Error ,
168174 {
169- u64:: try_from ( v) . map_err ( |_| de :: Error :: custom ( "negative amount" ) )
175+ Ok ( u64:: try_from ( v) . unwrap_or ( 0 ) )
170176 }
171177
172178 fn visit_str < E > ( self , v : & str ) -> Result < Self :: Value , E >
173179 where
174180 E : de:: Error ,
175181 {
176- v. parse ( ) . map_err ( de:: Error :: custom)
182+ // Try u64 first, fall back to i64 parse and clamp negatives to 0
183+ v. parse :: < u64 > ( ) . or_else ( |_| {
184+ v. parse :: < i64 > ( )
185+ . map ( |n| u64:: try_from ( n) . unwrap_or ( 0 ) )
186+ . map_err ( de:: Error :: custom)
187+ } )
177188 }
178189 }
179190
0 commit comments