1use alloy::primitives::{Address, B128, U256};
6use alloy::sol_types::{eip712_domain, Eip712Domain};
7use either::Either;
8use rust_decimal::Decimal;
9use serde::{Deserialize, Serialize};
10use std::fmt;
11use std::str::FromStr;
12
13pub type Cloid = B128;
19
20pub type OidOrCloid = Either<u64, Cloid>;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
29#[serde(rename_all = "PascalCase")]
30pub enum Chain {
31 #[default]
32 Mainnet,
33 Testnet,
34}
35
36impl Chain {
37 pub fn is_mainnet(&self) -> bool {
39 matches!(self, Chain::Mainnet)
40 }
41
42 pub fn as_str(&self) -> &'static str {
44 match self {
45 Chain::Mainnet => "Mainnet",
46 Chain::Testnet => "Testnet",
47 }
48 }
49
50 pub fn signature_chain_id(&self) -> &'static str {
52 match self {
53 Chain::Mainnet => "0xa4b1", Chain::Testnet => "0x66eee", }
56 }
57
58 pub fn evm_chain_id(&self) -> u64 {
60 match self {
61 Chain::Mainnet => 999,
62 Chain::Testnet => 998,
63 }
64 }
65}
66
67impl fmt::Display for Chain {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 Chain::Mainnet => write!(f, "Mainnet"),
71 Chain::Testnet => write!(f, "Testnet"),
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "lowercase")]
83pub enum Side {
84 Buy,
85 Sell,
86}
87
88impl Side {
89 pub fn is_buy(&self) -> bool {
91 matches!(self, Side::Buy)
92 }
93
94 pub fn as_bool(&self) -> bool {
96 self.is_buy()
97 }
98}
99
100impl fmt::Display for Side {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 match self {
103 Side::Buy => write!(f, "buy"),
104 Side::Sell => write!(f, "sell"),
105 }
106 }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct PredictionSide {
117 pub outcome: u64,
118 pub side: usize,
119 pub name: String,
120 pub symbol: String,
121 pub token: String,
122 pub asset_id: usize,
123 pub mid: Option<String>,
124 pub sz_decimals: u8,
125 pub supports_priority_fee: bool,
126}
127
128impl fmt::Display for PredictionSide {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 write!(f, "{}", self.symbol)
131 }
132}
133
134impl From<PredictionSide> for String {
135 fn from(side: PredictionSide) -> Self {
136 side.symbol
137 }
138}
139
140impl From<&PredictionSide> for String {
141 fn from(side: &PredictionSide) -> Self {
142 side.symbol.clone()
143 }
144}
145
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct PredictionMarket {
150 pub outcome: u64,
151 pub name: String,
152 pub description: String,
153 pub title: String,
154 pub slug: String,
155 pub underlying: Option<String>,
156 pub target_price: Option<String>,
157 pub expiry: Option<String>,
158 pub period: Option<String>,
159 pub collateral: String,
160 pub min_order_value: String,
161 pub aliases: Vec<String>,
162 pub yes: PredictionSide,
163 pub no: PredictionSide,
164 pub sides: Vec<PredictionSide>,
165}
166
167impl PredictionMarket {
168 pub fn matches(&self, query: &str) -> bool {
169 let normalized = query.to_lowercase();
170 let mut values = vec![
171 self.slug.clone(),
172 self.title.to_lowercase(),
173 self.name.to_lowercase(),
174 self.underlying.clone().unwrap_or_default().to_lowercase(),
175 self.yes.symbol.to_lowercase(),
176 self.no.symbol.to_lowercase(),
177 self.yes.token.to_lowercase(),
178 self.no.token.to_lowercase(),
179 ];
180 values.extend(self.aliases.iter().map(|alias| alias.to_lowercase()));
181 values.iter().any(|value| value == &normalized || value.contains(&normalized))
182 }
183}
184
185#[derive(Debug, Clone, Default)]
187pub struct PredictionMarketFilter {
188 pub query: Option<String>,
189 pub underlying: Option<String>,
190 pub target_price: Option<String>,
191 pub expiry: Option<String>,
192}
193
194impl FromStr for Side {
195 type Err = String;
196
197 fn from_str(s: &str) -> Result<Self, Self::Err> {
198 match s.to_lowercase().as_str() {
199 "buy" | "b" | "long" => Ok(Side::Buy),
200 "sell" | "s" | "short" => Ok(Side::Sell),
201 _ => Err(format!("invalid side: {}", s)),
202 }
203 }
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
212#[serde(rename_all = "lowercase")]
213pub enum TIF {
214 #[default]
216 Ioc,
217 Gtc,
219 Alo,
221 Market,
223}
224
225impl fmt::Display for TIF {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 match self {
228 TIF::Ioc => write!(f, "ioc"),
229 TIF::Gtc => write!(f, "gtc"),
230 TIF::Alo => write!(f, "alo"),
231 TIF::Market => write!(f, "market"),
232 }
233 }
234}
235
236impl FromStr for TIF {
237 type Err = String;
238
239 fn from_str(s: &str) -> Result<Self, Self::Err> {
240 match s.to_lowercase().as_str() {
241 "ioc" => Ok(TIF::Ioc),
242 "gtc" => Ok(TIF::Gtc),
243 "alo" | "post_only" => Ok(TIF::Alo),
244 "market" => Ok(TIF::Market),
245 _ => Err(format!("invalid tif: {}", s)),
246 }
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
256pub enum TimeInForce {
257 Alo,
258 Ioc,
259 Gtc,
260 FrontendMarket,
261}
262
263impl From<TIF> for TimeInForce {
264 fn from(tif: TIF) -> Self {
265 match tif {
266 TIF::Ioc => TimeInForce::Ioc,
267 TIF::Gtc => TimeInForce::Gtc,
268 TIF::Alo => TimeInForce::Alo,
269 TIF::Market => TimeInForce::Ioc, }
271 }
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
280#[serde(rename_all = "lowercase")]
281pub enum TpSl {
282 Tp,
284 Sl,
286}
287
288impl fmt::Display for TpSl {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 match self {
291 TpSl::Tp => write!(f, "tp"),
292 TpSl::Sl => write!(f, "sl"),
293 }
294 }
295}
296
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
303#[serde(rename_all = "camelCase")]
304pub enum OrderGrouping {
305 #[default]
307 Na,
308 NormalTpsl,
310 PositionTpsl,
312}
313
314impl fmt::Display for OrderGrouping {
315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316 match self {
317 OrderGrouping::Na => write!(f, "na"),
318 OrderGrouping::NormalTpsl => write!(f, "normalTpsl"),
319 OrderGrouping::PositionTpsl => write!(f, "positionTpsl"),
320 }
321 }
322}
323
324pub const CORE_MAINNET_EIP712_DOMAIN: Eip712Domain = eip712_domain! {
330 name: "Exchange",
331 version: "1",
332 chain_id: 1337,
333 verifying_contract: Address::ZERO,
334};
335
336#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
342pub struct Signature {
343 #[serde(
344 serialize_with = "serialize_u256_hex",
345 deserialize_with = "deserialize_u256_hex"
346 )]
347 pub r: U256,
348 #[serde(
349 serialize_with = "serialize_u256_hex",
350 deserialize_with = "deserialize_u256_hex"
351 )]
352 pub s: U256,
353 pub v: u64,
354}
355
356impl From<alloy::signers::Signature> for Signature {
357 fn from(sig: alloy::signers::Signature) -> Self {
358 Self {
359 r: sig.r(),
360 s: sig.s(),
361 v: if sig.v() { 28 } else { 27 },
362 }
363 }
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
372#[serde(rename_all = "camelCase")]
373pub enum OrderTypePlacement {
374 Limit {
376 tif: TimeInForce,
377 },
378 #[serde(rename_all = "camelCase")]
380 Trigger {
381 is_market: bool,
382 #[serde(with = "decimal_normalized")]
383 trigger_px: Decimal,
384 tpsl: TpSl,
385 },
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
394#[serde(rename_all = "camelCase")]
395pub struct OrderRequest {
396 #[serde(rename = "a")]
398 pub asset: usize,
399 #[serde(rename = "b")]
401 pub is_buy: bool,
402 #[serde(rename = "p", with = "decimal_normalized")]
404 pub limit_px: Decimal,
405 #[serde(rename = "s", with = "decimal_normalized")]
407 pub sz: Decimal,
408 #[serde(rename = "r")]
410 pub reduce_only: bool,
411 #[serde(rename = "t")]
413 pub order_type: OrderTypePlacement,
414 #[serde(
416 rename = "c",
417 serialize_with = "serialize_cloid_hex",
418 deserialize_with = "deserialize_cloid_hex"
419 )]
420 pub cloid: Cloid,
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize)]
429#[serde(rename_all = "camelCase")]
430pub struct BatchOrder {
431 pub orders: Vec<OrderRequest>,
432 pub grouping: OrderGrouping,
433}
434
435#[derive(Debug, Clone, Serialize, Deserialize)]
441#[serde(rename_all = "camelCase")]
442pub struct Modify {
443 #[serde(with = "oid_or_cloid")]
444 pub oid: OidOrCloid,
445 pub order: OrderRequest,
446}
447
448#[derive(Debug, Clone, Serialize, Deserialize)]
450#[serde(rename_all = "camelCase")]
451pub struct BatchModify {
452 pub modifies: Vec<Modify>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct Cancel {
463 #[serde(rename = "a")]
464 pub asset: usize,
465 #[serde(rename = "o")]
466 pub oid: u64,
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize)]
471#[serde(rename_all = "camelCase")]
472pub struct BatchCancel {
473 pub cancels: Vec<Cancel>,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize)]
478#[serde(rename_all = "camelCase")]
479pub struct CancelByCloid {
480 pub asset: u32,
481 #[serde(with = "const_hex_b128")]
482 pub cloid: B128,
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize)]
487#[serde(rename_all = "camelCase")]
488pub struct BatchCancelCloid {
489 pub cancels: Vec<CancelByCloid>,
490}
491
492#[derive(Debug, Clone, Serialize, Deserialize)]
494#[serde(rename_all = "camelCase")]
495pub struct ScheduleCancel {
496 pub time: Option<u64>,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
505#[serde(rename_all = "camelCase")]
506pub struct TwapSpec {
507 #[serde(rename = "a")]
508 pub asset: String,
509 #[serde(rename = "b")]
510 pub is_buy: bool,
511 #[serde(rename = "s")]
512 pub sz: String,
513 #[serde(rename = "r")]
514 pub reduce_only: bool,
515 #[serde(rename = "m")]
516 pub duration_minutes: i64,
517 #[serde(rename = "t")]
518 pub randomize: bool,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523#[serde(rename_all = "camelCase")]
524pub struct TwapOrder {
525 pub twap: TwapSpec,
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize)]
530#[serde(rename_all = "camelCase")]
531pub struct TwapCancel {
532 #[serde(rename = "a")]
533 pub asset: String,
534 #[serde(rename = "t")]
535 pub twap_id: i64,
536}
537
538#[derive(Debug, Clone, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct UpdateLeverage {
546 pub asset: u32,
547 pub is_cross: bool,
548 pub leverage: i32,
549}
550
551#[derive(Debug, Clone, Serialize, Deserialize)]
553#[serde(rename_all = "camelCase")]
554pub struct UpdateIsolatedMargin {
555 pub asset: u32,
556 pub is_buy: bool,
557 pub ntli: i64,
558}
559
560#[derive(Debug, Clone, Serialize, Deserialize)]
562#[serde(rename_all = "camelCase")]
563pub struct TopUpIsolatedOnlyMargin {
564 pub asset: u32,
565 pub leverage: String,
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize)]
574#[serde(rename_all = "camelCase")]
575pub struct UsdSend {
576 pub hyperliquid_chain: Chain,
577 pub signature_chain_id: String,
578 pub destination: String,
579 pub amount: String,
580 pub time: u64,
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize)]
585#[serde(rename_all = "camelCase")]
586pub struct SpotSend {
587 pub hyperliquid_chain: Chain,
588 pub signature_chain_id: String,
589 pub token: String,
590 pub destination: String,
591 pub amount: String,
592 pub time: u64,
593}
594
595#[derive(Debug, Clone, Serialize, Deserialize)]
597#[serde(rename_all = "camelCase")]
598pub struct Withdraw3 {
599 pub hyperliquid_chain: Chain,
600 pub signature_chain_id: String,
601 pub destination: String,
602 pub amount: String,
603 pub time: u64,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
608#[serde(rename_all = "camelCase")]
609pub struct UsdClassTransfer {
610 pub hyperliquid_chain: Chain,
611 pub signature_chain_id: String,
612 pub amount: String,
613 pub to_perp: bool,
614 pub nonce: u64,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize)]
619#[serde(rename_all = "camelCase")]
620pub struct SendAsset {
621 pub hyperliquid_chain: Chain,
622 pub signature_chain_id: String,
623 pub destination: String,
624 pub source_dex: String,
625 pub destination_dex: String,
626 pub token: String,
627 pub amount: String,
628 pub from_sub_account: String,
629 pub nonce: u64,
630}
631
632#[derive(Debug, Clone, Serialize, Deserialize)]
638#[serde(rename_all = "camelCase")]
639pub struct VaultTransfer {
640 pub vault_address: String,
641 pub is_deposit: bool,
642 pub usd: f64,
643}
644
645#[derive(Debug, Clone, Serialize, Deserialize)]
651#[serde(rename_all = "camelCase")]
652pub struct ApproveAgent {
653 pub hyperliquid_chain: Chain,
654 pub signature_chain_id: String,
655 pub agent_address: String,
656 pub agent_name: Option<String>,
657 pub nonce: u64,
658}
659
660#[derive(Debug, Clone, Serialize, Deserialize)]
662#[serde(rename_all = "camelCase")]
663pub struct ApproveBuilderFee {
664 pub hyperliquid_chain: Chain,
665 pub signature_chain_id: String,
666 pub max_fee_rate: String,
667 pub builder: String,
668 pub nonce: u64,
669}
670
671#[derive(Debug, Clone, Serialize, Deserialize)]
677#[serde(rename_all = "camelCase")]
678pub struct UserSetAbstraction {
679 pub hyperliquid_chain: Chain,
680 pub signature_chain_id: String,
681 pub user: String,
682 pub abstraction: String,
683 pub nonce: u64,
684}
685
686#[derive(Debug, Clone, Serialize, Deserialize)]
688#[serde(rename_all = "camelCase")]
689pub struct AgentSetAbstraction {
690 pub abstraction: String,
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize)]
699#[serde(rename_all = "camelCase")]
700pub struct CDeposit {
701 pub hyperliquid_chain: Chain,
702 pub signature_chain_id: String,
703 pub wei: u128,
704 pub nonce: u64,
705}
706
707#[derive(Debug, Clone, Serialize, Deserialize)]
709#[serde(rename_all = "camelCase")]
710pub struct CWithdraw {
711 pub hyperliquid_chain: Chain,
712 pub signature_chain_id: String,
713 pub wei: u128,
714 pub nonce: u64,
715}
716
717#[derive(Debug, Clone, Serialize, Deserialize)]
719#[serde(rename_all = "camelCase")]
720pub struct TokenDelegate {
721 pub hyperliquid_chain: Chain,
722 pub signature_chain_id: String,
723 pub validator: String,
724 pub is_undelegate: bool,
725 pub wei: u128,
726 pub nonce: u64,
727}
728
729#[derive(Debug, Clone, Serialize, Deserialize)]
735#[serde(rename_all = "camelCase")]
736pub struct ReserveRequestWeight {
737 pub weight: i32,
738}
739
740#[derive(Debug, Clone, Serialize, Deserialize)]
742#[serde(rename_all = "camelCase")]
743pub struct Noop {}
744
745#[derive(Debug, Clone, Serialize, Deserialize)]
747#[serde(rename_all = "camelCase")]
748pub struct ValidatorL1Stream {
749 pub risk_free_rate: String,
750}
751
752#[derive(Debug, Clone, Serialize, Deserialize)]
754#[serde(rename_all = "camelCase")]
755pub struct ClosePosition {
756 pub asset: String,
757 pub user: String,
758}
759
760#[derive(Debug, Clone, Serialize, Deserialize)]
766#[serde(tag = "type")]
767#[serde(rename_all = "camelCase")]
768pub enum Action {
769 Order(BatchOrder),
771 BatchModify(BatchModify),
772
773 Cancel(BatchCancel),
775 CancelByCloid(BatchCancelCloid),
776 ScheduleCancel(ScheduleCancel),
777
778 TwapOrder(TwapOrder),
780 TwapCancel(TwapCancel),
781
782 UpdateLeverage(UpdateLeverage),
784 UpdateIsolatedMargin(UpdateIsolatedMargin),
785 TopUpIsolatedOnlyMargin(TopUpIsolatedOnlyMargin),
786
787 UsdSend(UsdSend),
789 SpotSend(SpotSend),
790 Withdraw3(Withdraw3),
791 UsdClassTransfer(UsdClassTransfer),
792 SendAsset(SendAsset),
793
794 VaultTransfer(VaultTransfer),
796
797 ApproveAgent(ApproveAgent),
799 ApproveBuilderFee(ApproveBuilderFee),
800
801 UserSetAbstraction(UserSetAbstraction),
803 AgentSetAbstraction(AgentSetAbstraction),
804
805 CDeposit(CDeposit),
807 CWithdraw(CWithdraw),
808 TokenDelegate(TokenDelegate),
809
810 ReserveRequestWeight(ReserveRequestWeight),
812
813 Noop(Noop),
815
816 ValidatorL1Stream(ValidatorL1Stream),
818
819 ClosePosition(ClosePosition),
821}
822
823impl Action {
824 pub fn hash(
826 &self,
827 nonce: u64,
828 vault_address: Option<Address>,
829 expires_after: Option<u64>,
830 ) -> Result<alloy::primitives::B256, rmp_serde::encode::Error> {
831 crate::signing::rmp_hash(self, nonce, vault_address, expires_after)
832 }
833}
834
835#[derive(Debug, Clone, Serialize, Deserialize)]
841#[serde(rename_all = "camelCase")]
842pub struct ActionRequest {
843 pub action: Action,
844 pub nonce: u64,
845 pub signature: Signature,
846 #[serde(skip_serializing_if = "Option::is_none")]
847 pub vault_address: Option<Address>,
848 #[serde(skip_serializing_if = "Option::is_none")]
849 pub expires_after: Option<u64>,
850}
851
852#[derive(Debug, Clone, Serialize, Deserialize)]
858pub struct Builder {
859 #[serde(rename = "b")]
861 pub address: String,
862 #[serde(rename = "f")]
864 pub fee: u16,
865}
866
867pub mod decimal_normalized {
873 use rust_decimal::Decimal;
874 use serde::{de, Deserialize, Deserializer, Serializer};
875 use std::str::FromStr;
876
877 pub fn serialize<S>(value: &Decimal, serializer: S) -> Result<S::Ok, S::Error>
878 where
879 S: Serializer,
880 {
881 let normalized = value.normalize();
882 serializer.serialize_str(&normalized.to_string())
883 }
884
885 pub fn deserialize<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
886 where
887 D: Deserializer<'de>,
888 {
889 let s = String::deserialize(deserializer)?;
890 Decimal::from_str(&s)
891 .map(|d| d.normalize())
892 .map_err(de::Error::custom)
893 }
894}
895
896pub mod oid_or_cloid {
898 use super::Cloid;
899 use either::Either;
900 use serde::{de, Deserializer, Serializer};
901
902 pub fn serialize<S>(value: &Either<u64, Cloid>, serializer: S) -> Result<S::Ok, S::Error>
903 where
904 S: Serializer,
905 {
906 match value {
907 Either::Left(oid) => serializer.serialize_u64(*oid),
908 Either::Right(cloid) => serializer.serialize_str(&format!("{:#x}", cloid)),
909 }
910 }
911
912 pub fn deserialize<'de, D>(deserializer: D) -> Result<Either<u64, Cloid>, D::Error>
913 where
914 D: Deserializer<'de>,
915 {
916 struct Visitor;
917
918 impl<'de> serde::de::Visitor<'de> for Visitor {
919 type Value = Either<u64, Cloid>;
920
921 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
922 f.write_str("a u64 oid or a hex string cloid")
923 }
924
925 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
926 Ok(Either::Left(v))
927 }
928
929 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
930 v.parse::<Cloid>().map(Either::Right).map_err(de::Error::custom)
931 }
932 }
933
934 deserializer.deserialize_any(Visitor)
935 }
936}
937
938pub mod const_hex_b128 {
940 use alloy::primitives::B128;
941 use serde::{Deserialize, Deserializer, Serializer};
942
943 pub fn serialize<S>(value: &B128, serializer: S) -> Result<S::Ok, S::Error>
944 where
945 S: Serializer,
946 {
947 serializer.serialize_str(&format!("{:#x}", value))
948 }
949
950 pub fn deserialize<'de, D>(deserializer: D) -> Result<B128, D::Error>
951 where
952 D: Deserializer<'de>,
953 {
954 let s = String::deserialize(deserializer)?;
955 s.parse::<B128>().map_err(serde::de::Error::custom)
956 }
957}
958
959fn serialize_cloid_hex<S>(value: &Cloid, serializer: S) -> Result<S::Ok, S::Error>
960where
961 S: serde::Serializer,
962{
963 serializer.serialize_str(&format!("{:#x}", value))
964}
965
966fn deserialize_cloid_hex<'de, D>(deserializer: D) -> Result<Cloid, D::Error>
967where
968 D: serde::Deserializer<'de>,
969{
970 let s = String::deserialize(deserializer)?;
971 s.parse::<Cloid>().map_err(serde::de::Error::custom)
972}
973
974fn serialize_u256_hex<S>(value: &U256, serializer: S) -> Result<S::Ok, S::Error>
975where
976 S: serde::Serializer,
977{
978 serializer.serialize_str(&format!("{:#x}", value))
979}
980
981fn deserialize_u256_hex<'de, D>(deserializer: D) -> Result<U256, D::Error>
982where
983 D: serde::Deserializer<'de>,
984{
985 let s = String::deserialize(deserializer)?;
986 let s = s.strip_prefix("0x").unwrap_or(&s);
987 U256::from_str_radix(s, 16).map_err(serde::de::Error::custom)
988}