Skip to content

Commit

Permalink
Merge pull request #22 from logical-mechanism/cli-review-02
Browse files Browse the repository at this point in the history
Cli review 02
  • Loading branch information
logicalmechanism authored Dec 17, 2024
2 parents bd9060c + 4fd66f4 commit bbe4b7b
Show file tree
Hide file tree
Showing 22 changed files with 819 additions and 198 deletions.
2 changes: 1 addition & 1 deletion seedelf-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "seedelf-cli"
version = "0.1.0"
version = "0.2.0"
edition = "2021"

[dependencies]
Expand Down
13 changes: 10 additions & 3 deletions seedelf-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Seedelf CLI

Seedelf is a stealth wallet that hides the receiver and spender with Schnorr proofs.
Seedelf is a stealth wallet that hides the receiver and spender.

- TODO -> Build github action workflow for tagged releases

## Installation

Installing on path

Expand Down Expand Up @@ -39,11 +43,14 @@ Commands:
help Print this message or the help of the given subcommand(s)

Options:
--preprod This forces each command to use the pre-production environment
--preprod Use this flag to interact with the pre-production environment
-h, --help Print help
-V, --version Print version

```

Create a Seedelf with the `seedelf-new` command. The Seedelf is funded with the `fund` command. Send funds to another Seedelf with the `transfer` command.
### Basic Usage

Create a Seedelf with the `seedelf-new` command. The Seedelf is funded with the `fund` command. Send funds to another Seedelf with the `transfer` command. Funds can be send to an address with the `sweep` command. Use the `--help` option to see more information.

Some commands will prompt to open a localhost for cip30 wallet interaction.
71 changes: 63 additions & 8 deletions seedelf-cli/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,62 @@ use pallas_addresses::{
ShelleyPaymentPart, StakeKeyHash,
};

/// Determines whether the given address belongs to the correct Cardano network.
///
/// Checks if the provided address matches the expected network based on the `network_flag`.
///
/// # Arguments
///
/// * `addr` - A Cardano address to verify.
/// * `network_flag` - A boolean flag indicating the expected network:
/// - `true` checks for Testnet.
/// - `false` checks for Mainnet.
///
/// # Returns
///
/// * `true` if the address matches the expected network.
/// * `false` otherwise.
pub fn is_on_correct_network(addr: Address, network_flag: bool) -> bool {
if network_flag {
pallas_addresses::Address::network(&addr) == Some(pallas_addresses::Network::Testnet)
Address::network(&addr) == Some(Network::Testnet)
} else {
pallas_addresses::Address::network(&addr) == Some(pallas_addresses::Network::Mainnet)
Address::network(&addr) == Some(Network::Mainnet)
}
}

/// Determines whether the given address is not a script address.
///
/// This function checks if the provided Cardano address does not contain a script.
///
/// # Arguments
///
/// * `addr` - A Cardano address to verify.
///
/// # Returns
///
/// * `true` if the address does not contain a script.
/// * `false` if the address contains a script.
pub fn is_not_a_script(addr: Address) -> bool {
!pallas_addresses::Address::has_script(&addr)
!Address::has_script(&addr)
}

/// Given a network flag produce the Address type for the wallet contract.
/// Generates the wallet contract address for the specified Cardano network.
///
/// This function constructs a Shelley address for the wallet contract based on the given `network_flag`.
/// If the flag indicates Testnet, the Testnet network and pre-production stake hash are used.
/// Otherwise, the Mainnet network and stake hash are used.
///
/// # Arguments
///
/// * `network_flag` - A boolean flag specifying the network:
/// - `true` for Testnet.
/// - `false` for Mainnet.
///
/// # Returns
///
/// * `Address` - The wallet contract address for the specified network.
pub fn wallet_contract(network_flag: bool) -> Address {
// wallet script address
// Construct the Shelley wallet address based on the network flag.
let shelly_wallet_address: ShelleyAddress = if network_flag {
ShelleyAddress::new(
Network::Testnet,
Expand Down Expand Up @@ -58,9 +99,23 @@ pub fn wallet_contract(network_flag: bool) -> Address {
Address::from(shelly_wallet_address.clone())
}

// This addres is not staked
/// Generates a collateral address for the specified Cardano network.
///
/// This function creates a Shelley address for collateral purposes. The address is not staked,
/// meaning it has a `Null` delegation part. The `network_flag` determines whether the address
/// is for the Testnet or Mainnet.
///
/// # Arguments
///
/// * `network_flag` - A boolean flag specifying the network:
/// - `true` for Testnet.
/// - `false` for Mainnet.
///
/// # Returns
///
/// * `Address` - The collateral address for the specified network.
pub fn collateral_address(network_flag: bool) -> Address {
// wallet script address
// Construct the Shelley wallet address based on the network flag.
let shelly_wallet_address: ShelleyAddress = if network_flag {
ShelleyAddress::new(
Network::Testnet,
Expand All @@ -84,6 +139,6 @@ pub fn collateral_address(network_flag: bool) -> Address {
ShelleyDelegationPart::Null,
)
};
// we need this as the address type and not the shelley
// Convert the Shelley address to the generic Address type.
Address::from(shelly_wallet_address.clone())
}
82 changes: 79 additions & 3 deletions seedelf-cli/src/assets.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use pallas_crypto::hash::Hash;
use serde::{Deserialize, Serialize};

// An Asset is a non-lovelace value
/// Represents an asset in the Cardano blockchain.
///
/// An `Asset` is identified by a `policy_id` and a `token_name`, and it tracks
/// the amount of tokens associated with the asset.
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Hash, Clone)]
pub struct Asset {
pub policy_id: Hash<28>,
Expand All @@ -10,6 +13,13 @@ pub struct Asset {
}

impl Asset {
/// Creates a new `Asset` instance.
///
/// # Arguments
///
/// * `policy_id` - A hex-encoded string representing the policy ID.
/// * `token_name` - A hex-encoded string representing the token name.
/// * `amount` - The amount of tokens for the asset.
pub fn new(policy_id: String, token_name: String, amount: u64) -> Self {
Self {
policy_id: Hash::new(
Expand All @@ -23,6 +33,16 @@ impl Asset {
}
}

/// Adds two assets together if they have the same `policy_id` and `token_name`.
///
/// # Arguments
///
/// * `other` - The other asset to add.
///
/// # Returns
///
/// * `Ok(Self)` - The resulting `Asset` with the combined amounts.
/// * `Err(String)` - If the `policy_id` or `token_name` do not match.
pub fn add(&self, other: &Asset) -> Result<Self, String> {
if self.policy_id != other.policy_id || self.token_name != other.token_name {
return Err(format!(
Expand All @@ -36,6 +56,16 @@ impl Asset {
})
}

/// Subtracts the amount of another asset if they have the same `policy_id` and `token_name`.
///
/// # Arguments
///
/// * `other` - The other asset to subtract.
///
/// # Returns
///
/// * `Ok(Self)` - The resulting `Asset` after subtraction.
/// * `Err(String)` - If the `policy_id` or `token_name` do not match.
pub fn sub(&self, other: &Asset) -> Result<Self, String> {
if self.policy_id != other.policy_id || self.token_name != other.token_name {
return Err(format!(
Expand All @@ -49,6 +79,17 @@ impl Asset {
})
}

/// Compares two assets for equivalence in `policy_id` and `token_name`,
/// and checks if the amount is greater or equal.
///
/// # Arguments
///
/// * `other` - The other asset to compare against.
///
/// # Returns
///
/// * `true` if the `policy_id` and `token_name` match and the amount is greater or equal.
/// * `false` otherwise.
pub fn compare(&self, other: Asset) -> bool {
if self.policy_id != other.policy_id || self.token_name != other.token_name {
false
Expand All @@ -58,16 +99,27 @@ impl Asset {
}
}

/// Represents a collection of `Asset` instances.
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Hash, Clone)]
pub struct Assets {
pub items: Vec<Asset>,
}

impl Assets {
/// Creates a new, empty `Assets` instance.
pub fn new() -> Self {
Self { items: Vec::new() }
}

/// Adds an asset to the collection, combining amounts if the asset already exists.
///
/// # Arguments
///
/// * `other` - The asset to add.
///
/// # Returns
///
/// * A new `Assets` instance with the updated list of assets.
pub fn add(&self, other: Asset) -> Self {
let mut new_items: Vec<Asset> = self.items.clone();
if let Some(existing) = new_items.iter_mut().find(|existing| {
Expand All @@ -80,6 +132,15 @@ impl Assets {
Self { items: new_items }
}

/// Subtracts an asset from the collection, removing it if the amount becomes zero.
///
/// # Arguments
///
/// * `other` - The asset to subtract.
///
/// # Returns
///
/// * A new `Assets` instance with updated asset amounts.
pub fn sub(&self, other: Asset) -> Self {
let mut new_items: Vec<Asset> = self.items.clone();
if let Some(existing) = new_items.iter_mut().find(|existing| {
Expand All @@ -92,6 +153,7 @@ impl Assets {
Self { items: new_items }.remove_zero_amounts()
}

/// Removes assets with zero amounts from the collection.
pub fn remove_zero_amounts(&self) -> Self {
let filtered_items: Vec<Asset> = self
.items
Expand All @@ -104,6 +166,7 @@ impl Assets {
}
}

/// Checks if all assets in `other` are contained in this collection.
pub fn contains(&self, other: Assets) -> bool {
// search all other tokens and make sure they exist in these assets
for other_token in other.items {
Expand All @@ -125,6 +188,7 @@ impl Assets {
true
}

/// Checks if any asset in `other` exists in this collection.
pub fn any(&self, other: Assets) -> bool {
if other.items.is_empty() {
return true
Expand All @@ -143,8 +207,9 @@ impl Assets {
false
}

/// Merges two collections of assets, combining amounts of matching assets.
pub fn merge(&self, other: Assets) -> Self {
let mut merged = self.clone(); // Clone the current `Assets` as a starting point
let mut merged: Assets = self.clone(); // Clone the current `Assets` as a starting point

for other_asset in other.items {
merged = merged.add(other_asset); // Use `add` to handle merging logic
Expand All @@ -153,8 +218,9 @@ impl Assets {
merged
}

/// Separates two collections of assets, subtracting amounts of matching assets.
pub fn separate(&self, other: Assets) -> Self {
let mut separated = self.clone(); // Clone the current `Assets` as a starting point
let mut separated: Assets = self.clone(); // Clone the current `Assets` as a starting point

for other_asset in other.items {
separated = separated.sub(other_asset); // Use `add` to handle merging logic
Expand All @@ -169,6 +235,16 @@ impl Assets {

}

/// Converts a string into a `u64` value.
///
/// # Arguments
///
/// * `input` - The string to parse into a `u64`.
///
/// # Returns
///
/// * `Ok(u64)` - If the conversion is successful.
/// * `Err(String)` - If the conversion fails.
pub fn string_to_u64(input: String) -> Result<u64, String> {
match input.parse::<u64>() {
Ok(value) => Ok(value),
Expand Down
45 changes: 6 additions & 39 deletions seedelf-cli/src/commands/balance.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,19 @@
use blstrs::Scalar;
use reqwest::Error;
use seedelf_cli::constants::{WALLET_CONTRACT_HASH, SEEDELF_POLICY_ID};
use seedelf_cli::koios::{credential_utxos, extract_bytes_with_logging, tip, contains_policy_id, UtxoResponse};
use seedelf_cli::display;
use seedelf_cli::koios::UtxoResponse;
use seedelf_cli::utxos;
use crate::setup;

pub async fn run(network_flag: bool) -> Result<(), Error> {
if network_flag {
println!("\nRunning In Preprod Environment");
}

match tip(network_flag).await {
Ok(tips) => {
if let Some(tip) = tips.get(0) {
println!("\nBlock Number: {} @ Time: {}", tip.block_no, tip.block_time);
}
}
Err(err) => {
eprintln!("Failed to fetch blockchain tip: {}\nWait a few moments and try again.", err);
}
}
display::preprod_text(network_flag);
display::block_number_and_time(network_flag).await;

let scalar: Scalar = setup::load_wallet();
let mut all_utxos: Vec<UtxoResponse> = Vec::new();

match credential_utxos(WALLET_CONTRACT_HASH, network_flag).await {
Ok(utxos) => {
for utxo in utxos {
// Extract bytes
if let Some(inline_datum) = extract_bytes_with_logging(&utxo.inline_datum) {
// utxo must be owned by this secret scaler
if inline_datum.is_owned(scalar) {
// its owned but lets not count the seedelf in the balance
if !contains_policy_id(&utxo.asset_list, SEEDELF_POLICY_ID) {
all_utxos.push(utxo.clone());
}
}
}
}
}
Err(err) => {
eprintln!("Failed to fetch UTxOs: {}\nWait a few moments and try again.", err);
}
}

let all_utxos: Vec<UtxoResponse> = utxos::collect_all_wallet_utxos(scalar, network_flag).await;
let (total_lovelace, tokens) = utxos::assets_of(all_utxos.clone());

println!("Wallet Has {} UTxOs", all_utxos.len());
println!("\nWallet Has {} UTxOs", all_utxos.len());
println!("\nBalance: {:.6} ₳", total_lovelace as f64 / 1_000_000.0);

if tokens.items.len() > 0 {
Expand Down
Loading

0 comments on commit bbe4b7b

Please sign in to comment.