mirror of
https://github.com/creating2morrow/neveko.git
synced 2025-01-03 09:29:39 +00:00
add fee estimator before jwp creation
This commit is contained in:
parent
dea7a3a52a
commit
dbcddfa6a4
6 changed files with 123 additions and 43 deletions
|
@ -3,3 +3,4 @@ format_code_in_doc_comments = true
|
|||
imports_granularity = "Crate"
|
||||
imports_layout = "Vertical"
|
||||
wrap_comments = true
|
||||
ignore = ["lib.rs"]
|
||||
|
|
|
@ -91,8 +91,8 @@ async fn find_tunnels() {
|
|||
tokio::time::sleep(Duration::new(120, 0)).await;
|
||||
create_tunnel();
|
||||
}
|
||||
// TODO(c2m): why is i2p-zero http proxy always giving "destination not found" error?
|
||||
// if !has_http_tunnel { create_http_proxy(); }
|
||||
// TODO(c2m): why is i2p-zero http proxy always giving "destination not
|
||||
// found" error? if !has_http_tunnel { create_http_proxy(); }
|
||||
}
|
||||
|
||||
pub async fn start() {
|
||||
|
@ -133,7 +133,8 @@ fn create_tunnel() {
|
|||
// TODO(c2m): use i2p-zero http proxy
|
||||
// fn create_http_proxy() {
|
||||
// info!("creating http proxy");
|
||||
// let output = Command::new("./i2p-zero-linux.v1.20/router/bin/tunnel-control.sh")
|
||||
// let output =
|
||||
// Command::new("./i2p-zero-linux.v1.20/router/bin/tunnel-control.sh")
|
||||
// .args(["http.create", "4444"])
|
||||
// .spawn()
|
||||
// .expect("i2p-zero failed to create a http proxy");
|
||||
|
|
|
@ -935,7 +935,7 @@ pub async fn get_info() -> reqres::XmrDaemonGetInfoResponse {
|
|||
match client.post(host).json(&req).send().await {
|
||||
Ok(response) => {
|
||||
let res = response.json::<reqres::XmrDaemonGetInfoResponse>().await;
|
||||
debug!("{} response: {:?}", DaemonFields::GetInfo.value(), res);
|
||||
// add debug log here if needed for adding more info to home screen in gui
|
||||
match res {
|
||||
Ok(res) => res,
|
||||
_ => Default::default(),
|
||||
|
@ -949,16 +949,13 @@ pub async fn get_info() -> reqres::XmrDaemonGetInfoResponse {
|
|||
pub async fn get_height() -> reqres::XmrDaemonGetHeightResponse {
|
||||
info!("fetching daemon height");
|
||||
let client = reqwest::Client::new();
|
||||
let host = get_rpc_daemon();
|
||||
let req = reqres::XmrRpcRequest {
|
||||
jsonrpc: DaemonFields::Version.value(),
|
||||
id: DaemonFields::Id.value(),
|
||||
method: DaemonFields::GetHeight.value(),
|
||||
};
|
||||
match client.post(host).json(&req).send().await {
|
||||
let args = args::Args::parse();
|
||||
let daemon = String::from(args.monero_rpc_daemon);
|
||||
let req = format!("{}/{}", daemon, DaemonFields::GetHeight.value());
|
||||
match client.post(req).send().await {
|
||||
Ok(response) => {
|
||||
let res = response.json::<reqres::XmrDaemonGetHeightResponse>().await;
|
||||
debug!("{} response: {:?}", DaemonFields::GetHeight.value(), res);
|
||||
// don't log this one. The fee estimator blows up logs (T_T)
|
||||
match res {
|
||||
Ok(res) => res,
|
||||
_ => Default::default(),
|
||||
|
@ -983,7 +980,7 @@ pub async fn get_block(height: u64) -> reqres::XmrDaemonGetBlockResponse {
|
|||
match client.post(host).json(&req).send().await {
|
||||
Ok(response) => {
|
||||
let res = response.json::<reqres::XmrDaemonGetBlockResponse>().await;
|
||||
debug!("{} response: {:?}", DaemonFields::GetBlock.value(), res);
|
||||
// don't log this one. The fee estimator blows up logs (T_T)
|
||||
match res {
|
||||
Ok(res) => res,
|
||||
_ => Default::default(),
|
||||
|
@ -997,15 +994,19 @@ pub async fn get_block(height: u64) -> reqres::XmrDaemonGetBlockResponse {
|
|||
pub async fn get_transactions(txs_hashes: Vec<String>) -> reqres::XmrDaemonGetTransactionsResponse {
|
||||
info!("fetching {} transactions", txs_hashes.len());
|
||||
let client = reqwest::Client::new();
|
||||
let host = get_rpc_daemon();
|
||||
let args = args::Args::parse();
|
||||
let daemon = String::from(args.monero_rpc_daemon);
|
||||
let url = format!("{}/{}", daemon, DaemonFields::GetTransactions.value());
|
||||
let req = reqres::XmrDaemonGetTransactionsRequest {
|
||||
txs_hashes,
|
||||
decode_as_json: true,
|
||||
};
|
||||
match client.post(host).json(&req).send().await {
|
||||
match client.post(url).json(&req).send().await {
|
||||
Ok(response) => {
|
||||
let res = response.json::<reqres::XmrDaemonGetTransactionsResponse>().await;
|
||||
debug!("{} response: {:?}", DaemonFields::GetTransactions.value(), res);
|
||||
let res = response
|
||||
.json::<reqres::XmrDaemonGetTransactionsResponse>()
|
||||
.await;
|
||||
// don't log this one. The fee estimator blows up logs (T_T)
|
||||
match res {
|
||||
Ok(res) => res,
|
||||
_ => Default::default(),
|
||||
|
|
|
@ -563,7 +563,10 @@ pub struct XmrDaemonGetBlockResult {
|
|||
pub miner_tx_hash: String,
|
||||
pub status: String,
|
||||
pub top_hash: String,
|
||||
pub tx_hashes: Vec<String>,
|
||||
/// For some reason this field just disappears on non-
|
||||
///
|
||||
/// coinbase transactions instead of being an empty list.
|
||||
pub tx_hashes: Option<Vec<String>>,
|
||||
pub untrusted: bool,
|
||||
}
|
||||
// responses
|
||||
|
@ -659,13 +662,14 @@ impl Default for XmrDaemonGetBlockResponse {
|
|||
miner_tx_hash: utils::empty_string(),
|
||||
status: utils::empty_string(),
|
||||
top_hash: utils::empty_string(),
|
||||
tx_hashes: Vec::new(),
|
||||
tx_hashes: Some(Vec::new()),
|
||||
untrusted: false,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Only extract the json string. TODO(c2m): map to a struct
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct XmrDaemonGetTransactionsResponse {
|
||||
pub txs_as_json: Vec<String>,
|
||||
|
|
|
@ -593,7 +593,7 @@ fn validate_installation_hash(sw: ExternalSoftware, filename: &String) -> bool {
|
|||
actual_hash == expected_hash
|
||||
}
|
||||
|
||||
/// The highly ineffecient fee estimator.
|
||||
/// ### The highly ineffecient fee estimator.
|
||||
///
|
||||
/// Get the current height. Start fetching blocks
|
||||
///
|
||||
|
@ -603,21 +603,53 @@ fn validate_installation_hash(sw: ExternalSoftware, filename: &String) -> bool {
|
|||
///
|
||||
/// extract the `txnFee` from the `as_json` field.
|
||||
///
|
||||
/// Once we have accumulated n=30 fees paid return the
|
||||
/// Once we have accumulated n>=30 fees paid return the
|
||||
///
|
||||
/// average fee paid from the most recent 30 transactions.
|
||||
///
|
||||
/// Note, it may take more than one block to do this,
|
||||
///
|
||||
/// especially on stagenet.
|
||||
pub fn estimate_fee() -> u128 {
|
||||
|
||||
0
|
||||
pub async fn estimate_fee() -> u128 {
|
||||
let mut height: u64 = 0;
|
||||
let mut count: u64 = 1;
|
||||
let mut v_fee: Vec<u128> = Vec::new();
|
||||
loop {
|
||||
debug!("current height: {}", height);
|
||||
if v_fee.len() >= 30 { break; }
|
||||
let r_height = monero::get_height().await;
|
||||
height = r_height.height - count;
|
||||
let block = monero::get_block(height).await;
|
||||
if block.result.block_header.num_txes > 0 {
|
||||
debug!("fetching {} txs", block.result.block_header.num_txes);
|
||||
let tx_hashes: Option<Vec<String>> = block.result.tx_hashes;
|
||||
let transactions = monero::get_transactions(tx_hashes.unwrap()).await;
|
||||
for tx in transactions.txs_as_json {
|
||||
let pre_fee_split = tx.split("txnFee\":");
|
||||
let mut v1: Vec<String> = pre_fee_split.map(|s| String::from(s)).collect();
|
||||
let fee_split = v1.remove(1);
|
||||
let post_fee_split = fee_split.split(",");
|
||||
let mut v2: Vec<String> = post_fee_split.map(|s| String::from(s)).collect();
|
||||
let fee: u128 = match v2.remove(0).trim().parse::<u128>() {
|
||||
Ok(n) => n,
|
||||
Err(_e) => 0,
|
||||
};
|
||||
v_fee.push(fee);
|
||||
}
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
&v_fee.iter().sum() / v_fee.len() as u128
|
||||
}
|
||||
|
||||
/// Combine the results `estimate_fee()` and `get_balance()` to
|
||||
///
|
||||
/// determine whether or not a transfer is possible.
|
||||
pub fn can_transfer() -> bool {
|
||||
false
|
||||
/// determine whether or not a transfer for a given invoice is possible.
|
||||
pub async fn can_transfer(invoice: u128) -> bool {
|
||||
let balance = monero::get_balance().await;
|
||||
let fee = estimate_fee().await;
|
||||
debug!("fee estimated to: {}", fee);
|
||||
debug!("balance: {}", balance.result.unlocked_balance);
|
||||
debug!("fee + invoice = {}", invoice + fee);
|
||||
balance.result.unlocked_balance > (fee + invoice)
|
||||
}
|
||||
|
|
|
@ -74,6 +74,9 @@ pub struct AddressBookApp {
|
|||
approve_contact: bool,
|
||||
approve_payment: bool,
|
||||
added: bool,
|
||||
can_transfer: bool,
|
||||
can_transfer_tx: Sender<bool>,
|
||||
can_transfer_rx: Receiver<bool>,
|
||||
compose: Compose,
|
||||
contact: String,
|
||||
find_contact: String,
|
||||
|
@ -89,6 +92,7 @@ pub struct AddressBookApp {
|
|||
invoice_rx: Receiver<reqres::Invoice>,
|
||||
is_adding: bool,
|
||||
is_composing: bool,
|
||||
is_estimating_fee: bool,
|
||||
is_pinging: bool,
|
||||
is_loading: bool,
|
||||
is_message_sent: bool,
|
||||
|
@ -107,6 +111,7 @@ pub struct AddressBookApp {
|
|||
|
||||
impl Default for AddressBookApp {
|
||||
fn default() -> Self {
|
||||
let (can_transfer_tx, can_transfer_rx) = std::sync::mpsc::channel();
|
||||
let (contact_add_tx, contact_add_rx) = std::sync::mpsc::channel();
|
||||
let (contact_info_tx, contact_info_rx) = std::sync::mpsc::channel();
|
||||
let (contact_timeout_tx, contact_timeout_rx) = std::sync::mpsc::channel();
|
||||
|
@ -118,6 +123,9 @@ impl Default for AddressBookApp {
|
|||
approve_contact: false,
|
||||
approve_payment: false,
|
||||
added: false,
|
||||
can_transfer: false,
|
||||
can_transfer_rx,
|
||||
can_transfer_tx,
|
||||
compose: Default::default(),
|
||||
contact: utils::empty_string(),
|
||||
contacts: Vec::new(),
|
||||
|
@ -133,6 +141,7 @@ impl Default for AddressBookApp {
|
|||
invoice_rx,
|
||||
is_adding: false,
|
||||
is_composing: false,
|
||||
is_estimating_fee: false,
|
||||
is_loading: false,
|
||||
is_message_sent: false,
|
||||
is_pinging: false,
|
||||
|
@ -164,6 +173,7 @@ impl eframe::App for AddressBookApp {
|
|||
self.is_pinging = false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(added_contact) = self.contact_add_rx.try_recv() {
|
||||
self.s_added_contact = added_contact;
|
||||
if self.s_added_contact.cid != utils::empty_string() {
|
||||
|
@ -171,6 +181,7 @@ impl eframe::App for AddressBookApp {
|
|||
self.is_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(timeout) = self.contact_timeout_rx.try_recv() {
|
||||
self.is_timeout = true;
|
||||
if timeout {
|
||||
|
@ -180,9 +191,18 @@ impl eframe::App for AddressBookApp {
|
|||
self.contact = utils::empty_string();
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(invoice) = self.invoice_rx.try_recv() {
|
||||
self.s_invoice = invoice;
|
||||
if self.s_invoice.pay_threshold > 0 {
|
||||
send_can_transfer_req(
|
||||
self.can_transfer_tx.clone(),
|
||||
ctx.clone(), self.s_invoice.pay_threshold
|
||||
);
|
||||
self.is_estimating_fee = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(payment) = self.payment_rx.try_recv() {
|
||||
self.is_payment_processed = payment;
|
||||
if self.is_payment_processed {
|
||||
|
@ -191,6 +211,7 @@ impl eframe::App for AddressBookApp {
|
|||
self.showing_status = false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(message) = self.send_message_rx.try_recv() {
|
||||
self.is_message_sent = message;
|
||||
if self.is_message_sent {
|
||||
|
@ -200,6 +221,11 @@ impl eframe::App for AddressBookApp {
|
|||
}
|
||||
}
|
||||
|
||||
if let Ok(can_transfer) = self.can_transfer_rx.try_recv() {
|
||||
self.can_transfer = can_transfer;
|
||||
self.is_estimating_fee = false;
|
||||
}
|
||||
|
||||
// initial contact load
|
||||
if !self.contacts_init {
|
||||
self.contacts = contact::find_all();
|
||||
|
@ -258,14 +284,19 @@ impl eframe::App for AddressBookApp {
|
|||
ui.add(egui::Spinner::new());
|
||||
ui.label("creating jwp may take a few minutes...");
|
||||
}
|
||||
if self.is_estimating_fee {
|
||||
ui.add(egui::Spinner::new());
|
||||
ui.label("running nevmes jwp fee estimator...");
|
||||
}
|
||||
ui.heading(self.status.i2p.clone());
|
||||
ui.label(format!("pay to: {}", address));
|
||||
ui.label(format!("amount: {} piconero(s)", amount));
|
||||
ui.label(format!("expiration: {} blocks", expire));
|
||||
if !self.is_loading {
|
||||
if self.s_invoice.address != utils::empty_string() {
|
||||
if self.s_invoice.address != utils::empty_string() && self.can_transfer {
|
||||
if ui.button("Approve").clicked() {
|
||||
// activate xmr "transfer", check the hash, update db and refresh
|
||||
// Note it is simply disabled on insufficient funds as calcd by fee estimator
|
||||
let d: reqres::Destination = reqres::Destination { address, amount };
|
||||
send_payment_req(
|
||||
self.payment_tx.clone(),
|
||||
|
@ -764,3 +795,13 @@ fn change_nick_req(contact: String, nick: String) {
|
|||
clear_gui_db(String::from("gui-nick"), String::from(&contact));
|
||||
write_gui_db(String::from("gui-nick"), String::from(&contact), nick);
|
||||
}
|
||||
|
||||
fn send_can_transfer_req(tx: Sender<bool>, ctx: egui::Context, invoice: u128) {
|
||||
log::debug!("async send_can_transfer_req");
|
||||
tokio::spawn(async move {
|
||||
let can_transfer = utils::can_transfer(invoice).await;
|
||||
log::debug!("can transfer: {}", can_transfer);
|
||||
let _ = tx.send(can_transfer);
|
||||
ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue