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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ edition = "2024"
rust-version = "1.91.0"
authors = ["Blockstream"]
readme = "README.md"
repository = "https://github.com/Blockstream/simplicity-dex"
keywords = ["liquid", "bitcoin", "options", "simplicity"]
categories = ["cryptography::cryptocurrencies"]

[workspace.dependencies]
anyhow = { version = "1.0.100" }
Expand Down
4 changes: 4 additions & 0 deletions crates/cli-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
readme.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true

[[bin]]
name = "simplicity-dex"
Expand Down
4 changes: 4 additions & 0 deletions crates/coin-store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
readme.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true

[dependencies]
simplicityhl = { workspace = true }
Expand Down
3 changes: 3 additions & 0 deletions crates/coin-store/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ pub enum StoreError {

#[error("Migration error")]
Migration(#[from] sqlx::migrate::MigrateError),

#[error("Value overflow during calculation")]
ValueOverflow,
}
4 changes: 2 additions & 2 deletions crates/coin-store/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub struct Filter {
pub(crate) asset_id: Option<AssetId>,
pub(crate) script_pubkey: Option<Script>,
pub(crate) required_value: Option<u64>,
pub(crate) limit: Option<usize>,
pub(crate) limit: Option<i64>,
pub(crate) include_spent: bool,
}

Expand Down Expand Up @@ -34,7 +34,7 @@ impl Filter {
}

#[must_use]
pub const fn limit(mut self, limit: usize) -> Self {
pub const fn limit(mut self, limit: i64) -> Self {
self.limit = Some(limit);
self
}
Expand Down
55 changes: 31 additions & 24 deletions crates/coin-store/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,13 @@ impl Store {
outpoint: &OutPoint,
txout: &TxOut,
blinder_key: Option<[u8; 32]>,
) -> Result<(AssetId, u64, bool), StoreError> {
) -> Result<(AssetId, i64, bool), StoreError> {
if let (Some(asset), Some(value)) = (txout.asset.explicit(), txout.value.explicit()) {
return Ok((asset, value, false));
return Ok((
asset,
i64::try_from(value).expect("UTXO values never exceed i64 max (9.2e18 vs max BTC supply ~2.1e15 sats)"),
false,
));
}

let Some(key) = blinder_key else {
Expand All @@ -87,7 +91,12 @@ impl Store {

let secret_key = SecretKey::from_slice(&key)?;
let secrets = txout.unblind(secp256k1::SECP256K1, secret_key)?;
Ok((secrets.asset, secrets.value, true))
Ok((
secrets.asset,
i64::try_from(secrets.value)
.expect("UTXO values never exceed i64 max (9.2e18 vs max BTC supply ~2.1e15 sats)"),
true,
))
}

async fn internal_insert(
Expand All @@ -101,23 +110,18 @@ impl Store {

let txid: &[u8] = outpoint.txid.as_ref();
let vout = i64::from(outpoint.vout);
let script_pubkey = txout.script_pubkey.as_bytes();
let asset_bytes = asset_id.into_inner().0.to_vec();
let value_i64 = value as i64;
let serialized = encode::serialize(&txout);
let is_conf_i64 = i64::from(is_confidential);

sqlx::query(
"INSERT INTO utxos (txid, vout, script_pubkey, asset_id, value, serialized, is_confidential)
VALUES (?, ?, ?, ?, ?, ?, ?)",
)
.bind(txid)
.bind(vout)
.bind(script_pubkey)
.bind(asset_bytes.as_slice())
.bind(value_i64)
.bind(&serialized)
.bind(is_conf_i64)
.bind(txout.script_pubkey.as_bytes())
.bind(asset_id.into_inner().0.as_slice())
.bind(value)
.bind(encode::serialize(&txout))
.bind(i64::from(is_confidential))
.execute(&mut *tx)
.await?;

Expand Down Expand Up @@ -206,8 +210,12 @@ impl Store {
/// Fetches UTXOs in batches until the required value is met (early termination).
/// This avoids loading all matching UTXOs when only a subset is needed.
async fn query_until_sufficient(&self, filter: &Filter) -> Result<QueryResult, StoreError> {
let required = filter.required_value.unwrap();
let Some(required) = filter.required_value else {
return Ok(QueryResult::Empty);
};

let mut entries = Vec::new();

let mut total_value: u64 = 0;
let mut offset: i64 = 0;

Expand All @@ -219,9 +227,9 @@ impl Store {
}

for row in rows {
total_value = total_value.saturating_add(row.value as u64);
let entry = row.into_entry()?;
entries.push(entry);
total_value = total_value.checked_add(row.value).ok_or(StoreError::ValueOverflow)?;

entries.push(row.into_entry()?);

// Early termination: we have enough value
if total_value >= required {
Expand Down Expand Up @@ -285,8 +293,7 @@ impl Store {

/// Fetches all matching UTXOs (used when no `required_value` optimization applies).
async fn query_all(&self, filter: &Filter) -> Result<QueryResult, StoreError> {
let limit = filter.limit.map(|l| l as i64);
let rows = self.fetch_rows(filter, limit, None).await?;
let rows = self.fetch_rows(filter, filter.limit, None).await?;

if rows.is_empty() {
return Ok(QueryResult::Empty);
Expand All @@ -296,7 +303,7 @@ impl Store {
let mut total_value: u64 = 0;

for row in rows {
total_value = total_value.saturating_add(row.value as u64);
total_value = total_value.saturating_add(row.value);

entries.push(row.into_entry()?);
}
Expand All @@ -319,10 +326,10 @@ impl Store {
#[derive(sqlx::FromRow)]
struct UtxoRow {
txid: Vec<u8>,
vout: i64,
vout: u32,
serialized: Vec<u8>,
is_confidential: i64,
value: i64,
value: u64,
blinding_key: Option<Vec<u8>>,
}

Expand All @@ -334,7 +341,7 @@ impl UtxoRow {
.map_err(|_| sqlx::Error::Decode("Invalid txid length".into()))?;

let txid = Txid::from_byte_array(txid_array);
let outpoint = OutPoint::new(txid, self.vout as u32);
let outpoint = OutPoint::new(txid, self.vout);

let txout: TxOut = encode::deserialize(&self.serialized)?;

Expand Down Expand Up @@ -556,7 +563,7 @@ mod tests {
.unwrap();

let filter = Filter::new().asset_id(asset);
let results = store.query(&[filter.clone()]).await.unwrap();
let results = store.query(std::slice::from_ref(&filter)).await.unwrap();
assert!(matches!(&results[0], QueryResult::Found(e) if e.len() == 1));

store
Expand Down
3 changes: 3 additions & 0 deletions crates/options-relay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true

[dependencies]
nostr = { version = "0.44.2" }
Expand Down
2 changes: 1 addition & 1 deletion crates/options-relay/src/client/publishing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct PublishingClient {
impl PublishingClient {
#[instrument(skip_all, level = "debug", err)]
pub async fn connect(config: RelayConfig, signer: impl IntoNostrSigner) -> Result<Self, RelayError> {
let mut reader = ReadOnlyClient::connect(config).await?;
let reader = ReadOnlyClient::connect(config).await?;

reader.set_signer(signer).await;

Expand Down
2 changes: 1 addition & 1 deletion crates/options-relay/src/client/read_only.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl ReadOnlyClient {
&self.client
}

pub(crate) async fn set_signer(&mut self, signer: impl IntoNostrSigner) {
pub(crate) async fn set_signer(&self, signer: impl IntoNostrSigner) {
self.client.automatic_authentication(true);

self.client.set_signer(signer).await;
Expand Down
4 changes: 4 additions & 0 deletions crates/signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
readme.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true

[dependencies]
thiserror = "2"
Expand Down
3 changes: 0 additions & 3 deletions crates/signer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,13 @@ impl Signer {
self.keypair.x_only_public_key().0
}

#[must_use]
pub fn p2pk_address(&self, params: &'static AddressParams) -> Result<Address, SignerError> {
let public_key = self.keypair.x_only_public_key().0;
let address = get_p2pk_address(&public_key, params)?;

Ok(address)
}

#[must_use]
pub fn p2pk_script_hash(&self, params: &'static AddressParams) -> Result<[u8; 32], SignerError> {
let address = self.p2pk_address(params)?;

Expand All @@ -61,7 +59,6 @@ impl Signer {
Ok(script_hash)
}

#[must_use]
pub fn print_details(&self) -> Result<(), SignerError> {
let public_key = self.public_key();
let address = self.p2pk_address(&AddressParams::LIQUID_TESTNET)?;
Expand Down