use crate::blocks::swap::SwapTransaction; use crate::common::asset_names::is_canonical_padded_asset_or_base; use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::common::nft_assets::nft_asset_name; use crate::common::types::{COIN_LENGTH, SWAP_FEE}; use crate::encode; use crate::records::wallet_registry::{ require_canonical_registered_short_address, resolve_pubkey_from_short_address, }; use crate::sled::Db; use crate::verifications::async_funcs::checks::balance_check::balance_checkup; use crate::verifications::async_funcs::checks::mempool_check::memcheck; use crate::verifications::async_funcs::checks::time_checks::is_within_30_days; use crate::verifications::async_funcs::checks::verify_db::{ db_bytes_verification, db_hex_verification, }; use crate::wallets::structures::Wallet; use crate::Utc; impl SwapTransaction { pub async fn verify(&self, db: &Db) -> Result { // Transactions already present in the mempool can short-circuit // the deeper verification path and reuse their stored signature. let hash = self.unsigned_swap.hash().await; if memcheck(&self.signature2, &hash).await { return Ok(self.signature2.clone()); } // Both swap participants must provide valid wallet addresses and // matching signatures over the shared unsigned swap payload. if !(Wallet::short_address_validation(&self.unsigned_swap.sender1) && Wallet::short_address_validation(&self.unsigned_swap.sender2)) { return Err("Sender1 or Sender2 Wallet Address is Invalid.".to_string()); } require_canonical_registered_short_address( db, &self.unsigned_swap.sender1, "Sender1 Wallet Address", )?; require_canonical_registered_short_address( db, &self.unsigned_swap.sender2, "Sender2 Wallet Address", )?; // Sender1 must sign the same unsigned swap payload that sender2 // signs, proving agreement to the offered side of the swap. let sender1_pubkey = resolve_pubkey_from_short_address(db, &self.unsigned_swap.sender1) .map_err(|_| "Sender1 Wallet Address is not registered.".to_string())? .ok_or_else(|| "Sender1 Wallet Address is not registered.".to_string())?; let sender1_pubkey_hex = encode(&sender1_pubkey); if !Wallet::verify_transaction_with_public_key(&hash, &self.signature1, &sender1_pubkey_hex) .await { return Err("Invalid signature1 the RewardsTransaction.".to_string()); } // Sender2's registered public key verifies the counterparty // signature on the same swap hash. let sender2_pubkey = resolve_pubkey_from_short_address(db, &self.unsigned_swap.sender2) .map_err(|_| "Sender2 Wallet Address is not registered.".to_string())? .ok_or_else(|| "Sender2 Wallet Address is not registered.".to_string())?; let sender2_pubkey_hex = encode(&sender2_pubkey); if !Wallet::verify_transaction_with_public_key(&hash, &self.signature2, &sender2_pubkey_hex) .await { return Err("Invalid signature2 the RewardsTransaction.".to_string()); } // Asset names use the fixed padded coin-length format. if self.unsigned_swap.ticker1.len() != COIN_LENGTH || self.unsigned_swap.ticker2.len() != COIN_LENGTH { return Err( "Ticker lengths are invalid. Consider padding with empty spaces".to_string(), ); } let network_base_coin_padded = block_extension_and_paths().1; if !is_canonical_padded_asset_or_base(&self.unsigned_swap.ticker1, &network_base_coin_padded) || !is_canonical_padded_asset_or_base( &self.unsigned_swap.ticker2, &network_base_coin_padded, ) { return Err( "Swap assets must be the base coin or canonical alphanumeric asset ids." .to_string(), ); } // Build concrete asset names for NFT series swaps before checking // balances and registry existence. let asset1 = nft_asset_name(&self.unsigned_swap.ticker1, self.unsigned_swap.nft_series1); let asset2 = nft_asset_name(&self.unsigned_swap.ticker2, self.unsigned_swap.nft_series2); // The base coin name comes from the active network settings so // mainnet and testnet validate against their own ticker. let network_base_coin = network_base_coin_padded.trim().to_lowercase(); // Numbered NFT swaps are single-item transfers, while non-series // assets must already exist as a token, NFT, or base coin. if self.unsigned_swap.nft_series1 > 0 { if self.unsigned_swap.value1 != 1 { return Err("Series NFTs must swap exactly 1 item.".to_string()); } if !db_bytes_verification(db, "nfts", &asset1).await { return Err("Ticker1 NFT item does not exist.".to_string()); } } else if !(self.unsigned_swap.ticker1.trim().to_lowercase() == network_base_coin || db_bytes_verification(db, "tokens", &self.unsigned_swap.ticker1).await || db_bytes_verification(db, "nfts", &self.unsigned_swap.ticker1).await) { return Err("Ticker1 does not exist.".to_string()); } if self.unsigned_swap.nft_series2 > 0 { if self.unsigned_swap.value2 != 1 { return Err("Series NFTs must swap exactly 1 item.".to_string()); } if !db_bytes_verification(db, "nfts", &asset2).await { return Err("Ticker2 NFT item does not exist.".to_string()); } } else if !(self.unsigned_swap.ticker2.trim().to_lowercase() == network_base_coin || db_bytes_verification(db, "tokens", &self.unsigned_swap.ticker2).await || db_bytes_verification(db, "nfts", &self.unsigned_swap.ticker2).await) { return Err("Ticker2 does not exist.".to_string()); } // Each signer pays the fixed swap fee for their side of the trade. if self.unsigned_swap.txfee1 < SWAP_FEE { return Err(format!( "Swap sender1 transaction fee is below the minimum required fee of {SWAP_FEE}." )); } if self.unsigned_swap.txfee2 < SWAP_FEE { return Err(format!( "Swap sender2 transaction fee is below the minimum required fee of {SWAP_FEE}." )); } // Each side must be able to cover the offered amount plus any // asset-denominated tip and the base-coin transaction fee. let full_value1 = self.unsigned_swap.value1 + self.unsigned_swap.tip1; if !balance_checkup( db, full_value1, self.unsigned_swap.txfee1, asset1.clone(), &self.unsigned_swap.sender1, ) .await { return Err("Insuficient funds for this Swap Transaction!".to_string()); } let full_value2 = self.unsigned_swap.value2 + self.unsigned_swap.tip2; if !balance_checkup( db, full_value2, self.unsigned_swap.txfee2, asset2, &self.unsigned_swap.sender2, ) .await { return Err("Insuficient funds for this Swap Transaction!".to_string()); } // Swap offers are bounded both by signature age and by the // explicit offer-expiration window carried in the transaction. if !is_within_30_days(self.unsigned_swap.timestamp).await { return Err( "Timestamp is to old. Transactions must be broadcast within 30 days of signing." .to_string(), ); } let now = Utc::now().timestamp() as u32; let offer_expiration = self.unsigned_swap.offer_expiration; let timestamp = self.unsigned_swap.timestamp; if offer_expiration < timestamp { return Err( "Offer expiration cannot be earlier than the transaction timestamp.".to_string(), ); } if offer_expiration > timestamp.saturating_add(30 * 24 * 60 * 60) { return Err( "Offer expiration cannot be more than 30 days after the transaction timestamp." .to_string(), ); } if now > offer_expiration { return Err("This swap offer has expired.".to_string()); } // Saved-chain duplicates are rejected by txid even if the mempool // did not already contain the transaction. let tree = "txid"; if !db_hex_verification(db, tree, &hash).await { return Err("This transaction already exists.".to_string()); } // Verification returns no auxiliary cleanup marker for this transaction type. let sign = ""; Ok(sign.to_string()) } }