sov_universal_wallet/lib.rs
1mod visitors;
2extern crate self as sov_universal_wallet;
3pub use visitors::display;
4#[cfg(feature = "serde")]
5pub use visitors::json_to_borsh;
6
7pub mod schema;
8pub mod ty;
9
10pub extern crate bech32;
11
12#[cfg(feature = "macros")]
13/// Implements the [`UniversalWallet`](schema::UniversalWallet) trait for the
14/// annotated struct or enum.
15///
16/// The schema generated by the trait allows two main features.
17/// First, the borsh-encoding of the type to be dispalyed in a human-readable format, with the exact
18/// formatting controlled by attributes on the fields of the type.
19/// Second, it allows a JSON-encoding of the type to be translated into borsh-encoding, without
20/// needing access to the original Rust definition of the type.
21///
22/// ## Attributes: `#[sov_wallet(bound = "T: Trait")]`
23///
24/// Tells the proc-macro to add the specified bound to the where clause
25/// of the generated implementation instead of adding the default `T: UniversalWallet` (where `T`
26/// is the type of the annotated field).
27///
28/// This annotation may only be applied to fields, not items.
29///
30/// ## Attributes: #[sov_wallet(hidden)]`
31///
32/// Causes the field to be hidden from the user during display. This is often used for data
33/// that can't be displayed in a human-readable format, such as merkle proofs. If the field is not
34/// present in the `borsh` serialization of the type, use `#[sov_wallet(skip)]` instead.
35///
36/// This annotation may only be applied to fields, not items.
37///
38/// ```rust
39/// use sov_universal_wallet::schema::{Schema, safe_string::SafeString};
40/// use sov_universal_wallet::UniversalWallet;
41///
42/// #[derive(UniversalWallet, borsh::BorshSerialize)]
43/// pub struct Unreadable {
44/// name: SafeString,
45/// #[sov_wallet(hidden)]
46/// opaque_contents: Vec<u8>,
47/// }
48/// let serialized = borsh::to_vec(&Unreadable { name: "foo.txt".try_into().unwrap(), opaque_contents: vec![23, 74, 119, 119, 2, 232, 22]}).unwrap();
49/// assert_eq!(Schema::of_single_type::<Unreadable>().unwrap().display(0, &serialized).unwrap(), r#"{ name: "foo.txt" }"#);
50/// ```
51/// Notice also the use of the SafeString type here - this is to ensure the string can be safely
52/// displayed to the user. By default, unconstrained Strings are forbidden in schemas; for blobs of
53/// data, use byte arrays/vectors directly. If a String is absolutely required, a newtype wrapper
54/// can be used.
55///
56/// ## Attributes: `#[sov_wallet(as_ty = "path::to::Type")]`
57///
58/// Inserts the schema of the specified type in place of the schema for the annotated field. Note that the subsituted type
59/// must have exactly the same borsh serialization as the original.
60///
61/// This is useful when you want to display a foreign type that doesn't implement [`UniversalWallet`](schema::UniversalWallet),
62/// or when you want to override the default schema for a type in a particular context.
63///
64/// ```rust
65/// use sov_universal_wallet::{schema::Schema, UniversalWallet};
66///
67/// // A foreign type that doesn't derive UniversalWallet
68/// #[derive(borsh::BorshSerialize)]
69/// pub struct Foreign(u64);
70///
71/// #[derive(UniversalWallet, borsh::BorshSerialize)]
72/// pub struct Tagged {
73/// #[sov_wallet(as_ty = "u64")]
74/// data: Foreign,
75/// tag: i8,
76/// }
77/// let serialized = borsh::to_vec(&Tagged { data: Foreign(300_000), tag: -5 }).unwrap();
78/// assert_eq!(Schema::of_single_type::<Tagged>().unwrap().display(0, &serialized).unwrap(), r#"{ data: 300000, tag: -5 }"#);
79/// ```
80///
81/// ## Attributes: `#[sov_wallet(skip)]`
82///
83/// Causes the field to be excluded from the Schema entirely. This should be used if the field is not present in
84/// the `borsh` serialization of the type. If the type is present in the serialization but should not be displayed,
85/// use `#[sov_wallet(hidden)]` instead.
86///
87/// ```rust
88/// use sov_universal_wallet::{schema::Schema, UniversalWallet};
89/// #[derive(UniversalWallet, borsh::BorshSerialize)]
90/// pub struct File {
91/// #[borsh(skip)]
92/// #[sov_wallet(skip)]
93/// checksum: Option<[u8;32]>,
94/// contents: Vec<u8>,
95/// }
96/// let serialized = borsh::to_vec(&File { contents: vec![1, 2, 3], checksum: None }).unwrap();
97/// assert_eq!(Schema::of_single_type::<File>().unwrap().display(0, &serialized).unwrap(), r#"{ contents: 0x010203 }"#);
98/// ```
99///
100/// ## Attributes: `#[sov_wallet(fixed_point({decimals}))]`
101///
102/// Specifies fixed-point formatting for an integer field. The decimals specification can be one of
103/// the following:
104/// - an integer literal: directly specifies the number of decimal places to use
105/// - `from_field({n})`: where `n` is an integer denoting a sibling field in the same structure
106/// (by index). That field must be an unsigned integer type, and at runtime its value must be at
107/// most 255. That field's value will be used as the number of decimal points when formatting the
108/// fixed-point number.
109///
110/// ```rust
111/// use sov_universal_wallet::{schema::Schema, UniversalWallet};
112/// #[derive(UniversalWallet, borsh::BorshSerialize)]
113/// pub struct Coins {
114/// #[sov_wallet(fixed_point(from_field(1)))]
115/// amount: u128,
116/// #[sov_wallet(hidden)]
117/// decimals: u8
118/// }
119/// let serialized = borsh::to_vec(&Coins { amount: 475200, decimals: 3 }).unwrap();
120/// assert_eq!(Schema::of_single_type::<Coins>().unwrap().display(0, &serialized).unwrap(), r#"{ amount: 475.2 }"#);
121/// ```
122///
123/// **Security note**: uniquely, this formats the display using user-submitted input. If the
124/// accuracy of the displayed string is important for security, it is crucial that the submitted
125/// value for the amount of decimals be treated as the source of truth, as that will be what the
126/// user will have been presented with.
127/// For example, when using the schema to sign on-chain messages referencing cryptocurrency
128/// amounts, any message where the decimals field does not match the currency's canonical decimal
129/// count **must** be considered invalid and rejected.
130///
131/// ## Attributes: `#[sov_wallet(display({encoding}))]`
132///
133/// Specifies the encoding to use when displaying a byte sequence. The encoding can be one of the following:
134/// - hex: displays the type as a hexadecimal string with the prefix "0x"
135/// - decimal: displays the type as a list of decimal numbers in square brackets
136/// - bech32(prefix = "my_prefix_expr"): displays the type as a bech32-encoded string with the specified human-readable part.
137/// - bech32m(prefix = "my_prefix_expr"): displays the type as a bech32-encoded string with the specified human-readable part.
138///
139/// This annotation may only be applied to fields, not items. The field must have type `[u8;N]` or `Vec<u8>` to use this attribute.
140///
141/// ```rust
142/// use sov_universal_wallet::{schema::Schema, UniversalWallet};
143///
144/// fn prefix() -> &'static str {
145/// "celestia"
146/// }
147///
148/// #[derive(UniversalWallet, borsh::BorshSerialize)]
149/// pub struct CelestiaAddress(
150/// #[sov_wallet(display(bech32(prefix = "prefix()")))]
151/// [u8;32],
152/// );
153/// let serialized = borsh::to_vec(&CelestiaAddress([1; 32])).unwrap();
154/// assert_eq!(Schema::of_single_type::<CelestiaAddress>().unwrap().display(0, &serialized).unwrap(), "celestia1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsagv2r7");
155/// ```
156pub use sov_universal_wallet_macros::UniversalWallet;