From 4e8dc523504e508cc284cf59e997a98b61f6968e Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Thu, 10 Nov 2022 21:20:31 -0500
Subject: [PATCH] p2pool: add [Ping] into GUI, add [simple], add address regex
 check

---
 Cargo.lock       | 147 ++++++++--------
 Cargo.toml       |   4 +-
 README.md        |   3 +-
 src/README.md    |  16 +-
 src/constants.rs |  10 +-
 src/gupax.rs     |   5 +-
 src/main.rs      | 211 ++++++++++++-----------
 src/node.rs      | 430 +++++++++++++++++++++++++++++------------------
 src/p2pool.rs    | 181 +++++++++++++++++---
 src/state.rs     |   4 +
 10 files changed, 633 insertions(+), 378 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index b66d5cc..63b854c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,7 +4,7 @@ version = 3
 
 [[package]]
 name = "Gupax"
-version = "0.1.0"
+version = "0.2.0"
 dependencies = [
  "anyhow",
  "arti-client",
@@ -15,7 +15,7 @@ dependencies = [
  "eframe",
  "egui",
  "egui_extras",
- "env_logger 0.9.1",
+ "env_logger 0.9.3",
  "figment",
  "flate2",
  "hex-literal",
@@ -112,9 +112,9 @@ dependencies = [
 
 [[package]]
 name = "ahash"
-version = "0.8.0"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72"
+checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107"
 dependencies = [
  "cfg-if",
  "getrandom 0.2.8",
@@ -294,9 +294,9 @@ dependencies = [
 
 [[package]]
 name = "asynchronous-codec"
-version = "0.6.0"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690"
+checksum = "06a0daa378f5fd10634e44b0a29b2a87b890657658e072a30d6f26e57ddee182"
 dependencies = [
  "bytes",
  "futures-sink",
@@ -434,18 +434,18 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
 
 [[package]]
 name = "bytemuck"
-version = "1.12.1"
+version = "1.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da"
+checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
 dependencies = [
  "bytemuck_derive",
 ]
 
 [[package]]
 name = "bytemuck_derive"
-version = "1.2.1"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9e1f5fa78f69496407a27ae9ed989e3c3b072310286f5ef385525e4cbc24a9"
+checksum = "5fe233b960f12f8007e3db2d136e3cb1c291bfd7396e384ee76025fc1a3932b4"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -487,9 +487,9 @@ dependencies = [
 
 [[package]]
 name = "calloop"
-version = "0.10.1"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a22a6a8f622f797120d452c630b0ab12e1331a1a753e2039ce7868d4ac77b4ee"
+checksum = "595eb0438b3c6d262395fe30e6de9a61beb57ea56290b00a07f227fe6e20cbf2"
 dependencies = [
  "log",
  "nix 0.24.2",
@@ -506,9 +506,9 @@ checksum = "ceea694ffdf0118d2df95ace6fd9edfc6d27f88408d0d73b390f2d9e5699b3f2"
 
 [[package]]
 name = "cc"
-version = "1.0.74"
+version = "1.0.76"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574"
+checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
 dependencies = [
  "jobserver",
 ]
@@ -603,9 +603,9 @@ dependencies = [
 
 [[package]]
 name = "cocoa"
-version = "0.24.0"
+version = "0.24.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832"
+checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a"
 dependencies = [
  "bitflags",
  "block",
@@ -893,9 +893,9 @@ dependencies = [
 
 [[package]]
 name = "cxx"
-version = "1.0.80"
+version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a"
+checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888"
 dependencies = [
  "cc",
  "cxxbridge-flags",
@@ -905,9 +905,9 @@ dependencies = [
 
 [[package]]
 name = "cxx-build"
-version = "1.0.80"
+version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827"
+checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3"
 dependencies = [
  "cc",
  "codespan-reporting",
@@ -920,15 +920,15 @@ dependencies = [
 
 [[package]]
 name = "cxxbridge-flags"
-version = "1.0.80"
+version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a"
+checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f"
 
 [[package]]
 name = "cxxbridge-macro"
-version = "1.0.80"
+version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7"
+checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1187,9 +1187,9 @@ dependencies = [
 
 [[package]]
 name = "educe"
-version = "0.4.19"
+version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c07b7cc9cd8c08d10db74fca3b20949b9b6199725c04a0cce6d543496098fcac"
+checksum = "cb0188e3c3ba8df5753894d54461f0e39bc91741dc5b22e1c46999ec2c71f4e4"
 dependencies = [
  "enum-ordinalize",
  "proc-macro2",
@@ -1225,7 +1225,7 @@ version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc9fcd393c3daaaf5909008a1d948319d538b79c51871e4df0993260260a94e4"
 dependencies = [
- "ahash 0.8.0",
+ "ahash 0.8.2",
  "epaint",
  "nohash-hasher",
  "tracing",
@@ -1297,9 +1297,9 @@ dependencies = [
 
 [[package]]
 name = "enum-ordinalize"
-version = "3.1.11"
+version = "3.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2170fc0efee383079a8bdd05d6ea2a184d2a0f07a1c1dcabdb2fd5e9f24bc36c"
+checksum = "a62bb1df8b45ecb7ffa78dca1c17a438fb193eb083db0b1b494d2a61bcb5096a"
 dependencies = [
  "num-bigint",
  "num-traits",
@@ -1324,9 +1324,9 @@ dependencies = [
 
 [[package]]
 name = "env_logger"
-version = "0.9.1"
+version = "0.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272"
+checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
 dependencies = [
  "atty",
  "humantime 2.1.0",
@@ -1342,7 +1342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5ba04741be7f6602b1a1b28f1082cce45948a7032961c52814f8946b28493300"
 dependencies = [
  "ab_glyph",
- "ahash 0.8.0",
+ "ahash 0.8.2",
  "atomic_refcell",
  "bytemuck",
  "emath",
@@ -1983,9 +1983,9 @@ dependencies = [
 
 [[package]]
 name = "hyper"
-version = "0.14.20"
+version = "0.14.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac"
+checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -2110,9 +2110,9 @@ dependencies = [
 
 [[package]]
 name = "ipnet"
-version = "2.5.0"
+version = "2.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
+checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
 
 [[package]]
 name = "itertools"
@@ -2211,9 +2211,9 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
 
 [[package]]
 name = "libloading"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
 dependencies = [
  "cfg-if",
  "winapi",
@@ -2221,9 +2221,9 @@ dependencies = [
 
 [[package]]
 name = "libm"
-version = "0.2.5"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565"
+checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
 
 [[package]]
 name = "libsqlite3-sys"
@@ -2266,9 +2266,9 @@ dependencies = [
 
 [[package]]
 name = "lzma-sys"
-version = "0.1.19"
+version = "0.1.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e06754c4acf47d49c727d5665ca9fb828851cda315ed3bd51edd148ef78a8772"
+checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
 dependencies = [
  "cc",
  "libc",
@@ -2292,9 +2292,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
 
 [[package]]
 name = "memmap2"
-version = "0.5.7"
+version = "0.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498"
+checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc"
 dependencies = [
  "libc",
 ]
@@ -2389,9 +2389,9 @@ dependencies = [
 
 [[package]]
 name = "native-tls"
-version = "0.2.10"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
 dependencies = [
  "lazy_static",
  "libc",
@@ -2623,9 +2623,9 @@ dependencies = [
 
 [[package]]
 name = "num_cpus"
-version = "1.13.1"
+version = "1.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
 dependencies = [
  "hermit-abi",
  "libc",
@@ -2652,15 +2652,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "num_threads"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "objc"
 version = "0.2.7"
@@ -2964,14 +2955,14 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
 
 [[package]]
 name = "png"
-version = "0.17.6"
+version = "0.17.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c"
+checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638"
 dependencies = [
  "bitflags",
  "crc32fast",
  "flate2",
- "miniz_oxide 0.5.4",
+ "miniz_oxide 0.6.2",
 ]
 
 [[package]]
@@ -2991,9 +2982,9 @@ dependencies = [
 
 [[package]]
 name = "ppv-lite86"
-version = "0.2.16"
+version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
 [[package]]
 name = "proc-macro-crate"
@@ -3171,9 +3162,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.6.0"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
+checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -3182,9 +3173,9 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.6.27"
+version = "0.6.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
 
 [[package]]
 name = "remove_dir_all"
@@ -3291,7 +3282,7 @@ dependencies = [
  "hashlink",
  "libsqlite3-sys",
  "smallvec",
- "time 0.3.16",
+ "time 0.3.17",
 ]
 
 [[package]]
@@ -3378,9 +3369,9 @@ dependencies = [
 
 [[package]]
 name = "scoped-tls"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
 
 [[package]]
 name = "scoped_threadpool"
@@ -3647,7 +3638,7 @@ dependencies = [
  "num-bigint",
  "num-traits",
  "thiserror",
- "time 0.3.16",
+ "time 0.3.17",
 ]
 
 [[package]]
@@ -3911,13 +3902,11 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.16"
+version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca"
+checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
 dependencies = [
  "itoa",
- "libc",
- "num_threads",
  "serde",
  "time-core",
  "time-macros",
@@ -3931,9 +3920,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
 
 [[package]]
 name = "time-macros"
-version = "0.2.5"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b"
+checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
 dependencies = [
  "time-core",
 ]
@@ -4356,7 +4345,7 @@ dependencies = [
  "serde",
  "signature",
  "thiserror",
- "time 0.3.16",
+ "time 0.3.17",
  "tor-basic-utils",
  "tor-checkable",
  "tor-circmgr",
@@ -4513,7 +4502,7 @@ dependencies = [
  "serde_with",
  "signature",
  "thiserror",
- "time 0.3.16",
+ "time 0.3.17",
  "tinystr",
  "tor-bytes",
  "tor-cert",
@@ -5341,7 +5330,7 @@ dependencies = [
  "hmac",
  "pbkdf2",
  "sha1",
- "time 0.3.16",
+ "time 0.3.17",
  "zstd",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index 088c556..c1d11aa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "Gupax"
-version = "0.1.0"
-authors = ["hinto-janaiyo <hinto-janaiyo@protonmail.com>"]
+version = "0.2.0"
+authors = ["hinto-janaiyo <hinto.janaiyo@protonmail.com>"]
 description = "GUI for P2Pool+XMRig"
 documentation = "https://github.com/hinto-janaiyo/gupax"
 edition = "2021"
diff --git a/README.md b/README.md
index f3eec8d..7d96f05 100644
--- a/README.md
+++ b/README.md
@@ -88,11 +88,10 @@ https://user-images.githubusercontent.com/101352116/194763334-d8e936c9-a71e-474e
 * A Monero node/wallet
 
 ## Build
-Windows/macOS/Linux:
 ```
 cargo build --release
 ```
-On macOS, if you want the binary to have an icon in `Finder`, you must install [`cargo-bundle`](https://github.com/burtonageo/cargo-bundle) and compile uwith:
+On macOS, if you want the binary to have an icon in `Finder`, you must install [`cargo-bundle`](https://github.com/burtonageo/cargo-bundle) and compile with:
 ```
 cargo bundle --release
 ```
diff --git a/src/README.md b/src/README.md
index 67fcfee..b9bf19d 100644
--- a/src/README.md
+++ b/src/README.md
@@ -8,13 +8,15 @@
 | File/Folder    | Purpose |
 |----------------|---------|
 | `constants.rs` | General constants needed in Gupax
-| `gupax.rs`     | Impl for `Gupax` tab
-| `main.rs`      | Struct/enum/impl for `App/Tab/State`, init functions, main function
-| `node.rs`      | Struct/impl for Community Nodes
-| `p2pool.rs`    | Impl for `P2Pool` tab
-| `state.rs`     | Struct/impl for `gupax.toml`, the disk state. This holds the structs representing tabs with mutable state (Gupax/P2Pool/XMRig)
-| `status.rs`    | Struct/impl for `Status` tab
-| `xmrig.rs`     | Impl for `XMRig` tab
+| `ferris.rs`    | Cute crab `--ferris`
+| `gupax.rs`     | `Gupax` tab
+| `main.rs`      | `App/Tab/State` + misc functions
+| `node.rs`      | Community node feature
+| `p2pool.rs`    | `P2Pool` tab
+| `state.rs`     | `gupax.toml` config code. This holds the structs representing tabs with mutable state (Gupax/P2Pool/XMRig)
+| `status.rs`    | `Status` tab
+| `update.rs`    | Update code for the `Gupax` tab
+| `xmrig.rs`     | `XMRig` tab
 
 ## Bootstrap
 This is how Gupax works internally when starting up, divided into 3 sections.
diff --git a/src/constants.rs b/src/constants.rs
index bedc9ae..6be837d 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -69,13 +69,19 @@ pub const GUPAX_SAVE_BEFORE_QUIT: &'static str = "Automatically save any changed
 pub const GUPAX_PATH_P2POOL: &'static str = "The location of the P2Pool binary, both absolute and relative paths are accepted";
 pub const GUPAX_PATH_XMRIG: &'static str = "The location of the XMRig binary, both absolute and relative paths are accepted";
 // P2Pool
-pub const P2POOL_MAIN: &'static str = "The P2Pool main-chain. This P2Pool finds shares faster, but has a higher difficulty. Suitable for miners with more than 50kH/s";
-pub const P2POOL_MINI: &'static str = "The P2Pool mini-chain. This P2Pool finds shares slower, but has a lower difficulty. Suitable for miners with less than 50kH/s";
+pub const P2POOL_MAIN: &'static str = "Use the P2Pool main-chain. This P2Pool finds shares faster, but has a higher difficulty. Suitable for miners with more than 50kH/s";
+pub const P2POOL_MINI: &'static str = "Use the P2Pool mini-chain. This P2Pool finds shares slower, but has a lower difficulty. Suitable for miners with less than 50kH/s";
 pub const P2POOL_OUT: &'static str = "How many out-bound peers (you connecting to others) to connect to?";
 pub const P2POOL_IN: &'static str = "How many in-bound peers (others connecting to you) to connect to?";
 pub const P2POOL_LOG: &'static str = "Verbosity of the console log";
 pub const P2POOL_COMMUNITY: &'static str = "Connect to a community trusted Monero node: This is convenient because you don't have to download the Monero blockchain but it comes at the cost of privacy";
 pub const P2POOL_MANUAL: &'static str = "Manually specify your own Monero node settings";
+pub const P2POOL_AUTO_NODE: &'static str = "Automatically ping the community Monero nodes at Gupax startup";
+pub const P2POOL_AUTO_SELECT: &'static str = "Automatically select the fastest community Monero node after pinging";
+pub const P2POOL_SELECT_FASTEST: &'static str = "Select the fastest community Monero node";
+pub const P2POOL_PING: &'static str = "Ping the built-in community Monero nodes";
+pub const P2POOL_ADDRESS: &'static str = "You must use a primary Monero address to mine on P2Pool (starts with a 4). It is highly recommended to create a new wallet for P2Pool mining; wallet addresses are public on P2Pool!";
+
 // XMRig
 pub const XMRIG_P2POOL: &'static str = "Mine to your own P2Pool instance (localhost:3333)";
 pub const XMRIG_MANUAL: &'static str = "Manually specify where to mine to";
diff --git a/src/gupax.rs b/src/gupax.rs
index 04efe93..35043de 100644
--- a/src/gupax.rs
+++ b/src/gupax.rs
@@ -17,7 +17,8 @@
 
 use std::path::Path;
 use crate::{App,State};
-use egui::WidgetType::Button;
+use egui::TextStyle::Monospace;
+use egui::RichText;
 use crate::constants::*;
 use crate::state::{Gupax,Version};
 use crate::update::*;
@@ -73,7 +74,7 @@ impl Gupax {
 					ui.set_enabled(updating);
 					let prog = *update.lock().unwrap().prog.lock().unwrap();
 					let msg = format!("{}\n{}{}", *update.lock().unwrap().msg.lock().unwrap(), prog, "%");
-					ui.add_sized([width, height*1.4], egui::Label::new(msg));
+					ui.add_sized([width, height*1.4], egui::Label::new(RichText::text_style(RichText::new(msg), Monospace)));
 					let height = height/2.0;
 					if updating {
 						ui.add_sized([width, height], egui::Spinner::new().size(height));
diff --git a/src/main.rs b/src/main.rs
index 8a4d7ed..34de8cd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -34,6 +34,9 @@ use eframe::{egui,NativeOptions};
 use log::*;
 use env_logger::{Builder,WriteStyle};
 
+// Regex
+use regex::Regex;
+
 // std
 use std::io::Write;
 use std::process::exit;
@@ -64,9 +67,7 @@ pub struct App {
 	tab: Tab, // What tab are we on?
 	quit: bool, // Was the quit button clicked?
 	quit_confirm: bool, // Was the quit confirmed?
-	ping: bool, // Was the ping button clicked?
-	pinging: Arc<Mutex<bool>>, // Is a ping in progress?
-	node: Arc<Mutex<NodeStruct>>, // Data on community nodes
+	ping: Arc<Mutex<Ping>>, // Ping data found in [node.rs]
 	width: f32, // Top-level width
 	height: f32, // Top-level height
 	// State
@@ -88,9 +89,10 @@ pub struct App {
 	dir: String, // Directory [Gupax] binary is in
 	resolution: Vec2, // Frame resolution
 	os: &'static str, // OS
-	version: String, // Gupax version
+	version: &'static str, // Gupax version
 	name_version: String, // [Gupax vX.X.X]
 	banner: RetainedImage, // Gupax banner image
+	addr_regex: Regex, // [4.*] Monero Address Regex
 }
 
 impl App {
@@ -108,11 +110,9 @@ impl App {
 			tab: Tab::default(),
 			quit: false,
 			quit_confirm: false,
-			ping: false,
-			pinging: Arc::new(Mutex::new(false)),
+			ping: Arc::new(Mutex::new(Ping::new())),
 			width: 1280.0,
 			height: 720.0,
-			node: Arc::new(Mutex::new(NodeStruct::default())),
 			og: Arc::new(Mutex::new(State::default())),
 			state: State::default(),
 			update: Arc::new(Mutex::new(Update::new(String::new(), PathBuf::new(), PathBuf::new(), true))),
@@ -126,9 +126,10 @@ impl App {
 			dir: "".to_string(),
 			resolution: Vec2::new(1280.0, 720.0),
 			os: OS,
-			version: format!("{}", GUPAX_VERSION),
+			version: GUPAX_VERSION,
 			name_version: format!("Gupax {}", GUPAX_VERSION),
-			banner: RetainedImage::from_image_bytes("banner.png", BYTES_BANNER).expect("oops"),
+			banner: RetainedImage::from_image_bytes("banner.png", BYTES_BANNER).unwrap(),
+			addr_regex: Regex::new("^4[A-Za-z1-9]+$").unwrap(),
 		};
 		// Apply arg state
 		let mut app = parse_args(app);
@@ -188,21 +189,15 @@ fn init_text_styles(ctx: &egui::Context, width: f32) {
 	let scale = width / 26.666;
 	let mut style = (*ctx.style()).clone();
 	style.text_styles = [
-//		(Small, FontId::new(10.0, Proportional)),
-//		(Body, FontId::new(25.0, Proportional)),
-//		(Button, FontId::new(25.0, Proportional)),
-//		(Monospace, FontId::new(25.0, Proportional)),
-//		(Heading, FontId::new(30.0, Proportional)),
-//		(Name("Tab".into()), FontId::new(50.0, Proportional)),
-//		(Name("Bottom".into()), FontId::new(25.0, Proportional)),
 		(Small, FontId::new(scale/3.0, Proportional)),
 		(Body, FontId::new(scale/2.0, Proportional)),
 		(Button, FontId::new(scale/2.0, Proportional)),
-		(Monospace, FontId::new(scale/2.0, Proportional)),
+		(Monospace, FontId::new(scale/2.0, egui::FontFamily::Monospace)),
 		(Heading, FontId::new(scale/1.5, Proportional)),
 		(Name("Tab".into()), FontId::new(scale*1.2, Proportional)),
 		(Name("Bottom".into()), FontId::new(scale/2.0, Proportional)),
 	].into();
+//	style.visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) };
 //	style.spacing.slider_width = scale;
 //	style.spacing.text_edit_width = scale;
 //	style.spacing.button_padding = Vec2::new(scale/2.0, scale/2.0);
@@ -211,6 +206,14 @@ fn init_text_styles(ctx: &egui::Context, width: f32) {
 	ctx.request_repaint();
 }
 
+//fn init_color(ctx: &egui::Context) {
+//	let mut style = (*ctx.style()).clone();
+//	style.visuals.widgets.inactive.fg_stroke.color = Color32::from_rgb(100, 100, 100);
+//	style.visuals.selection.bg_fill = Color32::from_rgb(255, 125, 50);
+//	style.visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) };
+//	ctx.set_style(style);
+//}
+
 fn init_logger() {
 //	#[cfg(debug_assertions)]
 	let filter = LevelFilter::Info;
@@ -395,6 +398,9 @@ impl eframe::App for App {
 		// *-------*
 		// | DEBUG |
 		// *-------*
+//		crate::node::ping();
+//		std::process::exit(0);
+//		init_color(ctx);
 
 		// This sets the top level Ui dimensions.
 		// Used as a reference for other uis.
@@ -413,6 +419,11 @@ impl eframe::App for App {
 		// contains Arc<Mutex>'s that cannot be compared easily.
 		// They don't need to be compared anyway.
 		let og = self.og.lock().unwrap().clone();
+		// The [P2Pool Node] selection needs to be the same
+		// for both [State] and [Og] because of [Auto-select]
+		// Wrapping [node] within an [Arc<Mutex>] is a lot more work
+		// so sending it into the [Ping] thread is not viable.
+		self.state.p2pool.node = self.og.lock().unwrap().p2pool.node;
 		if og.gupax != self.state.gupax || og.p2pool != self.state.p2pool || og.xmrig != self.state.xmrig {
 			self.diff = true;
 		} else {
@@ -503,10 +514,7 @@ impl eframe::App for App {
 					ui.style_mut().override_text_style = Some(Name("Tab".into()));
 					ui.style_mut().visuals.widgets.inactive.fg_stroke.color = Color32::from_rgb(100, 100, 100);
 					ui.style_mut().visuals.selection.bg_fill = Color32::from_rgb(255, 120, 120);
-					ui.style_mut().visuals.selection.stroke = Stroke {
-						width: 5.0,
-						color: Color32::from_rgb(255, 255, 255),
-					};
+					ui.style_mut().visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) };
 					if ui.add_sized([width, height], egui::SelectableLabel::new(self.tab == Tab::About, "About")).clicked() { self.tab = Tab::About; }
 					ui.separator();
 					if ui.add_sized([width, height], egui::SelectableLabel::new(self.tab == Tab::Status, "Status")).clicked() { self.tab = Tab::Status; }
@@ -523,104 +531,113 @@ impl eframe::App for App {
 
 		// Bottom: app info + state/process buttons
 		egui::TopBottomPanel::bottom("bottom").show(ctx, |ui| {
-			let width = self.width/8.0;
 			let height = self.height/18.0;
 			ui.style_mut().override_text_style = Some(Name("Bottom".into()));
 			ui.horizontal(|ui| {
 				ui.group(|ui| {
+					// [Gupax Version] + [OS] + [P2Pool on/off] + [XMRig on/off]
+					let width = ((self.width/2.0)/4.0)-(SPACE*2.0);
 					ui.add_sized([width, height], Label::new(&*self.name_version));
 					ui.separator();
 					ui.add_sized([width, height], Label::new(self.os));
 					ui.separator();
-					ui.add_sized([width/1.5, height], Label::new("P2Pool"));
 					if self.p2pool {
-						ui.add_sized([width/4.0, height], Label::new(RichText::new("⏺").color(Color32::from_rgb(100, 230, 100))));
+						ui.add_sized([width, height], Label::new(RichText::new("P2Pool  ⏺").color(Color32::from_rgb(100, 230, 100))));
 					} else {
-						ui.add_sized([width/4.0, height], Label::new(RichText::new("⏺").color(Color32::from_rgb(230, 50, 50))));
+						ui.add_sized([width, height], Label::new(RichText::new("P2Pool  ⏺").color(Color32::from_rgb(230, 50, 50))));
 					}
 					ui.separator();
-					ui.add_sized([width/1.5, height], Label::new("XMRig"));
 					if self.xmrig {
-							ui.add_sized([width/4.0, height], Label::new(RichText::new("⏺").color(Color32::from_rgb(100, 230, 100))));
+						ui.add_sized([width, height], Label::new(RichText::new("XMRig  ⏺").color(Color32::from_rgb(100, 230, 100))));
 					} else {
-							ui.add_sized([width/4.0, height], Label::new(RichText::new("⏺").color(Color32::from_rgb(230, 50, 50))));
+						ui.add_sized([width, height], Label::new(RichText::new("XMRig  ⏺").color(Color32::from_rgb(230, 50, 50))));
 					}
 				});
 
 				ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
-					ui.group(|ui| {
-						if self.diff == false {
-							ui.set_enabled(false)
-						}
-						let width = width / 2.0;
-						if ui.add_sized([width, height], egui::Button::new("Save")).on_hover_text("Save changes").clicked() {
-							self.og.lock().unwrap().gupax = self.state.gupax.clone();
-							self.og.lock().unwrap().p2pool = self.state.p2pool.clone();
-							self.og.lock().unwrap().xmrig = self.state.xmrig.clone();
-							self.og.lock().unwrap().save();
-						}
-						if ui.add_sized([width, height], egui::Button::new("Reset")).on_hover_text("Reset changes").clicked() {
-							self.state.gupax = self.og.lock().unwrap().gupax.clone();
-							self.state.p2pool = self.og.lock().unwrap().p2pool.clone();
-							self.state.xmrig = self.og.lock().unwrap().xmrig.clone();
-						}
-					});
-
-					let width = (ui.available_width() / 3.0) - 6.2;
-					match self.tab {
-						Tab::P2pool => {
-							ui.group(|ui| {
-								if self.p2pool {
-									if ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() { self.p2pool = false; }
-									if ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { self.p2pool = false; }
-									ui.add_enabled_ui(false, |ui| {
-										ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start P2Pool");
-									});
-								} else {
-									ui.add_enabled_ui(false, |ui| {
-										ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart P2Pool");
-										ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop P2Pool");
-									});
-									if ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start P2Pool").clicked() { self.p2pool = true; }
-								}
-							});
-						}
-						Tab::Xmrig => {
-							ui.group(|ui| {
-								if self.xmrig {
-									if ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { self.xmrig = false; }
-									if ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { self.xmrig = false; }
-									ui.add_enabled_ui(false, |ui| {
-										ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start XMRig");
-									});
-								} else {
-									ui.add_enabled_ui(false, |ui| {
-										ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart XMRig");
-										ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop XMRig");
-									});
-									if ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start XMRig").clicked() { self.xmrig = true; }
-								}
-							});
-						}
-						_ => (),
+				// [Start/Stop/Restart] + [Simple/Advanced] + [Save/Reset]
+				let width = (ui.available_width()/3.0)-(SPACE*3.0);
+				ui.group(|ui| {
+					if self.diff == false {
+						ui.set_enabled(false)
+					}
+					let width = width / 2.0;
+					if ui.add_sized([width, height], egui::Button::new("Reset")).on_hover_text("Reset changes").clicked() {
+						self.state.gupax = self.og.lock().unwrap().gupax.clone();
+						self.state.p2pool = self.og.lock().unwrap().p2pool.clone();
+						self.state.xmrig = self.og.lock().unwrap().xmrig.clone();
+					}
+					if ui.add_sized([width, height], egui::Button::new("Save")).on_hover_text("Save changes").clicked() {
+						self.og.lock().unwrap().gupax = self.state.gupax.clone();
+						self.og.lock().unwrap().p2pool = self.state.p2pool.clone();
+						self.og.lock().unwrap().xmrig = self.state.xmrig.clone();
+						self.og.lock().unwrap().save();
 					}
 				});
+
+				match self.tab {
+					Tab::P2pool => {
+						ui.group(|ui| {
+							let width = width / 1.5;
+							if ui.add_sized([width, height], egui::SelectableLabel::new(!self.state.p2pool.simple, "Advanced")).clicked() {
+								self.state.p2pool.simple = false;
+							}
+							ui.separator();
+							if ui.add_sized([width, height], egui::SelectableLabel::new(self.state.p2pool.simple, "Simple")).clicked() {
+								self.state.p2pool.simple = true;
+							}
+						});
+						ui.group(|ui| {
+							let width = (ui.available_width()/3.0)-5.0;
+							if self.p2pool {
+								if ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() { self.p2pool = false; }
+								if ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { self.p2pool = false; }
+								ui.add_enabled_ui(false, |ui| {
+									ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start P2Pool");
+								});
+							} else {
+								ui.add_enabled_ui(false, |ui| {
+									ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart P2Pool");
+									ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop P2Pool");
+								});
+								if ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start P2Pool").clicked() { self.p2pool = true; }
+							}
+						});
+					}
+					Tab::Xmrig => {
+						ui.group(|ui| {
+							let width = width / 1.5;
+							if ui.add_sized([width, height], egui::SelectableLabel::new(!self.state.xmrig.simple, "Advanced")).clicked() {
+								self.state.xmrig.simple = false;
+							}
+							ui.separator();
+							if ui.add_sized([width, height], egui::SelectableLabel::new(self.state.xmrig.simple, "Simple")).clicked() {
+								self.state.xmrig.simple = true;
+							}
+						});
+						ui.group(|ui| {
+							let width = (ui.available_width()/3.0)-5.0;
+							if self.xmrig {
+								if ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { self.xmrig = false; }
+								if ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { self.xmrig = false; }
+								ui.add_enabled_ui(false, |ui| {
+									ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start XMRig");
+								});
+							} else {
+								ui.add_enabled_ui(false, |ui| {
+									ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart XMRig");
+									ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop XMRig");
+								});
+								if ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start XMRig").clicked() { self.xmrig = true; }
+							}
+						});
+					}
+					_ => (),
+				}
+			});
 			});
 		});
 
-		// If ping was pressed, start thread
-		if self.ping {
-			self.ping = false;
-			self.pinging = Arc::new(Mutex::new(true));
-			let node_clone = Arc::clone(&self.node);
-			let pinging_clone = Arc::clone(&self.pinging);
-			thread::spawn(move|| {
-				let result = NodeStruct::ping();
-				*node_clone.lock().unwrap() = result.nodes;
-				*pinging_clone.lock().unwrap() = false;
-			});
-		}
-
 		// Middle panel, contents of the [Tab]
 		egui::CentralPanel::default().show(ctx, |ui| {
 			// This sets the Ui dimensions after Top/Bottom are filled
@@ -655,7 +672,7 @@ impl eframe::App for App {
 					Gupax::show(&mut self.state.gupax, &self.og, &self.state.version, &self.update, self.width, self.height, ctx, ui);
 				}
 				Tab::P2pool => {
-					P2pool::show(&mut self.state.p2pool, self.width, self.height, ctx, ui);
+					P2pool::show(&mut self.state.p2pool, &self.og, self.p2pool, &self.ping, &self.addr_regex, self.width, self.height, ctx, ui);
 				}
 				Tab::Xmrig => {
 					Xmrig::show(&mut self.state.xmrig, self.width, self.height, ctx, ui);
diff --git a/src/node.rs b/src/node.rs
index bbfdbd5..c2d5b06 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -20,10 +20,13 @@ use std::time::{Instant,Duration};
 use std::collections::HashMap;
 use std::error::Error;
 use std::thread;
+use std::sync::{Arc,Mutex};
 use egui::Color32;
 use rand::Rng;
 use log::*;
+use reqwest::blocking::ClientBuilder;
 
+//---------------------------------------------------------------------------------------------------- Node list
 // Community Monerod nodes. All of these have ZMQ on 18083.
 // Adding/removing nodes will need changes to pretty
 // much all the code in this file, and the code that
@@ -33,37 +36,27 @@ pub const CAKE: &'static str = "xmr-node.cakewallet.com:18081";
 pub const CAKE_EU: &'static str = "xmr-node-eu.cakewallet.com:18081";
 pub const CAKE_UK: &'static str = "xmr-node-uk.cakewallet.com:18081";
 pub const CAKE_US: &'static str = "xmr-node-usa-east.cakewallet.com:18081";
+pub const MAJESTICBANK_IS: &'static str = "node.majesticbank.is:18089";
+pub const MAJESTICBANK_SU: &'static str = "node.majesticbank.su:18089";
 pub const MONERUJO: &'static str = "nodex.monerujo.io:18081";
 pub const RINO: &'static str = "node.community.rino.io:18081";
-pub const SELSTA: &'static str = "selsta1.featherwallet.net:18081";
+pub const SELSTA_1: &'static str = "selsta1.featherwallet.net:18081";
+pub const SELSTA_2: &'static str = "selsta2.featherwallet.net:18081";
 pub const SETH: &'static str = "node.sethforprivacy.com:18089";
 pub const SUPPORTXMR: &'static str = "node.supportxmr.com:18081";
 pub const SUPPORTXMR_IR: &'static str = "node.supportxmr.ir:18081";
+pub const SINGAPORE: &'static str = "singapore.node.xmr.pm:18089";
 pub const XMRVSBEAST: &'static str = "p2pmd.xmrvsbeast.com:18081";
 
-pub const NODE_IPS: [&'static str; 12] = [
-	C3POOL,CAKE,CAKE_EU,CAKE_UK,CAKE_US,MONERUJO,RINO,
-	SELSTA,SETH,SUPPORTXMR,SUPPORTXMR_IR,XMRVSBEAST,
+pub const NODE_IPS: [&'static str; 16] = [
+	C3POOL,CAKE,CAKE_EU,CAKE_UK,CAKE_US,MAJESTICBANK_IS,MAJESTICBANK_SU,MONERUJO,
+	RINO,SELSTA_1,SELSTA_2,SETH,SUPPORTXMR,SUPPORTXMR_IR,SINGAPORE,XMRVSBEAST,
 ];
 
-#[derive(Debug)]
-pub struct NodeStruct {
-	c3pool: Data, cake: Data, cake_eu: Data, cake_uk: Data, cake_us: Data, monerujo: Data,
-	rino: Data, selsta: Data, seth: Data, supportxmr: Data, supportxmr_ir: Data, xmrvsbeast: Data,
-}
-
-#[derive(Debug)]
-pub struct Data {
-	pub ms: u128,
-	pub color: Color32,
-	pub id: NodeEnum,
-	pub ip: &'static str,
-}
-
 #[derive(Copy,Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
 pub enum NodeEnum {
-	C3pool, Cake, CakeEu, CakeUk, CakeUs, Monerujo, Rino,
-	Selsta, Seth, SupportXmr, SupportXmrIr, XmrVsBeast,
+	C3pool,Cake,CakeEu,CakeUk,CakeUs,MajesticBankIs,MajesticBankSu,Monerujo,
+	Rino,Selsta1,Selsta2,Seth,SupportXmr,SupportXmrIr,Singapore,XmrVsBeast,
 }
 
 impl std::fmt::Display for NodeEnum {
@@ -72,152 +65,265 @@ impl std::fmt::Display for NodeEnum {
 	}
 }
 
+//---------------------------------------------------------------------------------------------------- Node data
+#[derive(Debug)]
+pub struct NodeData {
+	pub id: NodeEnum,
+	pub ip: &'static str,
+	pub ms: u128,
+	pub color: Color32,
+}
+
+impl NodeData {
+	pub fn new_vec() -> Vec<Self> {
+		let mut vec = Vec::new();
+		for ip in NODE_IPS.iter() {
+			vec.push(Self {
+				id: ip_to_enum(ip),
+				ip,
+				ms: 0,
+				color: Color32::LIGHT_GRAY,
+			});
+		}
+		vec
+	}
+}
+
+//---------------------------------------------------------------------------------------------------- Ping data
+#[derive(Debug)]
+pub struct Ping {
+	pub nodes: Vec<NodeData>,
+	pub fastest: NodeEnum,
+	pub pinging: bool,
+	pub msg: String,
+	pub prog: f32,
+	pub pinged: bool,
+}
+
+impl Ping {
+	pub fn new() -> Self {
+		Self {
+			nodes: NodeData::new_vec(),
+			fastest: NodeEnum::C3pool,
+			pinging: false,
+			msg: "No ping in progress".to_string(),
+			prog: 0.0,
+			pinged: false,
+		}
+	}
+}
+
 #[derive(Debug)]
 pub struct PingResult {
-	pub nodes: NodeStruct,
+	pub nodes: Vec<NodeData>,
 	pub fastest: NodeEnum,
 }
 
-use crate::NodeEnum::*;
-impl NodeStruct {
-	pub fn default() -> Self {
-		let ms = 0;
-		let color = Color32::GRAY;
-		Self {
-			c3pool:        Data { ms, color, id: C3pool,       ip: C3POOL, },
-			cake:          Data { ms, color, id: Cake,         ip: CAKE, },
-			cake_eu:       Data { ms, color, id: CakeEu,       ip: CAKE_EU, },
-			cake_uk:       Data { ms, color, id: CakeUk,       ip: CAKE_UK, },
-			cake_us:       Data { ms, color, id: CakeUs,       ip: CAKE_US, },
-			monerujo:      Data { ms, color, id: Monerujo,     ip: MONERUJO, },
-			rino:          Data { ms, color, id: Rino,         ip: RINO, },
-			selsta:        Data { ms, color, id: Selsta,       ip: SELSTA, },
-			seth:          Data { ms, color, id: Seth,         ip: SETH, },
-			supportxmr:    Data { ms, color, id: SupportXmr,   ip: SUPPORTXMR, },
-			supportxmr_ir: Data { ms, color, id: SupportXmrIr, ip: SUPPORTXMR_IR, },
-			xmrvsbeast:    Data { ms, color, id: XmrVsBeast,   ip: XMRVSBEAST, },
-		}
-	}
-
-	// This is for pinging the community nodes to
-	// find the fastest/slowest one for the user.
-	// The process:
-	//   - Send [get_info] JSON-RPC requests over HTTP
-	//   - To prevent fingerprinting, randomly send [2-4] calls
-	//   - Measure each request in milliseconds as [u128]
-	//   - Timeout on requests over 5 seconds
-	//   - Calculate average time
-	//   - Add data to appropriate struct
-	//   - Sort fastest to lowest
-	//   - Return [PingResult(NodeStruct, NodeEnum)] (data and fastest node)
- 	//
-	// This is done linearly since per IP since
-	// multi-threading might affect performance.
-	//
-	// <300ms  = GREEN
-	// <1000ms = YELLOW
-	// >1000ms = RED
-	// timeout = BLACK
-	// default = GRAY
-	pub fn ping() -> PingResult {
-		info!("Starting community node pings...");
-		// Get node list
-		let mut nodes = NodeStruct::default();
-
-		// Create JSON request
-		let mut get_info = HashMap::new();
-		get_info.insert("jsonrpc", "2.0");
-		get_info.insert("id", "0");
-		get_info.insert("method", "get_info");
-
-		// Misc Settings
-		let mut vec: Vec<(u128, NodeEnum)> = Vec::new();
-
-		// Create HTTP Client
-		let timeout_sec = Duration::from_millis(5000);
-		let client = reqwest::blocking::ClientBuilder::new();
-		let client = reqwest::blocking::ClientBuilder::timeout(client, timeout_sec);
-		let client = reqwest::blocking::ClientBuilder::build(client).unwrap();
-
-		for ip in NODE_IPS.iter() {
-			// Match IP
-			let id = match *ip {
-				C3POOL        => C3pool,
-				CAKE          => Cake,
-				CAKE_EU       => CakeEu,
-				CAKE_UK       => CakeUk,
-				CAKE_US       => CakeUs,
-				MONERUJO      => Monerujo,
-				RINO          => Rino,
-				SELSTA        => Selsta,
-				SETH          => Seth,
-				SUPPORTXMR    => SupportXmr,
-				SUPPORTXMR_IR => SupportXmrIr,
-				_ => XmrVsBeast,
-			};
-			// Misc
-			let mut timeout = false;
-			let mut mid = Duration::new(0, 0);
-			let max = rand::thread_rng().gen_range(2..4);
-
-			// Start JSON-RPC request
-			for i in 1..=max {
-				let now = Instant::now();
-				let http = "http://".to_owned() + &**ip + "/json_rpc";
-				match client.post(http).json(&get_info).send() {
-					Ok(_) => mid += now.elapsed(),
-					Err(err) => {
-						error!("Timeout on [{:#?}: {}] (over 5 seconds) | {}", id, ip, err);
-						mid += timeout_sec;
-						timeout = true;
-					},
-				};
-			}
-
-			// Calculate average
-			let ms = mid.as_millis() / 3;
-			vec.push((ms, id));
-			info!("{}ms ... {} calls ... {}", ms, max, ip);
-			let color: Color32;
-			if timeout == true {
-				color = Color32::BLACK
-			} else if ms >= 1000 {
-				color = Color32::LIGHT_RED
-			} else if ms >= 300 {
-				color = Color32::LIGHT_YELLOW
-			} else {
-				color = Color32::LIGHT_GREEN
-			}
-			match id {
-				C3pool       => { nodes.c3pool.ms = ms; nodes.c3pool.color = color; },
-				Cake         => { nodes.cake.ms = ms; nodes.cake.color = color; },
-				CakeEu       => { nodes.cake_eu.ms = ms; nodes.cake_eu.color = color; },
-				CakeUk       => { nodes.cake_uk.ms = ms; nodes.cake_uk.color = color; },
-				CakeUs       => { nodes.cake_us.ms = ms; nodes.cake_us.color = color; },
-				Monerujo     => { nodes.monerujo.ms = ms; nodes.monerujo.color = color; },
-				Rino         => { nodes.rino.ms = ms; nodes.rino.color = color; },
-				Selsta       => { nodes.selsta.ms = ms; nodes.selsta.color = color; },
-				Seth         => { nodes.seth.ms = ms; nodes.seth.color = color; },
-				SupportXmr   => { nodes.supportxmr.ms = ms; nodes.supportxmr.color = color; },
-				SupportXmrIr => { nodes.supportxmr_ir.ms = ms; nodes.supportxmr_ir.color = color; },
-				XmrVsBeast   => { nodes.xmrvsbeast.ms = ms; nodes.xmrvsbeast.color = color; },
-			}
-		}
-
-		// Calculate fastest out of all nodes
-		let mut best_ms: u128 = vec[0].0;
-		let mut fastest: NodeEnum = vec[0].1;
-		for (ms, id) in vec.iter() {
-			if ms < &best_ms {
-				fastest = *id;
-				best_ms = *ms;
-			}
-		}
-		// These values have weird behavior.
-		// The values don't update if not printed beforehand,
-		// so the match below on [fastest] gets funky.
-		info!("Fastest node ... {:#?} @ {:#?}ms", fastest, best_ms);
-		info!("Community node ping ... OK");
-		PingResult { nodes, fastest, }
+//---------------------------------------------------------------------------------------------------- IP <-> Enum functions
+// Function for returning IP/Enum
+pub fn ip_to_enum(ip: &'static str) -> NodeEnum {
+	match ip {
+		C3POOL          => C3pool,
+		CAKE            => Cake,
+		CAKE_EU         => CakeEu,
+		CAKE_UK         => CakeUk,
+		CAKE_US         => CakeUs,
+		MAJESTICBANK_IS => MajesticBankIs,
+		MAJESTICBANK_SU => MajesticBankSu,
+		MONERUJO        => Monerujo,
+		RINO            => Rino,
+		SELSTA_1        => Selsta1,
+		SELSTA_2        => Selsta2,
+		SETH            => Seth,
+		SINGAPORE       => Singapore,
+		SUPPORTXMR      => SupportXmr,
+		SUPPORTXMR_IR   => SupportXmrIr,
+		_               => XmrVsBeast,
 	}
 }
+
+pub fn enum_to_ip(node: NodeEnum) -> &'static str {
+	match node {
+		C3pool         => C3POOL,
+		Cake           => CAKE,
+		CakeEu         => CAKE_EU,
+		CakeUk         => CAKE_UK,
+		CakeUs         => CAKE_US,
+		MajesticBankIs => MAJESTICBANK_IS,
+		MajesticBankSu => MAJESTICBANK_SU,
+		Monerujo       => MONERUJO,
+		Rino           => RINO,
+		Selsta1        => SELSTA_1,
+		Selsta2        => SELSTA_2,
+		Seth           => SETH,
+		Singapore      => SINGAPORE,
+		SupportXmr     => SUPPORTXMR,
+		SupportXmrIr   => SUPPORTXMR_IR,
+		_              => XMRVSBEAST
+	}
+}
+
+// 5000 = 4 max length
+pub fn format_ms(ms: u128) -> String {
+	match ms.to_string().len() {
+		1 => format!("{}ms   ", ms),
+		2 => format!("{}ms  ", ms),
+		3 => format!("{}ms ", ms),
+		_ => format!("{}ms", ms),
+	}
+}
+
+// MajesticBankIs = 14 max length
+pub fn format_enum(id: NodeEnum) -> String {
+	match id.to_string().len() {
+		1  => format!("{}             ", id),
+		2  => format!("{}            ", id),
+		3  => format!("{}           ", id),
+		4  => format!("{}          ", id),
+		5  => format!("{}         ", id),
+		6  => format!("{}        ", id),
+		7  => format!("{}       ", id),
+		8  => format!("{}      ", id),
+		9  => format!("{}     ", id),
+		10 => format!("{}    ", id),
+		11 => format!("{}   ", id),
+		12 => format!("{}  ", id),
+		13 => format!("{} ", id),
+		_  => format!("{}", id),
+	}
+}
+
+//---------------------------------------------------------------------------------------------------- Main Ping function
+// This is for pinging the community nodes to
+// find the fastest/slowest one for the user.
+// The process:
+//   - Send 3 [get_info] JSON-RPC requests over HTTP
+//   - Measure each request in milliseconds as [u128]
+//   - Timeout on requests over 5 seconds
+//   - Calculate average time
+//   - Add data to appropriate struct
+//   - Sort fastest to lowest
+//   - Return [PingResult] (data and fastest node)
+//
+// This is done linearly since per IP since
+// multi-threading might affect performance.
+//
+// <300ms  = GREEN
+// <1000ms = YELLOW
+// >1000ms = RED
+// timeout = BLACK
+// default = GRAY
+use crate::NodeEnum::*;
+pub fn ping(ping: Arc<Mutex<Ping>>) -> PingResult {
+	// Start ping
+	ping.lock().unwrap().pinging = true;
+	ping.lock().unwrap().prog = 0.0;
+	let info = format!("{}", "Creating HTTPS Client");
+	info!("Ping | {}", info);
+	ping.lock().unwrap().msg = info;
+	let percent = (100 / (NODE_IPS.len() - 1)) as f32 / 3.0;
+
+	// Create Node vector
+	let mut nodes = Vec::new();
+
+	// Create JSON request
+	let mut get_info = HashMap::new();
+	get_info.insert("jsonrpc", "2.0");
+	get_info.insert("id", "0");
+	get_info.insert("method", "get_info");
+
+	// Misc Settings
+	let mut vec: Vec<(NodeEnum, u128)> = Vec::new();
+
+	// Create HTTP Client
+	let timeout_sec = Duration::from_millis(5000);
+	let client = ClientBuilder::new();
+	let client = ClientBuilder::timeout(client, timeout_sec);
+	let client = ClientBuilder::build(client).unwrap();
+
+	for ip in NODE_IPS.iter() {
+		// Match IP
+		let id = match *ip {
+			C3POOL          => C3pool,
+			CAKE            => Cake,
+			CAKE_EU         => CakeEu,
+			CAKE_UK         => CakeUk,
+			CAKE_US         => CakeUs,
+			MAJESTICBANK_IS => MajesticBankIs,
+			MAJESTICBANK_SU => MajesticBankSu,
+			MONERUJO        => Monerujo,
+			RINO            => Rino,
+			SELSTA_1        => Selsta1,
+			SELSTA_2        => Selsta2,
+			SETH            => Seth,
+			SINGAPORE       => Singapore,
+			SUPPORTXMR      => SupportXmr,
+			SUPPORTXMR_IR   => SupportXmrIr,
+			_ => XmrVsBeast,
+		};
+		// Misc
+		let mut timeout = 0;
+		let mut mid = Duration::new(0, 0);
+
+		// Start JSON-RPC request
+		for i in 1..=3 {
+			ping.lock().unwrap().msg = format!("{}: {} [{}/3]", id, ip, i);
+			let now = Instant::now();
+			let http = "http://".to_string() + &**ip + "/json_rpc";
+			match client.post(http).json(&get_info).send() {
+				Ok(_) => mid += now.elapsed(),
+				Err(err) => {
+					mid += timeout_sec;
+					timeout += 1;
+					let error = format!("Timeout [{}/3] ... {:#?} ... {}", timeout, id, ip);
+					error!("Ping | {}", error);
+					ping.lock().unwrap().msg = error;
+				},
+			};
+			ping.lock().unwrap().prog += percent;
+		}
+
+		// Calculate average
+		let ms = mid.as_millis() / 3;
+		vec.push((id, ms));
+		let info = format!("{}ms ... {}: {}", ms, id, ip);
+		info!("Ping | {}", info);
+		ping.lock().unwrap().msg = format!("{}", info);
+		let color: Color32;
+		if timeout == 3 {
+			color = Color32::BLACK;
+		} else if ms >= 1000 {
+			// RED
+			color = Color32::from_rgb(230, 50, 50);
+		} else if ms >= 300 {
+			// YELLOW
+			color = Color32::from_rgb(230, 230, 100);
+		} else {
+			// GREEN
+			color = Color32::from_rgb(100, 230, 100);
+		}
+		nodes.push(NodeData { id, ip, ms, color })
+	}
+
+	let percent = (100.0 - ping.lock().unwrap().prog) / 2.0;
+	ping.lock().unwrap().prog += percent;
+	ping.lock().unwrap().msg = "Calculating fastest node".to_string();
+	// Calculate fastest out of all nodes
+	let mut fastest: NodeEnum = vec[0].0;
+	let mut best_ms: u128 = vec[0].1;
+	for (id, ms) in vec.iter() {
+		if ms < &best_ms {
+			fastest = *id;
+			best_ms = *ms;
+		}
+	}
+	let info = format!("Fastest node: {}ms ... {} @ {}", best_ms, fastest, enum_to_ip(fastest));
+	let percent = (100.0 - ping.lock().unwrap().prog) / 2.0;
+	info!("Ping | {}", info);
+	ping.lock().unwrap().prog = 100.0;
+	ping.lock().unwrap().msg = info;
+	ping.lock().unwrap().pinging = false;
+	ping.lock().unwrap().pinged = true;
+	info!("Ping ... OK");
+	PingResult { nodes, fastest }
+}
diff --git a/src/p2pool.rs b/src/p2pool.rs
index a07883b..9ddf7c5 100644
--- a/src/p2pool.rs
+++ b/src/p2pool.rs
@@ -17,26 +17,163 @@
 
 use crate::App;
 use crate::constants::*;
-use crate::state::P2pool;
-use crate::node::NodeEnum;
-use crate::node::{RINO,SETH,SELSTA};
-
-//	pub simple: bool,
-//	pub mini: bool,
-//	pub out_peers: u8,
-//	pub in_peers: u8,
-//	pub log_level: u8,
-//	pub node: crate::node::NodeEnum,
-//	pub monerod: String,
-//	pub rpc: u16,
-//	pub zmq: u16,
-//	pub address: String,
-
+use crate::state::*;
+use crate::node::*;
+use crate::node::NodeEnum::*;
+use std::sync::{Arc,Mutex};
+use std::thread;
+use log::*;
+use egui::{TextEdit,SelectableLabel,ComboBox,Label};
+use egui::TextStyle::*;
+use egui::FontFamily::Proportional;
+use egui::{FontId,Button,Color32,RichText};
+use regex::Regex;
 
 impl P2pool {
-	pub fn show(&mut self, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) {
+	pub fn show(&mut self, og: &Arc<Mutex<State>>, online: bool, ping: &Arc<Mutex<Ping>>, addr_regex: &Regex, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) {
+	let text_edit = height / 20.0;
+	// Console
+	ui.group(|ui| {
+		let height = height / SPACE;
+		let width = width - SPACE;
+		ui.add_sized([width, height*3.0], TextEdit::multiline(&mut "".to_string()));
+		ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(&mut "".to_string()), r#"Type a command (e.g "help" or "status") and press Enter"#));
+	});
+
+	let height = ui.available_height();
+	// [Simple]
+	if self.simple {
+		// [Node]
+		let height = height / 6.0;
+		ui.spacing_mut().slider_width = width - 8.0;
+		ui.spacing_mut().icon_width = width / 25.0;
+		ui.vertical(|ui| {
+		ui.horizontal(|ui| {
+			// [Ping List]
+			let id = og.lock().unwrap().p2pool.node;
+			let ip = enum_to_ip(id);
+			let mut ms = 0;
+			let mut color = Color32::LIGHT_GRAY;
+			for data in ping.lock().unwrap().nodes.iter() {
+				if data.id == id {
+					ms = data.ms;
+					color = data.color;
+					break
+				}
+			}
+			let text = RichText::new(format!("⏺ {}ms | {} | {}", ms, id, ip)).color(color);
+			ComboBox::from_id_source("nodes").selected_text(RichText::text_style(text, Monospace)).show_ui(ui, |ui| {
+				for data in ping.lock().unwrap().nodes.iter() {
+					let ms = crate::node::format_ms(data.ms);
+					let id = crate::node::format_enum(data.id);
+					let text = RichText::text_style(RichText::new(format!("⏺ {} | {} | {}", ms, id, data.ip)).color(data.color), Monospace);
+					ui.selectable_value(&mut og.lock().unwrap().p2pool.node, data.id, text);
+				}
+			});
+		});
+
+		ui.add_space(5.0);
+
+		ui.horizontal(|ui| {
+		let width = (width/2.0)-4.0;
+		// [Select fastest node]
+		if ui.add_sized([width, height], Button::new("Select fastest node")).on_hover_text(P2POOL_SELECT_FASTEST).clicked() {
+			let pinged = ping.lock().unwrap().pinged;
+			let fastest = ping.lock().unwrap().fastest;
+			if pinged && og.lock().unwrap().p2pool.node != fastest {
+				og.lock().unwrap().p2pool.node = ping.lock().unwrap().fastest;
+				og.lock().unwrap().save();
+			}
+		}
+		// [Ping Button]
+		ui.set_enabled(!ping.lock().unwrap().pinging);
+		if ui.add_sized([width, height], Button::new("Ping community nodes")).on_hover_text(P2POOL_PING).clicked() {
+			let ping = Arc::clone(&ping);
+			let og_clone = Arc::clone(og);
+			ping.lock().unwrap().pinging = true;
+			thread::spawn(move|| {
+				info!("Spawning ping thread...");
+				let ping_result = crate::node::ping(ping.clone());
+				ping.lock().unwrap().nodes = ping_result.nodes;
+				ping.lock().unwrap().fastest = ping_result.fastest;
+				if og_clone.lock().unwrap().p2pool.auto_select {
+					og_clone.lock().unwrap().p2pool.node = ping_result.fastest;
+					og_clone.lock().unwrap().save();
+				}
+			});
+		}});
+
+		ui.vertical(|ui| {
+			let height = height / 2.0;
+			let pinging = ping.lock().unwrap().pinging;
+			ui.set_enabled(pinging);
+			let prog = ping.lock().unwrap().prog.round();
+			let msg = RichText::text_style(RichText::new(format!("{} ... {}%", ping.lock().unwrap().msg, prog)), Monospace);
+			let height = height / 1.25;
+			ui.add_space(5.0);
+			ui.add_sized([width, height], Label::new(msg));
+			ui.add_space(5.0);
+			if pinging {
+				ui.add_sized([width, height], egui::Spinner::new().size(height));
+			} else {
+				ui.add_sized([width, height], egui::Label::new("..."));
+			}
+			ui.add_sized([width, height], egui::ProgressBar::new(prog.round()/100.0));
+			ui.add_space(5.0);
+		});
+		});
+
+		ui.group(|ui| {
+		ui.horizontal(|ui| {
+			let width = (width/2.0)-(SPACE*1.5);
+			// [Auto-node] + [Auto-select]
+			let mut style = (*ctx.style()).clone();
+			style.spacing.icon_width_inner = height/1.5;
+			style.spacing.icon_width = height;
+			style.spacing.icon_spacing = 20.0;
+			ctx.set_style(style);
+			ui.add_sized([width, height], egui::Checkbox::new(&mut self.auto_select, "Auto-select")).on_hover_text(P2POOL_AUTO_SELECT);
+			ui.separator();
+			ui.add_sized([width, height], egui::Checkbox::new(&mut self.auto_node, "Auto-node")).on_hover_text(P2POOL_AUTO_NODE);
+		})});
+
+		// [Address]
+		let height = ui.available_height();
+		ui.horizontal(|ui| {
+			let width = width / 100.0;
+			ui.add_sized([width*6.0, height], Label::new("Address"));
+			if self.address.is_empty() {
+				ui.add_sized([width, height], Label::new(RichText::new("➖").color(Color32::LIGHT_GRAY)));
+			} else if self.address.len() == 95 && Regex::is_match(addr_regex, &self.address) {
+				ui.add_sized([width, height], Label::new(RichText::new("✔").color(Color32::from_rgb(100, 230, 100))));
+			} else {
+				ui.add_sized([width, height], Label::new(RichText::new("❌").color(Color32::from_rgb(230, 50, 50))));
+			}
+			ui.spacing_mut().text_edit_width = (width*9.0)-(SPACE*2.5);
+			ui.style_mut().override_text_style = Some(Monospace);
+			ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4...")).on_hover_text(P2POOL_ADDRESS);
+		});
+//		ui.horizontal(|ui| {
+//			ui.add_sized([width, height/2.0], Label::new("Address:"));
+//			ui.add_sized([width, height], TextEdit::multiline(&mut self.address));
+//		})});
+	// [Advanced]
+	} else {
 		// TODO:
 		// ping code
+		// If ping was pressed, start thread
+//		if self.ping {
+//			self.ping = false;
+//			self.pinging = Arc::new(Mutex::new(true));
+//			let node_clone = Arc::clone(&self.node);
+//			let pinging_clone = Arc::clone(&self.pinging);
+//			thread::spawn(move|| {
+//				let result = NodeStruct::ping();
+//				*node_clone.lock().unwrap() = result.nodes;
+//				*pinging_clone.lock().unwrap() = false;
+//			});
+//		}
+
 		// If ping-ING, display stats
 //		if *self.pinging.lock().unwrap() {
 //			egui::CentralPanel::default().show(ctx, |ui| {
@@ -58,14 +195,7 @@ impl P2pool {
 //			return
 //		}
 
-		let height = ui.available_height() / 10.0;
-		let mut width = ui.available_width() - 50.0;
-		ui.group(|ui| {
-			ui.add_sized([width, height*4.0], egui::TextEdit::multiline(&mut "".to_owned()));
-			ui.add_sized([width, 30.0], egui::TextEdit::singleline(&mut "".to_owned()));
-		});
-
-		width = width - 30.0;
+		let width = width - 30.0;
 		let mut style = (*ctx.style()).clone();
 		let height = ui.available_height()/1.2;
 		ui.horizontal(|ui| {
@@ -105,7 +235,7 @@ impl P2pool {
 			egui::ComboBox::from_label(self.node.to_string()).selected_text(RINO).show_ui(ui, |ui| {
 					ui.selectable_value(&mut self.node, NodeEnum::Rino, RINO);
 					ui.selectable_value(&mut self.node, NodeEnum::Seth, SETH);
-					ui.selectable_value(&mut self.node, NodeEnum::Selsta, SELSTA);
+					ui.selectable_value(&mut self.node, NodeEnum::Selsta1, SELSTA_1);
 			});
 //			);
 			});
@@ -138,4 +268,5 @@ impl P2pool {
 			ui.text_edit_singleline(&mut self.address);
 		})});
 	}
+	}
 }
diff --git a/src/state.rs b/src/state.rs
index d246f63..9fd367d 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -61,6 +61,8 @@ impl State {
 			p2pool: P2pool {
 				simple: true,
 				mini: true,
+				auto_node: true,
+				auto_select: true,
 				out_peers: 10,
 				in_peers: 10,
 				log_level: 3,
@@ -311,6 +313,8 @@ pub struct Gupax {
 pub struct P2pool {
 	pub simple: bool,
 	pub mini: bool,
+	pub auto_node: bool,
+	pub auto_select: bool,
 	pub out_peers: u16,
 	pub in_peers: u16,
 	pub log_level: u8,