cardano_sdk/cardano/
address.rs

1//  This Source Code Form is subject to the terms of the Mozilla Public
2//  License, v. 2.0. If a copy of the MPL was not distributed with this
3//  file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5use crate::{Credential, NetworkId, pallas};
6use anyhow::anyhow;
7use std::{cmp::Ordering, fmt, marker::PhantomData, str::FromStr, sync::Arc};
8
9pub mod kind;
10pub use kind::IsAddressKind;
11
12/// An address captures spending and delegation conditions of assets in the network.
13///
14/// Addresses can be one of two [`kind`]:
15///
16/// - [`kind::Byron`]: legacy, not longer used. Also called _"bootstrap"_ addresses sometimes.
17/// - [`kind::Shelley`]: most used and modern format, which can bear delegation rights.
18///
19/// An [`Address`] can be constructed in a variety of ways.
20///
21/// 1. Either directly using the provided builder:
22///    - [`Address<kind::Shelley>::new`]
23///    - [`Address<kind::Shelley>::with_delegation`]
24///
25/// 2. Using the [`address!`](crate::address!) or [`address_test!`](crate::address_test!) macros.
26///
27/// 3. Or by converting from another representation (e.g. bech32, base58 or base16 text strings, or
28///    raw bytes):
29///
30///    ```rust
31///    # use cardano_sdk::{Address, address::kind};
32///    // Parse a string as Shelley address; will fail if presented with a Byron address:
33///    assert!(
34///      <Address<kind::Shelley>>::try_from(
35///        "addr1v83gkkw3nqzakg5xynlurqcfqhgd65vkfvf5xv8tx25ufds2yvy2h"
36///      ).is_ok()
37///    );
38///
39///    assert!(
40///      <Address<kind::Shelley>>::try_from(
41///        "Ae2tdPwUPEYwNguM7TB3dMnZMfZxn1pjGHyGdjaF4mFqZF9L3bj6cdhiH8t"
42///      ).is_err()
43///    );
44///    ```
45///
46///    ```rust
47///    # use cardano_sdk::{Address, address::kind};
48///    // Parse a string as any address; will also success on Byron addresses:
49///    assert!(
50///      <Address<kind::Any>>::try_from(
51///        "addr1v83gkkw3nqzakg5xynlurqcfqhgd65vkfvf5xv8tx25ufds2yvy2h"
52///      ).is_ok()
53///    );
54///
55///    assert!(
56///      <Address<kind::Any>>::try_from(
57///        "Ae2tdPwUPEYwNguM7TB3dMnZMfZxn1pjGHyGdjaF4mFqZF9L3bj6cdhiH8t"
58///      ).is_ok()
59///    );
60///    ```
61///
62///    ```rust
63///    # use cardano_sdk::{Address, address::kind};
64///    // Also work with base16 encoded addresses:
65///    assert!(
66///      <Address<kind::Shelley>>::try_from(
67///        "61e28b59d19805db228624ffc1830905d0dd51964b134330eb32a9c4b6"
68///      ).is_ok()
69///    );
70///    ```
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct Address<T: IsAddressKind>(Arc<AddressKind>, PhantomData<T>);
73
74impl<T: IsAddressKind + Eq> PartialOrd for Address<T> {
75    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
76        Some(self.cmp(rhs))
77    }
78}
79
80impl<T: IsAddressKind + Eq> Ord for Address<T> {
81    fn cmp(&self, rhs: &Self) -> Ordering {
82        <Vec<u8>>::from(self).cmp(&<Vec<u8>>::from(rhs))
83    }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87enum AddressKind {
88    Byron(pallas::ByronAddress),
89    Shelley(pallas::ShelleyAddress),
90}
91
92// ------------------------------------------------------ Building (Shelley)
93
94impl Address<kind::Shelley> {
95    /// See also [`address!`](crate::address!)/[`address_test!`](crate::address_test!)
96    pub fn new(network: NetworkId, payment: Credential) -> Self {
97        Self::from(pallas::ShelleyAddress::new(
98            pallas::Network::from(network),
99            pallas::ShelleyPaymentPart::from(payment),
100            pallas::ShelleyDelegationPart::Null,
101        ))
102    }
103
104    /// See also [`address!`](crate::address!)/[`address_test!`](crate::address_test!)
105    pub fn with_delegation(mut self, delegation: Credential) -> Self {
106        self = Self::from(pallas::ShelleyAddress::new(
107            pallas::Network::from(self.network_id()),
108            pallas::ShelleyPaymentPart::from(self.payment()),
109            pallas::ShelleyDelegationPart::from(delegation),
110        ));
111
112        self
113    }
114}
115
116// ---------------------------------------------------- Inspecting (Shelley)
117
118impl Address<kind::Shelley> {
119    fn cast(&self) -> &pallas::ShelleyAddress {
120        match self.0.as_ref() {
121            AddressKind::Shelley(shelley) => shelley,
122            _ => unreachable!(),
123        }
124    }
125
126    // NOTE: Technically, this method should also be available on Byron kind. But that requires
127    // accessing the internal address attributes, which Pallas doesn't provide support for and this
128    // is quite out of scope of our mission right now.
129    pub fn network_id(&self) -> NetworkId {
130        NetworkId::from(self.cast().network())
131    }
132
133    pub fn payment(&self) -> Credential {
134        Credential::from(self.cast().payment())
135    }
136
137    pub fn delegation(&self) -> Option<Credential> {
138        Credential::try_from(self.cast().delegation()).ok()
139    }
140}
141
142// ------------------------------------------------------------- Constructing (Any)
143
144impl Default for Address<kind::Any> {
145    fn default() -> Self {
146        Self::from(
147            Address::new(NetworkId::MAINNET, Credential::default())
148                .with_delegation(Credential::default()),
149        )
150    }
151}
152
153// ------------------------------------------------------------- Inspecting (Any)
154
155impl<T: IsAddressKind> Address<T> {
156    /// Check whether an address is a [`kind::Byron`] address. To carry this proof at the
157    /// type-level, use [`Self::as_byron`].
158    ///
159    /// # examples
160    ///
161    /// ```rust
162    /// # use cardano_sdk::{address};
163    /// assert_eq!(
164    ///     address!(
165    ///         "37btjrVyb4KDXBNC4haBVPCrro8AQPHwvCMp3R\
166    ///          FhhSVWwfFmZ6wwzSK6JK1hY6wHNmtrpTf1kdbv\
167    ///          a8TCneM2YsiXT7mrzT21EacHnPpz5YyUdj64na"
168    ///     ).is_byron(),
169    ///     true,
170    /// );
171    /// ```
172    ///
173    /// ```rust
174    /// # use cardano_sdk::{address};
175    /// assert_eq!(
176    ///     address!("addr1v83gkkw3nqzakg5xynlurqcfqhgd65vkfvf5xv8tx25ufds2yvy2h").is_byron(),
177    ///     false,
178    /// );
179    /// ```
180    pub fn is_byron(&self) -> bool {
181        matches!(self.0.as_ref(), &AddressKind::Byron(..))
182    }
183
184    /// Refine the kind of the address, assuming it is a [`kind::Byron`] to enable specific methods
185    /// for this kind.
186    pub fn as_byron(&self) -> Option<Address<kind::Byron>> {
187        if self.is_byron() {
188            return Some(Address(self.0.clone(), PhantomData));
189        }
190
191        None
192    }
193
194    /// Check whether an address is a [`kind::Byron`] address. To carry this proof at the
195    /// type-level, use [`Self::as_shelley`].
196    ///
197    /// # examples
198    ///
199    /// ```rust
200    /// # use cardano_sdk::{address};
201    /// assert_eq!(
202    ///     address!(
203    ///         "37btjrVyb4KDXBNC4haBVPCrro8AQPHwvCMp3R\
204    ///          FhhSVWwfFmZ6wwzSK6JK1hY6wHNmtrpTf1kdbv\
205    ///          a8TCneM2YsiXT7mrzT21EacHnPpz5YyUdj64na"
206    ///     ).is_shelley(),
207    ///     false,
208    /// );
209    /// ```
210    ///
211    /// ```rust
212    /// # use cardano_sdk::{address};
213    /// assert_eq!(
214    ///     address!("addr1v83gkkw3nqzakg5xynlurqcfqhgd65vkfvf5xv8tx25ufds2yvy2h").is_shelley(),
215    ///     true,
216    /// );
217    /// ```
218    pub fn is_shelley(&self) -> bool {
219        matches!(&self.0.as_ref(), AddressKind::Shelley(..))
220    }
221
222    /// Refine the kind of the address, assuming it is a [`kind::Shelley`] to enable specific methods
223    /// for this kind.
224    pub fn as_shelley(&self) -> Option<Address<kind::Shelley>> {
225        if self.is_shelley() {
226            return Some(Address(self.0.clone(), PhantomData));
227        }
228
229        None
230    }
231}
232
233// ----------------------------------------------------------- Converting (from)
234
235impl From<pallas::ByronAddress> for Address<kind::Byron> {
236    fn from(byron_address: pallas::ByronAddress) -> Self {
237        Self(Arc::new(AddressKind::Byron(byron_address)), PhantomData)
238    }
239}
240
241impl From<pallas::ShelleyAddress> for Address<kind::Shelley> {
242    fn from(shelley_address: pallas::ShelleyAddress) -> Self {
243        Self(Arc::new(AddressKind::Shelley(shelley_address)), PhantomData)
244    }
245}
246
247impl From<Address<kind::Byron>> for Address<kind::Any> {
248    fn from(byron_address: Address<kind::Byron>) -> Self {
249        Self(byron_address.0, PhantomData)
250    }
251}
252
253impl From<Address<kind::Shelley>> for Address<kind::Any> {
254    fn from(shelley_address: Address<kind::Shelley>) -> Self {
255        Self(shelley_address.0, PhantomData)
256    }
257}
258
259impl TryFrom<pallas::Address> for Address<kind::Any> {
260    type Error = anyhow::Error;
261
262    fn try_from(address: pallas::Address) -> anyhow::Result<Self> {
263        match address {
264            pallas_addresses::Address::Byron(byron) => Ok(Address::<kind::Any>(
265                Arc::new(AddressKind::Byron(byron)),
266                PhantomData,
267            )),
268            pallas_addresses::Address::Shelley(shelley) => Ok(Address::<kind::Any>(
269                Arc::new(AddressKind::Shelley(shelley)),
270                PhantomData,
271            )),
272            pallas_addresses::Address::Stake(_) => {
273                Err(anyhow!("found stake address masquerading as address"))
274            }
275        }
276    }
277}
278
279impl TryFrom<pallas::Address> for Address<kind::Shelley> {
280    type Error = anyhow::Error;
281
282    fn try_from(address: pallas::Address) -> anyhow::Result<Self> {
283        match address {
284            pallas_addresses::Address::Shelley(shelley) => Ok(Address::<kind::Shelley>(
285                Arc::new(AddressKind::Shelley(shelley)),
286                PhantomData,
287            )),
288            pallas_addresses::Address::Byron(_) | pallas_addresses::Address::Stake(_) => {
289                Err(anyhow!("not a shelley address"))
290            }
291        }
292    }
293}
294
295impl<T: IsAddressKind> TryFrom<&str> for Address<T>
296where
297    Address<T>: TryFrom<pallas::Address, Error = anyhow::Error>,
298{
299    type Error = anyhow::Error;
300
301    fn try_from(text: &str) -> anyhow::Result<Self> {
302        Self::try_from(pallas::Address::from_str(text).map_err(|e| anyhow!(e))?)
303    }
304}
305
306impl<T: IsAddressKind> std::str::FromStr for Address<T>
307where
308    Address<T>: for<'a> TryFrom<&'a str, Error = anyhow::Error>,
309{
310    type Err = anyhow::Error;
311
312    fn from_str(s: &str) -> anyhow::Result<Address<T>> {
313        Self::try_from(s)
314    }
315}
316
317impl<T: IsAddressKind> TryFrom<&[u8]> for Address<T>
318where
319    Address<T>: TryFrom<pallas::Address, Error = anyhow::Error>,
320{
321    type Error = anyhow::Error;
322
323    fn try_from(bytes: &[u8]) -> anyhow::Result<Self> {
324        Self::try_from(pallas::Address::from_bytes(bytes).map_err(|e| anyhow!(e))?)
325    }
326}
327
328// --------------------------------------------------------------- Converting (to)
329
330impl<T: IsAddressKind> fmt::Display for Address<T> {
331    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
332        match self.0.as_ref() {
333            AddressKind::Byron(byron) => f.write_str(byron.to_base58().as_str()),
334            AddressKind::Shelley(shelley) => f.write_str(
335                shelley
336                    .to_bech32()
337                    .expect("failed to convert to bech32!?")
338                    .as_str(),
339            ),
340        }
341    }
342}
343
344impl<T: IsAddressKind> From<&Address<T>> for Vec<u8> {
345    fn from(address: &Address<T>) -> Self {
346        match address.0.as_ref() {
347            AddressKind::Byron(byron) => byron.to_vec(),
348            AddressKind::Shelley(shelley) => shelley.to_vec(),
349        }
350    }
351}
352
353// --------------------------------------------------------------- Wasm
354
355#[cfg(any(test, feature = "test-utils"))]
356pub mod tests {
357
358    // -------------------------------------------------------------- Generators
359
360    pub mod generators {
361        use crate::{Address, address::kind::*, any};
362        use proptest::{option, prelude::*};
363
364        prop_compose! {
365            pub fn address_shelley()(
366                network_id in any::network_id(),
367                payment_credential in any::credential(),
368                delegation_credential_opt in option::of(any::credential()),
369            ) -> Address<Shelley> {
370                let address = Address::new(network_id, payment_credential);
371
372                if let Some(delegation_credential) = delegation_credential_opt {
373                    return address.with_delegation(delegation_credential)
374                }
375
376                address
377            }
378        }
379    }
380}