zinc-core 0.3.0

Core Rust library for Zinc Bitcoin + Ordinals wallet
Documentation
#![cfg(not(target_arch = "wasm32"))]

use crate::offer::OfferEnvelopeV1;
use crate::offer_nostr::NostrOfferEvent;
use crate::offer_relay::NostrRelayClient;
use bdk_wallet::bitcoin::secp256k1::{Keypair, Secp256k1, SecretKey, XOnlyPublicKey};
use std::str::FromStr;

fn test_secret_hex() -> &'static str {
    "0001020304050607080900010203040506070809000102030405060708090001"
}

fn pubkey_hex_from_secret(secret_hex: &str) -> String {
    let secret_key = SecretKey::from_str(secret_hex).expect("valid secret key");
    let secp = Secp256k1::new();
    let keypair = Keypair::from_secret_key(&secp, &secret_key);
    let (xonly, _) = XOnlyPublicKey::from_keypair(&keypair);
    xonly.to_string()
}

fn sample_offer(seller_pubkey_hex: &str) -> OfferEnvelopeV1 {
    OfferEnvelopeV1 {
        version: 1,
        seller_pubkey_hex: seller_pubkey_hex.to_string(),
        network: "regtest".to_string(),
        inscription_id: "6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0"
            .to_string(),
        seller_outpoint: "6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799:0"
            .to_string(),
        ask_sats: 100_000,
        fee_rate_sat_vb: 2,
        psbt_base64: "cHNidP8BAHECAAAAAf//////////////////////////////////////////AAAAAAD9////AqCGAQAAAAAAIgAgx0Jv4z2frfr6f3Ff9rR9lSxDgP3UzrA1n6g0bHTqfQAAAAAAAAAA".to_string(),
        created_at_unix: 1_710_000_000,
        expires_at_unix: 1_710_086_400,
        nonce: 42,
    }
}

fn sample_event() -> NostrOfferEvent {
    let seller_pubkey_hex = pubkey_hex_from_secret(test_secret_hex());
    let offer = sample_offer(&seller_pubkey_hex);
    NostrOfferEvent::from_offer(&offer, test_secret_hex(), 1_710_000_100).expect("valid event")
}

#[test]
fn event_frame_contains_nostr_event_payload() {
    let event = sample_event();
    let frame = NostrRelayClient::event_frame(&event).expect("frame");
    let parsed: serde_json::Value = serde_json::from_str(&frame).expect("json frame");

    assert_eq!(parsed[0], "EVENT");
    assert_eq!(parsed[1]["id"], event.id);
    assert_eq!(parsed[1]["pubkey"], event.pubkey);
}

#[test]
fn req_frame_targets_offer_kind_and_schema_tag() {
    let frame = NostrRelayClient::req_frame("sub-1", 25).expect("frame");
    let parsed: serde_json::Value = serde_json::from_str(&frame).expect("json frame");

    assert_eq!(parsed[0], "REQ");
    assert_eq!(parsed[1], "sub-1");
    assert_eq!(parsed[2]["kinds"][0], crate::offer_nostr::OFFER_EVENT_KIND);
    assert_eq!(parsed[2]["#z"][0], "zinc-offer-v1");
    assert_eq!(parsed[2]["limit"], 25);
}

#[test]
fn parse_ok_frame_extracts_acceptance_and_message() {
    let ok = NostrRelayClient::parse_ok_frame(r#"["OK","abcd1234",true,"accepted"]"#, "abcd1234")
        .expect("valid ok frame");

    assert!(ok.0);
    assert_eq!(ok.1, "accepted");
}

#[test]
fn parse_event_frame_requires_matching_subscription() {
    let event = sample_event();
    let frame = format!(
        r#"["EVENT","sub-expected",{}]"#,
        serde_json::to_string(&event).expect("serialize event")
    );

    assert!(NostrRelayClient::parse_event_frame(&frame, "sub-other").is_none());
    assert!(NostrRelayClient::parse_event_frame(&frame, "sub-expected").is_some());
}

#[test]
fn wss_transport_compiles_with_tls_connect_api() {
    let _ = tokio_tungstenite::connect_async_tls_with_config::<&str>;
}