use crate::wallet::{Protocol as WalletProtocol, SecurityLevel};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DefinitionType {
Basket,
Protocol,
Certificate,
}
impl DefinitionType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Basket => "basket",
Self::Protocol => "protocol",
Self::Certificate => "certificate",
}
}
pub fn try_from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"basket" => Some(Self::Basket),
"protocol" => Some(Self::Protocol),
"certificate" => Some(Self::Certificate),
_ => None,
}
}
pub fn lookup_service(&self) -> &'static str {
match self {
Self::Basket => super::LS_BASKETMAP,
Self::Protocol => super::LS_PROTOMAP,
Self::Certificate => super::LS_CERTMAP,
}
}
pub fn broadcast_topic(&self) -> &'static str {
match self {
Self::Basket => super::TM_BASKETMAP,
Self::Protocol => super::TM_PROTOMAP,
Self::Certificate => super::TM_CERTMAP,
}
}
pub fn wallet_basket(&self) -> &'static str {
match self {
Self::Basket => "basketmap",
Self::Protocol => "protomap",
Self::Certificate => "certmap",
}
}
pub fn wallet_protocol(&self) -> (u8, &'static str) {
match self {
Self::Basket => super::BASKETMAP_PROTOCOL,
Self::Protocol => super::PROTOMAP_PROTOCOL,
Self::Certificate => super::CERTMAP_PROTOCOL,
}
}
pub fn expected_field_count(&self) -> usize {
match self {
Self::Basket => 6, Self::Protocol => 6, Self::Certificate => 7, }
}
}
impl std::fmt::Display for DefinitionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl std::str::FromStr for DefinitionType {
type Err = crate::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_str(s).ok_or_else(|| {
crate::Error::InvalidDefinitionData(format!("Unknown definition type: {}", s))
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenData {
pub txid: String,
pub output_index: u32,
pub satoshis: u64,
pub locking_script: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub beef: Option<Vec<u8>>,
}
impl TokenData {
pub fn new(txid: String, output_index: u32, satoshis: u64, locking_script: String) -> Self {
Self {
txid,
output_index,
satoshis,
locking_script,
beef: None,
}
}
pub fn with_beef(
txid: String,
output_index: u32,
satoshis: u64,
locking_script: String,
beef: Vec<u8>,
) -> Self {
Self {
txid,
output_index,
satoshis,
locking_script,
beef: Some(beef),
}
}
pub fn outpoint(&self) -> String {
format!("{}.{}", self.txid, self.output_index)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BasketDefinitionData {
#[serde(rename = "definitionType")]
pub definition_type: DefinitionType,
#[serde(rename = "basketID")]
pub basket_id: String,
pub name: String,
#[serde(rename = "iconURL")]
pub icon_url: String,
pub description: String,
#[serde(rename = "documentationURL")]
pub documentation_url: String,
#[serde(
rename = "registryOperator",
default,
skip_serializing_if = "String::is_empty"
)]
pub registry_operator: String,
}
impl BasketDefinitionData {
pub fn new(basket_id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
definition_type: DefinitionType::Basket,
basket_id: basket_id.into(),
name: name.into(),
icon_url: String::new(),
description: String::new(),
documentation_url: String::new(),
registry_operator: String::new(),
}
}
pub fn with_icon_url(mut self, url: impl Into<String>) -> Self {
self.icon_url = url.into();
self
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
pub fn with_documentation_url(mut self, url: impl Into<String>) -> Self {
self.documentation_url = url.into();
self
}
pub fn identifier(&self) -> &str {
&self.basket_id
}
pub fn get_definition_type(&self) -> DefinitionType {
DefinitionType::Basket
}
pub fn get_registry_operator(&self) -> &str {
&self.registry_operator
}
pub fn to_pushdrop_fields(&self, registry_operator: &str) -> Vec<Vec<u8>> {
vec![
self.basket_id.as_bytes().to_vec(),
self.name.as_bytes().to_vec(),
self.icon_url.as_bytes().to_vec(),
self.description.as_bytes().to_vec(),
self.documentation_url.as_bytes().to_vec(),
registry_operator.as_bytes().to_vec(),
]
}
pub fn from_pushdrop_fields(fields: &[Vec<u8>]) -> crate::Result<Self> {
if fields.len() != 6 {
return Err(crate::Error::InvalidDefinitionData(format!(
"Expected 6 fields for basket, got {}",
fields.len()
)));
}
let basket_id = String::from_utf8(fields[0].clone())
.map_err(|e| crate::Error::InvalidDefinitionData(format!("Invalid basketID: {}", e)))?;
let name = String::from_utf8(fields[1].clone())
.map_err(|e| crate::Error::InvalidDefinitionData(format!("Invalid name: {}", e)))?;
let icon_url = String::from_utf8(fields[2].clone())
.map_err(|e| crate::Error::InvalidDefinitionData(format!("Invalid iconURL: {}", e)))?;
let description = String::from_utf8(fields[3].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid description: {}", e))
})?;
let documentation_url = String::from_utf8(fields[4].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid documentationURL: {}", e))
})?;
let registry_operator = String::from_utf8(fields[5].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid registryOperator: {}", e))
})?;
Ok(Self {
definition_type: DefinitionType::Basket,
basket_id,
name,
icon_url,
description,
documentation_url,
registry_operator,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProtocolDefinitionData {
#[serde(rename = "definitionType")]
pub definition_type: DefinitionType,
#[serde(rename = "protocolID")]
pub protocol_id: WalletProtocol,
pub name: String,
#[serde(rename = "iconURL")]
pub icon_url: String,
pub description: String,
#[serde(rename = "documentationURL")]
pub documentation_url: String,
#[serde(
rename = "registryOperator",
default,
skip_serializing_if = "String::is_empty"
)]
pub registry_operator: String,
}
impl ProtocolDefinitionData {
pub fn new(protocol_id: WalletProtocol, name: impl Into<String>) -> Self {
Self {
definition_type: DefinitionType::Protocol,
protocol_id,
name: name.into(),
icon_url: String::new(),
description: String::new(),
documentation_url: String::new(),
registry_operator: String::new(),
}
}
pub fn with_icon_url(mut self, url: impl Into<String>) -> Self {
self.icon_url = url.into();
self
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
pub fn with_documentation_url(mut self, url: impl Into<String>) -> Self {
self.documentation_url = url.into();
self
}
pub fn identifier(&self) -> String {
format!(
"[{}, \"{}\"]",
self.protocol_id.security_level as u8, self.protocol_id.protocol_name
)
}
pub fn get_definition_type(&self) -> DefinitionType {
DefinitionType::Protocol
}
pub fn get_registry_operator(&self) -> &str {
&self.registry_operator
}
pub fn to_pushdrop_fields(&self, registry_operator: &str) -> crate::Result<Vec<Vec<u8>>> {
let protocol_json = format!(
"[{}, \"{}\"]",
self.protocol_id.security_level as u8, self.protocol_id.protocol_name
);
Ok(vec![
protocol_json.as_bytes().to_vec(),
self.name.as_bytes().to_vec(),
self.icon_url.as_bytes().to_vec(),
self.description.as_bytes().to_vec(),
self.documentation_url.as_bytes().to_vec(),
registry_operator.as_bytes().to_vec(),
])
}
pub fn from_pushdrop_fields(fields: &[Vec<u8>]) -> crate::Result<Self> {
if fields.len() != 6 {
return Err(crate::Error::InvalidDefinitionData(format!(
"Expected 6 fields for protocol, got {}",
fields.len()
)));
}
let protocol_json = String::from_utf8(fields[0].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid protocolID JSON: {}", e))
})?;
let protocol_id = deserialize_wallet_protocol(&protocol_json)?;
let name = String::from_utf8(fields[1].clone())
.map_err(|e| crate::Error::InvalidDefinitionData(format!("Invalid name: {}", e)))?;
let icon_url = String::from_utf8(fields[2].clone())
.map_err(|e| crate::Error::InvalidDefinitionData(format!("Invalid iconURL: {}", e)))?;
let description = String::from_utf8(fields[3].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid description: {}", e))
})?;
let documentation_url = String::from_utf8(fields[4].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid documentationURL: {}", e))
})?;
let registry_operator = String::from_utf8(fields[5].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid registryOperator: {}", e))
})?;
Ok(Self {
definition_type: DefinitionType::Protocol,
protocol_id,
name,
icon_url,
description,
documentation_url,
registry_operator,
})
}
}
pub fn deserialize_wallet_protocol(s: &str) -> crate::Result<WalletProtocol> {
let arr: Vec<serde_json::Value> = serde_json::from_str(s).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid wallet protocol format: {}", e))
})?;
if arr.len() != 2 {
return Err(crate::Error::InvalidDefinitionData(
"Invalid wallet protocol format, expected array of length 2".to_string(),
));
}
let security_level = arr[0]
.as_u64()
.ok_or_else(|| crate::Error::InvalidDefinitionData("Invalid security level".to_string()))?;
if security_level > 2 {
return Err(crate::Error::InvalidDefinitionData(
"Security level must be 0, 1, or 2".to_string(),
));
}
let protocol = arr[1]
.as_str()
.ok_or_else(|| crate::Error::InvalidDefinitionData("Invalid protocol ID".to_string()))?;
let sec_level = match security_level {
0 => SecurityLevel::Silent,
1 => SecurityLevel::App,
2 => SecurityLevel::Counterparty,
_ => unreachable!(),
};
Ok(WalletProtocol::new(sec_level, protocol))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CertificateFieldDescriptor {
#[serde(rename = "friendlyName")]
pub friendly_name: String,
pub description: String,
#[serde(rename = "type")]
pub field_type: String,
#[serde(rename = "fieldIcon")]
pub field_icon: String,
}
impl CertificateFieldDescriptor {
pub fn text(friendly_name: impl Into<String>) -> Self {
Self {
friendly_name: friendly_name.into(),
description: String::new(),
field_type: "text".to_string(),
field_icon: String::new(),
}
}
pub fn image_url(friendly_name: impl Into<String>) -> Self {
Self {
friendly_name: friendly_name.into(),
description: String::new(),
field_type: "imageURL".to_string(),
field_icon: String::new(),
}
}
pub fn new(friendly_name: impl Into<String>, field_type: impl Into<String>) -> Self {
Self {
friendly_name: friendly_name.into(),
description: String::new(),
field_type: field_type.into(),
field_icon: String::new(),
}
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
pub fn with_icon(mut self, icon: impl Into<String>) -> Self {
self.field_icon = icon.into();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CertificateDefinitionData {
#[serde(rename = "definitionType")]
pub definition_type: DefinitionType,
#[serde(rename = "type")]
pub cert_type: String,
pub name: String,
#[serde(rename = "iconURL")]
pub icon_url: String,
pub description: String,
#[serde(rename = "documentationURL")]
pub documentation_url: String,
pub fields: HashMap<String, CertificateFieldDescriptor>,
#[serde(
rename = "registryOperator",
default,
skip_serializing_if = "String::is_empty"
)]
pub registry_operator: String,
}
impl CertificateDefinitionData {
pub fn new(cert_type: impl Into<String>, name: impl Into<String>) -> Self {
Self {
definition_type: DefinitionType::Certificate,
cert_type: cert_type.into(),
name: name.into(),
icon_url: String::new(),
description: String::new(),
documentation_url: String::new(),
fields: HashMap::new(),
registry_operator: String::new(),
}
}
pub fn with_icon_url(mut self, url: impl Into<String>) -> Self {
self.icon_url = url.into();
self
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
pub fn with_documentation_url(mut self, url: impl Into<String>) -> Self {
self.documentation_url = url.into();
self
}
pub fn with_field(
mut self,
name: impl Into<String>,
descriptor: CertificateFieldDescriptor,
) -> Self {
self.fields.insert(name.into(), descriptor);
self
}
pub fn identifier(&self) -> &str {
&self.cert_type
}
pub fn get_definition_type(&self) -> DefinitionType {
DefinitionType::Certificate
}
pub fn get_registry_operator(&self) -> &str {
&self.registry_operator
}
pub fn to_pushdrop_fields(&self, registry_operator: &str) -> crate::Result<Vec<Vec<u8>>> {
let fields_json = serde_json::to_string(&self.fields).map_err(|e| {
crate::Error::RegistryError(format!("Failed to serialize fields: {}", e))
})?;
Ok(vec![
self.cert_type.as_bytes().to_vec(),
self.name.as_bytes().to_vec(),
self.icon_url.as_bytes().to_vec(),
self.description.as_bytes().to_vec(),
self.documentation_url.as_bytes().to_vec(),
fields_json.as_bytes().to_vec(),
registry_operator.as_bytes().to_vec(),
])
}
pub fn from_pushdrop_fields(fields: &[Vec<u8>]) -> crate::Result<Self> {
if fields.len() != 7 {
return Err(crate::Error::InvalidDefinitionData(format!(
"Expected 7 fields for certificate, got {}",
fields.len()
)));
}
let cert_type = String::from_utf8(fields[0].clone())
.map_err(|e| crate::Error::InvalidDefinitionData(format!("Invalid type: {}", e)))?;
let name = String::from_utf8(fields[1].clone())
.map_err(|e| crate::Error::InvalidDefinitionData(format!("Invalid name: {}", e)))?;
let icon_url = String::from_utf8(fields[2].clone())
.map_err(|e| crate::Error::InvalidDefinitionData(format!("Invalid iconURL: {}", e)))?;
let description = String::from_utf8(fields[3].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid description: {}", e))
})?;
let documentation_url = String::from_utf8(fields[4].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid documentationURL: {}", e))
})?;
let fields_json = String::from_utf8(fields[5].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid fields JSON: {}", e))
})?;
let cert_fields: HashMap<String, CertificateFieldDescriptor> =
serde_json::from_str(&fields_json).unwrap_or_default();
let registry_operator = String::from_utf8(fields[6].clone()).map_err(|e| {
crate::Error::InvalidDefinitionData(format!("Invalid registryOperator: {}", e))
})?;
Ok(Self {
definition_type: DefinitionType::Certificate,
cert_type,
name,
icon_url,
description,
documentation_url,
fields: cert_fields,
registry_operator,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DefinitionData {
Basket(BasketDefinitionData),
Protocol(ProtocolDefinitionData),
Certificate(CertificateDefinitionData),
}
impl DefinitionData {
pub fn get_definition_type(&self) -> DefinitionType {
match self {
Self::Basket(_) => DefinitionType::Basket,
Self::Protocol(_) => DefinitionType::Protocol,
Self::Certificate(_) => DefinitionType::Certificate,
}
}
pub fn get_registry_operator(&self) -> &str {
match self {
Self::Basket(d) => &d.registry_operator,
Self::Protocol(d) => &d.registry_operator,
Self::Certificate(d) => &d.registry_operator,
}
}
pub fn set_registry_operator(&mut self, operator: String) {
match self {
Self::Basket(d) => d.registry_operator = operator,
Self::Protocol(d) => d.registry_operator = operator,
Self::Certificate(d) => d.registry_operator = operator,
}
}
pub fn identifier(&self) -> String {
match self {
Self::Basket(d) => d.basket_id.clone(),
Self::Protocol(d) => d.identifier(),
Self::Certificate(d) => d.cert_type.clone(),
}
}
pub fn name(&self) -> &str {
match self {
Self::Basket(d) => &d.name,
Self::Protocol(d) => &d.name,
Self::Certificate(d) => &d.name,
}
}
pub fn to_pushdrop_fields(&self, registry_operator: &str) -> crate::Result<Vec<Vec<u8>>> {
match self {
Self::Basket(d) => Ok(d.to_pushdrop_fields(registry_operator)),
Self::Protocol(d) => d.to_pushdrop_fields(registry_operator),
Self::Certificate(d) => d.to_pushdrop_fields(registry_operator),
}
}
pub fn as_basket(&self) -> Option<&BasketDefinitionData> {
match self {
Self::Basket(d) => Some(d),
_ => None,
}
}
pub fn as_protocol(&self) -> Option<&ProtocolDefinitionData> {
match self {
Self::Protocol(d) => Some(d),
_ => None,
}
}
pub fn as_certificate(&self) -> Option<&CertificateDefinitionData> {
match self {
Self::Certificate(d) => Some(d),
_ => None,
}
}
}
impl From<BasketDefinitionData> for DefinitionData {
fn from(d: BasketDefinitionData) -> Self {
Self::Basket(d)
}
}
impl From<ProtocolDefinitionData> for DefinitionData {
fn from(d: ProtocolDefinitionData) -> Self {
Self::Protocol(d)
}
}
impl From<CertificateDefinitionData> for DefinitionData {
fn from(d: CertificateDefinitionData) -> Self {
Self::Certificate(d)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistryRecord {
#[serde(flatten)]
pub definition: DefinitionData,
#[serde(flatten)]
pub token: TokenData,
}
impl RegistryRecord {
pub fn new(definition: DefinitionData, token: TokenData) -> Self {
Self { definition, token }
}
pub fn basket(definition: BasketDefinitionData, token: TokenData) -> Self {
Self {
definition: DefinitionData::Basket(definition),
token,
}
}
pub fn protocol(definition: ProtocolDefinitionData, token: TokenData) -> Self {
Self {
definition: DefinitionData::Protocol(definition),
token,
}
}
pub fn certificate(definition: CertificateDefinitionData, token: TokenData) -> Self {
Self {
definition: DefinitionData::Certificate(definition),
token,
}
}
pub fn token(&self) -> &TokenData {
&self.token
}
pub fn get_definition_type(&self) -> DefinitionType {
self.definition.get_definition_type()
}
pub fn get_registry_operator(&self) -> &str {
self.definition.get_registry_operator()
}
pub fn identifier(&self) -> String {
self.definition.identifier()
}
pub fn txid(&self) -> &str {
&self.token.txid
}
pub fn output_index(&self) -> u32 {
self.token.output_index
}
pub fn outpoint(&self) -> String {
self.token.outpoint()
}
pub fn as_basket(&self) -> Option<&BasketDefinitionData> {
self.definition.as_basket()
}
pub fn as_protocol(&self) -> Option<&ProtocolDefinitionData> {
self.definition.as_protocol()
}
pub fn as_certificate(&self) -> Option<&CertificateDefinitionData> {
self.definition.as_certificate()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BasketQuery {
#[serde(rename = "basketID", skip_serializing_if = "Option::is_none")]
pub basket_id: Option<String>,
#[serde(rename = "registryOperators", skip_serializing_if = "Option::is_none")]
pub registry_operators: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
impl BasketQuery {
pub fn new() -> Self {
Self::default()
}
pub fn with_basket_id(mut self, id: impl Into<String>) -> Self {
self.basket_id = Some(id.into());
self
}
pub fn with_registry_operator(mut self, operator: impl Into<String>) -> Self {
self.registry_operators = Some(vec![operator.into()]);
self
}
pub fn with_registry_operators(mut self, operators: Vec<String>) -> Self {
self.registry_operators = Some(operators);
self
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ProtocolQuery {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "registryOperators", skip_serializing_if = "Option::is_none")]
pub registry_operators: Option<Vec<String>>,
#[serde(rename = "protocolID", skip_serializing_if = "Option::is_none")]
pub protocol_id: Option<WalletProtocol>,
}
impl ProtocolQuery {
pub fn new() -> Self {
Self::default()
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn with_registry_operator(mut self, operator: impl Into<String>) -> Self {
self.registry_operators = Some(vec![operator.into()]);
self
}
pub fn with_registry_operators(mut self, operators: Vec<String>) -> Self {
self.registry_operators = Some(operators);
self
}
pub fn with_protocol_id(mut self, protocol_id: WalletProtocol) -> Self {
self.protocol_id = Some(protocol_id);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CertificateQuery {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub cert_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "registryOperators", skip_serializing_if = "Option::is_none")]
pub registry_operators: Option<Vec<String>>,
}
impl CertificateQuery {
pub fn new() -> Self {
Self::default()
}
pub fn with_cert_type(mut self, cert_type: impl Into<String>) -> Self {
self.cert_type = Some(cert_type.into());
self
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn with_registry_operator(mut self, operator: impl Into<String>) -> Self {
self.registry_operators = Some(vec![operator.into()]);
self
}
pub fn with_registry_operators(mut self, operators: Vec<String>) -> Self {
self.registry_operators = Some(operators);
self
}
}
#[derive(Debug, Clone)]
pub struct RegisterDefinitionResult {
pub success: Option<BroadcastSuccess>,
pub failure: Option<BroadcastFailure>,
}
impl RegisterDefinitionResult {
pub fn is_success(&self) -> bool {
self.success.is_some()
}
pub fn is_failure(&self) -> bool {
self.failure.is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BroadcastSuccess {
pub txid: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BroadcastFailure {
pub code: String,
pub description: String,
}
#[derive(Debug, Clone)]
pub struct RevokeDefinitionResult {
pub success: Option<BroadcastSuccess>,
pub failure: Option<BroadcastFailure>,
}
impl RevokeDefinitionResult {
pub fn is_success(&self) -> bool {
self.success.is_some()
}
pub fn is_failure(&self) -> bool {
self.failure.is_some()
}
}
#[derive(Debug, Clone)]
pub struct UpdateDefinitionResult {
pub success: Option<BroadcastSuccess>,
pub failure: Option<BroadcastFailure>,
}
impl UpdateDefinitionResult {
pub fn is_success(&self) -> bool {
self.success.is_some()
}
pub fn is_failure(&self) -> bool {
self.failure.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_definition_type_as_str() {
assert_eq!(DefinitionType::Basket.as_str(), "basket");
assert_eq!(DefinitionType::Protocol.as_str(), "protocol");
assert_eq!(DefinitionType::Certificate.as_str(), "certificate");
}
#[test]
fn test_definition_type_try_from_str() {
assert_eq!(
DefinitionType::try_from_str("basket"),
Some(DefinitionType::Basket)
);
assert_eq!(
DefinitionType::try_from_str("PROTOCOL"),
Some(DefinitionType::Protocol)
);
assert_eq!(
DefinitionType::try_from_str("Certificate"),
Some(DefinitionType::Certificate)
);
assert_eq!(DefinitionType::try_from_str("invalid"), None);
}
#[test]
fn test_definition_type_from_str() {
assert_eq!(
"basket".parse::<DefinitionType>().unwrap(),
DefinitionType::Basket
);
assert_eq!(
"PROTOCOL".parse::<DefinitionType>().unwrap(),
DefinitionType::Protocol
);
assert!("invalid".parse::<DefinitionType>().is_err());
}
#[test]
fn test_definition_type_display() {
assert_eq!(format!("{}", DefinitionType::Basket), "basket");
assert_eq!(format!("{}", DefinitionType::Protocol), "protocol");
assert_eq!(format!("{}", DefinitionType::Certificate), "certificate");
}
#[test]
fn test_definition_type_lookup_service() {
assert_eq!(DefinitionType::Basket.lookup_service(), "ls_basketmap");
assert_eq!(DefinitionType::Protocol.lookup_service(), "ls_protomap");
assert_eq!(DefinitionType::Certificate.lookup_service(), "ls_certmap");
}
#[test]
fn test_definition_type_broadcast_topic() {
assert_eq!(DefinitionType::Basket.broadcast_topic(), "tm_basketmap");
assert_eq!(DefinitionType::Protocol.broadcast_topic(), "tm_protomap");
assert_eq!(DefinitionType::Certificate.broadcast_topic(), "tm_certmap");
}
#[test]
fn test_definition_type_wallet_basket() {
assert_eq!(DefinitionType::Basket.wallet_basket(), "basketmap");
assert_eq!(DefinitionType::Protocol.wallet_basket(), "protomap");
assert_eq!(DefinitionType::Certificate.wallet_basket(), "certmap");
}
#[test]
fn test_definition_type_wallet_protocol() {
assert_eq!(DefinitionType::Basket.wallet_protocol(), (1, "basketmap"));
assert_eq!(DefinitionType::Protocol.wallet_protocol(), (1, "protomap"));
assert_eq!(
DefinitionType::Certificate.wallet_protocol(),
(1, "certmap")
);
}
#[test]
fn test_definition_type_expected_field_count() {
assert_eq!(DefinitionType::Basket.expected_field_count(), 6);
assert_eq!(DefinitionType::Protocol.expected_field_count(), 6);
assert_eq!(DefinitionType::Certificate.expected_field_count(), 7);
}
#[test]
fn test_token_data_new() {
let token = TokenData::new("abc123".to_string(), 0, 1000, "76a914...88ac".to_string());
assert_eq!(token.txid, "abc123");
assert_eq!(token.output_index, 0);
assert_eq!(token.satoshis, 1000);
assert!(token.beef.is_none());
}
#[test]
fn test_token_data_with_beef() {
let beef = vec![0xbe, 0xef];
let token = TokenData::with_beef(
"abc123".to_string(),
1,
500,
"76a914...88ac".to_string(),
beef,
);
assert_eq!(token.txid, "abc123");
assert_eq!(token.output_index, 1);
assert!(token.beef.is_some());
}
#[test]
fn test_token_data_outpoint() {
let token = TokenData::new("abc123".to_string(), 2, 1000, "script".to_string());
assert_eq!(token.outpoint(), "abc123.2");
}
#[test]
fn test_basket_definition_new() {
let data = BasketDefinitionData::new("my_basket", "My Basket");
assert_eq!(data.definition_type, DefinitionType::Basket);
assert_eq!(data.basket_id, "my_basket");
assert_eq!(data.name, "My Basket");
assert_eq!(data.icon_url, "");
assert_eq!(data.description, "");
assert_eq!(data.documentation_url, "");
}
#[test]
fn test_basket_definition_builder() {
let data = BasketDefinitionData::new("my_basket", "My Basket")
.with_icon_url("https://example.com/icon.png")
.with_description("A test basket")
.with_documentation_url("https://example.com/docs");
assert_eq!(data.icon_url, "https://example.com/icon.png");
assert_eq!(data.description, "A test basket");
assert_eq!(data.documentation_url, "https://example.com/docs");
}
#[test]
fn test_basket_definition_serialization() {
let data = BasketDefinitionData::new("my_basket", "My Basket").with_description("Test");
let json = serde_json::to_string(&data).unwrap();
assert!(json.contains("\"basketID\":\"my_basket\""));
assert!(json.contains("\"iconURL\":\"\""));
assert!(json.contains("\"documentationURL\":\"\""));
assert!(json.contains("\"definitionType\":\"basket\""));
}
#[test]
fn test_basket_definition_pushdrop_fields() {
let data = BasketDefinitionData::new("my_basket", "My Basket")
.with_icon_url("icon.png")
.with_description("desc")
.with_documentation_url("docs.html");
let fields = data.to_pushdrop_fields("02abc123");
assert_eq!(fields.len(), 6);
assert_eq!(String::from_utf8(fields[0].clone()).unwrap(), "my_basket");
assert_eq!(String::from_utf8(fields[1].clone()).unwrap(), "My Basket");
assert_eq!(String::from_utf8(fields[2].clone()).unwrap(), "icon.png");
assert_eq!(String::from_utf8(fields[3].clone()).unwrap(), "desc");
assert_eq!(String::from_utf8(fields[4].clone()).unwrap(), "docs.html");
assert_eq!(String::from_utf8(fields[5].clone()).unwrap(), "02abc123");
}
#[test]
fn test_basket_definition_from_pushdrop_fields() {
let fields = vec![
b"my_basket".to_vec(),
b"My Basket".to_vec(),
b"icon.png".to_vec(),
b"desc".to_vec(),
b"docs.html".to_vec(),
b"02abc123".to_vec(),
];
let data = BasketDefinitionData::from_pushdrop_fields(&fields).unwrap();
assert_eq!(data.basket_id, "my_basket");
assert_eq!(data.name, "My Basket");
assert_eq!(data.icon_url, "icon.png");
assert_eq!(data.description, "desc");
assert_eq!(data.documentation_url, "docs.html");
assert_eq!(data.registry_operator, "02abc123");
}
#[test]
fn test_protocol_definition_new() {
let protocol = WalletProtocol::new(SecurityLevel::App, "my_protocol");
let data = ProtocolDefinitionData::new(protocol, "My Protocol");
assert_eq!(data.definition_type, DefinitionType::Protocol);
assert_eq!(data.protocol_id.protocol_name, "my_protocol");
assert_eq!(data.name, "My Protocol");
}
#[test]
fn test_protocol_definition_identifier() {
let protocol = WalletProtocol::new(SecurityLevel::App, "my_protocol");
let data = ProtocolDefinitionData::new(protocol, "My Protocol");
assert_eq!(data.identifier(), "[1, \"my_protocol\"]");
}
#[test]
fn test_protocol_definition_serialization() {
let protocol = WalletProtocol::new(SecurityLevel::App, "my_protocol");
let data = ProtocolDefinitionData::new(protocol, "My Protocol");
let json = serde_json::to_string(&data).unwrap();
assert!(json.contains("\"protocolID\""));
assert!(json.contains("\"iconURL\":\"\""));
assert!(json.contains("\"documentationURL\":\"\""));
assert!(json.contains("\"definitionType\":\"protocol\""));
}
#[test]
fn test_protocol_definition_pushdrop_fields() {
let protocol = WalletProtocol::new(SecurityLevel::App, "my_protocol");
let data = ProtocolDefinitionData::new(protocol, "My Protocol").with_description("desc");
let fields = data.to_pushdrop_fields("02abc123").unwrap();
assert_eq!(fields.len(), 6);
let protocol_json = String::from_utf8(fields[0].clone()).unwrap();
assert!(protocol_json.contains("1")); assert!(protocol_json.contains("my_protocol"));
}
#[test]
fn test_deserialize_wallet_protocol() {
let protocol = deserialize_wallet_protocol("[1,\"my_protocol\"]").unwrap();
assert_eq!(protocol.security_level, SecurityLevel::App);
assert_eq!(protocol.protocol_name, "my_protocol");
let protocol2 = deserialize_wallet_protocol("[0, \"silent_protocol\"]").unwrap();
assert_eq!(protocol2.security_level, SecurityLevel::Silent);
assert_eq!(protocol2.protocol_name, "silent_protocol");
let protocol3 = deserialize_wallet_protocol("[2, \"counterparty_protocol\"]").unwrap();
assert_eq!(protocol3.security_level, SecurityLevel::Counterparty);
}
#[test]
fn test_deserialize_wallet_protocol_errors() {
assert!(deserialize_wallet_protocol("invalid").is_err());
assert!(deserialize_wallet_protocol("[1]").is_err());
assert!(deserialize_wallet_protocol("[1, 2, 3]").is_err());
assert!(deserialize_wallet_protocol("[3, \"proto\"]").is_err()); assert!(deserialize_wallet_protocol("[1, 123]").is_err()); }
#[test]
fn test_certificate_field_descriptor() {
let field = CertificateFieldDescriptor::text("Email")
.with_description("User email")
.with_icon("email-icon");
assert_eq!(field.friendly_name, "Email");
assert_eq!(field.field_type, "text");
assert_eq!(field.description, "User email");
assert_eq!(field.field_icon, "email-icon");
}
#[test]
fn test_certificate_field_descriptor_image_url() {
let field = CertificateFieldDescriptor::image_url("Avatar");
assert_eq!(field.field_type, "imageURL");
}
#[test]
fn test_certificate_field_descriptor_serialization() {
let field = CertificateFieldDescriptor::text("Email").with_description("desc");
let json = serde_json::to_string(&field).unwrap();
assert!(json.contains("\"friendlyName\":\"Email\""));
assert!(json.contains("\"fieldIcon\":\"\""));
}
#[test]
fn test_certificate_definition_new() {
let data = CertificateDefinitionData::new("cert_type_123", "My Certificate");
assert_eq!(data.definition_type, DefinitionType::Certificate);
assert_eq!(data.cert_type, "cert_type_123");
assert_eq!(data.name, "My Certificate");
assert!(data.fields.is_empty());
}
#[test]
fn test_certificate_definition_with_fields() {
let data = CertificateDefinitionData::new("cert_type", "Cert")
.with_field("email", CertificateFieldDescriptor::text("Email"))
.with_field("avatar", CertificateFieldDescriptor::image_url("Avatar"));
assert_eq!(data.fields.len(), 2);
assert!(data.fields.contains_key("email"));
assert!(data.fields.contains_key("avatar"));
}
#[test]
fn test_certificate_definition_pushdrop_fields() {
let data = CertificateDefinitionData::new("cert_type", "My Cert")
.with_description("desc")
.with_field("email", CertificateFieldDescriptor::text("Email"));
let fields = data.to_pushdrop_fields("02abc123").unwrap();
assert_eq!(fields.len(), 7);
assert_eq!(String::from_utf8(fields[0].clone()).unwrap(), "cert_type");
assert_eq!(String::from_utf8(fields[1].clone()).unwrap(), "My Cert");
let fields_json = String::from_utf8(fields[5].clone()).unwrap();
assert!(fields_json.contains("email"));
}
#[test]
fn test_definition_data_enum() {
let basket = DefinitionData::Basket(BasketDefinitionData::new("b", "Basket"));
assert_eq!(basket.get_definition_type(), DefinitionType::Basket);
assert!(basket.as_basket().is_some());
assert!(basket.as_protocol().is_none());
let protocol = DefinitionData::Protocol(ProtocolDefinitionData::new(
WalletProtocol::new(SecurityLevel::App, "p"),
"Protocol",
));
assert_eq!(protocol.get_definition_type(), DefinitionType::Protocol);
assert!(protocol.as_protocol().is_some());
}
#[test]
fn test_definition_data_from() {
let basket = BasketDefinitionData::new("b", "Basket");
let data: DefinitionData = basket.into();
assert_eq!(data.get_definition_type(), DefinitionType::Basket);
}
#[test]
fn test_registry_record() {
let basket = BasketDefinitionData::new("b", "Basket");
let token = TokenData::new("txid".to_string(), 0, 1, "script".to_string());
let record = RegistryRecord::basket(basket, token);
assert_eq!(record.get_definition_type(), DefinitionType::Basket);
assert_eq!(record.txid(), "txid");
assert_eq!(record.output_index(), 0);
assert_eq!(record.outpoint(), "txid.0");
assert!(record.as_basket().is_some());
}
#[test]
fn test_basket_query() {
let query = BasketQuery::new()
.with_basket_id("my_basket")
.with_registry_operator("02abc...");
assert_eq!(query.basket_id, Some("my_basket".to_string()));
assert_eq!(query.registry_operators, Some(vec!["02abc...".to_string()]));
}
#[test]
fn test_basket_query_serialization() {
let query = BasketQuery::new().with_basket_id("test");
let json = serde_json::to_string(&query).unwrap();
assert!(json.contains("\"basketID\":\"test\""));
}
#[test]
fn test_protocol_query() {
let protocol = WalletProtocol::new(SecurityLevel::App, "proto");
let query = ProtocolQuery::new()
.with_protocol_id(protocol)
.with_name("Protocol");
assert!(query.protocol_id.is_some());
assert_eq!(query.name, Some("Protocol".to_string()));
}
#[test]
fn test_protocol_query_serialization() {
let query = ProtocolQuery::new().with_name("test");
let json = serde_json::to_string(&query).unwrap();
assert!(json.contains("\"name\":\"test\""));
}
#[test]
fn test_certificate_query() {
let query = CertificateQuery::new()
.with_cert_type("cert_type")
.with_registry_operators(vec!["op1".to_string(), "op2".to_string()]);
assert_eq!(query.cert_type, Some("cert_type".to_string()));
assert_eq!(
query.registry_operators,
Some(vec!["op1".to_string(), "op2".to_string()])
);
}
#[test]
fn test_certificate_query_serialization() {
let query = CertificateQuery::new().with_cert_type("test");
let json = serde_json::to_string(&query).unwrap();
assert!(json.contains("\"type\":\"test\""));
}
#[test]
fn test_register_definition_result() {
let success_result = RegisterDefinitionResult {
success: Some(BroadcastSuccess {
txid: "abc123".to_string(),
message: "success".to_string(),
}),
failure: None,
};
assert!(success_result.is_success());
assert!(!success_result.is_failure());
let failure_result = RegisterDefinitionResult {
success: None,
failure: Some(BroadcastFailure {
code: "ERR".to_string(),
description: "Failed".to_string(),
}),
};
assert!(!failure_result.is_success());
assert!(failure_result.is_failure());
}
#[test]
fn test_update_definition_result() {
let success_result = UpdateDefinitionResult {
success: Some(BroadcastSuccess {
txid: "abc123".to_string(),
message: "success".to_string(),
}),
failure: None,
};
assert!(success_result.is_success());
assert!(!success_result.is_failure());
let failure_result = UpdateDefinitionResult {
success: None,
failure: Some(BroadcastFailure {
code: "ERR".to_string(),
description: "Failed".to_string(),
}),
};
assert!(!failure_result.is_success());
assert!(failure_result.is_failure());
}
#[test]
fn test_revoke_definition_result() {
let success_result = RevokeDefinitionResult {
success: Some(BroadcastSuccess {
txid: "abc123".to_string(),
message: "success".to_string(),
}),
failure: None,
};
assert!(success_result.is_success());
assert!(!success_result.is_failure());
let failure_result = RevokeDefinitionResult {
success: None,
failure: Some(BroadcastFailure {
code: "ERR".to_string(),
description: "Failed".to_string(),
}),
};
assert!(!failure_result.is_success());
assert!(failure_result.is_failure());
}
}