1use borsh::{BorshDeserialize, BorshSerialize};
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use smallvec::SmallVec;
12use std::fmt::{Display, Formatter};
13use thiserror::Error;
14use wasm_bindgen::prelude::*;
15use workflow_wasm::{
16 convert::{Cast, CastFromJs, TryCastFromJs},
17 extensions::object::*,
18};
19
20mod bech32;
21
22#[derive(Error, PartialEq, Eq, Debug, Clone)]
24pub enum AddressError {
25 #[error("The address has an invalid prefix {0}")]
26 InvalidPrefix(String),
27
28 #[error("The address prefix is missing")]
29 MissingPrefix,
30
31 #[error("The address has an invalid version {0}")]
32 InvalidVersion(u8),
33
34 #[error("The address has an invalid version {0}")]
35 InvalidVersionString(String),
36
37 #[error("The address contains an invalid character {0}")]
38 DecodingError(char),
39
40 #[error("The address checksum is invalid (must be exactly 8 bytes)")]
41 BadChecksumSize,
42
43 #[error("The address checksum is invalid")]
44 BadChecksum,
45
46 #[error("The address payload is invalid")]
47 BadPayload,
48
49 #[error("The address is invalid")]
50 InvalidAddress,
51
52 #[error("The address array is invalid")]
53 InvalidAddressArray,
54
55 #[error("{0}")]
56 WASM(String),
57}
58
59impl From<workflow_wasm::error::Error> for AddressError {
60 fn from(e: workflow_wasm::error::Error) -> Self {
61 AddressError::WASM(e.to_string())
62 }
63}
64
65#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
67#[borsh(use_discriminant = true)]
68pub enum Prefix {
69 #[serde(rename = "kaspa")]
70 Mainnet,
71 #[serde(rename = "kaspatest")]
72 Testnet,
73 #[serde(rename = "kaspasim")]
74 Simnet,
75 #[serde(rename = "kaspadev")]
76 Devnet,
77 #[cfg(test)]
78 A,
79 #[cfg(test)]
80 B,
81}
82
83impl Prefix {
84 fn as_str(&self) -> &'static str {
85 match self {
86 Prefix::Mainnet => "kaspa",
87 Prefix::Testnet => "kaspatest",
88 Prefix::Simnet => "kaspasim",
89 Prefix::Devnet => "kaspadev",
90 #[cfg(test)]
91 Prefix::A => "a",
92 #[cfg(test)]
93 Prefix::B => "b",
94 }
95 }
96
97 #[inline(always)]
98 fn is_test(&self) -> bool {
99 #[cfg(not(test))]
100 return false;
101 #[cfg(test)]
102 matches!(self, Prefix::A | Prefix::B)
103 }
104}
105
106impl Display for Prefix {
107 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
108 f.write_str(self.as_str())
109 }
110}
111
112impl TryFrom<&str> for Prefix {
113 type Error = AddressError;
114
115 fn try_from(prefix: &str) -> Result<Self, Self::Error> {
116 match prefix {
117 "kaspa" => Ok(Prefix::Mainnet),
118 "kaspatest" => Ok(Prefix::Testnet),
119 "kaspasim" => Ok(Prefix::Simnet),
120 "kaspadev" => Ok(Prefix::Devnet),
121 #[cfg(test)]
122 "a" => Ok(Prefix::A),
123 #[cfg(test)]
124 "b" => Ok(Prefix::B),
125 _ => Err(AddressError::InvalidPrefix(prefix.to_string())),
126 }
127 }
128}
129
130#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
135#[repr(u8)]
136#[borsh(use_discriminant = true)]
137#[wasm_bindgen(js_name = "AddressVersion")]
138pub enum Version {
139 PubKey = 0,
141 PubKeyECDSA = 1,
143 ScriptHash = 8,
145}
146
147impl TryFrom<&str> for Version {
148 type Error = AddressError;
149
150 fn try_from(value: &str) -> Result<Self, Self::Error> {
151 match value {
152 "PubKey" => Ok(Version::PubKey),
153 "PubKeyECDSA" => Ok(Version::PubKeyECDSA),
154 "ScriptHash" => Ok(Version::ScriptHash),
155 _ => Err(AddressError::InvalidVersionString(value.to_owned())),
156 }
157 }
158}
159
160impl Version {
161 pub fn public_key_len(&self) -> usize {
162 match self {
163 Version::PubKey => 32,
164 Version::PubKeyECDSA => 33,
165 Version::ScriptHash => 32,
166 }
167 }
168}
169
170impl TryFrom<u8> for Version {
171 type Error = AddressError;
172
173 fn try_from(value: u8) -> Result<Self, Self::Error> {
174 match value {
175 0 => Ok(Version::PubKey),
176 1 => Ok(Version::PubKeyECDSA),
177 8 => Ok(Version::ScriptHash),
178 _ => Err(AddressError::InvalidVersion(value)),
179 }
180 }
181}
182
183impl Display for Version {
184 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
185 match self {
186 Version::PubKey => write!(f, "PubKey"),
187 Version::PubKeyECDSA => write!(f, "PubKeyECDSA"),
188 Version::ScriptHash => write!(f, "ScriptHash"),
189 }
190 }
191}
192
193pub const PAYLOAD_VECTOR_SIZE: usize = 36;
198
199pub type PayloadVec = SmallVec<[u8; PAYLOAD_VECTOR_SIZE]>;
201
202#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash, CastFromJs)]
206#[wasm_bindgen(inspectable)]
207pub struct Address {
208 #[wasm_bindgen(skip)]
209 pub prefix: Prefix,
210 #[wasm_bindgen(skip)]
211 pub version: Version,
212 #[wasm_bindgen(skip)]
213 pub payload: PayloadVec,
214}
215
216impl std::fmt::Debug for Address {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 if self.version == Version::PubKey {
219 write!(f, "{}", String::from(self))
220 } else {
221 write!(f, "{} ({})", String::from(self), self.version)
222 }
223 }
224}
225
226impl Address {
227 pub fn new(prefix: Prefix, version: Version, payload: &[u8]) -> Self {
228 if !prefix.is_test() {
229 assert_eq!(payload.len(), version.public_key_len());
230 }
231 Self { prefix, payload: PayloadVec::from_slice(payload), version }
232 }
233}
234
235#[wasm_bindgen]
236impl Address {
237 #[wasm_bindgen(constructor)]
238 pub fn constructor(address: &str) -> Address {
239 address.try_into().unwrap_or_else(|err| panic!("Address::constructor() - address error `{}`: {err}", address))
240 }
241
242 #[wasm_bindgen(js_name=validate)]
243 pub fn validate(address: &str) -> bool {
244 Self::try_from(address).is_ok()
245 }
246
247 #[wasm_bindgen(js_name = toString)]
249 pub fn address_to_string(&self) -> String {
250 self.into()
251 }
252
253 #[wasm_bindgen(getter, js_name = "version")]
254 pub fn version_to_string(&self) -> String {
255 self.version.to_string()
256 }
257
258 #[wasm_bindgen(getter, js_name = "prefix")]
259 pub fn prefix_to_string(&self) -> String {
260 self.prefix.to_string()
261 }
262
263 #[wasm_bindgen(setter, js_name = "setPrefix")]
264 pub fn set_prefix_from_str(&mut self, prefix: &str) {
265 self.prefix = Prefix::try_from(prefix).unwrap_or_else(|err| panic!("Address::prefix() - invalid prefix `{prefix}`: {err}"));
266 }
267
268 #[wasm_bindgen(getter, js_name = "payload")]
269 pub fn payload_to_string(&self) -> String {
270 self.encode_payload()
271 }
272
273 pub fn short(&self, n: usize) -> String {
274 let payload = self.encode_payload();
275 let n = std::cmp::min(n, payload.len() / 4);
276 format!("{}:{}....{}", self.prefix, &payload[0..n], &payload[payload.len() - n..])
277 }
278}
279
280impl Display for Address {
281 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
282 write!(f, "{}", String::from(self))
283 }
284}
285
286impl BorshSerialize for Address {
292 fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
293 borsh::BorshSerialize::serialize(&self.prefix, writer)?;
294 borsh::BorshSerialize::serialize(&self.version, writer)?;
295 borsh::BorshSerialize::serialize(&self.payload.as_slice(), writer)?;
297 Ok(())
298 }
299}
300
301impl BorshDeserialize for Address {
302 fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
303 let prefix: Prefix = borsh::BorshDeserialize::deserialize_reader(reader)?;
304 let version: Version = borsh::BorshDeserialize::deserialize_reader(reader)?;
305 let payload: Vec<u8> = borsh::BorshDeserialize::deserialize_reader(reader)?;
306 Ok(Self::new(prefix, version, &payload))
307 }
308}
309
310impl From<Address> for String {
311 fn from(address: Address) -> Self {
312 (&address).into()
313 }
314}
315
316impl From<&Address> for String {
317 fn from(address: &Address) -> Self {
318 format!("{}:{}", address.prefix, address.encode_payload())
319 }
320}
321
322impl TryFrom<String> for Address {
323 type Error = AddressError;
324
325 fn try_from(value: String) -> Result<Self, Self::Error> {
326 value.as_str().try_into()
327 }
328}
329
330impl TryFrom<&str> for Address {
331 type Error = AddressError;
332
333 fn try_from(value: &str) -> Result<Self, Self::Error> {
334 match value.split_once(':') {
335 Some((prefix, payload)) => Self::decode_payload(prefix.try_into()?, payload),
336 None => Err(AddressError::MissingPrefix),
337 }
338 }
339}
340
341impl Serialize for Address {
342 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
343 where
344 S: Serializer,
345 {
346 serializer.serialize_str(&self.to_string())
347 }
348}
349
350impl<'de> Deserialize<'de> for Address {
351 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
352 where
353 D: Deserializer<'de>,
354 {
355 #[derive(Default)]
356 pub struct AddressVisitor<'de> {
357 marker: std::marker::PhantomData<Address>,
358 lifetime: std::marker::PhantomData<&'de ()>,
359 }
360 impl<'de> serde::de::Visitor<'de> for AddressVisitor<'de> {
361 type Value = Address;
362
363 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
364 #[cfg(target_arch = "wasm32")]
365 {
366 write!(formatter, "string-type: string, str; bytes-type: slice of bytes, vec of bytes; map; number-type - pointer")
367 }
368 #[cfg(not(target_arch = "wasm32"))]
369 {
370 write!(formatter, "string-type: string, str; bytes-type: slice of bytes, vec of bytes; map")
371 }
372 }
373
374 #[cfg(target_arch = "wasm32")]
376 fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
377 where
378 E: serde::de::Error,
379 {
380 self.visit_u32(v as u32)
381 }
382 #[cfg(target_arch = "wasm32")]
383 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
384 where
385 E: serde::de::Error,
386 {
387 self.visit_u32(v as u32)
388 }
389
390 #[cfg(target_arch = "wasm32")]
391 fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
392 where
393 E: serde::de::Error,
394 {
395 self.visit_u32(v as u32)
396 }
397 #[cfg(target_arch = "wasm32")]
398 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
399 where
400 E: serde::de::Error,
401 {
402 self.visit_u32(v as u32)
403 }
404 #[cfg(target_arch = "wasm32")]
405 fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
406 where
407 E: serde::de::Error,
408 {
409 use wasm_bindgen::convert::RefFromWasmAbi;
410 let instance_ref = unsafe { Self::Value::ref_from_abi(v) }; Ok(instance_ref.clone())
412 }
413 #[cfg(target_arch = "wasm32")]
414 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
415 where
416 E: serde::de::Error,
417 {
418 self.visit_u32(v as u32)
419 }
420
421 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
422 where
423 E: serde::de::Error,
424 {
425 Address::try_from(v).map_err(serde::de::Error::custom)
426 }
427
428 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
429 where
430 E: serde::de::Error,
431 {
432 Address::try_from(v).map_err(serde::de::Error::custom)
433 }
434
435 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
436 where
437 E: serde::de::Error,
438 {
439 Address::try_from(v).map_err(serde::de::Error::custom)
440 }
441
442 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
443 where
444 E: serde::de::Error,
445 {
446 let str = std::str::from_utf8(v).map_err(serde::de::Error::custom)?;
447 Address::try_from(str).map_err(serde::de::Error::custom)
448 }
449
450 fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
451 where
452 E: serde::de::Error,
453 {
454 let str = std::str::from_utf8(v).map_err(serde::de::Error::custom)?;
455 Address::try_from(str).map_err(serde::de::Error::custom)
456 }
457
458 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
459 where
460 E: serde::de::Error,
461 {
462 let str = std::str::from_utf8(&v).map_err(serde::de::Error::custom)?;
463 Address::try_from(str).map_err(serde::de::Error::custom)
464 }
465
466 fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
467 where
468 A: serde::de::MapAccess<'de>,
469 {
470 let mut prefix: Option<String> = None;
471 let mut payload: Option<String> = None;
472
473 while let Some((key, value)) = access.next_entry::<String, String>()? {
474 #[cfg(test)]
475 web_sys::console::log_3(&"key value: ".into(), &key.clone().into(), &value.clone().into());
476
477 match key.as_ref() {
478 "prefix" => {
479 prefix = Some(value.to_string());
480 }
481 "payload" => {
482 payload = Some(value.to_string());
483 }
484 "version" => continue,
485 unknown_field => {
486 return Err(serde::de::Error::unknown_field(unknown_field, &["prefix", "payload", "version"]))
487 }
488 }
489 if prefix.is_some() && payload.is_some() {
490 break;
491 }
492 }
493 let (prefix, payload) = match (prefix, payload) {
494 (Some(prefix), Some(payload)) => (prefix, payload),
495 (None, _) => return Err(serde::de::Error::missing_field("prefix")),
496 (_, None) => return Err(serde::de::Error::missing_field("payload")),
497 };
498 Address::decode_payload(prefix.as_str().try_into().map_err(serde::de::Error::custom)?, &payload)
499 .map_err(serde::de::Error::custom)
500 }
501 }
502
503 deserializer.deserialize_any(AddressVisitor::default())
504 }
505}
506
507impl TryCastFromJs for Address {
508 type Error = AddressError;
509 fn try_cast_from<'a, R>(value: &'a R) -> Result<Cast<Self>, Self::Error>
510 where
511 R: AsRef<JsValue> + 'a,
512 {
513 Self::resolve(value, || {
514 if let Some(string) = value.as_ref().as_string() {
515 Address::try_from(string)
516 } else if let Some(object) = js_sys::Object::try_from(value.as_ref()) {
517 let prefix: Prefix = object.get_string("prefix")?.as_str().try_into()?;
518 let payload = object.get_string("payload")?; Address::decode_payload(prefix, &payload)
520 } else {
521 Err(AddressError::InvalidAddress)
522 }
523 })
524 }
525}
526
527#[wasm_bindgen]
528extern "C" {
529 #[wasm_bindgen(extends = js_sys::Array, typescript_type = "Address | string")]
533 pub type AddressT;
534 #[wasm_bindgen(extends = js_sys::Array, typescript_type = "(Address | string)[]")]
538 pub type AddressOrStringArrayT;
539 #[wasm_bindgen(extends = js_sys::Array, typescript_type = "Address[]")]
543 pub type AddressArrayT;
544 #[wasm_bindgen(typescript_type = "Address | undefined")]
548 pub type AddressOrUndefinedT;
549}
550
551impl TryFrom<AddressOrStringArrayT> for Vec<Address> {
552 type Error = AddressError;
553 fn try_from(js_value: AddressOrStringArrayT) -> Result<Self, Self::Error> {
554 if js_value.is_array() {
555 js_value.iter().map(Address::try_owned_from).collect::<Result<Vec<Address>, AddressError>>()
556 } else {
557 Err(AddressError::InvalidAddressArray)
558 }
559 }
560}
561
562#[cfg(test)]
563mod tests {
564 use crate::*;
565
566 fn cases() -> Vec<(Address, &'static str)> {
567 vec![
569 (Address::new(Prefix::A, Version::PubKey, b""), "a:qqeq69uvrh"),
570 (Address::new(Prefix::A, Version::ScriptHash, b""), "a:pq99546ray"),
571 (Address::new(Prefix::B, Version::ScriptHash, b" "), "b:pqsqzsjd64fv"),
572 (Address::new(Prefix::B, Version::ScriptHash, b"-"), "b:pqksmhczf8ud"),
573 (Address::new(Prefix::B, Version::ScriptHash, b"0"), "b:pqcq53eqrk0e"),
574 (Address::new(Prefix::B, Version::ScriptHash, b"1"), "b:pqcshg75y0vf"),
575 (Address::new(Prefix::B, Version::ScriptHash, b"-1"), "b:pqknzl4e9y0zy"),
576 (Address::new(Prefix::B, Version::ScriptHash, b"11"), "b:pqcnzt888ytdg"),
577 (Address::new(Prefix::B, Version::ScriptHash, b"abc"), "b:ppskycc8txxxn2w"),
578 (Address::new(Prefix::B, Version::ScriptHash, b"1234598760"), "b:pqcnyve5x5unsdekxqeusxeyu2"),
579 (Address::new(Prefix::B, Version::ScriptHash, b"abcdefghijklmnopqrstuvwxyz"), "b:ppskycmyv4nxw6rfdf4kcmtwdac8zunnw36hvamc09aqtpppz8lk"),
580 (Address::new(Prefix::B, Version::ScriptHash, b"000000000000000000000000000000000000000000"), "b:pqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrq7ag684l3"),
581 (Address::new(Prefix::Testnet, Version::PubKey, &[0u8; 32]), "kaspatest:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhqrxplya"),
582 (Address::new(Prefix::Testnet, Version::PubKeyECDSA, &[0u8; 33]), "kaspatest:qyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhe837j2d"),
583 (Address::new(Prefix::Testnet, Version::PubKeyECDSA, b"\xba\x01\xfc\x5f\x4e\x9d\x98\x79\x59\x9c\x69\xa3\xda\xfd\xb8\x35\xa7\x25\x5e\x5f\x2e\x93\x4e\x93\x22\xec\xd3\xaf\x19\x0a\xb0\xf6\x0e"), "kaspatest:qxaqrlzlf6wes72en3568khahq66wf27tuhfxn5nytkd8tcep2c0vrse6gdmpks"),
584 (Address::new(Prefix::Mainnet, Version::PubKey, &[0u8; 32]), "kaspa:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e"),
585 (Address::new(Prefix::Mainnet, Version::PubKey, b"\x5f\xff\x3c\x4d\xa1\x8f\x45\xad\xcd\xd4\x99\xe4\x46\x11\xe9\xff\xf1\x48\xba\x69\xdb\x3c\x4e\xa2\xdd\xd9\x55\xfc\x46\xa5\x95\x22"), "kaspa:qp0l70zd5x85ttwd6jv7g3s3a8llzj96d8dncn4zmhv4tlzx5k2jyqh70xmfj"),
586 ]
587 }
589
590 #[test]
591 fn check_into_string() {
592 for (address, expected_address_str) in cases() {
593 let address_str: String = address.into();
594 assert_eq!(address_str, expected_address_str);
595 }
596 }
597
598 #[test]
599 fn check_from_string() {
600 for (expected_address, address_str) in cases() {
601 let address: Address = address_str.to_string().try_into().expect("Test failed");
602 assert_eq!(address, expected_address);
603 }
604 }
605
606 #[test]
607 fn test_errors() {
608 let address_str: String = "kaspa:qqqqqqqqqqqqq1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e".to_string();
610 let address: Result<Address, AddressError> = address_str.try_into();
611 assert_eq!(Err(AddressError::DecodingError('1')), address);
612
613 let invalid_char = 124u8 as char;
614 let address_str: String = format!("kaspa:qqqqqqqqqqqqq{invalid_char}qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e");
615 let address: Result<Address, AddressError> = address_str.try_into();
616 assert_eq!(Err(AddressError::DecodingError(invalid_char)), address);
617
618 let invalid_char = 129u8 as char;
619 let address_str: String = format!("kaspa:qqqqqqqqqqqqq{invalid_char}qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e");
620 let address: Result<Address, AddressError> = address_str.try_into();
621 assert!(matches!(address, Err(AddressError::DecodingError(_))));
622
623 let address_str: String = "kaspa1:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e".to_string();
624 let address: Result<Address, AddressError> = address_str.try_into();
625 assert_eq!(Err(AddressError::InvalidPrefix("kaspa1".into())), address);
626
627 let address_str: String = "kaspaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e".to_string();
628 let address: Result<Address, AddressError> = address_str.try_into();
629 assert_eq!(Err(AddressError::MissingPrefix), address);
630
631 let address_str: String = "kaspa:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4l".to_string();
632 let address: Result<Address, AddressError> = address_str.try_into();
633 assert_eq!(Err(AddressError::BadChecksum), address);
634
635 let address_str: String = "kaspa:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e".to_string();
636 let address: Result<Address, AddressError> = address_str.try_into();
637 assert_eq!(Err(AddressError::BadChecksum), address);
638 }
640
641 use js_sys::Object;
642 use wasm_bindgen::{JsValue, __rt::IntoJsResult};
643 use wasm_bindgen_test::wasm_bindgen_test;
644 use workflow_wasm::{extensions::ObjectExtension, serde::from_value, serde::to_value};
645
646 #[wasm_bindgen_test]
647 pub fn test_wasm_serde_constructor() {
648 let str = "kaspa:qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j";
649 let a = Address::constructor(str);
650 let value = to_value(&a).unwrap();
651
652 assert_eq!(JsValue::from_str("string"), value.js_typeof());
653 assert_eq!(value, JsValue::from_str(str));
654 assert_eq!(a, from_value(value).unwrap());
655 }
656
657 #[wasm_bindgen_test]
658 pub fn test_wasm_js_serde_object() {
659 let expected = Address::constructor("kaspa:qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j");
660
661 use web_sys::console;
662 console::log_4(
663 &"address: ".into(),
664 &expected.version_to_string().into(),
665 &expected.prefix_to_string().into(),
666 &expected.payload_to_string().into(),
667 );
668
669 let obj = Object::new();
670 obj.set("version", &JsValue::from_str("PubKey")).unwrap();
671 obj.set("prefix", &JsValue::from_str("kaspa")).unwrap();
672 obj.set("payload", &JsValue::from_str("qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j")).unwrap();
673
674 assert_eq!(JsValue::from_str("object"), obj.js_typeof());
675
676 let obj_js = obj.into_js_result().unwrap();
677 let actual = from_value(obj_js).unwrap();
678 assert_eq!(expected, actual);
679 }
680
681 #[wasm_bindgen_test]
682 pub fn test_wasm_serde_object() {
683 use wasm_bindgen::convert::IntoWasmAbi;
684
685 let expected = Address::constructor("kaspa:qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j");
686 let wasm_js_value: JsValue = expected.clone().into_abi().into();
687
688 let actual = from_value(wasm_js_value).unwrap();
689 assert_eq!(expected, actual);
690 }
691}