Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Cargo.lock
# Disable logs that have obtained during run
/logs
*.log
tmp/.env
.env

# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
Expand All @@ -43,7 +43,7 @@ dist/
# Logs
logs/*

/.simplicity-dex.config.toml
/config.toml
/.cache
taker/
simplicity-dex
Expand Down
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ anyhow = { version = "1.0.100" }

tracing = { version = "0.1.41" }

contracts = { git = "https://github.com/BlockstreamResearch/simplicity-contracts.git", rev = "1e1c430", package = "contracts" }
cli-helper = { git = "https://github.com/BlockstreamResearch/simplicity-contracts.git", rev = "1e1c430", package = "cli" }
simplicityhl-core = { version = "0.3.3", features = ["encoding"] }
contracts = { git = "https://github.com/BlockstreamResearch/simplicity-contracts.git", rev = "94993a0", package = "contracts" }
cli-helper = { git = "https://github.com/BlockstreamResearch/simplicity-contracts.git", rev = "94993a0", package = "cli" }
simplicityhl-core = { version = "0.3.4", features = ["encoding"] }

simplicityhl = { version = "0.4.0" }

Expand Down
16 changes: 9 additions & 7 deletions crates/cli-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,21 @@ clap = { version = "4", features = ["derive", "env"] }

tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

thiserror = "2"
thiserror = { version = "2" }
anyhow = { workspace = true }

tracing = { workspace = true }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

serde = { version = "1", features = ["derive"] }
bincode = "2"
toml = { version = "0.8" }
hex = { version = "0.4" }
dotenvy = { version = "0.15" }
bincode = { version = "2" }
toml = { version = "0.8" }
hex = { version = "0.4" }
dotenvy = { version = "0.15" }
humantime = { version = "2.3.0" }

nostr = "0.44.2"
nostr-sdk = "0.44.1"

nostr = { version = "0.44.2" }
nostr-sdk = { version = "0.44.1" }

minreq = { version = "2.14", features = ["https", "json-using-serde"] }
118 changes: 93 additions & 25 deletions crates/cli-client/src/cli/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,18 @@ pub fn parse_expiry(expiry: &str) -> Result<i64, Error> {
// Try parsing as relative duration (+30d, +2h, +1w, etc.)
if let Some(duration_str) = expiry.strip_prefix('+') {
let now = current_timestamp();
let duration_secs = parse_duration(duration_str)?;
return Ok(now + duration_secs);
let std_duration: std::time::Duration = duration_str
.parse::<humantime::Duration>()
.map_err(|err| Error::HumantimeParse {
str: duration_str.to_string(),
err,
})?
.into();

let secs =
i64::try_from(std_duration.as_secs()).map_err(|_| Error::Config("Duration too large".to_string()))?;

return Ok(now + secs);
}

Err(Error::Config(format!(
Expand All @@ -230,29 +240,6 @@ pub fn current_timestamp() -> i64 {
.unwrap_or(0)
}

pub fn parse_duration(s: &str) -> Result<i64, Error> {
let s = s.trim();
if s.is_empty() {
return Err(Error::Config("Empty duration".to_string()));
}

let (num_str, unit) = s.split_at(s.len() - 1);
let num: i64 = num_str
.parse()
.map_err(|_| Error::Config(format!("Invalid duration number: {num_str}")))?;

let multiplier = match unit {
"s" => 1,
"m" => 60,
"h" => 3600,
"d" => 86_400,
"w" => 604_800,
_ => return Err(Error::Config(format!("Invalid duration unit: {unit}. Use s/m/h/d/w"))),
};

Ok(num * multiplier)
}

pub fn extract_entries_from_result(result: &UtxoQueryResult) -> Vec<&UtxoEntry> {
match result {
UtxoQueryResult::Found(entries, _) | UtxoQueryResult::InsufficientValue(entries, _) => entries.iter().collect(),
Expand Down Expand Up @@ -456,6 +443,8 @@ pub async fn format_asset_value_with_tag(
mod tests {
use super::*;

const ACCEPTABLE_THRESHOLD: i64 = 2;

#[test]
#[allow(clippy::cast_possible_wrap)]
fn test_format_relative_time() {
Expand All @@ -479,4 +468,83 @@ mod tests {
assert_eq!(truncate_with_ellipsis("hello world", 8), "hello...");
assert_eq!(truncate_with_ellipsis("abc", 3), "abc");
}

#[test]
fn test_parse_expiry_unix_timestamp() {
let ts = 1_704_067_200_i64;
assert_eq!(parse_expiry("1704067200").unwrap(), ts);
}

#[test]
fn test_parse_expiry_zero_timestamp() {
assert_eq!(parse_expiry("0").unwrap(), 0);
}

#[test]
fn test_parse_expiry_relative_days() {
let now = current_timestamp();
let result = parse_expiry("+30d").unwrap();
let expected = now + 30 * 24 * 3600;
assert!((result - expected).abs() < ACCEPTABLE_THRESHOLD);
}

#[test]
fn test_parse_expiry_relative_hours() {
let now = current_timestamp();
let result = parse_expiry("+2h").unwrap();
let expected = now + 2 * 3600;
assert!((result - expected).abs() < ACCEPTABLE_THRESHOLD);
}

#[test]
fn test_parse_expiry_relative_weeks() {
let now = current_timestamp();
let result = parse_expiry("+1w").unwrap();
let expected = now + 7 * 24 * 3600;
assert!((result - expected).abs() < ACCEPTABLE_THRESHOLD);
}

#[test]
fn test_parse_expiry_relative_minutes() {
let now = current_timestamp();
let result = parse_expiry("+45min").unwrap();
let expected = now + 45 * 60;
assert!((result - expected).abs() < ACCEPTABLE_THRESHOLD);
}

#[test]
fn test_parse_expiry_combined_duration() {
let now = current_timestamp();
let result = parse_expiry("+1d2h").unwrap();
let expected = now + 24 * 3600 + 2 * 3600;
assert!((result - expected).abs() < ACCEPTABLE_THRESHOLD);
}

#[test]
fn test_parse_expiry_invalid_format() {
let result = parse_expiry("invalid");
assert!(result.is_err());
match result {
Err(Error::Config(msg)) => {
assert!(msg.contains("Invalid expiry format"));
}
_ => panic!("Expected Config error"),
}
}

#[test]
fn test_parse_expiry_invalid_relative_duration() {
let result = parse_expiry("+invalid_duration");
assert!(result.is_err());
match result {
Err(Error::HumantimeParse { .. }) => {}
_ => panic!("Expected HumantimeParse error"),
}
}

#[test]
fn test_parse_expiry_negative_relative_duration() {
let result = parse_expiry("-30d");
assert!(result.is_err());
}
}
3 changes: 3 additions & 0 deletions crates/cli-client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ pub enum Error {
#[error("Hex to array error: {0}")]
HexToArray(#[from] HexToArrayError),

#[error("Failed to parse duration from string, str: '{str}', err: '{err}'")]
HumantimeParse { err: humantime::DurationError, str: String },

#[error("Metadata encode error: {0}")]
MetadataEncode(bincode::error::EncodeError),

Expand Down
4 changes: 2 additions & 2 deletions crates/signer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use simplicityhl::elements::secp256k1_zkp::{self as secp256k1, Keypair, Message,
use simplicityhl::elements::{Address, AddressParams, BlockHash, Transaction, TxOut};
use simplicityhl::simplicity::bitcoin::XOnlyPublicKey;
use simplicityhl::simplicity::hashes::Hash as _;
use simplicityhl_core::{ProgramError, get_and_verify_env, get_p2pk_address, get_p2pk_program, hash_script_pubkey};
use simplicityhl_core::{ProgramError, get_and_verify_env, get_p2pk_address, get_p2pk_program, hash_script};

#[derive(thiserror::Error, Debug)]
pub enum SignerError {
Expand Down Expand Up @@ -56,7 +56,7 @@ impl Signer {
pub fn p2pk_script_hash(&self, params: &'static AddressParams) -> Result<[u8; 32], SignerError> {
let address = self.p2pk_address(params)?;

let mut script_hash: [u8; 32] = hash_script_pubkey(&address);
let mut script_hash: [u8; 32] = hash_script(&address.script_pubkey());
script_hash.reverse();

Ok(script_hash)
Expand Down