1use serde_json::{json, Value};
2
3use crate::{data::{ByteString, Constr0, Constr1, PlutusDataJson}, WError};
4use whisky_macros::ImplConstr;
5
6use super::{byte_string, constr0, constr1};
7
8#[derive(Clone, Debug)]
9pub enum Credential {
10 VerificationKey(VerificationKey),
11 Script(Script),
12}
13
14impl Credential {
15 pub fn new((hash, is_script): (&str, bool)) -> Self {
16 if is_script {
17 Credential::Script(Script::from(hash))
18 } else {
19 Credential::VerificationKey(VerificationKey::from(hash))
20 }
21 }
22}
23
24#[derive(Clone, Debug, ImplConstr)]
25pub struct VerificationKey(pub Constr0<ByteString>);
26
27#[derive(Clone, Debug, ImplConstr)]
28pub struct Script(pub Constr1<ByteString>);
29
30impl PlutusDataJson for Credential {
31 fn to_json(&self) -> Value {
32 match self {
33 Credential::VerificationKey(vk) => vk.to_json(),
34 Credential::Script(script) => script.to_json(),
35 }
36 }
37
38 fn from_json(value: &Value) -> Result<Self, WError> {
39 let tag = value
40 .get("constructor")
41 .ok_or_else(|| WError::new("Credential::from_json", "missing 'constructor' field"))?
42 .as_u64()
43 .ok_or_else(|| WError::new("Credential::from_json", "invalid 'constructor' value"))?;
44
45 match tag {
46 0 => VerificationKey::from_json(value).map(Credential::VerificationKey),
47 1 => Script::from_json(value).map(Credential::Script),
48 _ => Err(WError::new(
49 "Credential::from_json",
50 &format!("unknown constructor tag: {}", tag),
51 )),
52 }
53 }
54}
55
56#[derive(Clone, Debug)]
57pub struct Address {
58 pub payment_key_hash: String,
59 pub stake_credential: Option<String>,
60 pub is_script_payment_key: bool,
61 pub is_script_stake_key: bool,
62}
63
64impl Address {
65 pub fn new(
66 payment_key_hash: &str,
67 stake_credential: Option<&str>,
68 is_script_payment_key: bool,
69 is_script_stake_key: bool,
70 ) -> Self {
71 Address {
72 payment_key_hash: payment_key_hash.to_string(),
73 stake_credential: stake_credential.map(|s| s.to_string()),
74 is_script_payment_key,
75 is_script_stake_key,
76 }
77 }
78}
79
80impl PlutusDataJson for Address {
81 fn to_json(&self) -> Value {
82 if self.is_script_payment_key {
83 script_address(
84 &self.payment_key_hash,
85 self.stake_credential.as_deref(),
86 self.is_script_stake_key,
87 )
88 } else {
89 pub_key_address(
90 &self.payment_key_hash,
91 self.stake_credential.as_deref(),
92 self.is_script_stake_key,
93 )
94 }
95 }
96
97 fn to_json_string(&self) -> String {
98 self.to_json().to_string()
99 }
100
101 fn to_constr_field(&self) -> Vec<serde_json::Value> {
102 vec![self.to_json()]
103 }
104
105 fn from_json(value: &Value) -> Result<Self, WError> {
106 let tag = value
108 .get("constructor")
109 .ok_or_else(|| WError::new("Address::from_json", "missing 'constructor' field"))?
110 .as_u64()
111 .ok_or_else(|| WError::new("Address::from_json", "invalid 'constructor' value"))?;
112
113 if tag != 0 {
114 return Err(WError::new(
115 "Address::from_json",
116 &format!("expected constructor tag 0 for Address, got {}", tag),
117 ));
118 }
119
120 let fields = value
121 .get("fields")
122 .ok_or_else(|| WError::new("Address::from_json", "missing 'fields' field"))?
123 .as_array()
124 .ok_or_else(|| WError::new("Address::from_json", "invalid 'fields' value"))?;
125
126 if fields.len() != 2 {
127 return Err(WError::new(
128 "Address::from_json",
129 "expected 2 fields for Address",
130 ));
131 }
132
133 let payment_cred = &fields[0];
135 let payment_tag = payment_cred
136 .get("constructor")
137 .and_then(|c| c.as_u64())
138 .ok_or_else(|| WError::new("Address::from_json", "invalid payment credential"))?;
139 let is_script_payment_key = payment_tag == 1;
140
141 let payment_fields = payment_cred
142 .get("fields")
143 .and_then(|f| f.as_array())
144 .ok_or_else(|| WError::new("Address::from_json", "invalid payment credential fields"))?;
145
146 let payment_key_hash = payment_fields
147 .first()
148 .and_then(|f| f.get("bytes"))
149 .and_then(|b| b.as_str())
150 .ok_or_else(|| WError::new("Address::from_json", "invalid payment key hash"))?
151 .to_string();
152
153 let stake_cred = &fields[1];
156 let stake_tag = stake_cred
157 .get("constructor")
158 .and_then(|c| c.as_u64())
159 .ok_or_else(|| WError::new("Address::from_json", "invalid stake credential"))?;
160
161 let (stake_credential, is_script_stake_key) = if stake_tag == 1 {
162 (None, false)
164 } else {
165 let stake_fields = stake_cred
167 .get("fields")
168 .and_then(|f| f.as_array())
169 .ok_or_else(|| WError::new("Address::from_json", "invalid stake credential fields"))?;
170
171 if stake_fields.is_empty() {
172 (None, false)
173 } else {
174 let inner_wrapper = stake_fields.first()
176 .and_then(|f| f.get("fields"))
177 .and_then(|f| f.as_array())
178 .and_then(|f| f.first())
179 .ok_or_else(|| WError::new("Address::from_json", "invalid stake credential structure"))?;
180
181 let inner_tag = inner_wrapper
182 .get("constructor")
183 .and_then(|c| c.as_u64())
184 .ok_or_else(|| WError::new("Address::from_json", "invalid stake credential inner tag"))?;
185
186 let is_script = inner_tag == 1;
187
188 let stake_hash = inner_wrapper
189 .get("fields")
190 .and_then(|f| f.as_array())
191 .and_then(|f| f.first())
192 .and_then(|f| f.get("bytes"))
193 .and_then(|b| b.as_str())
194 .ok_or_else(|| WError::new("Address::from_json", "invalid stake key hash"))?
195 .to_string();
196
197 (Some(stake_hash), is_script)
198 }
199 };
200
201 Ok(Address {
202 payment_key_hash,
203 stake_credential,
204 is_script_payment_key,
205 is_script_stake_key,
206 })
207 }
208}
209
210pub fn payment_pub_key_hash(pub_key_hash: &str) -> Value {
211 byte_string(pub_key_hash)
212}
213
214pub fn pub_key_hash(pub_key_hash: &str) -> Value {
215 byte_string(pub_key_hash)
216}
217
218pub fn maybe_staking_hash(stake_credential: &str, is_script_stake_key: bool) -> Value {
219 if stake_credential.is_empty() {
220 constr1(json!([]))
221 } else if is_script_stake_key {
222 constr0(vec![constr0(vec![constr1(vec![byte_string(
223 stake_credential,
224 )])])])
225 } else {
226 constr0(vec![constr0(vec![constr0(vec![byte_string(
227 stake_credential,
228 )])])])
229 }
230}
231
232pub fn pub_key_address(
233 bytes: &str,
234 stake_credential: Option<&str>,
235 is_script_stake_key: bool,
236) -> Value {
237 constr0(vec![
238 constr0(vec![byte_string(bytes)]),
239 maybe_staking_hash(stake_credential.unwrap_or(""), is_script_stake_key),
240 ])
241}
242
243pub fn script_address(
244 bytes: &str,
245 stake_credential: Option<&str>,
246 is_script_stake_key: bool,
247) -> Value {
248 constr0(vec![
249 constr1(vec![byte_string(bytes)]),
250 maybe_staking_hash(stake_credential.unwrap_or(""), is_script_stake_key),
251 ])
252}