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