zinc-core 0.4.0

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

use crate::listing::ListingEnvelopeV1;
use crate::listing_nostr::NostrListingEvent;
use crate::listing_relay::NostrListingRelayClient;
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_listing(seller_pubkey_hex: &str) -> ListingEnvelopeV1 {
    ListingEnvelopeV1 {
        version: 1,
        seller_pubkey_hex: seller_pubkey_hex.to_string(),
        coordinator_pubkey_hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
            .to_string(),
        network: "regtest".to_string(),
        inscription_id: "6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0"
            .to_string(),
        seller_outpoint: "6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799:0"
            .to_string(),
        passthrough_outpoint: "25f976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799:0"
            .to_string(),
        seller_payout_script_pubkey_hex: "0014751e76e8199196d454941c45d1b3a323f1433bd6".to_string(),
        ask_sats: 100_000,
        postage_sats: 10_000,
        fee_rate_sat_vb: 2,
        tx1_base64: "cHNidP8BAAoCAAAAAQAAAAA=".to_string(),
        sale_psbt_base64: "cHNidP8BAAoCAAAAAQAAAAA=".to_string(),
        recovery_psbt_base64: "cHNidP8BAAoCAAAAAQAAAAA=".to_string(),
        created_at_unix: 1_710_000_000,
        expires_at_unix: 1_710_086_400,
        nonce: 42,
    }
}

fn sample_event() -> NostrListingEvent {
    let seller_pubkey_hex = pubkey_hex_from_secret(test_secret_hex());
    let listing = sample_listing(&seller_pubkey_hex);
    NostrListingEvent::from_listing(&listing, test_secret_hex(), 1_710_000_100)
        .expect("valid event")
}

#[test]
fn event_frame_contains_nostr_listing_event_payload() {
    let event = sample_event();
    let frame = NostrListingRelayClient::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_listing_kind_and_schema_tag() {
    let frame = NostrListingRelayClient::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::listing_nostr::LISTING_EVENT_KIND
    );
    assert_eq!(parsed[2]["#z"][0], "zinc-listing-v1");
    assert_eq!(parsed[2]["limit"], 25);
}

#[test]
fn parse_ok_frame_extracts_acceptance_and_message() {
    let ok =
        NostrListingRelayClient::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!(NostrListingRelayClient::parse_event_frame(&frame, "sub-other").is_none());
    assert!(NostrListingRelayClient::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>;
}