From fca5801ef0b23df48566b27ee05dbc1498427176 Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Wed, 28 Dec 2022 16:04:26 -0500
Subject: [PATCH] Status Submenu: separate [Regex], [AtomicUnit], [PayoutOrd]

This fixes some funcs, tests and separates some structs into separate files.
---
 README.md     |   2 +-
 src/README.md |   4 +-
 src/disk.rs   | 142 ++++++++++-------------
 src/helper.rs | 296 ++++++-----------------------------------------
 src/main.rs   |  76 ++++++-------
 src/regex.rs  |  89 +++++++++++++++
 src/xmr.rs    | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 533 insertions(+), 386 deletions(-)
 create mode 100644 src/regex.rs
 create mode 100644 src/xmr.rs

diff --git a/README.md b/README.md
index f281a62..d495d80 100644
--- a/README.md
+++ b/README.md
@@ -482,7 +482,7 @@ You need [`cargo`](https://www.rust-lang.org/learn/get-started), Rust's build to
 
 The `--release` profile in Gupax is set to prefer code performance & small binary sizes over compilation speed (see [`Cargo.toml`](https://github.com/hinto-janaiyo/gupax/blob/main/Cargo.toml)). Gupax itself (with all dependencies already built) takes around 1m30s to build (vs 10s on a normal `--release`) with a Ryzen 5950x.
 
-There are `28` unit tests throughout the codebase files, you should probably run:
+There are `30` unit tests throughout the codebase files, you should probably run:
 ```
 cargo test
 ```
diff --git a/src/README.md b/src/README.md
index d1ab7da..dd7a65e 100644
--- a/src/README.md
+++ b/src/README.md
@@ -18,7 +18,7 @@
 ## Structure
 | File/Folder  | Purpose |
 |--------------|---------|
-| constants.rs | General constants needed in Gupax
+| constants.rs | General constants used in Gupax
 | disk.rs      | Code for writing to disk: `state.toml/node.toml/pool.toml`; This holds the structs for the [State] struct
 | ferris.rs    | Cute crab bytes
 | gupax.rs     | `Gupax` tab
@@ -26,9 +26,11 @@
 | main.rs      | The main `App` struct that holds all data + misc data/functions
 | node.rs      | Community node ping code for the `P2Pool` simple tab
 | p2pool.rs    | `P2Pool` tab
+| regex.rs     | General regexes used in Gupax
 | status.rs    | `Status` tab
 | sudo.rs      | Code for handling `sudo` escalation for XMRig on Unix
 | update.rs    | Update code for the `Gupax` tab
+| xmr.rs       | Code for handling actual XMR, `AtomicUnit` & `PayoutOrd`
 | xmrig.rs     | `XMRig` tab
 
 ## Thread Model
diff --git a/src/disk.rs b/src/disk.rs
index ef00254..1d10b95 100644
--- a/src/disk.rs
+++ b/src/disk.rs
@@ -46,6 +46,7 @@ use crate::{
 	constants::*,
 	gupax::Ratio,
 	Tab,
+	xmr::*,
 };
 use log::*;
 
@@ -78,17 +79,13 @@ pub const POOL_TOML: &str = "pool.toml";
 pub const GUPAX_P2POOL_API_DIRECTORY:    &str = r"p2pool\";
 #[cfg(target_family = "unix")]
 pub const GUPAX_P2POOL_API_DIRECTORY:    &str = "p2pool/";
-pub const GUPAX_P2POOL_API_PAYOUT:       &str = "payout";
-pub const GUPAX_P2POOL_API_BLOCK:        &str = "block";
-pub const GUPAX_P2POOL_API_TOTAL_PAYOUT: &str = "total_payout";
-pub const GUPAX_P2POOL_API_TOTAL_XMR:    &str = "total_xmr";
-pub const GUPAX_P2POOL_API_TOTAL_BLOCK:  &str = "total_block";
-pub const GUPAX_P2POOL_API_FILE_ARRAY: [&str; 5] = [
+pub const GUPAX_P2POOL_API_PAYOUT:       &str = "payout_log";
+pub const GUPAX_P2POOL_API_TOTAL_PAYOUT: &str = "payout";
+pub const GUPAX_P2POOL_API_TOTAL_XMR:    &str = "xmr";
+pub const GUPAX_P2POOL_API_FILE_ARRAY: [&str; 3] = [
 	GUPAX_P2POOL_API_PAYOUT,
-	GUPAX_P2POOL_API_BLOCK,
 	GUPAX_P2POOL_API_TOTAL_PAYOUT,
 	GUPAX_P2POOL_API_TOTAL_XMR,
-	GUPAX_P2POOL_API_TOTAL_BLOCK,
 ];
 
 #[cfg(target_os = "windows")]
@@ -562,21 +559,16 @@ impl Pool {
 }
 
 //---------------------------------------------------------------------------------------------------- Gupax-P2Pool API
-#[derive(Clone,Eq,PartialEq,Debug)]
+#[derive(Clone,Debug)]
 pub struct GupaxP2poolApi {
 	pub payout: HumanNumber,
-	pub xmr: HumanNumber,
-	pub block: HumanNumber,
-	pub int_payout: u128,
-	pub int_xmr: u128,
-	pub int_block: u128,
+	pub xmr: AtomicUnit,
+	pub int_payout: u64,
+	pub payout_ord: PayoutOrd, // Ordered Vec of payouts
 	pub log_payout: String,
-	pub log_block: String,
+	pub path_xmr: PathBuf,
 	pub path_int_payout: PathBuf,
-	pub path_int_xmr: PathBuf,
-	pub path_int_block: PathBuf,
 	pub path_log_payout: PathBuf,
-	pub path_log_block: PathBuf,
 }
 
 impl Default for GupaxP2poolApi { fn default() -> Self { Self::new() } }
@@ -585,38 +577,27 @@ impl GupaxP2poolApi {
 	pub fn new() -> Self {
 		Self {
 			payout: HumanNumber::unknown(),
-			xmr: HumanNumber::unknown(),
-			block: HumanNumber::unknown(),
+			xmr: AtomicUnit::new(),
 			int_payout: 0,
-			int_xmr: 0,
-			int_block: 0,
 			log_payout: String::new(),
-			log_block: String::new(),
+			payout_ord: PayoutOrd::new(),
+			path_xmr: PathBuf::new(),
 			path_int_payout: PathBuf::new(),
-			path_int_xmr: PathBuf::new(),
-			path_int_block: PathBuf::new(),
 			path_log_payout: PathBuf::new(),
-			path_log_block: PathBuf::new(),
 		}
 	}
 
 	pub fn fill_paths(&mut self, gupax_p2pool_dir: &PathBuf) {
+		let mut path_xmr        = gupax_p2pool_dir.clone();
 		let mut path_int_payout = gupax_p2pool_dir.clone();
-		let mut path_int_xmr    = gupax_p2pool_dir.clone();
-		let mut path_int_block  = gupax_p2pool_dir.clone();
 		let mut path_log_payout = gupax_p2pool_dir.clone();
-		let mut path_log_block  = gupax_p2pool_dir.clone();
 		path_int_payout.push(GUPAX_P2POOL_API_TOTAL_PAYOUT);
-		path_int_xmr.push(GUPAX_P2POOL_API_TOTAL_XMR);
-		path_int_block.push(GUPAX_P2POOL_API_TOTAL_BLOCK);
+		path_xmr.push(GUPAX_P2POOL_API_TOTAL_XMR);
 		path_log_payout.push(GUPAX_P2POOL_API_PAYOUT);
-		path_log_block.push(GUPAX_P2POOL_API_BLOCK);
 		*self = Self {
 			path_int_payout,
-			path_int_xmr,
-			path_int_block,
+			path_xmr,
 			path_log_payout,
-			path_log_block,
 			..std::mem::take(self)
 		};
 	}
@@ -633,7 +614,7 @@ impl GupaxP2poolApi {
 			match std::fs::File::create(&path) {
 				Ok(mut f)  => {
 					match file {
-						GUPAX_P2POOL_API_TOTAL_PAYOUT|GUPAX_P2POOL_API_TOTAL_XMR|GUPAX_P2POOL_API_TOTAL_BLOCK => f.write_all(b"0")?,
+						GUPAX_P2POOL_API_TOTAL_PAYOUT|GUPAX_P2POOL_API_TOTAL_XMR => writeln!(f, "0")?,
 						_ => (),
 					}
 					info!("GupaxP2poolApi | [{}] create ... OK", path.display());
@@ -645,50 +626,63 @@ impl GupaxP2poolApi {
 	}
 
 	pub fn read_all_files_and_update(&mut self) -> Result<(), TomlError> {
-		let int_payout = match read_to_string(File::IntPayout, &self.path_int_payout)?.as_str().parse::<u128>() {
+		let int_payout = match read_to_string(File::IntPayout, &self.path_int_payout)?.trim().parse::<u64>() {
 			Ok(o)  => o,
 			Err(e) => { warn!("GupaxP2poolApi | [int_payout] parse error: {}", e); return Err(TomlError::Parse("int_payout")) }
 		};
-		let int_xmr = match read_to_string(File::IntXmr, &self.path_int_xmr)?.as_str().parse::<u128>() {
-			Ok(o)  => o,
-			Err(e) => { warn!("GupaxP2poolApi | [int_xmr] parse error: {}", e); return Err(TomlError::Parse("int_xmr")) }
+ 		let xmr = match read_to_string(File::IntXmr, &self.path_xmr)?.trim().parse::<u128>() {
+			Ok(o)  => AtomicUnit::from_u128(o),
+			Err(e) => { warn!("GupaxP2poolApi | [xmr] parse error: {}", e); return Err(TomlError::Parse("xmr")) }
 		};
-		let int_block = match read_to_string(File::IntBlock, &self.path_int_block)?.as_str().parse::<u128>() {
-			Ok(o)  => o,
-			Err(e) => { warn!("GupaxP2poolApi | [int_block] parse error: {}", e); return Err(TomlError::Parse("int_block")) }
-		};
-		let payout     = HumanNumber::from_u128(int_payout);
-		let xmr        = HumanNumber::from_u128(int_xmr);
-		let block      = HumanNumber::from_u128(int_block);
+		let payout     = HumanNumber::from_u64(int_payout);
 		let log_payout = read_to_string(File::LogPayout, &self.path_log_payout)?;
-		let log_block  = read_to_string(File::LogBlock, &self.path_log_block)?;
+		self.payout_ord.update_from_payout_log(&log_payout);
 		*self = Self {
 			payout,
 			xmr,
-			block,
 			int_payout,
-			int_xmr,
-			int_block,
 			log_payout,
-			log_block,
 			..std::mem::take(self)
 		};
 		Ok(())
 	}
 
+	// Takes raw int and raw log line and appends it.
+	pub fn add_payout(&mut self, xmr: u128, log_payout_line: &str) {
+		self.log_payout.push_str(log_payout_line);
+		self.int_payout += 1;
+		self.payout = HumanNumber::from_u64(self.int_payout);
+		self.xmr = self.xmr.add_u128(xmr);
+	}
+
 	pub fn write_to_all_files(&self) -> Result<(), TomlError> {
-		Self::save(&self.int_payout.to_string(), &self.path_int_payout)?;
-		Self::save(&self.int_xmr.to_string(),    &self.path_int_xmr)?;
-		Self::save(&self.int_block.to_string(),  &self.path_int_block)?;
-		Self::save(&self.log_payout, &self.path_log_payout)?;
-		Self::save(&self.log_block,  &self.path_log_block)?;
+		Self::disk_overwrite(&self.int_payout.to_string(), &self.path_int_payout)?;
+		Self::disk_overwrite(&self.xmr.to_u128().to_string(), &self.path_xmr)?;
+		Self::disk_append(&self.log_payout, &self.path_log_payout)?;
 		Ok(())
 	}
 
-	pub fn save(string: &str, path: &PathBuf) -> Result<(), TomlError> {
-		match fs::write(path, string) {
-			Ok(_) => { info!("GupaxP2poolApi | Save [{}] ... OK", path.display()); Ok(()) },
-			Err(e) => { error!("GupaxP2poolApi | Save [{}] ... FAIL: {}", path.display(), e); Err(TomlError::Io(e)) },
+	pub fn disk_append(string: &str, path: &PathBuf) -> Result<(), TomlError> {
+		use std::io::Write;
+		let mut file = match fs::OpenOptions::new().append(true).open(path) {
+			Ok(f) => f,
+			Err(e) => { error!("GupaxP2poolApi | Append [{}] ... FAIL: {}", path.display(), e); return Err(TomlError::Io(e)) },
+		};
+		match writeln!(file, "{}", string) {
+			Ok(_) => { debug!("GupaxP2poolApi | Append [{}] ... OK", path.display()); Ok(()) },
+			Err(e) => { error!("GupaxP2poolApi | Append [{}] ... FAIL: {}", path.display(), e); Err(TomlError::Io(e)) },
+		}
+	}
+
+	pub fn disk_overwrite(string: &str, path: &PathBuf) -> Result<(), TomlError> {
+		use std::io::Write;
+		let mut file = match fs::OpenOptions::new().write(true).open(path) {
+			Ok(f) => f,
+			Err(e) => { error!("GupaxP2poolApi | Overwrite [{}] ... FAIL: {}", path.display(), e); return Err(TomlError::Io(e)) },
+		};
+		match writeln!(file, "{}", string) {
+			Ok(_) => { debug!("GupaxP2poolApi | Overwrite [{}] ... OK", path.display()); Ok(()) },
+			Err(e) => { error!("GupaxP2poolApi | Overwrite [{}] ... FAIL: {}", path.display(), e); Err(TomlError::Io(e)) },
 		}
 	}
 }
@@ -742,10 +736,8 @@ pub enum File {
 
 	// Gupax-P2Pool API
 	LogPayout, // payout       | Raw log lines of P2Pool payouts received
-	LogBlock,  // block        | Raw log lines of actual blocks found via P2Pool
 	IntPayout, // total_payout | Single [u128] representing total payouts
 	IntXmr,    // total_xmr    | Single [u128] representing total XMR mined in atomic units
-	IntBlock,  // total_block  | Single [u128] representing total blocks found
 }
 
 //---------------------------------------------------------------------------------------------------- [Submenu] enum for [Status] tab
@@ -1284,6 +1276,7 @@ mod test {
 	#[test]
 	fn create_and_serde_gupax_p2pool_api() {
 		use crate::disk::GupaxP2poolApi;
+		use crate::xmr::AtomicUnit;
 
 		// Get API dir, fill paths.
 		let mut api = GupaxP2poolApi::new();
@@ -1292,32 +1285,23 @@ mod test {
 		GupaxP2poolApi::fill_paths(&mut api, &path);
 		println!("{:#?}", api);
 
-		// Create and read all files, write some fake data.
+		// Create, write some fake data.
 		GupaxP2poolApi::create_all_files(&path).unwrap();
-		GupaxP2poolApi::read_all_files_and_update(&mut api).unwrap();
+		api.log_payout = "NOTICE  2022-01-27 01:30:23.1377 P2Pool You received a payout of 0.000000000001 XMR in block 2642816".to_string();
 		api.int_payout = 1;
-		api.int_xmr    = 2;
-		api.int_block  = 3;
-		api.log_payout = "P2Pool You received a payout of 0.000000000001 XMR in block 2642816".to_string();
-		api.log_block  = "client 127.0.0.1:51111 user asdf found a mainchain block at height 2642816, submitting it".to_string();
+		api.xmr        = AtomicUnit::from_u128(2);
 		GupaxP2poolApi::write_to_all_files(&api).unwrap();
 		println!("AFTER WRITE: {:#?}", api);
 
-		// Reset internal stats, read file data.
-		api.int_payout = 0;
-		api.int_xmr    = 0;
-		api.int_block  = 0;
-		api.log_payout.clear();
-		api.log_block.clear();
+		// Read
 		GupaxP2poolApi::read_all_files_and_update(&mut api).unwrap();
 		println!("AFTER READ: {:#?}", api);
 
 		// Assert that the file read mutated the internal struct correctly.
 		assert_eq!(api.int_payout, 1);
-		assert_eq!(api.int_xmr,    2);
-		assert_eq!(api.int_block,  3);
-		assert_eq!(api.log_payout, "P2Pool You received a payout of 0.000000000001 XMR in block 2642816");
-		assert_eq!(api.log_block,  "client 127.0.0.1:51111 user asdf found a mainchain block at height 2642816, submitting it");
+		assert_eq!(api.xmr.to_u128(), 2);
+		assert!(!api.payout_ord.is_empty());
+		assert!(api.log_payout.contains("2022-01-27 01:30:23.1377 P2Pool You received a payout of 0.000000000001 XMR in block 2642816\n"));
 	}
 
 	#[test]
diff --git a/src/helper.rs b/src/helper.rs
index 8bf25a0..ef6db67 100644
--- a/src/helper.rs
+++ b/src/helper.rs
@@ -46,6 +46,9 @@ use crate::{
 	constants::*,
 	SudoState,
 	human::*,
+	GupaxP2poolApi,
+	P2poolRegex,
+	xmr::*,
 };
 use sysinfo::SystemExt;
 use serde::{Serialize,Deserialize};
@@ -77,6 +80,7 @@ pub struct Helper {
 	pub img_xmrig: Arc<Mutex<ImgXmrig>>,          // A static "image" of the data XMRig started with
 	pub_api_p2pool: Arc<Mutex<PubP2poolApi>>,     // P2Pool API state (for Helper/P2Pool thread)
 	pub_api_xmrig: Arc<Mutex<PubXmrigApi>>,       // XMRig API state (for Helper/XMRig thread)
+	pub gupax_p2pool_api: Arc<Mutex<GupaxP2poolApi>>,           //
 	priv_api_p2pool_local: Arc<Mutex<PrivP2poolLocalApi>>,      // Serde struct(s) for P2Pool's API files
 	priv_api_p2pool_network: Arc<Mutex<PrivP2poolNetworkApi>>,
 	priv_api_p2pool_pool: Arc<Mutex<PrivP2poolPoolApi>>,
@@ -223,7 +227,7 @@ impl std::fmt::Display for ProcessName   {
 //---------------------------------------------------------------------------------------------------- [Helper]
 impl Helper {
 	//---------------------------------------------------------------------------------------------------- General Functions
-	pub fn new(instant: std::time::Instant, pub_sys: Arc<Mutex<Sys>>, p2pool: Arc<Mutex<Process>>, xmrig: Arc<Mutex<Process>>, gui_api_p2pool: Arc<Mutex<PubP2poolApi>>, gui_api_xmrig: Arc<Mutex<PubXmrigApi>>, img_p2pool: Arc<Mutex<ImgP2pool>>, img_xmrig: Arc<Mutex<ImgXmrig>>) -> Self {
+	pub fn new(instant: std::time::Instant, pub_sys: Arc<Mutex<Sys>>, p2pool: Arc<Mutex<Process>>, xmrig: Arc<Mutex<Process>>, gui_api_p2pool: Arc<Mutex<PubP2poolApi>>, gui_api_xmrig: Arc<Mutex<PubXmrigApi>>, img_p2pool: Arc<Mutex<ImgP2pool>>, img_xmrig: Arc<Mutex<ImgXmrig>>, gupax_p2pool_api: Arc<Mutex<GupaxP2poolApi>>) -> Self {
 		Self {
 			instant,
 			pub_sys,
@@ -241,26 +245,33 @@ impl Helper {
 			gui_api_xmrig,
 			img_p2pool,
 			img_xmrig,
+			gupax_p2pool_api,
 		}
 	}
 
-	// Reads a PTY which combines STDOUT/STDERR for me, yay
-	fn read_pty(output_parse: Arc<Mutex<String>>, output_pub: Arc<Mutex<String>>, reader: Box<dyn std::io::Read + Send>, name: ProcessName) {
+	fn read_pty_xmrig(output_parse: Arc<Mutex<String>>, output_pub: Arc<Mutex<String>>, reader: Box<dyn std::io::Read + Send>) {
 		use std::io::BufRead;
 		let mut stdout = std::io::BufReader::new(reader).lines();
 		// We don't need to write twice for XMRig, since we dont parse it... yet.
-		if name == ProcessName::Xmrig {
-			while let Some(Ok(line)) = stdout.next() {
-//				println!("{}", line); // For debugging.
-//				if let Err(e) = writeln!(output_parse.lock().unwrap(), "{}", line) { error!("PTY | Output error: {}", e); }
-				if let Err(e) = writeln!(output_pub.lock().unwrap(), "{}", line) { error!("PTY | Output error: {}", e); }
-			}
-		} else {
-			while let Some(Ok(line)) = stdout.next() {
-//				println!("{}", line); // For debugging.
-				if let Err(e) = writeln!(output_parse.lock().unwrap(), "{}", line) { error!("PTY | Output error: {}", e); }
-				if let Err(e) = writeln!(output_pub.lock().unwrap(), "{}", line) { error!("PTY | Output error: {}", e); }
+		while let Some(Ok(line)) = stdout.next() {
+//			println!("{}", line); // For debugging.
+			if let Err(e) = writeln!(output_pub.lock().unwrap(), "{}", line) { error!("XMRig PTY | Output error: {}", e); }
+		}
+	}
+
+	fn read_pty_p2pool(output_parse: Arc<Mutex<String>>, output_pub: Arc<Mutex<String>>, reader: Box<dyn std::io::Read + Send>, regex: P2poolRegex, gupax_p2pool_api: Arc<Mutex<GupaxP2poolApi>>) {
+		use std::io::BufRead;
+		let mut stdout = std::io::BufReader::new(reader).lines();
+		while let Some(Ok(line)) = stdout.next() {
+//			println!("{}", line); // For debugging.
+			if regex.payout.is_match(&line) {
+				debug!("P2Pool PTY | Found payout, attempting write: {}", line);
+				let (date, atomic_unit, block) = PayoutOrd::parse_line(&line, &regex);
+				GupaxP2poolApi::add_payout(&mut gupax_p2pool_api.lock().unwrap(), atomic_unit.to_u128(), &line);
+				if let Err(e) = GupaxP2poolApi::write_to_all_files(&gupax_p2pool_api.lock().unwrap()) { error!("P2Pool PTY GupaxP2poolApi | Write error: {}", e); }
 			}
+			if let Err(e) = writeln!(output_parse.lock().unwrap(), "{}", line) { error!("P2Pool PTY Parse | Output error: {}", e); }
+			if let Err(e) = writeln!(output_pub.lock().unwrap(), "{}", line) { error!("P2Pool PTY Pub | Output error: {}", e); }
 		}
 	}
 
@@ -337,9 +348,10 @@ impl Helper {
 		let process = Arc::clone(&helper.lock().unwrap().p2pool);
 		let gui_api = Arc::clone(&helper.lock().unwrap().gui_api_p2pool);
 		let pub_api = Arc::clone(&helper.lock().unwrap().pub_api_p2pool);
+		let gupax_p2pool_api = Arc::clone(&helper.lock().unwrap().gupax_p2pool_api);
 		let path = path.clone();
 		thread::spawn(move || {
-			Self::spawn_p2pool_watchdog(process, gui_api, pub_api, args, path, api_path_local, api_path_network, api_path_pool);
+			Self::spawn_p2pool_watchdog(process, gui_api, pub_api, args, path, api_path_local, api_path_network, api_path_pool, gupax_p2pool_api);
 		});
 	}
 
@@ -347,7 +359,7 @@ impl Helper {
 	// 6 characters separated with dots like so: [4abcde...abcdef]
 	fn head_tail_of_monero_address(address: &str) -> String {
 		if address.len() < 95 { return "???".to_string() }
-		let head = &address[0..5];
+		let head = &address[0..6];
 		let tail = &address[89..95];
 		head.to_owned() + "..." + tail
 	}
@@ -443,7 +455,7 @@ impl Helper {
 	}
 
 	// The P2Pool watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works.
-	fn spawn_p2pool_watchdog(process: Arc<Mutex<Process>>, gui_api: Arc<Mutex<PubP2poolApi>>, pub_api: Arc<Mutex<PubP2poolApi>>, args: Vec<String>, path: std::path::PathBuf, api_path_local: std::path::PathBuf, api_path_network: std::path::PathBuf, api_path_pool: std::path::PathBuf) {
+	fn spawn_p2pool_watchdog(process: Arc<Mutex<Process>>, gui_api: Arc<Mutex<PubP2poolApi>>, pub_api: Arc<Mutex<PubP2poolApi>>, args: Vec<String>, path: std::path::PathBuf, api_path_local: std::path::PathBuf, api_path_network: std::path::PathBuf, api_path_pool: std::path::PathBuf, gupax_p2pool_api: Arc<Mutex<GupaxP2poolApi>>) {
 		// 1a. Create PTY
 		debug!("P2Pool | Creating PTY...");
 		let pty = portable_pty::native_pty_system();
@@ -473,12 +485,16 @@ impl Helper {
 		lock.stdin = Some(pair.master);
 		drop(lock);
 
+		let regex = P2poolRegex::new();
+
 		// 3. Spawn PTY read thread
 		debug!("P2Pool | Spawning PTY read thread...");
 		let output_parse = Arc::clone(&process.lock().unwrap().output_parse);
 		let output_pub = Arc::clone(&process.lock().unwrap().output_pub);
+		let gupax_p2pool_api = Arc::clone(&gupax_p2pool_api);
+		let regex_clone = regex.clone();
 		thread::spawn(move || {
-			Self::read_pty(output_parse, output_pub, reader, ProcessName::P2pool);
+			Self::read_pty_p2pool(output_parse, output_pub, reader, regex_clone, gupax_p2pool_api);
 		});
 		let output_parse = Arc::clone(&process.lock().unwrap().output_parse);
 		let output_pub = Arc::clone(&process.lock().unwrap().output_pub);
@@ -498,7 +514,6 @@ impl Helper {
 				Err(e) => warn!("P2Pool | Creating default empty API file ... FAIL ... {}", e),
 			}
 		}
-		let regex = P2poolRegex::new();
 		let start = process.lock().unwrap().start;
 
 		// Reset stats before loop
@@ -875,7 +890,7 @@ impl Helper {
 		let output_parse = Arc::clone(&process.lock().unwrap().output_parse);
 		let output_pub = Arc::clone(&process.lock().unwrap().output_pub);
 		thread::spawn(move || {
-			Self::read_pty(output_parse, output_pub, reader, ProcessName::Xmrig);
+			Self::read_pty_xmrig(output_parse, output_pub, reader);
 		});
 		// We don't parse anything in XMRigs output... yet.
 //		let output_parse = Arc::clone(&process.lock().unwrap().output_parse);
@@ -1161,189 +1176,6 @@ impl Helper {
 	}
 }
 
-//---------------------------------------------------------------------------------------------------- Regexes
-// Not to be confused with the [Regexes] struct in [main.rs], this one is meant
-// for parsing the output of P2Pool and finding payouts and total XMR found.
-// Why Regex instead of the standard library?
-//    1. I'm already using Regex
-//    2. It's insanely faster
-//
-// The following STDLIB implementation takes [0.003~] seconds to find all matches given a [String] with 30k lines:
-//     let mut n = 0;
-//     for line in P2POOL_OUTPUT.lines() {
-//         if line.contains("payout of [0-9].[0-9]+ XMR") { n += 1; }
-//     }
-//
-// This regex function takes [0.0003~] seconds (10x faster):
-//     let regex = Regex::new("payout of [0-9].[0-9]+ XMR").unwrap();
-//     let n = regex.find_iter(P2POOL_OUTPUT).count();
-//
-// Both are nominally fast enough where it doesn't matter too much but meh, why not use regex.
-struct P2poolRegex {
-	payout: regex::Regex,
-	float: regex::Regex,
-	date: regex::Regex,
-	block: regex::Regex,
-	int: regex::Regex,
-}
-
-impl P2poolRegex {
-	fn new() -> Self {
-		Self {
-			payout: regex::Regex::new("payout of [0-9].[0-9]+ XMR").unwrap(),
-			float: regex::Regex::new("[0-9].[0-9]+").unwrap(),
-			date: regex::Regex::new("[0-9]+-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+.[0-9]+").unwrap(),
-			block: regex::Regex::new("block [0-9]+").unwrap(),
-			int: regex::Regex::new("[0-9]+").unwrap(),
-		}
-	}
-}
-
-//---------------------------------------------------------------------------------------------------- XMR AtomicUnit
-#[derive(Debug, Clone)]
-struct AtomicUnit(u128);
-
-impl AtomicUnit {
-	fn new() -> Self {
-		Self(0)
-	}
-
-	fn sum_vec(vec: &Vec<Self>) -> Self {
-		let mut sum = 0;
-		for int in vec {
-			sum += int.0;
-		}
-		Self(sum)
-	}
-
-	fn from_f64(f: f64) -> Self {
-		Self((f * 1_000_000_000_000.0) as u128)
-	}
-
-	fn to_f64(&self) -> f64 {
-		self.0 as f64 / 1_000_000_000_000.0
-	}
-
-	fn to_human_number_12_point(&self) -> HumanNumber {
-		let f = self.0 as f64 / 1_000_000_000_000.0;
-		HumanNumber::from_f64_12_point(f)
-	}
-
-	fn to_human_number_no_fmt(&self) -> HumanNumber {
-		let f = self.0 as f64 / 1_000_000_000_000.0;
-		HumanNumber::from_f64_no_fmt(f)
-	}
-}
-
-// Displays AtomicUnit as a real XMR floating point.
-impl std::fmt::Display for AtomicUnit {
-	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-		write!(f, "{}", Self::to_human_number_12_point(self))
-	}
-}
-
-//---------------------------------------------------------------------------------------------------- [PayoutOrd]
-// This is the struct for ordering P2Pool payout lines into a structured and ordered vector of elements.
-// The structure goes as follows:
-//
-// Vec<(String, AtomicUnit, u64)>
-// "2022-08-17 12:16:11.8662" | 0.002382256231 XMR | Block 2573821
-// [0] = DATE
-// [1] = XMR IN ATOMIC-UNITS
-// [2] = MONERO BLOCK
-#[derive(Debug, Clone)]
-pub struct PayoutOrd(Vec<(String, AtomicUnit, HumanNumber)>);
-
-impl PayoutOrd {
-	fn new() -> Self {
-		Self(vec![(String::from("????-??-?? ??:??:??.????"), AtomicUnit::new(), HumanNumber::unknown())])
-	}
-
-	// Takes in input of ONLY P2Pool payout logs and
-	// converts it into a usable [PayoutOrd]
-	// It expects log lines like this:
-	// "NOTICE  2022-04-11 00:20:17.2571 P2Pool You received a payout of 0.001371623621 XMR in block 2562511"
-	// For efficiency reasons, I'd like to know the byte size
-	// we should allocate for the vector so we aren't adding every loop.
-	// Given a log [str], the equation for how many bytes the final vec will be is:
-	// (BYTES_OF_DATE + BYTES OF XMR + BYTES OF BLOCK) * amount_of_lines
-	// The first three are more or less constants (monero block 10m is in 10,379 years...): [23, 14, 7] (sum: 44)
-	// Add 16 more bytes for wrapper type overhead and it's an even [60] bytes per line.
-	fn update_from_payout_log(&mut self, log: &str, regex: &P2poolRegex) {
-		let amount_of_lines = log.lines().count();
-		let mut vec: Vec<(String, AtomicUnit, HumanNumber)> = Vec::with_capacity(60 * amount_of_lines);
-		for line in log.lines() {
-			// Date
-			let date = match regex.date.find(line) {
-				Some(date) => date.as_str().to_string(),
-				None => { error!("P2Pool | Date parse error: [{}]", line); "????-??-?? ??:??:??.????".to_string() },
-			};
-			// AtomicUnit
-			let atomic_unit = if let Some(word) = regex.payout.find(line) {
-				if let Some(word) = regex.float.find(word.as_str()) {
-					match word.as_str().parse::<f64>() {
-						Ok(au) => AtomicUnit::from_f64(au),
-						Err(e) => { error!("P2Pool | AtomicUnit parse error: [{}] on [{}]", e, line); AtomicUnit::new() },
-					}
-				} else {
-					AtomicUnit::new()
-				}
-			} else {
-				AtomicUnit::new()
-			};
-			// Block
-			let block = if let Some(word) = regex.block.find(line) {
-				if let Some(word) = regex.int.find(word.as_str()) {
-					match word.as_str().parse::<u64>() {
-						Ok(b) => HumanNumber::from_u64(b),
-						Err(e) => { error!("P2Pool | Block parse error: [{}] on [{}]", e, line); HumanNumber::unknown() },
-					}
-				} else {
-					HumanNumber::unknown()
-				}
-			} else {
-				HumanNumber::unknown()
-			};
-			vec.push((date, atomic_unit, block));
-		}
-		*self = Self(vec);
-	}
-
-	// Takes the raw components (no wrapper types), convert them and pushes to existing [Self]
-	fn push(&mut self, date: String, atomic_unit: u128, block: u64) {
-		let atomic_unit = AtomicUnit(atomic_unit);
-		let block = HumanNumber::from_u64(block);
-		self.0.push((date, atomic_unit, block));
-	}
-
-	// Sort [Self] from highest payout to lowest
-	fn sort_payout_high_to_low(&mut self) {
-		// This is a little confusing because wrapper types are basically 1 element tuples so:
-		// self.0 = The [Vec] within [PayoutOrd]
-		// b.1.0  = [b] is [(String, AtomicUnit, HumanNumber)], [.1] is the [AtomicUnit] inside it, [.0] is the [u128] inside that
-		// a.1.0  = Same deal, but we compare it with the previous value (b)
-		self.0.sort_by(|a, b| b.1.0.cmp(&a.1.0));
-	}
-
-	fn sort_payout_low_to_high(&mut self) {
-		self.0.sort_by(|a, b| a.1.0.cmp(&b.1.0));
-	}
-
-	// Recent <-> Oldest relies on the line order.
-	// The raw log lines will be shown instead of this struct.
-}
-
-impl Default for PayoutOrd { fn default() -> Self { Self::new() } }
-
-impl std::fmt::Display for PayoutOrd {
-	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-		for i in &self.0 {
-			writeln!(f, "{} | {} XMR | Block {}", i.0, i.1, i.2)?;
-		}
-		Ok(())
-	}
-}
-
 //---------------------------------------------------------------------------------------------------- [ImgP2pool]
 // A static "image" of data that P2Pool started with.
 // This is just a snapshot of the user data when they initially started P2Pool.
@@ -1946,64 +1778,6 @@ struct Result {
 //---------------------------------------------------------------------------------------------------- TESTS
 #[cfg(test)]
 mod test {
-	#[test]
-	fn update_p2pool_payout_log() {
-		use crate::helper::PayoutOrd;
-		use crate::helper::P2poolRegex;
-		let log =
-r#"NOTICE  2021-12-21 01:01:01.1111 P2Pool You received a payout of 0.001000000000 XMR in block 1
-NOTICE  2021-12-21 02:01:01.1111 P2Pool You received a payout of 0.002000000000 XMR in block 2
-NOTICE  2021-12-21 03:01:01.1111 P2Pool You received a payout of 0.003000000000 XMR in block 3
-"#;
-		let mut payout_ord = PayoutOrd::new();
-		println!("BEFORE: {}", payout_ord);
-		PayoutOrd::update_from_payout_log(&mut payout_ord, log, &P2poolRegex::new());
-		println!("AFTER: {}", payout_ord);
-		let should_be =
-r#"2021-12-21 01:01:01.1111 | 0.001000000000 XMR | Block 1
-2021-12-21 02:01:01.1111 | 0.002000000000 XMR | Block 2
-2021-12-21 03:01:01.1111 | 0.003000000000 XMR | Block 3
-"#;
-		assert_eq!(payout_ord.to_string(), should_be)
-	}
-
-	#[test]
-	fn sort_p2pool_payout_ord() {
-		use crate::helper::PayoutOrd;
-		use crate::helper::AtomicUnit;
-		use crate::helper::HumanNumber;
-		let mut payout_ord = PayoutOrd(vec![
-			("2022-09-08 18:42:55.4636".to_string(), AtomicUnit(1000000000), HumanNumber::from_u64(2654321)),
-			("2022-09-09 16:18:26.7582".to_string(), AtomicUnit(2000000000), HumanNumber::from_u64(2654322)),
-			("2022-09-10 11:15:21.1272".to_string(), AtomicUnit(3000000000), HumanNumber::from_u64(2654323)),
-		]);
-		println!("OG: {:#?}", payout_ord);
-
-		// High to Low
-		PayoutOrd::sort_payout_high_to_low(&mut payout_ord);
-		println!("AFTER PAYOUT HIGH TO LOW: {:#?}", payout_ord);
-		let should_be =
-r#"2022-09-10 11:15:21.1272 | 0.003000000000 XMR | Block 2,654,323
-2022-09-09 16:18:26.7582 | 0.002000000000 XMR | Block 2,654,322
-2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321
-"#;
-		println!("SHOULD_BE:\n{}", should_be);
-		println!("IS:\n{}", payout_ord);
-		assert_eq!(payout_ord.to_string(), should_be);
-
-		// Low to High
-		PayoutOrd::sort_payout_low_to_high(&mut payout_ord);
-		println!("AFTER PAYOUT LOW TO HIGH: {:#?}", payout_ord);
-		let should_be =
-r#"2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321
-2022-09-09 16:18:26.7582 | 0.002000000000 XMR | Block 2,654,322
-2022-09-10 11:15:21.1272 | 0.003000000000 XMR | Block 2,654,323
-"#;
-		println!("SHOULD_BE:\n{}", should_be);
-		println!("IS:\n{}", payout_ord);
-		assert_eq!(payout_ord.to_string(), should_be);
-	}
-
 	#[test]
 	fn reset_gui_output() {
 		let max = crate::helper::GUI_OUTPUT_LEEWAY;
diff --git a/src/main.rs b/src/main.rs
index ae92085..3de27fe 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -37,7 +37,7 @@ use eframe::{egui,NativeOptions};
 use log::*;
 use env_logger::{Builder,WriteStyle};
 // Regex
-use regex::Regex;
+use ::regex::Regex;
 // Serde
 use serde::{Serialize,Deserialize};
 // std
@@ -63,7 +63,9 @@ mod xmrig;
 mod update;
 mod helper;
 mod human;
-use {ferris::*,constants::*,node::*,disk::*,update::*,gupax::*,helper::*};
+mod regex;
+mod xmr;
+use {crate::regex::*,ferris::*,constants::*,node::*,disk::*,update::*,gupax::*,helper::*};
 
 // Sudo (dummy values for Windows)
 mod sudo;
@@ -134,7 +136,7 @@ pub struct App {
 	// This is a file-based API that contains data for permanent stats.
 	// The below struct holds everything needed for it, the paths, the
 	// actual stats, and all the functions needed to mutate them.
-	gupax_p2pool_api: GupaxP2poolApi,
+	gupax_p2pool_api: Arc<Mutex<GupaxP2poolApi>>,
 	// Static stuff
 	pid: sysinfo::Pid, // Gupax's PID
 	max_threads: usize, // Max amount of detected system threads
@@ -213,7 +215,7 @@ impl App {
 			restart: Arc::new(Mutex::new(Restart::No)),
 			diff: false,
 			error_state: ErrorState::new(),
-			helper: Arc::new(Mutex::new(Helper::new(now, pub_sys.clone(), p2pool.clone(), xmrig.clone(), p2pool_api.clone(), xmrig_api.clone(), p2pool_img.clone(), xmrig_img.clone()))),
+			helper: Arc::new(Mutex::new(Helper::new(now, pub_sys.clone(), p2pool.clone(), xmrig.clone(), p2pool_api.clone(), xmrig_api.clone(), p2pool_img.clone(), xmrig_img.clone(), Arc::new(Mutex::new(GupaxP2poolApi::new()))))),
 			p2pool,
 			xmrig,
 			p2pool_api,
@@ -226,7 +228,7 @@ impl App {
 			resizing: false,
 			alpha: 0,
 			no_startup: false,
-			gupax_p2pool_api: GupaxP2poolApi::new(),
+			gupax_p2pool_api: Arc::new(Mutex::new(GupaxP2poolApi::new())),
 			pub_sys,
 			pid,
 			max_threads: num_cpus::get(),
@@ -346,16 +348,30 @@ impl App {
 		//----------------------------------------------------------------------------------------------------
 		// Read [GupaxP2poolApi] disk files
 		app.gupax_p2pool_api_path = crate::disk::get_gupax_p2pool_path(&app.os_data_path);
-		app.gupax_p2pool_api.fill_paths(&app.gupax_p2pool_api_path);
-		GupaxP2poolApi::create_all_files(&app.gupax_p2pool_api_path);
+		let mut gupax_p2pool_api = app.gupax_p2pool_api.lock().unwrap();
+		gupax_p2pool_api.fill_paths(&app.gupax_p2pool_api_path);
+		match GupaxP2poolApi::create_all_files(&app.gupax_p2pool_api_path) {
+			Ok(_) => debug!("App Init | Creating Gupax-P2Pool API files ... OK"),
+			Err(err) => {
+				error!("GupaxP2poolApi ... {}", err);
+				match err {
+					Io(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Path(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Serialize(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Deserialize(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Format(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Merge(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Error, ErrorButtons::ResetState),
+					Parse(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+				};
+			},
+		}
 		debug!("App Init | Reading Gupax-P2Pool API files...");
-		match app.gupax_p2pool_api.read_all_files_and_update() {
+		match gupax_p2pool_api.read_all_files_and_update() {
 			Ok(_) => {
 				info!(
-					"GupaxP2poolApi ... Payouts: {} | XMR (atomic-units): {} | Blocks: {}",
-					app.gupax_p2pool_api.payout,
-					app.gupax_p2pool_api.xmr,
-					app.gupax_p2pool_api.block,
+					"GupaxP2poolApi ... Payouts: {} | XMR (atomic-units): {}",
+					gupax_p2pool_api.payout,
+					gupax_p2pool_api.xmr,
 				);
 			},
 			Err(err) => {
@@ -371,6 +387,8 @@ impl App {
 				};
 			},
 		};
+		drop(gupax_p2pool_api);
+		app.helper.lock().unwrap().gupax_p2pool_api = Arc::clone(&app.gupax_p2pool_api);
 
 		//----------------------------------------------------------------------------------------------------
 		let mut og = app.og.lock().unwrap(); // Lock [og]
@@ -606,33 +624,6 @@ impl Images {
 	}
 }
 
-//---------------------------------------------------------------------------------------------------- [Regexes] struct
-#[derive(Clone, Debug)]
-pub struct Regexes {
-	pub name: Regex,
-	pub address: Regex,
-	pub ipv4: Regex,
-	pub domain: Regex,
-	pub port: Regex,
-}
-
-impl Regexes {
-	fn new() -> Self {
-		Regexes {
-			name: Regex::new("^[A-Za-z0-9-_.]+( [A-Za-z0-9-_.]+)*$").unwrap(),
-			address: Regex::new("^4[A-Za-z1-9]+$").unwrap(), // This still needs to check for (l, I, o, 0)
-			ipv4: Regex::new(r#"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"#).unwrap(),
-			domain: Regex::new(r#"^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$"#).unwrap(),
-			port: Regex::new(r#"^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$"#).unwrap(),
-		}
-	}
-
-	// Check if a Monero address is correct.
-	pub fn addr_ok(&self, address: &str) -> bool {
-		address.len() == 95 && Regex::is_match(&self.address, address) && !address.contains('0') && !address.contains('O') && !address.contains('l')
-	}
-}
-
 //---------------------------------------------------------------------------------------------------- [Pressed] enum
 // These represent the keys pressed during the frame.
 // I could use egui's [Key] but there is no option for
@@ -1682,9 +1673,7 @@ XMRig console byte length: {}\n
 {:#?}\n
 ------------------------------------------ XMRIG IMAGE ------------------------------------------
 {:#?}\n
------------------------------------------- P2POOL GUI API ------------------------------------------
-{:#?}\n
------------------------------------------- XMRIG GUI API ------------------------------------------
+------------------------------------------ GUPAX-P2POOL API ------------------------------------------
 {:#?}\n
 ------------------------------------------ WORKING STATE ------------------------------------------
 {:#?}\n
@@ -1717,8 +1706,7 @@ XMRig console byte length: {}\n
 							xmrig_gui_len,
 							self.p2pool_img.lock().unwrap(),
 							self.xmrig_img.lock().unwrap(),
-							self.p2pool_api.lock().unwrap(),
-							self.xmrig_api.lock().unwrap(),
+							self.gupax_p2pool_api.lock().unwrap(),
 							self.state,
 							self.og.lock().unwrap(),
 						);
diff --git a/src/regex.rs b/src/regex.rs
new file mode 100644
index 0000000..0a09f36
--- /dev/null
+++ b/src/regex.rs
@@ -0,0 +1,89 @@
+// Gupax - GUI Uniting P2Pool And XMRig
+//
+// Copyright (c) 2022 hinto-janaiyo
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+// Some regexes used throughout Gupax.
+
+use regex::Regex;
+
+//---------------------------------------------------------------------------------------------------- [Regexes] struct
+// General purpose Regexes, mostly used in the GUI.
+#[derive(Clone, Debug)]
+pub struct Regexes {
+	pub name: Regex,
+	pub address: Regex,
+	pub ipv4: Regex,
+	pub domain: Regex,
+	pub port: Regex,
+}
+
+impl Regexes {
+	pub fn new() -> Self {
+		Regexes {
+			name: Regex::new("^[A-Za-z0-9-_.]+( [A-Za-z0-9-_.]+)*$").unwrap(),
+			address: Regex::new("^4[A-Za-z1-9]+$").unwrap(), // This still needs to check for (l, I, o, 0)
+			ipv4: Regex::new(r#"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"#).unwrap(),
+			domain: Regex::new(r#"^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$"#).unwrap(),
+			port: Regex::new(r#"^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$"#).unwrap(),
+		}
+	}
+
+	// Check if a Monero address is correct.
+	// This actually only checks for length & Base58, and doesn't do any checksum validation
+	// (the last few bytes of a Monero address are a Keccak hash checksum) so some invalid addresses can trick this function.
+	pub fn addr_ok(&self, address: &str) -> bool {
+		address.len() == 95 && Regex::is_match(&self.address, address) && !address.contains('0') && !address.contains('O') && !address.contains('l')
+	}
+}
+
+
+//---------------------------------------------------------------------------------------------------- [P2poolRegex]
+// Meant for parsing the output of P2Pool and finding payouts and total XMR found.
+// Why Regex instead of the standard library?
+//    1. I'm already using Regex
+//    2. It's insanely faster
+//
+// The following STDLIB implementation takes [0.003~] seconds to find all matches given a [String] with 30k lines:
+//     let mut n = 0;
+//     for line in P2POOL_OUTPUT.lines() {
+//         if line.contains("payout of [0-9].[0-9]+ XMR") { n += 1; }
+//     }
+//
+// This regex function takes [0.0003~] seconds (10x faster):
+//     let regex = Regex::new("payout of [0-9].[0-9]+ XMR").unwrap();
+//     let n = regex.find_iter(P2POOL_OUTPUT).count();
+//
+// Both are nominally fast enough where it doesn't matter too much but meh, why not use regex.
+#[derive(Clone,Debug)]
+pub struct P2poolRegex {
+	pub payout: regex::Regex,
+	pub float: regex::Regex,
+	pub date: regex::Regex,
+	pub block: regex::Regex,
+	pub int: regex::Regex,
+}
+
+impl P2poolRegex {
+	pub fn new() -> Self {
+		Self {
+			payout: regex::Regex::new("payout of [0-9].[0-9]+ XMR").unwrap(),
+			float: regex::Regex::new("[0-9].[0-9]+").unwrap(),
+			date: regex::Regex::new("[0-9]+-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+.[0-9]+").unwrap(),
+			block: regex::Regex::new("block [0-9]+").unwrap(),
+			int: regex::Regex::new("[0-9]+").unwrap(),
+		}
+	}
+}
diff --git a/src/xmr.rs b/src/xmr.rs
new file mode 100644
index 0000000..a2d5cd3
--- /dev/null
+++ b/src/xmr.rs
@@ -0,0 +1,310 @@
+// Gupax - GUI Uniting P2Pool And XMRig
+//
+// Copyright (c) 2022 hinto-janaiyo
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+// This file is for handling actual XMR integers/floats using [AtomicUnit] & [PayoutOrd]
+// AtomicUnit is just a wrapper around a [u128] implementing common XMR Atomic Unit functions.
+// PayoutOrd is a wrapper around a [Vec] for sorting P2Pool payouts with this type signature:
+//     "Vec<(String, AtomicUnit, HumanNumber)>"
+// These represent:
+//     "(DATE, ATOMIC_UNIT, MONERO_BLOCK)"
+
+use crate::{
+	human::*,
+	P2poolRegex,
+};
+
+use log::*;
+
+//---------------------------------------------------------------------------------------------------- XMR AtomicUnit
+// After I initially wrote this struct, I forgot why I even needed it.
+// I get the XMR received as a float, I display it as a float and it wouldn't be
+// too bad if I wrote it to disk as a float, but then I realized [.cmp()] doesn't
+// work on [f64] and also that Rust makes sorting floats a pain so instead of deleting
+// this code and making some float sorter, I might as well use it.
+#[derive(Debug, Clone)]
+pub struct AtomicUnit(u128);
+
+impl AtomicUnit {
+	pub fn new() -> Self {
+		Self(0)
+	}
+
+	pub fn from_u128(u: u128) -> Self {
+		Self(u)
+	}
+
+	pub fn add_u128(&mut self, u: u128) -> Self {
+		Self(self.0 + u)
+	}
+
+	pub fn add_self(&mut self, atomic_unit: &Self) -> Self {
+		Self(self.0 + atomic_unit.0)
+	}
+
+	pub fn to_u128(&self) -> u128 {
+		self.0
+	}
+
+	pub fn sum_vec(vec: &Vec<Self>) -> Self {
+		let mut sum = 0;
+		for int in vec {
+			sum += int.0;
+		}
+		Self(sum)
+	}
+
+	pub fn from_f64(f: f64) -> Self {
+		Self((f * 1_000_000_000_000.0) as u128)
+	}
+
+	pub fn to_f64(&self) -> f64 {
+		self.0 as f64 / 1_000_000_000_000.0
+	}
+
+	pub fn to_human_number_12_point(&self) -> HumanNumber {
+		let f = self.0 as f64 / 1_000_000_000_000.0;
+		HumanNumber::from_f64_12_point(f)
+	}
+
+	pub fn to_human_number_no_fmt(&self) -> HumanNumber {
+		let f = self.0 as f64 / 1_000_000_000_000.0;
+		HumanNumber::from_f64_no_fmt(f)
+	}
+}
+
+// Displays AtomicUnit as a real XMR floating point.
+impl std::fmt::Display for AtomicUnit {
+	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+		write!(f, "{}", Self::to_human_number_12_point(self))
+	}
+}
+
+//---------------------------------------------------------------------------------------------------- [PayoutOrd]
+// This is the struct for ordering P2Pool payout lines into a structured and ordered vector of elements.
+// The structure goes as follows:
+//     "Vec<(String, AtomicUnit, HumanNumber)>"
+// Which displays as:
+//     "2022-08-17 12:16:11.8662" | 0.002382256231 XMR | Block 2573821
+//
+// [0] = DATE
+// [1] = XMR IN ATOMIC-UNITS
+// [2] = MONERO BLOCK
+#[derive(Debug,Clone)]
+pub struct PayoutOrd(Vec<(String, AtomicUnit, HumanNumber)>);
+
+impl PayoutOrd {
+	pub fn new() -> Self {
+		Self(vec![(String::from("????-??-?? ??:??:??.????"), AtomicUnit::new(), HumanNumber::unknown())])
+	}
+
+	pub fn from_vec(vec: Vec<(String, AtomicUnit, HumanNumber)>) -> Self {
+		Self(vec)
+	}
+
+	pub fn is_empty(&self) -> bool {
+		self.0.is_empty()
+	}
+
+	pub fn parse_line(line: &str, regex: &P2poolRegex) -> (String, AtomicUnit, HumanNumber) {
+		// Date
+		let date = match regex.date.find(line) {
+			Some(date) => date.as_str().to_string(),
+			None => { error!("P2Pool | Date parse error: [{}]", line); "????-??-?? ??:??:??.????".to_string() },
+		};
+		// AtomicUnit
+		let atomic_unit = if let Some(word) = regex.payout.find(line) {
+			if let Some(word) = regex.float.find(word.as_str()) {
+				match word.as_str().parse::<f64>() {
+					Ok(au) => AtomicUnit::from_f64(au),
+					Err(e) => { error!("P2Pool | AtomicUnit parse error: [{}] on [{}]", e, line); AtomicUnit::new() },
+				}
+			} else {
+				AtomicUnit::new()
+			}
+		} else {
+			AtomicUnit::new()
+		};
+		// Block
+		let block = if let Some(word) = regex.block.find(line) {
+			if let Some(word) = regex.int.find(word.as_str()) {
+				match word.as_str().parse::<u64>() {
+					Ok(b) => HumanNumber::from_u64(b),
+					Err(e) => { error!("P2Pool | Block parse error: [{}] on [{}]", e, line); HumanNumber::unknown() },
+				}
+			} else {
+				HumanNumber::unknown()
+			}
+		} else {
+			HumanNumber::unknown()
+		};
+		(date, atomic_unit, block)
+	}
+
+	// Takes in input of ONLY P2Pool payout logs and
+	// converts it into a usable [PayoutOrd]
+	// It expects log lines like this:
+	// "NOTICE  2022-04-11 00:20:17.2571 P2Pool You received a payout of 0.001371623621 XMR in block 2562511"
+	// For efficiency reasons, I'd like to know the byte size
+	// we should allocate for the vector so we aren't adding every loop.
+	// Given a log [str], the equation for how many bytes the final vec will be is:
+	// (BYTES_OF_DATE + BYTES OF XMR + BYTES OF BLOCK) * amount_of_lines
+	// The first three are more or less constants (monero block 10m is in 10,379 years...): [23, 14, 7] (sum: 44)
+	// Add 16 more bytes for wrapper type overhead and it's an even [60] bytes per line.
+	pub fn update_from_payout_log(&mut self, log: &str) {
+		let regex = P2poolRegex::new();
+		let amount_of_lines = log.lines().count();
+		let mut vec: Vec<(String, AtomicUnit, HumanNumber)> = Vec::with_capacity(60 * amount_of_lines);
+		for line in log.lines() {
+			debug!("PayoutOrg | Parsing line: [{}]", line);
+			vec.push(Self::parse_line(line, &regex));
+		}
+		*self = Self(vec);
+	}
+
+	// Takes the raw components (no wrapper types), convert them and pushes to existing [Self]
+	pub fn push(&mut self, date: &str, atomic_unit: u128, block: u64) {
+		let atomic_unit = AtomicUnit(atomic_unit);
+		let block = HumanNumber::from_u64(block);
+		self.0.push((date.to_string(), atomic_unit, block));
+	}
+
+	pub fn atomic_unit_sum(&self) -> AtomicUnit {
+		let mut sum: u128 = 0;
+		for (_, atomic_unit, _) in &self.0 {
+			sum += atomic_unit.to_u128();
+		}
+		AtomicUnit::from_u128(sum)
+	}
+
+	// Sort [Self] from highest payout to lowest
+	pub fn sort_payout_high_to_low(&mut self) {
+		// This is a little confusing because wrapper types are basically 1 element tuples so:
+		// self.0 = The [Vec] within [PayoutOrd]
+		// b.1.0  = [b] is [(String, AtomicUnit, HumanNumber)], [.1] is the [AtomicUnit] inside it, [.0] is the [u128] inside that
+		// a.1.0  = Same deal, but we compare it with the previous value (b)
+		self.0.sort_by(|a, b| b.1.0.cmp(&a.1.0));
+	}
+
+	pub fn sort_payout_low_to_high(&mut self) {
+		self.0.sort_by(|a, b| a.1.0.cmp(&b.1.0));
+	}
+
+	// Recent <-> Oldest relies on the line order.
+	// The raw log lines will be shown instead of this struct.
+}
+
+impl Default for PayoutOrd { fn default() -> Self { Self::new() } }
+
+impl std::fmt::Display for PayoutOrd {
+	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+		for i in &self.0 {
+			writeln!(f, "{} | {} XMR | Block {}", i.0, i.1, i.2)?;
+		}
+		Ok(())
+	}
+}
+
+//---------------------------------------------------------------------------------------------------- TESTS
+#[cfg(test)]
+mod test {
+	#[test]
+	fn update_p2pool_payout_log() {
+		use crate::xmr::PayoutOrd;
+		let log =
+r#"NOTICE  2021-12-21 01:01:01.1111 P2Pool You received a payout of 0.001000000000 XMR in block 1
+NOTICE  2021-12-21 02:01:01.1111 P2Pool You received a payout of 0.002000000000 XMR in block 2
+NOTICE  2021-12-21 03:01:01.1111 P2Pool You received a payout of 0.003000000000 XMR in block 3
+"#;
+		let mut payout_ord = PayoutOrd::new();
+		println!("BEFORE: {}", payout_ord);
+		PayoutOrd::update_from_payout_log(&mut payout_ord, log);
+		println!("AFTER: {}", payout_ord);
+		let should_be =
+r#"2021-12-21 01:01:01.1111 | 0.001000000000 XMR | Block 1
+2021-12-21 02:01:01.1111 | 0.002000000000 XMR | Block 2
+2021-12-21 03:01:01.1111 | 0.003000000000 XMR | Block 3
+"#;
+		assert_eq!(payout_ord.to_string(), should_be)
+	}
+
+	#[test]
+	fn push_to_payout_ord() {
+		use crate::xmr::PayoutOrd;
+		use crate::xmr::AtomicUnit;
+		use crate::human::HumanNumber;
+		let mut payout_ord = PayoutOrd::from_vec(vec![]);
+		let should_be = "2022-09-08 18:42:55.4636 | 0.000000000001 XMR | Block 2,654,321\n";
+		println!("BEFORE: {:#?}", payout_ord);
+		payout_ord.push("2022-09-08 18:42:55.4636", 1, 2654321);
+		println!("AFTER: {}", payout_ord);
+		println!("SHOULD_BE: {}", should_be);
+		assert_eq!(payout_ord.to_string(), should_be);
+	}
+
+	#[test]
+	fn sum_payout_ord_atomic_unit() {
+		use crate::xmr::PayoutOrd;
+		use crate::xmr::AtomicUnit;
+		use crate::human::HumanNumber;
+		let mut payout_ord = PayoutOrd::from_vec(vec![
+			("2022-09-08 18:42:55.4636".to_string(), AtomicUnit::from_u128(1), HumanNumber::from_u64(2654321)),
+			("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u128(1), HumanNumber::from_u64(2654322)),
+			("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u128(1), HumanNumber::from_u64(2654323)),
+		]);
+		println!("OG: {:#?}", payout_ord);
+		let sum = PayoutOrd::atomic_unit_sum(&payout_ord);
+		println!("SUM: {}", sum.to_u128());
+		assert_eq!(sum.to_u128(), 3);
+	}
+
+	#[test]
+	fn sort_p2pool_payout_ord() {
+		use crate::xmr::PayoutOrd;
+		use crate::xmr::AtomicUnit;
+		use crate::human::HumanNumber;
+		let mut payout_ord = PayoutOrd::from_vec(vec![
+			("2022-09-08 18:42:55.4636".to_string(), AtomicUnit::from_u128(1000000000), HumanNumber::from_u64(2654321)),
+			("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u128(2000000000), HumanNumber::from_u64(2654322)),
+			("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u128(3000000000), HumanNumber::from_u64(2654323)),
+		]);
+		println!("OG: {:#?}", payout_ord);
+
+		// High to Low
+		PayoutOrd::sort_payout_high_to_low(&mut payout_ord);
+		println!("AFTER PAYOUT HIGH TO LOW: {:#?}", payout_ord);
+		let should_be =
+r#"2022-09-10 11:15:21.1272 | 0.003000000000 XMR | Block 2,654,323
+2022-09-09 16:18:26.7582 | 0.002000000000 XMR | Block 2,654,322
+2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321
+"#;
+		println!("SHOULD_BE:\n{}", should_be);
+		println!("IS:\n{}", payout_ord);
+		assert_eq!(payout_ord.to_string(), should_be);
+
+		// Low to High
+		PayoutOrd::sort_payout_low_to_high(&mut payout_ord);
+		println!("AFTER PAYOUT LOW TO HIGH: {:#?}", payout_ord);
+		let should_be =
+r#"2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321
+2022-09-09 16:18:26.7582 | 0.002000000000 XMR | Block 2,654,322
+2022-09-10 11:15:21.1272 | 0.003000000000 XMR | Block 2,654,323
+"#;
+		println!("SHOULD_BE:\n{}", should_be);
+		println!("IS:\n{}", payout_ord);
+		assert_eq!(payout_ord.to_string(), should_be);
+	}
+}