use clap::Args;
use colored::Colorize;
use hex;
use pallas_addresses::Address;
use pallas_crypto::key::ed25519::SecretKey;
use pallas_traverse::fees;
use pallas_txbuilder::{BuildConway, BuiltTransaction, Input, Output, StagingTransaction};
use pallas_wallet::PrivateKey;
use rand_core::OsRng;
use seedelf_cli::address;
use seedelf_cli::assets::{Asset, Assets};
use seedelf_cli::constants::MAXIMUM_TOKENS_PER_UTXO;
use seedelf_cli::display::preprod_text;
use seedelf_cli::koios::{UtxoResponse, extract_bytes_with_logging};
use seedelf_cli::register::Register;
use seedelf_cli::transaction::wallet_minimum_lovelace_with_assets;
use seedelf_cli::utxos;
use seedelf_cli::web_server;
#[derive(Args)]
pub struct FundArgs {
#[arg(
short = 'a',
long,
help = "The address sending funds to the Seedelf.",
display_order = 1
)]
address: String,
#[arg(
short = 's',
long,
help = "The Seedelf receiving funds.",
display_order = 2
)]
seedelf: String,
#[arg(
short = 'l',
long,
help = "The amount of Lovelace being sent to the Seedelf.",
display_order = 3
)]
lovelace: Option<u64>,
#[arg(
long = "policy-id",
help = "The policy id for the asset.",
display_order = 4
)]
policy_id: Option<Vec<String>>,
#[arg(
long = "token-name",
help = "The token name for the asset.",
display_order = 5
)]
token_name: Option<Vec<String>>,
#[arg(long = "amount", help = "The amount for the asset.", display_order = 6)]
amount: Option<Vec<u64>>,
}
pub async fn run(args: FundArgs, network_flag: bool, variant: u64) -> Result<(), String> {
preprod_text(network_flag);
if args.lovelace.is_none()
&& (args.policy_id.is_none() || args.token_name.is_none() || args.amount.is_none())
{
return Err("No Lovelace or Assets Provided.".to_string());
}
let mut selected_tokens: Assets = Assets::new();
if let (Some(policy_id), Some(token_name), Some(amount)) =
(args.policy_id, args.token_name, args.amount)
{
if policy_id.len() != token_name.len() || policy_id.len() != amount.len() {
return Err(
"Error: Each --policy-id must have a corresponding --token-name and --amount."
.to_string(),
);
}
for ((pid, tkn), amt) in policy_id
.into_iter()
.zip(token_name.into_iter())
.zip(amount.into_iter())
{
if amt == 0 {
return Err("Error: Token Amount must be positive".to_string());
}
selected_tokens = selected_tokens.add(Asset::new(pid, tkn, amt));
}
}
let minimum_lovelace: u64 = wallet_minimum_lovelace_with_assets(selected_tokens.clone());
if args.lovelace.is_some_and(|l| l < minimum_lovelace) {
return Err("Not Enough Lovelace On UTxO".to_string());
}
let addr: Address = Address::from_bech32(args.address.as_str()).unwrap();
if !(address::is_not_a_script(addr.clone())
&& address::is_on_correct_network(addr.clone(), network_flag))
{
return Err("Supplied Address Is Incorrect".to_string());
}
let wallet_addr: Address = address::wallet_contract(network_flag, variant);
let mut draft_tx: StagingTransaction = StagingTransaction::new();
let lovelace: u64 = args.lovelace.unwrap_or(minimum_lovelace);
let lovelace_goal: u64 = lovelace;
let seedelf_utxo: UtxoResponse =
utxos::find_seedelf_utxo(args.seedelf.clone(), network_flag, variant)
.await
.ok_or("Seedelf Not Found".to_string())
.unwrap();
let seedelf_datum: Register = extract_bytes_with_logging(&seedelf_utxo.inline_datum)
.ok_or("Not Register Type".to_string())
.unwrap();
let all_utxos: Vec<UtxoResponse> =
utxos::collect_address_utxos(&args.address, network_flag).await;
let usuable_utxos: Vec<UtxoResponse> =
utxos::select(all_utxos, lovelace_goal, selected_tokens.clone());
if usuable_utxos.is_empty() {
return Err("Not Enough Lovelace/Tokens".to_string());
}
for utxo in usuable_utxos.clone() {
draft_tx = draft_tx.input(Input::new(
pallas_crypto::hash::Hash::new(
hex::decode(utxo.tx_hash.clone())
.expect("Invalid hex string")
.try_into()
.expect("Failed to convert to 32-byte array"),
),
utxo.tx_index,
));
}
let (total_lovelace, tokens) = utxos::assets_of(usuable_utxos);
let change_tokens: Assets = tokens.separate(selected_tokens.clone());
if total_lovelace < lovelace_goal {
return Err("Not Enough Lovelace/Tokens".to_string());
}
let tmp_fee: u64 = 200_000;
let datum_vector: Vec<u8> = seedelf_datum.rerandomize().to_vec();
let mut fund_output: Output =
Output::new(wallet_addr.clone(), lovelace).set_inline_datum(datum_vector.clone());
for asset in selected_tokens.items.clone() {
fund_output = fund_output
.add_asset(asset.policy_id, asset.token_name, asset.amount)
.unwrap();
}
draft_tx = draft_tx.output(fund_output).fee(tmp_fee);
let change_token_per_utxo: Vec<Assets> = change_tokens
.clone()
.split(MAXIMUM_TOKENS_PER_UTXO.try_into().unwrap());
let mut number_of_change_utxo: usize = change_token_per_utxo.len();
let mut lovelace_amount: u64 = total_lovelace;
for (i, change) in change_token_per_utxo.iter().enumerate() {
let minimum: u64 = wallet_minimum_lovelace_with_assets(change.clone());
let change_lovelace: u64 = if i == number_of_change_utxo - 1 {
lovelace_amount = lovelace_amount - lovelace - tmp_fee;
lovelace_amount
} else {
lovelace_amount -= minimum;
minimum
};
let mut change_output: Output = Output::new(addr.clone(), change_lovelace);
for asset in change.items.clone() {
change_output = change_output
.add_asset(asset.policy_id, asset.token_name, asset.amount)
.unwrap();
}
draft_tx = draft_tx.output(change_output);
}
if number_of_change_utxo == 0 {
let change_lovelace: u64 = lovelace_amount - lovelace - tmp_fee;
let change_output: Output = Output::new(addr.clone(), change_lovelace);
draft_tx = draft_tx.output(change_output);
number_of_change_utxo += 1;
}
let mut raw_tx: StagingTransaction = draft_tx.clone().clear_fee();
for i in 0..number_of_change_utxo {
raw_tx = raw_tx.remove_output(number_of_change_utxo - i);
}
let intermediate_tx: BuiltTransaction = draft_tx.build_conway_raw().unwrap();
let fake_signer_secret_key: SecretKey = SecretKey::new(OsRng);
let fake_signer_private_key: PrivateKey = PrivateKey::from(fake_signer_secret_key);
let tx_size: u64 = intermediate_tx
.sign(fake_signer_private_key)
.unwrap()
.tx_bytes
.0
.len()
.try_into()
.unwrap();
let tx_fee: u64 =
fees::compute_linear_fee_policy(tx_size, &(fees::PolicyParams::default())) + 1;
println!(
"{} {}",
"\nTx Size Fee:".bright_blue(),
tx_fee.to_string().bright_white()
);
let change_token_per_utxo: Vec<Assets> = change_tokens
.clone()
.split(MAXIMUM_TOKENS_PER_UTXO.try_into().unwrap());
let number_of_change_utxo: usize = change_token_per_utxo.len();
let mut lovelace_amount: u64 = total_lovelace;
for (i, change) in change_token_per_utxo.iter().enumerate() {
let minimum: u64 = wallet_minimum_lovelace_with_assets(change.clone());
let change_lovelace: u64 = if i == number_of_change_utxo - 1 {
lovelace_amount = lovelace_amount - lovelace - tx_fee;
lovelace_amount
} else {
lovelace_amount -= minimum;
minimum
};
let mut change_output: Output = Output::new(addr.clone(), change_lovelace);
for asset in change.items.clone() {
change_output = change_output
.add_asset(asset.policy_id, asset.token_name, asset.amount)
.unwrap();
}
raw_tx = raw_tx.output(change_output);
}
if number_of_change_utxo == 0 {
let change_lovelace: u64 = lovelace_amount - lovelace - tx_fee;
let change_output: Output = Output::new(addr.clone(), change_lovelace);
raw_tx = raw_tx.output(change_output);
}
raw_tx = raw_tx.fee(tx_fee);
let tx: BuiltTransaction = raw_tx.build_conway_raw().unwrap();
let tx_cbor: String = hex::encode(tx.tx_bytes);
println!("\nTx Cbor: {}", tx_cbor.clone().white());
web_server::run_web_server(tx_cbor, network_flag).await;
Ok(())
}