use crate::types::Bip21Options;
pub fn to_bip21_uri(address: &str, options: Bip21Options) -> String {
let mut uri = format!("bitcoin:{}", address);
let mut params = Vec::new();
if let Some(amount) = options.amount {
params.push(format!("amount={}", amount));
}
if let Some(label) = options.label {
params.push(format!("label={}", url_encode(&label)));
}
if let Some(message) = options.message {
params.push(format!("message={}", url_encode(&message)));
}
if !params.is_empty() {
uri.push('?');
uri.push_str(¶ms.join("&"));
}
uri
}
fn url_encode(s: &str) -> String {
let mut result = String::new();
for c in s.chars() {
match c {
'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~' => {
result.push(c);
}
' ' => result.push_str("%20"),
_ => {
for byte in c.to_string().as_bytes() {
result.push_str(&format!("%{:02X}", byte));
}
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_uri() {
let uri = to_bip21_uri("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", Bip21Options::new());
assert_eq!(uri, "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa");
}
#[test]
fn test_uri_with_amount() {
let uri = to_bip21_uri(
"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
Bip21Options::new().with_amount(1.5),
);
assert_eq!(uri, "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=1.5");
}
#[test]
fn test_uri_with_label() {
let uri = to_bip21_uri(
"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
Bip21Options::new().with_label("Donation"),
);
assert_eq!(uri, "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?label=Donation");
}
#[test]
fn test_uri_with_all_options() {
let uri = to_bip21_uri(
"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
Bip21Options::new()
.with_amount(0.001)
.with_label("Coffee")
.with_message("Thanks!"),
);
assert!(uri.contains("amount=0.001"));
assert!(uri.contains("label=Coffee"));
assert!(uri.contains("message=Thanks"));
}
#[test]
fn test_url_encode() {
assert_eq!(url_encode("hello world"), "hello%20world");
assert_eq!(url_encode("test"), "test");
}
}