layer_climb_address/address/
cosmos.rs1use std::{borrow::Cow, str::FromStr};
2
3use anyhow::{anyhow, bail, Context, Result};
4use cosmwasm_schema::cw_schema;
5use subtle_encoding::bech32;
6
7#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, bincode::Encode, bincode::Decode)]
11#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
12pub struct CosmosAddr {
13 bech32_addr: String,
14 prefix_len: usize,
16}
17
18impl CosmosAddr {
22 pub fn new_unchecked(value: impl ToString, prefix_len: usize) -> Self {
23 Self {
24 bech32_addr: value.to_string(),
25 prefix_len,
26 }
27 }
28
29 pub fn new_bytes(bytes: Vec<u8>, prefix: &str) -> Result<Self> {
30 if !prefix.chars().all(|c| matches!(c, 'a'..='z' | '0'..='9')) {
31 bail!("expected prefix to be lowercase alphanumeric characters only");
32 }
33
34 if bytes.len() > 255 {
35 bail!(
36 "account ID should be at most 255 bytes long, but was {} bytes long",
37 bytes.len()
38 );
39 }
40
41 let bech32_addr = bech32::encode(prefix, bytes);
42
43 Ok(Self {
44 bech32_addr,
45 prefix_len: prefix.len(),
46 })
47 }
48
49 pub fn new_pub_key(pub_key: &tendermint::PublicKey, prefix: &str) -> Result<Self> {
51 match pub_key {
52 tendermint::PublicKey::Secp256k1(encoded_point) => {
53 let id = tendermint::account::Id::from(*encoded_point);
54 Self::new_bytes(id.as_bytes().to_vec(), prefix)
55 }
56 _ => Err(anyhow!(
57 "Invalid public key type, currently only supports secp256k1"
58 )),
59 }
60 }
61
62 pub fn new_str(value: &str, prefix: Option<&str>) -> Result<Self> {
65 let (decoded_prefix, decoded_bytes) = if value.starts_with(|c: char| c.is_uppercase()) {
66 bech32::decode_upper(value)
67 } else {
68 bech32::decode(value)
69 }
70 .context(format!("invalid bech32: '{value}'"))?;
71
72 if let Some(prefix) = prefix {
73 if decoded_prefix != prefix {
74 bail!(
75 "Address prefix \"{}\" does not match expected prefix \"{}\"",
76 decoded_prefix,
77 prefix
78 );
79 }
80 }
81
82 Self::new_bytes(decoded_bytes, &decoded_prefix)
83 }
84
85 pub fn to_vec(&self) -> Vec<u8> {
86 let (_, bytes) = bech32::decode(&self.bech32_addr).unwrap();
87 bytes
88 }
89
90 pub fn prefix(&self) -> &str {
91 &self.bech32_addr[..self.prefix_len]
92 }
93
94 pub fn change_prefix(&self, new_prefix: &str) -> Result<Self> {
95 if self.prefix() == new_prefix {
96 Ok(self.clone())
97 } else {
98 Self::new_str(&self.bech32_addr, Some(new_prefix))
99 }
100 }
101}
102
103impl std::fmt::Display for CosmosAddr {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 write!(f, "{}", self.bech32_addr)
107 }
108}
109
110impl FromStr for CosmosAddr {
111 type Err = anyhow::Error;
112
113 fn from_str(s: &str) -> Result<Self> {
114 Self::new_str(s, None)
115 }
116}
117
118impl serde::Serialize for CosmosAddr {
119 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120 where
121 S: serde::Serializer,
122 {
123 serializer.serialize_str(&self.to_string())
124 }
125}
126
127impl<'de> serde::Deserialize<'de> for CosmosAddr {
128 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
129 where
130 D: serde::Deserializer<'de>,
131 {
132 let s = String::deserialize(deserializer)?;
133 s.parse().map_err(serde::de::Error::custom)
134 }
135}
136
137impl cw_schema::Schemaifier for CosmosAddr {
138 #[inline]
139 fn visit_schema(visitor: &mut cw_schema::SchemaVisitor) -> cw_schema::DefinitionReference {
140 let node = cw_schema::Node {
141 name: Cow::Borrowed(std::any::type_name::<Self>()),
142 description: None,
143 value: cw_schema::NodeType::String,
144 };
145
146 visitor.insert(Self::id(), node)
147 }
148}
149
150impl cosmwasm_schema::schemars::JsonSchema for CosmosAddr {
151 fn schema_name() -> String {
152 "CosmosAddr".into()
153 }
154
155 fn json_schema(
156 _generator: &mut cosmwasm_schema::schemars::r#gen::SchemaGenerator,
157 ) -> cosmwasm_schema::schemars::schema::Schema {
158 cosmwasm_schema::schemars::schema::Schema::Object(
159 cosmwasm_schema::schemars::schema::SchemaObject {
160 instance_type: Some(cosmwasm_schema::schemars::schema::SingleOrVec::Single(
161 Box::new(cosmwasm_schema::schemars::schema::InstanceType::String),
162 )),
163 format: Some("cosmos-address".into()),
164 ..Default::default()
165 },
166 )
167 }
168}
169
170impl TryFrom<cosmwasm_std::Addr> for CosmosAddr {
172 type Error = anyhow::Error;
173
174 fn try_from(addr: cosmwasm_std::Addr) -> Result<Self> {
175 Self::new_str(addr.as_str(), None)
176 }
177}
178
179impl From<CosmosAddr> for cosmwasm_std::Addr {
180 fn from(addr: CosmosAddr) -> Self {
181 cosmwasm_std::Addr::unchecked(addr.to_string())
182 }
183}
184
185impl TryFrom<&cosmwasm_std::Addr> for CosmosAddr {
186 type Error = anyhow::Error;
187
188 fn try_from(addr: &cosmwasm_std::Addr) -> Result<Self> {
189 Self::new_str(addr.as_str(), None)
190 }
191}