diff --git a/.gitignore b/.gitignore
index 241250e00..e187f6f7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,9 @@
/bin/darkfi-mmproxy/darkfi-mmproxy
/darkfi-mmproxy
+/bin/drk/drk
+/drk
+
/bin/darkirc/darkirc
/darkirc
diff --git a/Cargo.lock b/Cargo.lock
index f78a6f832..60b35ef29 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -131,6 +131,28 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+[[package]]
+name = "alsa"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2562ad8dcf0f789f65c6fdaad8a8a9708ed6b488e649da28c01656ad66b8b47"
+dependencies = [
+ "alsa-sys",
+ "bitflags 1.3.2",
+ "libc",
+ "nix 0.24.3",
+]
+
+[[package]]
+name = "alsa-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
[[package]]
name = "amplify"
version = "4.5.0"
@@ -823,6 +845,26 @@ dependencies = [
"which",
]
+[[package]]
+name = "bindgen"
+version = "0.69.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d"
+dependencies = [
+ "bitflags 2.4.1",
+ "cexpr",
+ "clang-sys",
+ "lazy_static",
+ "lazycell",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn 2.0.48",
+]
+
[[package]]
name = "bit-set"
version = "0.5.3"
@@ -1082,6 +1124,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
[[package]]
name = "cexpr"
version = "0.6.0"
@@ -1261,6 +1309,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+[[package]]
+name = "combine"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+dependencies = [
+ "bytes 1.5.0",
+ "memchr",
+]
+
[[package]]
name = "concurrent-queue"
version = "2.4.0"
@@ -1389,6 +1447,26 @@ dependencies = [
"libc",
]
+[[package]]
+name = "coreaudio-rs"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation-sys",
+ "coreaudio-sys",
+]
+
+[[package]]
+name = "coreaudio-sys"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9"
+dependencies = [
+ "bindgen 0.69.2",
+]
+
[[package]]
name = "corosensei"
version = "0.1.4"
@@ -1402,6 +1480,31 @@ dependencies = [
"windows-sys 0.33.0",
]
+[[package]]
+name = "cpal"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d959d90e938c5493000514b446987c07aed46c668faaa7d34d6c7a67b1a578c"
+dependencies = [
+ "alsa",
+ "core-foundation-sys",
+ "coreaudio-rs",
+ "dasp_sample",
+ "jni 0.19.0",
+ "js-sys",
+ "libc",
+ "mach2",
+ "ndk",
+ "ndk-context",
+ "oboe",
+ "once_cell",
+ "parking_lot 0.12.1",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows",
+]
+
[[package]]
name = "cpufeatures"
version = "0.2.12"
@@ -1699,7 +1802,7 @@ version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b"
dependencies = [
- "nix",
+ "nix 0.27.1",
"windows-sys 0.52.0",
]
@@ -2260,6 +2363,12 @@ dependencies = [
"num-order",
]
+[[package]]
+name = "dasp_sample"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
+
[[package]]
name = "data-encoding"
version = "2.5.0"
@@ -2558,6 +2667,33 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
+[[package]]
+name = "drk"
+version = "0.4.1"
+dependencies = [
+ "blake3 1.5.0",
+ "bs58",
+ "darkfi",
+ "darkfi-sdk",
+ "darkfi-serial",
+ "darkfi_dao_contract",
+ "darkfi_money_contract",
+ "easy-parallel",
+ "log",
+ "prettytable-rs",
+ "rand 0.8.5",
+ "rodio",
+ "rusqlite",
+ "serde",
+ "signal-hook",
+ "signal-hook-async-std",
+ "simplelog",
+ "smol",
+ "structopt",
+ "structopt-toml",
+ "url",
+]
+
[[package]]
name = "dwrote"
version = "0.11.0"
@@ -3811,6 +3947,40 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+[[package]]
+name = "jni"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
+dependencies = [
+ "cesu8",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
+name = "jni"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c"
+dependencies = [
+ "cesu8",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
[[package]]
name = "jobserver"
version = "0.1.27"
@@ -4023,6 +4193,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "mach2"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "matchers"
version = "0.1.0"
@@ -4118,6 +4297,26 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+[[package]]
+name = "minimp3-sys"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "minimp3_fixed"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42b0f14e7e75da97ae396c2656b10262a3d4afa2ec98f35795630eff0c8b951b"
+dependencies = [
+ "minimp3-sys",
+ "slice-ring-buffer",
+ "thiserror",
+]
+
[[package]]
name = "miniz_oxide"
version = "0.7.1"
@@ -4149,6 +4348,46 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389"
+[[package]]
+name = "ndk"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0"
+dependencies = [
+ "bitflags 1.3.2",
+ "jni-sys",
+ "ndk-sys",
+ "num_enum 0.5.11",
+ "raw-window-handle",
+ "thiserror",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-sys"
+version = "0.4.1+23.1.7779620"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "nix"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if 1.0.0",
+ "libc",
+]
+
[[package]]
name = "nix"
version = "0.27.1"
@@ -4208,6 +4447,17 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "num-integer"
version = "0.1.45"
@@ -4265,13 +4515,34 @@ dependencies = [
"libm",
]
+[[package]]
+name = "num_enum"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
+dependencies = [
+ "num_enum_derive 0.5.11",
+]
+
[[package]]
name = "num_enum"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
dependencies = [
- "num_enum_derive",
+ "num_enum_derive 0.7.2",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
]
[[package]]
@@ -4304,6 +4575,29 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "oboe"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8868cc237ee02e2d9618539a23a8d228b9bb3fc2e7a5b11eed3831de77c395d0"
+dependencies = [
+ "jni 0.20.0",
+ "ndk",
+ "ndk-context",
+ "num-derive",
+ "num-traits",
+ "oboe-sys",
+]
+
+[[package]]
+name = "oboe-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f44155e7fb718d3cfddcf70690b2b51ac4412f347cd9e4fbe511abe9cd7b5f2"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "oid-registry"
version = "0.6.1"
@@ -5161,10 +5455,16 @@ name = "randomx"
version = "1.1.11"
source = "git+https://github.com/darkrenaissance/RandomX#65c0b322edf5ab66fad53bd0d163b7a2e9c63125"
dependencies = [
- "bindgen",
+ "bindgen 0.66.1",
"bitflags 1.3.2",
]
+[[package]]
+name = "raw-window-handle"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
+
[[package]]
name = "rayon"
version = "1.8.0"
@@ -5378,6 +5678,16 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "rodio"
+version = "0.17.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b1bb7b48ee48471f55da122c0044fcc7600cfcc85db88240b89cb832935e611"
+dependencies = [
+ "cpal",
+ "minimp3_fixed",
+]
+
[[package]]
name = "routefinder"
version = "0.4.0"
@@ -6098,6 +6408,17 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7"
+[[package]]
+name = "slice-ring-buffer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7081c7e452cb62f5f0c32edd4e265391bdbb23e90905df8bb88a23d3b5166b77"
+dependencies = [
+ "libc",
+ "mach2",
+ "winapi",
+]
+
[[package]]
name = "slotmap"
version = "1.0.7"
@@ -7223,7 +7544,7 @@ dependencies = [
"humantime",
"humantime-serde",
"itertools 0.12.0",
- "num_enum",
+ "num_enum 0.7.2",
"pin-project",
"postage",
"rand 0.8.5",
@@ -7473,7 +7794,7 @@ dependencies = [
"hex",
"humantime",
"itertools 0.12.0",
- "num_enum",
+ "num_enum 0.7.2",
"rand 0.8.5",
"serde",
"signature 1.6.4",
@@ -8393,6 +8714,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+[[package]]
+name = "windows"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
[[package]]
name = "windows-core"
version = "0.52.0"
@@ -8433,6 +8763,21 @@ dependencies = [
"windows-targets 0.52.0",
]
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
[[package]]
name = "windows-targets"
version = "0.48.5"
@@ -8463,6 +8808,12 @@ dependencies = [
"windows_x86_64_msvc 0.52.0",
]
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
@@ -8481,6 +8832,12 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807"
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@@ -8499,6 +8856,12 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e"
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@@ -8517,6 +8880,12 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0"
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@@ -8535,6 +8904,12 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784"
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
@@ -8547,6 +8922,12 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@@ -8565,6 +8946,12 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa"
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
diff --git a/Cargo.toml b/Cargo.toml
index 5fcff8b98..79b34f4c7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,8 +23,7 @@ members = [
#"bin/darkfid",
"bin/darkfid2",
"bin/darkfi-mmproxy",
- #"bin/drk",
- #"bin/drk2",
+ "bin/drk",
#"bin/faucetd",
#"bin/fud/fu",
#"bin/fud/fud",
diff --git a/Makefile b/Makefile
index a1b037bfc..1843864e0 100644
--- a/Makefile
+++ b/Makefile
@@ -60,6 +60,13 @@ darkfi-mmproxy:
RUST_TARGET="$(RUST_TARGET)" \
RUSTFLAGS="$(RUSTFLAGS)"
+drk: contracts
+ $(MAKE) -C bin/$@ \
+ PREFIX="$(PREFIX)" \
+ CARGO="$(CARGO)" \
+ RUST_TARGET="$(RUST_TARGET)" \
+ RUSTFLAGS="$(RUSTFLAGS)"
+
darkirc: zkas
$(MAKE) -C bin/$@ \
PREFIX="$(PREFIX)" \
diff --git a/bin/drk/Cargo.toml b/bin/drk/Cargo.toml
index f151db5b5..9fd98bc00 100644
--- a/bin/drk/Cargo.toml
+++ b/bin/drk/Cargo.toml
@@ -9,23 +9,31 @@ license = "AGPL-3.0-only"
edition = "2021"
[dependencies]
-anyhow = "1.0.79"
-async-std = {version = "1.12.0", features = ["attributes"]}
+# Darkfi
+darkfi = {path = "../../", features = ["async-daemonize", "bs58", "rpc"]}
+darkfi_money_contract = {path = "../../src/contract/money", features = ["no-entrypoint", "client"]}
+darkfi_dao_contract = {path = "../../src/contract/dao", features = ["no-entrypoint", "client"]}
+darkfi-sdk = {path = "../../src/sdk", features = ["async"]}
+darkfi-serial = {path = "../../src/serial"}
+
+# Misc
blake3 = "1.5.0"
bs58 = "0.5.0"
-clap = {version = "4.4.14", features = ["derive"]}
-clap_complete = "4.4.6"
-darkfi = {path = "../../", features = ["blockchain", "rpc", "util", "wallet"]}
-darkfi-sdk = {path = "../../src/sdk"}
-darkfi-serial = {path = "../../src/serial", features = ["derive", "crypto"]}
-darkfi-money-contract = {path = "../../src/contract/money", features = ["no-entrypoint", "client"]}
-darkfi-dao-contract = {path = "../../src/contract/dao", features = ["no-entrypoint", "client"]}
+log = "0.4.20"
prettytable-rs = "0.10.0"
rand = "0.8.5"
-serde_json = "1.0.111"
-smol = "1.3.0"
-simplelog = "0.12.1"
+rodio = {version = "0.17.3", default-features = false, features = ["minimp3"]}
+rusqlite = {version = "0.30.0", features = ["sqlcipher"]}
+url = "2.5.0"
+
+# Daemon
+easy-parallel = "3.3.1"
signal-hook-async-std = "0.2.2"
signal-hook = "0.3.17"
-url = "2.5.0"
-rodio = {version = "0.17.3", default-features = false, features = ["minimp3"]}
+simplelog = "0.12.1"
+smol = "1.3.0"
+
+# Argument parsing
+serde = {version = "1.0.195", features = ["derive"]}
+structopt = "0.3.26"
+structopt-toml = "0.5.1"
diff --git a/bin/drk2/Makefile b/bin/drk/Makefile
similarity index 100%
rename from bin/drk2/Makefile
rename to bin/drk/Makefile
diff --git a/bin/drk/TODO b/bin/drk/TODO
deleted file mode 100644
index e8939f208..000000000
--- a/bin/drk/TODO
+++ /dev/null
@@ -1,4 +0,0 @@
-* Spent coins can be tracked through nullifiers
- * So in the wallet, once we broadcast a tx, we can spend a coin
- * Also extend the wallet.sql schema to support both confirmed and
- unconfirmed coins (use tx hash for that like DAO does)
diff --git a/bin/drk2/drk_config.toml b/bin/drk/drk_config.toml
similarity index 100%
rename from bin/drk2/drk_config.toml
rename to bin/drk/drk_config.toml
diff --git a/bin/drk/src/cli_util.rs b/bin/drk/src/cli_util.rs
index d37711a20..aaf2fcc2e 100644
--- a/bin/drk/src/cli_util.rs
+++ b/bin/drk/src/cli_util.rs
@@ -15,17 +15,17 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-use std::{io::Cursor, process::exit};
+use std::{io::Cursor, process::exit, str::FromStr};
-use darkfi::{
- util::{async_util::sleep, parse::decode_base10},
- Result,
-};
-use darkfi_sdk::crypto::TokenId;
use rodio::{source::Source, Decoder, OutputStream};
+use structopt_toml::clap::{App, Arg, Shell, SubCommand};
-use super::Drk;
+use darkfi::{cli_desc, system::sleep, util::parse::decode_base10, Error, Result};
+use darkfi_sdk::crypto::TokenId;
+use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
+
+/// Auxiliary function to parse provided string into a values pair.
pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
let v: Vec<&str> = s.split(':').collect();
if v.len() != 2 {
@@ -33,9 +33,8 @@ pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
exit(1);
}
- // TODO: We shouldn't be hardcoding everything to 8 decimals.
- let val0 = decode_base10(v[0], 8, true);
- let val1 = decode_base10(v[1], 8, true);
+ let val0 = decode_base10(v[0], BALANCE_BASE10_DECIMALS, true);
+ let val1 = decode_base10(v[1], BALANCE_BASE10_DECIMALS, true);
if val0.is_err() || val1.is_err() {
eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
@@ -45,6 +44,7 @@ pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
Ok((val0.unwrap(), val1.unwrap()))
}
+/// Auxiliary function to parse provided string into a tokens pair.
pub async fn parse_token_pair(drk: &Drk, s: &str) -> Result<(TokenId, TokenId)> {
let v: Vec<&str> = s.split(':').collect();
if v.len() != 2 {
@@ -85,3 +85,385 @@ pub async fn kaching() {
sleep(2).await;
}
+
+/// Auxiliary function to generate provided shell completions.
+pub fn generate_completions(shell: &str) -> Result<()> {
+ // Sub-commands
+
+ // Kaching
+ let kaching = SubCommand::with_name("kaching").about("Fun");
+
+ // Ping
+ let ping =
+ SubCommand::with_name("ping").about("Send a ping request to the darkfid RPC endpoint");
+
+ // Completions
+ let shell_arg = Arg::with_name("shell").help("The Shell you want to generate script for");
+
+ let completions = SubCommand::with_name("completions")
+ .about("Generate a SHELL completion script and print to stdout")
+ .arg(shell_arg);
+
+ // Wallet
+ let initialize =
+ Arg::with_name("initialize").long("initialize").help("Initialize wallet database");
+
+ let keygen =
+ Arg::with_name("keygen").long("keygen").help("Generate a new keypair in the wallet");
+
+ let balance =
+ Arg::with_name("balance").long("balance").help("Query the wallet for known balances");
+
+ let address =
+ Arg::with_name("address").long("address").help("Get the default address in the wallet");
+
+ let addresses =
+ Arg::with_name("addresses").long("addresses").help("Print all the addresses in the wallet");
+
+ let default_address = Arg::with_name("default-address")
+ .long("default-address")
+ .takes_value(true)
+ .help("Set the default address in the wallet");
+
+ let secrets =
+ Arg::with_name("secrets").long("secrets").help("Print all the secret keys from the wallet");
+
+ let import_secrets = Arg::with_name("import-secrets")
+ .long("import-secrets")
+ .help("Import secret keys from stdin into the wallet, separated by newlines");
+
+ let tree = Arg::with_name("tree").long("tree").help("Print the Merkle tree in the wallet");
+
+ let coins = Arg::with_name("coins").long("coins").help("Print all the coins in the wallet");
+
+ let wallet = SubCommand::with_name("wallet").about("Wallet operations").args(&vec![
+ initialize,
+ keygen,
+ balance,
+ address,
+ addresses,
+ default_address,
+ secrets,
+ import_secrets,
+ tree,
+ coins,
+ ]);
+
+ // Unspend
+ let coin = Arg::with_name("coin").help("base58-encoded coin to mark as unspent");
+
+ let unspend = SubCommand::with_name("unspend").about("Unspend a coin").arg(coin);
+
+ // Transfer
+ let amount = Arg::with_name("amount").help("Amount to send");
+
+ let token = Arg::with_name("token").help("Token ID to send");
+
+ let recipient = Arg::with_name("recipient").help("Recipient address");
+
+ let transfer = SubCommand::with_name("transfer")
+ .about("Create a payment transaction")
+ .args(&vec![amount, token, recipient]);
+
+ // Otc
+ let value_pair = Arg::with_name("value-pair")
+ .short("v")
+ .long("value-pair")
+ .takes_value(true)
+ .help("Value pair to send:recv (11.55:99.42)");
+
+ let token_pair = Arg::with_name("token-pair")
+ .short("t")
+ .long("token-pair")
+ .takes_value(true)
+ .help("Token pair to send:recv (f00:b4r)");
+
+ let init = SubCommand::with_name("init")
+ .about("Initialize the first half of the atomic swap")
+ .args(&vec![value_pair, token_pair]);
+
+ let join =
+ SubCommand::with_name("join").about("Build entire swap tx given the first half from stdin");
+
+ let inspect = SubCommand::with_name("inspect")
+ .about("Inspect a swap half or the full swap tx from stdin");
+
+ let sign = SubCommand::with_name("sign")
+ .about("Sign a transaction given from stdin as the first-half");
+
+ let otc = SubCommand::with_name("otc")
+ .about("OTC atomic swap")
+ .subcommands(vec![init, join, inspect, sign]);
+
+ // Inspect
+ let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");
+
+ // Broadcast
+ let broadcast =
+ SubCommand::with_name("broadcast").about("Read a transaction from stdin and broadcast it");
+
+ // Subscribe
+ let subscribe = SubCommand::with_name("subscribe").about(
+ "This subscription will listen for incoming blocks from darkfid and look \
+ through their transactions to see if there's any that interest us. \
+ With `drk` we look at transactions calling the money contract so we can \
+ find coins sent to us and fill our wallet with the necessary metadata.",
+ );
+
+ // DAO
+ let proposer_limit = Arg::with_name("proposer-limit")
+ .help("The minimum amount of governance tokens needed to open a proposal for this DAO");
+
+ let quorum = Arg::with_name("quorum")
+ .help("Minimal threshold of participating total tokens needed for a proposal to pass");
+
+ let approval_ratio = Arg::with_name("approval-ratio")
+ .help("The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)");
+
+ let gov_token_id = Arg::with_name("gov-token-id").help("DAO's governance token ID");
+
+ let create = SubCommand::with_name("create").about("Create DAO parameters").args(&vec![
+ proposer_limit,
+ quorum,
+ approval_ratio,
+ gov_token_id,
+ ]);
+
+ let view = SubCommand::with_name("view").about("View DAO data from stdin");
+
+ let dao_name = Arg::with_name("dao-name").help("Named identifier for the DAO");
+
+ let import =
+ SubCommand::with_name("import").about("Import DAO data from stdin").args(&vec![dao_name]);
+
+ let dao_alias = Arg::with_name("dao-alias").help("Numeric identifier for the DAO (optional)");
+
+ let list = SubCommand::with_name("list")
+ .about("List imported DAOs (or info about a specific one)")
+ .args(&vec![dao_alias]);
+
+ let dao_alias = Arg::with_name("dao-alias").help("Name or numeric identifier for the DAO");
+
+ let balance = SubCommand::with_name("balance")
+ .about("Show the balance of a DAO")
+ .args(&vec![dao_alias.clone()]);
+
+ let mint = SubCommand::with_name("mint")
+ .about("Mint an imported DAO on-chain")
+ .args(&vec![dao_alias.clone()]);
+
+ let recipient =
+ Arg::with_name("recipient").help("Pubkey to send tokens to with proposal success");
+
+ let amount = Arg::with_name("amount").help("Amount to send from DAO with proposal success");
+
+ let token = Arg::with_name("token").help("Token ID to send from DAO with proposal success");
+
+ let propose = SubCommand::with_name("propose")
+ .about("Create a proposal for a DAO")
+ .args(&vec![dao_alias.clone(), recipient, amount, token]);
+
+ let proposals = SubCommand::with_name("proposals")
+ .about("List DAO proposals")
+ .args(&vec![dao_alias.clone()]);
+
+ let proposal_id = Arg::with_name("proposal-id").help("Numeric identifier for the proposal");
+
+ let proposal = SubCommand::with_name("proposal")
+ .about("View a DAO proposal data")
+ .args(&vec![dao_alias.clone(), proposal_id.clone()]);
+
+ let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
+
+ let vote_weight =
+ Arg::with_name("vote-weight").help("Vote weight (amount of governance tokens)");
+
+ let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&vec![
+ dao_alias.clone(),
+ proposal_id.clone(),
+ vote,
+ vote_weight,
+ ]);
+
+ let exec = SubCommand::with_name("exec")
+ .about("Execute a DAO proposal")
+ .args(&vec![dao_alias, proposal_id]);
+
+ let dao = SubCommand::with_name("dao").about("DAO functionalities").subcommands(vec![
+ create, view, import, list, balance, mint, propose, proposals, proposal, vote, exec,
+ ]);
+
+ // Scan
+ let reset = Arg::with_name("reset")
+ .long("reset")
+ .help("Reset Merkle tree and start scanning from first block");
+
+ let list = Arg::with_name("list").long("list").help("List all available checkpoints");
+
+ let checkpoint = Arg::with_name("checkpoint")
+ .long("checkpoint")
+ .takes_value(true)
+ .help("Reset Merkle tree to checkpoint index and start scanning");
+
+ let scan = SubCommand::with_name("scan")
+ .about("Scan the blockchain and parse relevant transactions")
+ .args(&vec![reset, list, checkpoint]);
+
+ // Explorer
+ let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
+
+ let full = Arg::with_name("full").long("full").help("Print the full transaction information");
+
+ let encode = Arg::with_name("encode").long("encode").help("Encode transaction to base58");
+
+ let fetch_tx = SubCommand::with_name("fetch-tx")
+ .about("Fetch a blockchain transaction by hash")
+ .args(&vec![tx_hash, full, encode]);
+
+ let simulate_tx =
+ SubCommand::with_name("simulate-tx").about("Read a transaction from stdin and simulate it");
+
+ let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
+
+ let encode = Arg::with_name("encode")
+ .long("encode")
+ .help("Encode specific history record transaction to base58");
+
+ let txs_history = SubCommand::with_name("txs-history")
+ .about("Fetch broadcasted transactions history")
+ .args(&vec![tx_hash, encode]);
+
+ let explorer = SubCommand::with_name("explorer")
+ .about("Explorer related subcommands")
+ .subcommands(vec![fetch_tx, simulate_tx, txs_history]);
+
+ // Alias
+ let alias = Arg::with_name("alias").help("Token alias");
+
+ let token = Arg::with_name("token").help("Token to create alias for");
+
+ let add = SubCommand::with_name("add").about("Create a Token alias").args(&vec![alias, token]);
+
+ let alias = Arg::with_name("alias")
+ .short("a")
+ .long("alias")
+ .takes_value(true)
+ .help("Token alias to search for");
+
+ let token = Arg::with_name("token")
+ .short("t")
+ .long("token")
+ .takes_value(true)
+ .help("Token to search alias for");
+
+ let show = SubCommand::with_name("show")
+ .about(
+ "Print alias info of optional arguments. \
+ If no argument is provided, list all the aliases in the wallet.",
+ )
+ .args(&vec![alias, token]);
+
+ let alias = Arg::with_name("alias").help("Token alias to remove");
+
+ let remove = SubCommand::with_name("remove").about("Remove a Token alias").arg(alias);
+
+ let alias = SubCommand::with_name("alias")
+ .about("Manage Token aliases")
+ .subcommands(vec![add, show, remove]);
+
+ // Token
+ let import = SubCommand::with_name("import").about("Import a mint authority secret from stdin");
+
+ let generate_mint =
+ SubCommand::with_name("generate-mint").about("Generate a new mint authority");
+
+ let list =
+ SubCommand::with_name("list").about("List token IDs with available mint authorities");
+
+ let token = Arg::with_name("token").help("Token ID to mint");
+
+ let amount = Arg::with_name("amount").help("Amount to mint");
+
+ let recipient = Arg::with_name("recipient").help("Recipient of the minted tokens");
+
+ let mint =
+ SubCommand::with_name("mint").about("Mint tokens").args(&vec![token, amount, recipient]);
+
+ let token = Arg::with_name("token").help("Token ID to freeze");
+
+ let freeze = SubCommand::with_name("freeze").about("Freeze a token mint").arg(token);
+
+ let token = SubCommand::with_name("token").about("Token functionalities").subcommands(vec![
+ import,
+ generate_mint,
+ list,
+ mint,
+ freeze,
+ ]);
+
+ // Main arguments
+ let config = Arg::with_name("config")
+ .short("c")
+ .long("config")
+ .takes_value(true)
+ .help("Configuration file to use");
+
+ let wallet_path = Arg::with_name("wallet_path")
+ .long("wallet-path")
+ .takes_value(true)
+ .help("Path to wallet database");
+
+ let wallet_pass = Arg::with_name("wallet_pass")
+ .long("wallet-pass")
+ .takes_value(true)
+ .help("Password for the wallet database");
+
+ let endpoint = Arg::with_name("endpoint")
+ .short("e")
+ .long("endpoint")
+ .takes_value(true)
+ .help("darkfid JSON-RPC endpoint");
+
+ let command = vec![
+ kaching,
+ ping,
+ completions,
+ wallet,
+ unspend,
+ transfer,
+ otc,
+ inspect,
+ broadcast,
+ subscribe,
+ dao,
+ scan,
+ explorer,
+ alias,
+ token,
+ ];
+
+ let log = Arg::with_name("log")
+ .short("l")
+ .long("log")
+ .takes_value(true)
+ .help("Set log file to ouput into");
+
+ let verbose = Arg::with_name("verbose")
+ .short("v")
+ .multiple(true)
+ .help("Increase verbosity (-vvv supported)");
+
+ let mut app = App::new("drk")
+ .about(cli_desc!())
+ .args(&vec![config, wallet_path, wallet_pass, endpoint, log, verbose])
+ .subcommands(command);
+
+ let shell = match Shell::from_str(shell) {
+ Ok(s) => s,
+ Err(e) => return Err(Error::Custom(e)),
+ };
+
+ app.gen_completions_to("./drk", shell, &mut std::io::stdout());
+
+ Ok(())
+}
diff --git a/bin/drk2/src/dao.rs b/bin/drk/src/dao.rs
similarity index 100%
rename from bin/drk2/src/dao.rs
rename to bin/drk/src/dao.rs
diff --git a/bin/drk/src/deploy_contract.rs b/bin/drk/src/deploy_contract.rs
deleted file mode 100644
index faa1f1e45..000000000
--- a/bin/drk/src/deploy_contract.rs
+++ /dev/null
@@ -1,181 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-use std::{
- env::set_current_dir,
- fs::{read, read_dir, read_to_string, File},
- io::{ErrorKind, Write},
- path::{Path, PathBuf},
- str::FromStr,
-};
-
-use rand::{rngs::OsRng, RngCore};
-
-use darkfi::{
- crypto::keypair::SecretKey,
- node::{MemoryState, State},
- runtime::vm_runtime::Runtime,
- util::cli::{fg_green, fg_red},
- zkas::ZkBinary,
- Error, Result,
-};
-
-const CIRCUIT_DIR_NAME: &str = "proof";
-const CONTRACT_FILE_NAME: &str = "contract.wasm";
-const DEPLOY_KEY_NAME: &str = "deploy.key";
-
-/// Creates a new deploy key used for deploying private smart contracts.
-/// This key allows to update the wasm code and the zk circuits on chain
-/// by creating a signature. When deployed, the contract can be accessed
-/// by requesting the public counterpart of this secret key.
-pub fn create_deploy_key(mut rng: impl RngCore, path: &Path) -> Result {
- let secret = SecretKey::random(&mut rng);
- let mut file = File::create(path)?;
- file.write_all(bs58::encode(&secret.to_bytes()).into_string().as_bytes())?;
- Ok(secret)
-}
-
-/// Reads a deploy key from a file on the filesystem and returns it.
-fn read_deploy_key(s: &Path) -> core::result::Result {
- eprintln!("Trying to read deploy key from file: {:?}", s);
- let contents = read_to_string(s)?;
- let secret = SecretKey::from_str(&contents).unwrap();
- Ok(secret)
-}
-
-/// Creates necessary data to deploy a given smart contract on the network.
-/// For consistency, we point this function to a directory where our smart
-/// contract and the compiled circuits are contained. This is going to give
-/// us a uniform approach to scm and gives a generic layout of the source:
-/// ```text
-/// smart-contract
-/// ├── Cargo.toml
-/// ├── deploy.key
-/// ├── Makefile
-/// ├── proof
-/// │ ├── circuit0.zk
-/// │ ├── circuit0.zk.bin
-/// │ ├── circuit1.zk
-/// │ └── circuit1.zk.bin
-/// ├── contract.wasm
-/// ├── src
-/// │ └── lib.rs
-/// └── tests
-/// ```
-//pub fn create_deploy_data(path: &Path) -> Result {
-pub fn create_deploy_data(path: &Path) -> Result<()> {
- // Try to chdir into the contract directory
- if let Err(e) = set_current_dir(path) {
- eprintln!("Failed to chdir into {:?}", path);
- return Err(e.into())
- }
-
- let deploy_key: SecretKey;
-
- let deploy_key = match read_deploy_key(&PathBuf::from(DEPLOY_KEY_NAME)) {
- Ok(v) => deploy_key = v,
- Err(e) => {
- if e.kind() == ErrorKind::NotFound {
- // We didn't find a deploy key, generate a new one.
- eprintln!("Did not find an existing key, creating a new one.");
- match create_deploy_key(&mut OsRng, &PathBuf::from(DEPLOY_KEY_NAME)) {
- Ok(v) => {
- eprintln!("Created new deploy key in \"{}\".", DEPLOY_KEY_NAME);
- deploy_key = v;
- }
- Err(e) => {
- eprintln!("Failed to create new deploy key");
- return Err(e)
- }
- }
- }
- eprintln!("Failed to read deploy key");
- return Err(e.into())
- }
- };
-
- // Search for ZK circuits in the directory. If none are found, we'll bail.
- // The logic searches for `.zk.bin` files created by zkas.
- eprintln!("Searching for compiled ZK circuits in \"{}\" ...", CIRCUIT_DIR_NAME);
- let mut circuits = vec![];
- for i in read_dir(CIRCUIT_DIR_NAME)? {
- if let Err(e) = i {
- eprintln!("Error iterating over \"{}\" directory", CIRCUIT_DIR_NAME);
- return Err(e.into())
- }
-
- let f = i.unwrap();
- let fname = f.file_name();
- let fname = fname.to_str().unwrap();
-
- if fname.ends_with(".zk.bin") {
- // Validate that the files can be properly decoded
- eprintln!("{} {}", fg_green("Found:"), f.path().display());
- let buf = read(f.path())?;
- if let Err(e) = ZkBinary::decode(&buf) {
- eprintln!("{} Failed to decode zkas bincode in {:?}", fg_red("Error:"), f.path());
- return Err(e)
- }
-
- circuits.push(buf.clone());
- }
- }
-
- if circuits.is_empty() {
- return Err(Error::Custom("Found no valid ZK circuits".to_string()))
- }
-
- /* FIXME
- // Validate wasm binary. We inspect the bincode and try to load it into
- // the wasm runtime. If loaded, we then look for the `ENTRYPOINT` function
- // which we hardcode into our sdk and runtime and is the canonical way to
- // run wasm binaries on chain.
- eprintln!("Inspecting wasm binary in \"{}\"", CONTRACT_FILE_NAME);
- let wasm_bytes = read(CONTRACT_FILE_NAME)?;
- eprintln!("Initializing moch wasm runtime to check validity");
- let runtime = match Runtime::new(&wasm_bytes, MemoryState::new(State::dummy()?)) {
- Ok(v) => {
- eprintln!("Found {} wasm binary", fg_green("valid"));
- v
- }
- Err(e) => {
- eprintln!("Failed to initialize wasm runtime");
- return Err(e)
- }
- };
-
- eprintln!("Looking for entrypoint function inside the wasm");
- let cs = ContractSection::Exec;
- if let Err(e) = runtime.instance.exports.get_function(cs.name()) {
- eprintln!("{} Could not find entrypoint function", fg_red("Error:"));
- return Err(e.into())
- }
-
- // TODO: Create a ZK proof enforcing the deploy key relations with their public
- // counterparts (public key and contract address)
- let mut total_bytes = 0;
- total_bytes += wasm_bytes.len();
- for circuit in circuits {
- total_bytes += circuit.len();
- }
- */
-
- // TODO: Return the data back to the main function, and work further in creating
- // a transaction and broadcasting it.
- Ok(())
-}
diff --git a/bin/drk2/src/error.rs b/bin/drk/src/error.rs
similarity index 100%
rename from bin/drk2/src/error.rs
rename to bin/drk/src/error.rs
diff --git a/bin/drk/src/main.rs b/bin/drk/src/main.rs
index 7f8f59155..cae719b1b 100644
--- a/bin/drk/src/main.rs
+++ b/bin/drk/src/main.rs
@@ -17,92 +17,114 @@
*/
use std::{
+ fs,
io::{stdin, Read},
process::exit,
str::FromStr,
+ sync::Arc,
time::Instant,
};
-use anyhow::{anyhow, Context, Result};
-use clap::{CommandFactory, Parser, Subcommand};
-use clap_complete::{generate, Shell};
-use darkfi::{tx::Transaction, util::parse::decode_base10, zk::halo2::Field};
+use prettytable::{format, row, Table};
+use rand::rngs::OsRng;
+use smol::stream::StreamExt;
+use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
+use url::Url;
+
+use darkfi::{
+ async_daemonize, cli_desc,
+ rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue},
+ tx::Transaction,
+ util::{
+ parse::{decode_base10, encode_base10},
+ path::expand_path,
+ },
+ zk::halo2::Field,
+ Result,
+};
use darkfi_money_contract::model::Coin;
use darkfi_sdk::{
crypto::{PublicKey, SecretKey, TokenId},
pasta::{group::ff::PrimeField, pallas},
};
use darkfi_serial::{deserialize, serialize};
-use prettytable::{format, row, Table};
-use rand::rngs::OsRng;
-use serde_json::json;
-use simplelog::{ColorChoice, TermLogger, TerminalMode};
-use url::Url;
-use darkfi::{
- cli_desc,
- rpc::{client::RpcClient, jsonrpc::JsonRequest},
- util::{
- cli::{get_log_config, get_log_level},
- parse::encode_base10,
- },
-};
+/// Error codes
+mod error;
-/// Airdrop methods
-mod rpc_airdrop;
+/// darkfid JSON-RPC related methods
+mod rpc;
/// Payment methods
-mod rpc_transfer;
+mod transfer;
/// Swap methods
-mod rpc_swap;
-use rpc_swap::PartialSwapData;
-
-/// DAO methods
-mod rpc_dao;
+mod swap;
+use swap::PartialSwapData;
/// Token methods
-mod rpc_token;
-
-/// Blockchain methods
-mod rpc_blockchain;
+mod token;
/// CLI utility functions
mod cli_util;
-use cli_util::{kaching, parse_token_pair, parse_value_pair};
-
-/// Wallet functionality related to drk operations
-mod wallet;
-
-/// Wallet functionality related to DAO
-mod wallet_dao;
-use wallet_dao::DaoParams;
+use cli_util::{generate_completions, kaching, parse_token_pair, parse_value_pair};
/// Wallet functionality related to Money
-mod wallet_money;
+mod money;
+use money::BALANCE_BASE10_DECIMALS;
-/// Wallet functionality related to arbitrary tokens
-mod wallet_token;
+/// Wallet functionality related to Dao
+mod dao;
+use dao::DaoParams;
/// Wallet functionality related to transactions history
-mod wallet_txs_history;
+mod txs_history;
-#[derive(Parser)]
-#[command(about = cli_desc!())]
+/// Wallet database operations handler
+mod walletdb;
+use walletdb::{WalletDb, WalletPtr};
+
+const CONFIG_FILE: &str = "drk_config.toml";
+const CONFIG_FILE_CONTENTS: &str = include_str!("../drk_config.toml");
+
+// Dev Note: when adding/modifying args here,
+// don't forget to update cli_util::generate_completions()
+#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
+#[serde(default)]
+#[structopt(name = "drk", about = cli_desc!())]
struct Args {
- #[arg(short, action = clap::ArgAction::Count)]
- /// Increase verbosity (-vvv supported)
- verbose: u8,
+ #[structopt(short, long)]
+ /// Configuration file to use
+ config: Option,
- #[arg(short, long, default_value = "tcp://127.0.0.1:8340")]
+ #[structopt(long, default_value = "~/.local/darkfi/drk/wallet.db")]
+ /// Path to wallet database
+ wallet_path: String,
+
+ #[structopt(long, default_value = "changeme")]
+ /// Password for the wallet database
+ wallet_pass: String,
+
+ #[structopt(short, long, default_value = "tcp://127.0.0.1:8340")]
/// darkfid JSON-RPC endpoint
endpoint: Url,
- #[command(subcommand)]
+ #[structopt(subcommand)]
+ /// Sub command to execute
command: Subcmd,
+
+ #[structopt(short, long)]
+ /// Set log file to ouput into
+ log: Option,
+
+ #[structopt(short, parse(from_occurrences))]
+ /// Increase verbosity (-vvv supported)
+ verbose: u8,
}
-#[derive(Subcommand)]
+// Dev Note: when adding/modifying commands here,
+// don't forget to update cli_util::generate_completions()
+#[derive(Clone, Debug, Deserialize, StructOpt)]
enum Subcmd {
/// Fun
Kaching,
@@ -113,40 +135,48 @@ enum Subcmd {
/// Generate a SHELL completion script and print to stdout
Completions {
/// The Shell you want to generate script for
- shell: Shell,
+ shell: String,
},
/// Wallet operations
Wallet {
- #[arg(long)]
- /// Initialize wallet with data for Money Contract (run this first)
+ #[structopt(long)]
+ /// Initialize wallet database
initialize: bool,
- #[arg(long)]
+ #[structopt(long)]
/// Generate a new keypair in the wallet
keygen: bool,
- #[arg(long)]
+ #[structopt(long)]
/// Query the wallet for known balances
balance: bool,
- #[arg(long)]
+ #[structopt(long)]
/// Get the default address in the wallet
address: bool,
- #[arg(long)]
+ #[structopt(long)]
+ /// Print all the addresses in the wallet
+ addresses: bool,
+
+ #[structopt(long)]
+ /// Set the default address in the wallet
+ default_address: Option,
+
+ #[structopt(long)]
/// Print all the secret keys from the wallet
secrets: bool,
- #[arg(long)]
+ #[structopt(long)]
/// Import secret keys from stdin into the wallet, separated by newlines
import_secrets: bool,
- #[arg(long)]
+ #[structopt(long)]
/// Print the Merkle tree in the wallet
tree: bool,
- #[arg(long)]
+ #[structopt(long)]
/// Print all the coins in the wallet
coins: bool,
},
@@ -157,19 +187,6 @@ enum Subcmd {
coin: String,
},
- /// Airdrop some tokens
- Airdrop {
- /// Faucet JSON-RPC endpoint
- #[arg(short, long, default_value = "tls://faucetd.testnet.dark.fi:18340")]
- faucet_endpoint: Url,
-
- /// Amount to request from the faucet
- amount: String,
-
- /// Optional address to send tokens to (defaults to main address in wallet)
- address: Option,
- },
-
/// Create a payment transaction
Transfer {
/// Amount to send
@@ -180,18 +197,14 @@ enum Subcmd {
/// Recipient address
recipient: String,
-
- /// Mark if this is being sent to a DAO
- #[clap(long)]
- dao: bool,
-
- /// DAO bulla, if the tokens are being sent to a DAO
- dao_bulla: Option,
},
/// OTC atomic swap
- #[command(subcommand)]
- Otc(OtcSubcmd),
+ Otc {
+ #[structopt(subcommand)]
+ /// Sub command to execute
+ command: OtcSubcmd,
+ },
/// Inspect a transaction from stdin
Inspect,
@@ -199,52 +212,66 @@ enum Subcmd {
/// Read a transaction from stdin and broadcast it
Broadcast,
- /// Subscribe to incoming notifications from darkfid
- #[command(subcommand)]
- Subscribe(SubscribeSubcmd),
+ /// This subscription will listen for incoming blocks from darkfid and look
+ /// through their transactions to see if there's any that interest us.
+ /// With `drk` we look at transactions calling the money contract so we can
+ /// find coins sent to us and fill our wallet with the necessary metadata.
+ Subscribe,
/// DAO functionalities
- #[command(subcommand)]
- Dao(DaoSubcmd),
+ Dao {
+ #[structopt(subcommand)]
+ /// Sub command to execute
+ command: DaoSubcmd,
+ },
/// Scan the blockchain and parse relevant transactions
Scan {
- #[arg(long)]
- /// Reset Merkle tree and start scanning from first slot
+ #[structopt(long)]
+ /// Reset Merkle tree and start scanning from first block
reset: bool,
- #[arg(long)]
+ #[structopt(long)]
/// List all available checkpoints
list: bool,
- #[arg(short, long)]
+ #[structopt(long)]
/// Reset Merkle tree to checkpoint index and start scanning
checkpoint: Option,
},
/// Explorer related subcommands
- #[command(subcommand)]
- Explorer(ExplorerSubcmd),
+ Explorer {
+ #[structopt(subcommand)]
+ /// Sub command to execute
+ command: ExplorerSubcmd,
+ },
/// Manage Token aliases
- #[command(subcommand)]
- Alias(AliasSubcmd),
+ Alias {
+ #[structopt(subcommand)]
+ /// Sub command to execute
+ command: AliasSubcmd,
+ },
/// Token functionalities
- #[command(subcommand)]
- Token(TokenSubcmd),
+ Token {
+ #[structopt(subcommand)]
+ /// Sub command to execute
+ command: TokenSubcmd,
+ },
}
-#[derive(Subcommand)]
+#[derive(Clone, Debug, Deserialize, StructOpt)]
enum OtcSubcmd {
/// Initialize the first half of the atomic swap
Init {
/// Value pair to send:recv (11.55:99.42)
- #[clap(short, long)]
+ #[structopt(short, long)]
value_pair: String,
/// Token pair to send:recv (f00:b4r)
- #[clap(short, long)]
+ #[structopt(short, long)]
token_pair: String,
},
@@ -258,7 +285,7 @@ enum OtcSubcmd {
Sign,
}
-#[derive(Subcommand)]
+#[derive(Clone, Debug, Deserialize, StructOpt)]
enum DaoSubcmd {
/// Create DAO parameters
Create {
@@ -266,7 +293,7 @@ enum DaoSubcmd {
proposer_limit: String,
/// Minimal threshold of participating total tokens needed for a proposal to pass
quorum: String,
- /// The ratio of winning votes/total votes needed for a proposal to pass (2 decimals),
+ /// The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)
approval_ratio: f64,
/// DAO's governance token ID
gov_token_id: String,
@@ -354,18 +381,18 @@ enum DaoSubcmd {
},
}
-#[derive(Subcommand)]
+#[derive(Clone, Debug, Deserialize, StructOpt)]
enum ExplorerSubcmd {
/// Fetch a blockchain transaction by hash
FetchTx {
/// Transaction hash
tx_hash: String,
- #[arg(long)]
+ #[structopt(long)]
/// Print the full transaction information
full: bool,
- #[arg(long)]
+ #[structopt(long)]
/// Encode transaction to base58
encode: bool,
},
@@ -378,14 +405,13 @@ enum ExplorerSubcmd {
/// Fetch specific history record (optional)
tx_hash: Option,
- #[arg(long)]
- /// Encode specific history record transaction
- /// to base58.
+ #[structopt(long)]
+ /// Encode specific history record transaction to base58
encode: bool,
},
}
-#[derive(Subcommand)]
+#[derive(Clone, Debug, Deserialize, StructOpt)]
enum AliasSubcmd {
/// Create a Token alias
Add {
@@ -400,11 +426,11 @@ enum AliasSubcmd {
/// If no argument is provided, list all the aliases in the wallet.
Show {
/// Token alias to search for
- #[clap(short, long)]
+ #[structopt(short, long)]
alias: Option,
/// Token to search alias for
- #[clap(short, long)]
+ #[structopt(short, long)]
token: Option,
},
@@ -415,7 +441,7 @@ enum AliasSubcmd {
},
}
-#[derive(Subcommand)]
+#[derive(Clone, Debug, Deserialize, StructOpt)]
enum TokenSubcmd {
/// Import a mint authority secret from stdin
Import,
@@ -440,55 +466,73 @@ enum TokenSubcmd {
/// Freeze a token mint
Freeze {
- /// Token ID mint to freeze
+ /// Token ID to freeze
token: String,
},
}
-#[derive(Subcommand)]
-enum SubscribeSubcmd {
- /// This subscription will listen for incoming blocks from darkfid and look
- /// through their transactions to see if there's any that interest us.
- /// With `drk` we look at transactions calling the money contract so we can
- /// find coins sent to us and fill our wallet with the necessary metadata.
- Blocks,
-
- /// This subscription will listen for erroneous transactions that got
- /// removed from darkfid mempool.
- Transactions,
-}
-
+/// CLI-util structure
pub struct Drk {
+ /// Wallet database operations handler
+ pub wallet: WalletPtr,
+ /// JSON-RPC client to execute requests to darkfid daemon
pub rpc_client: RpcClient,
}
impl Drk {
- async fn new(endpoint: Url) -> Result {
- let rpc_client = RpcClient::new(endpoint, None).await?;
- Ok(Self { rpc_client })
+ async fn new(
+ wallet_path: String,
+ wallet_pass: String,
+ endpoint: Url,
+ ex: Arc>,
+ ) -> Result {
+ // Initialize wallet
+ let wallet_path = expand_path(&wallet_path)?;
+ if !wallet_path.exists() {
+ if let Some(parent) = wallet_path.parent() {
+ fs::create_dir_all(parent)?;
+ }
+ }
+ let wallet = match WalletDb::new(Some(wallet_path), Some(&wallet_pass)) {
+ Ok(w) => w,
+ Err(e) => {
+ eprintln!("Error initializing wallet: {e:?}");
+ exit(2);
+ }
+ };
+
+ // Initialize rpc client
+ let rpc_client = RpcClient::new(endpoint, ex).await?;
+
+ Ok(Self { wallet, rpc_client })
}
+ /// Initialize wallet with tables for drk
+ async fn initialize_wallet(&self) -> Result<()> {
+ let wallet_schema = include_str!("../wallet.sql");
+ if let Err(e) = self.wallet.exec_batch_sql(wallet_schema).await {
+ eprintln!("Error initializing wallet: {e:?}");
+ exit(2);
+ }
+
+ Ok(())
+ }
+
+ /// Auxilliary function to ping configured darkfid daemon for liveness.
async fn ping(&self) -> Result<()> {
+ eprintln!("Executing ping request to darkfid...");
let latency = Instant::now();
- let req = JsonRequest::new("ping", json!([]));
+ let req = JsonRequest::new("ping", JsonValue::Array(vec![]));
let rep = self.rpc_client.oneshot_request(req).await?;
let latency = latency.elapsed();
- println!("Got reply: {}", rep);
- println!("Latency: {:?}", latency);
+ eprintln!("Got reply: {rep:?}");
+ eprintln!("Latency: {latency:?}");
Ok(())
}
}
-#[async_std::main]
-async fn main() -> Result<()> {
- let args = Args::parse();
-
- if args.verbose > 0 {
- let log_level = get_log_level(args.verbose);
- let log_config = get_log_config(args.verbose);
- TermLogger::init(log_level, log_config, TerminalMode::Mixed, ColorChoice::Auto)?;
- }
-
+async_daemonize!(realmain);
+async fn realmain(args: Args, ex: Arc>) -> Result<()> {
match args.command {
Subcmd::Kaching => {
kaching().await;
@@ -496,23 +540,19 @@ async fn main() -> Result<()> {
}
Subcmd::Ping => {
- let drk = Drk::new(args.endpoint).await?;
- drk.ping().await.with_context(|| "Failed to ping darkfid RPC endpoint")?;
-
- Ok(())
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
+ drk.ping().await
}
- Subcmd::Completions { shell } => {
- let mut cmd = Args::command();
- generate(shell, &mut cmd, "./drk", &mut std::io::stdout());
- Ok(())
- }
+ Subcmd::Completions { shell } => generate_completions(&shell),
Subcmd::Wallet {
initialize,
keygen,
balance,
address,
+ addresses,
+ default_address,
secrets,
import_secrets,
tree,
@@ -522,6 +562,8 @@ async fn main() -> Result<()> {
!keygen &&
!balance &&
!address &&
+ !addresses &&
+ default_address.is_none() &&
!secrets &&
!tree &&
!coins &&
@@ -532,28 +574,33 @@ async fn main() -> Result<()> {
exit(2);
}
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
if initialize {
drk.initialize_wallet().await?;
- drk.initialize_money().await?;
- drk.initialize_dao().await?;
+ if let Err(e) = drk.initialize_money().await {
+ eprintln!("Failed to initialize Money: {e:?}");
+ exit(2);
+ }
+ if let Err(e) = drk.initialize_dao().await {
+ eprintln!("Failed to initialize DAO: {e:?}");
+ exit(2);
+ }
return Ok(())
}
if keygen {
- drk.money_keygen().await.with_context(|| "Failed to generate keypair")?;
+ if let Err(e) = drk.money_keygen().await {
+ eprintln!("Failed to generate keypair: {e:?}");
+ exit(2);
+ }
return Ok(())
}
if balance {
- let balmap =
- drk.money_balance().await.with_context(|| "Failed to fetch wallet balance")?;
+ let balmap = drk.money_balance().await?;
- let aliases_map = drk
- .get_aliases_mapped_by_token()
- .await
- .with_context(|| "Failed to fetch wallet aliases")?;
+ let aliases_map = drk.get_aliases_mapped_by_token().await?;
// Create a prettytable with the new data:
let mut table = Table::new();
@@ -565,40 +612,73 @@ async fn main() -> Result<()> {
None => "-",
};
- // FIXME: Don't hardcode to 8 decimals
- table.add_row(row![token_id, aliases, encode_base10(*balance, 8)]);
+ table.add_row(row![
+ token_id,
+ aliases,
+ encode_base10(*balance, BALANCE_BASE10_DECIMALS)
+ ]);
}
if table.is_empty() {
- println!("No unspent balances found");
+ eprintln!("No unspent balances found");
} else {
- println!("{}", table);
+ eprintln!("{table}");
}
return Ok(())
}
if address {
- let address = drk
- .wallet_address(1) // <-- TODO: Use is_default from the sql table
- .await
- .with_context(|| "Failed to fetch default address")?;
+ let address = match drk.default_address().await {
+ Ok(a) => a,
+ Err(e) => {
+ eprintln!("Failed to fetch default address: {e:?}");
+ exit(2);
+ }
+ };
- println!("{}", address);
+ eprintln!("{address}");
return Ok(())
}
- if secrets {
- let v = drk
- .get_money_secrets()
- .await
- .with_context(|| "Failed to fetch wallet secrets")?;
+ if addresses {
+ let addresses = drk.addresses().await?;
- drk.rpc_client.close().await?;
+ // Create a prettytable with the new data:
+ let mut table = Table::new();
+ table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+ table.set_titles(row!["Key ID", "Public Key", "Secret Key", "Is Default"]);
+ for (key_id, public_key, secret_key, is_default) in addresses {
+ let is_default = match is_default {
+ 1 => "*",
+ _ => "",
+ };
+ table.add_row(row![key_id, public_key, secret_key, is_default]);
+ }
+
+ if table.is_empty() {
+ eprintln!("No addresses found");
+ } else {
+ eprintln!("{table}");
+ }
+
+ return Ok(())
+ }
+
+ if let Some(idx) = default_address {
+ if let Err(e) = drk.set_default_address(idx).await {
+ eprintln!("Failed to set default address: {e:?}");
+ exit(2);
+ }
+ return Ok(())
+ }
+
+ if secrets {
+ let v = drk.get_money_secrets().await?;
for i in v {
- println!("{}", i);
+ eprintln!("{i}");
}
return Ok(())
@@ -611,49 +691,40 @@ async fn main() -> Result<()> {
if let Ok(line) = line {
let bytes = bs58::decode(&line.trim()).into_vec()?;
let Ok(secret) = deserialize(&bytes) else {
- eprintln!("Warning: Failed to deserialize secret on line {}", i);
+ eprintln!("Warning: Failed to deserialize secret on line {i}");
continue
};
secrets.push(secret);
}
}
- let pubkeys = drk
- .import_money_secrets(secrets)
- .await
- .with_context(|| "Failed to import secret keys into wallet")?;
-
- drk.rpc_client.close().await?;
+ let pubkeys = match drk.import_money_secrets(secrets).await {
+ Ok(p) => p,
+ Err(e) => {
+ eprintln!("Failed to import secret keys into wallet: {e:?}");
+ exit(2);
+ }
+ };
for key in pubkeys {
- println!("{}", key);
+ eprintln!("{key}");
}
return Ok(())
}
if tree {
- let v =
- drk.get_money_tree().await.with_context(|| "Failed to fetch Merkle tree")?;
- drk.rpc_client.close().await?;
+ let tree = drk.get_money_tree().await?;
- println!("{:#?}", v);
+ eprintln!("{tree:#?}");
return Ok(())
}
if coins {
- let coins = drk
- .get_coins(true)
- .await
- .with_context(|| "Failed to fetch coins from wallet")?;
+ let coins = drk.get_coins(true).await?;
- let aliases_map = drk
- .get_aliases_mapped_by_token()
- .await
- .with_context(|| "Failed to fetch wallet aliases")?;
-
- drk.rpc_client.close().await?;
+ let aliases_map = drk.get_aliases_mapped_by_token().await?;
if coins.is_empty() {
return Ok(())
@@ -700,7 +771,7 @@ async fn main() -> Result<()> {
]);
}
- println!("{}", table);
+ eprintln!("{table}");
return Ok(())
}
@@ -709,69 +780,84 @@ async fn main() -> Result<()> {
}
Subcmd::Unspend { coin } => {
- let bytes: [u8; 32] = bs58::decode(&coin).into_vec()?.try_into().unwrap();
+ let bytes: [u8; 32] = match bs58::decode(&coin).into_vec()?.try_into() {
+ Ok(b) => b,
+ Err(e) => {
+ eprintln!("Invalid coin: {e:?}");
+ exit(2);
+ }
+ };
let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
Some(v) => v,
- None => return Err(anyhow!("Invalid coin")),
+ None => {
+ eprintln!("Invalid coin");
+ exit(2);
+ }
};
let coin = Coin::from(elem);
- let drk = Drk::new(args.endpoint).await?;
- drk.unspend_coin(&coin).await.with_context(|| "Failed to mark coin as unspent")?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
+ if let Err(e) = drk.unspend_coin(&coin).await {
+ eprintln!("Failed to mark coin as unspent: {e:?}");
+ exit(2);
+ }
Ok(())
}
- Subcmd::Airdrop { faucet_endpoint, amount, address } => {
- let amount = f64::from_str(&amount).with_context(|| "Invalid amount")?;
- let drk = Drk::new(args.endpoint).await?;
+ Subcmd::Transfer { amount, token, recipient } => {
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
- let address = match address {
- Some(v) => PublicKey::from_str(v.as_str()).with_context(|| "Invalid address")?,
- None => drk.wallet_address(1).await.with_context(|| {
- "Failed to fetch default address, perhaps the wallet was not initialized?"
- })?,
+ if let Err(e) = f64::from_str(&amount) {
+ eprintln!("Invalid amount: {e:?}");
+ exit(2);
+ }
+
+ let rcpt = match PublicKey::from_str(&recipient) {
+ Ok(r) => r,
+ Err(e) => {
+ eprintln!("Invalid recipient: {e:?}");
+ exit(2);
+ }
};
- let txid = drk
- .request_airdrop(faucet_endpoint, amount, address)
- .await
- .with_context(|| "Failed to request airdrop")?;
+ let token_id = match drk.get_token(token).await {
+ Ok(t) => t,
+ Err(e) => {
+ eprintln!("Invalid token alias: {e:?}");
+ exit(2);
+ }
+ };
- println!("Transaction ID: {}", txid);
-
- Ok(())
- }
-
- Subcmd::Transfer { amount, token, recipient, dao, dao_bulla } => {
- let _ = f64::from_str(&amount).with_context(|| "Invalid amount")?;
- let rcpt = PublicKey::from_str(&recipient).with_context(|| "Invalid recipient")?;
- let drk = Drk::new(args.endpoint).await?;
- let token_id = drk.get_token(token).await.with_context(|| "Invalid token alias")?;
-
- let tx = drk
- .transfer(&amount, token_id, rcpt, dao, dao_bulla)
- .await
- .with_context(|| "Failed to create payment transaction")?;
+ let tx = match drk.transfer(&amount, token_id, rcpt).await {
+ Ok(t) => t,
+ Err(e) => {
+ eprintln!("Failed to create payment transaction: {e:?}");
+ exit(2);
+ }
+ };
println!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
- Subcmd::Otc(cmd) => {
- let drk = Drk::new(args.endpoint).await?;
+ Subcmd::Otc { command } => {
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
- match cmd {
+ match command {
OtcSubcmd::Init { value_pair, token_pair } => {
let (vp_send, vp_recv) = parse_value_pair(&value_pair)?;
let (tp_send, tp_recv) = parse_token_pair(&drk, &token_pair).await?;
- let half = drk
- .init_swap(vp_send, tp_send, vp_recv, tp_recv)
- .await
- .with_context(|| "Failed to create swap transaction half")?;
+ let half = match drk.init_swap(vp_send, tp_send, vp_recv, tp_recv).await {
+ Ok(h) => h,
+ Err(e) => {
+ eprintln!("Failed to create swap transaction half: {e:?}");
+ exit(2);
+ }
+ };
println!("{}", bs58::encode(&serialize(&half)).into_string());
Ok(())
@@ -783,10 +869,13 @@ async fn main() -> Result<()> {
let bytes = bs58::decode(&buf.trim()).into_vec()?;
let partial: PartialSwapData = deserialize(&bytes)?;
- let tx = drk
- .join_swap(partial)
- .await
- .with_context(|| "Failed to create a join swap transaction")?;
+ let tx = match drk.join_swap(partial).await {
+ Ok(tx) => tx,
+ Err(e) => {
+ eprintln!("Failed to create a join swap transaction: {e:?}");
+ exit(2);
+ }
+ };
println!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
@@ -797,7 +886,11 @@ async fn main() -> Result<()> {
stdin().read_to_string(&mut buf)?;
let bytes = bs58::decode(&buf.trim()).into_vec()?;
- drk.inspect_swap(bytes).await.with_context(|| "Failed to inspect swap")?;
+ if let Err(e) = drk.inspect_swap(bytes).await {
+ eprintln!("Failed to inspect swap: {e:?}");
+ exit(2);
+ };
+
Ok(())
}
@@ -807,9 +900,10 @@ async fn main() -> Result<()> {
let bytes = bs58::decode(&buf.trim()).into_vec()?;
let mut tx: Transaction = deserialize(&bytes)?;
- drk.sign_swap(&mut tx)
- .await
- .with_context(|| "Failed to sign joined swap transaction")?;
+ if let Err(e) = drk.sign_swap(&mut tx).await {
+ eprintln!("Failed to sign joined swap transaction: {e:?}");
+ exit(2);
+ };
println!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
@@ -817,103 +911,36 @@ async fn main() -> Result<()> {
}
}
- Subcmd::Inspect => {
- let mut buf = String::new();
- stdin().read_to_string(&mut buf)?;
- let bytes = bs58::decode(&buf.trim()).into_vec()?;
- let tx: Transaction = deserialize(&bytes)?;
- println!("{:#?}", tx);
- Ok(())
- }
-
- Subcmd::Broadcast => {
- eprintln!("Reading transaction from stdin...");
- let mut buf = String::new();
- stdin().read_to_string(&mut buf)?;
- let bytes = bs58::decode(&buf.trim()).into_vec()?;
- let tx = deserialize(&bytes)?;
-
- let drk = Drk::new(args.endpoint).await?;
-
- let txid =
- drk.broadcast_tx(&tx).await.with_context(|| "Failed to broadcast transaction")?;
-
- println!("Transaction ID: {}", txid);
-
- Ok(())
- }
-
- Subcmd::Subscribe(cmd) => match cmd {
- SubscribeSubcmd::Blocks => {
- let drk = Drk::new(args.endpoint.clone()).await?;
-
- drk.subscribe_blocks(args.endpoint.clone())
- .await
- .with_context(|| "Block subscription failed")?;
-
- Ok(())
- }
-
- SubscribeSubcmd::Transactions => {
- let drk = Drk::new(args.endpoint.clone()).await?;
-
- drk.subscribe_err_txs(args.endpoint)
- .await
- .with_context(|| "Erroneous transactions subscription failed")?;
-
- Ok(())
- }
- },
-
- Subcmd::Scan { reset, list, checkpoint } => {
- let drk = Drk::new(args.endpoint).await?;
-
- if reset {
- eprintln!("Reset requested.");
- drk.scan_blocks(true).await.with_context(|| "Failed during scanning")?;
-
- return Ok(())
- }
-
- if list {
- eprintln!("List requested.");
- // TODO: implement
-
- return Ok(())
- }
-
- if let Some(c) = checkpoint {
- eprintln!("Checkpoint requested: {}", c);
- // TODO: implement
-
- return Ok(())
- }
-
- drk.scan_blocks(false).await.with_context(|| "Failed during scanning")?;
- eprintln!("Finished scanning blockchain");
-
- Ok(())
- }
-
- Subcmd::Dao(cmd) => match cmd {
+ Subcmd::Dao { command } => match command {
DaoSubcmd::Create { proposer_limit, quorum, approval_ratio, gov_token_id } => {
- let _ = f64::from_str(&proposer_limit).with_context(|| "Invalid proposer limit")?;
- let _ = f64::from_str(&quorum).with_context(|| "Invalid quorum")?;
+ if let Err(e) = f64::from_str(&proposer_limit) {
+ eprintln!("Invalid proposer limit: {e:?}");
+ exit(2);
+ }
+ if let Err(e) = f64::from_str(&quorum) {
+ eprintln!("Invalid quorum: {e:?}");
+ exit(2);
+ }
- let proposer_limit = decode_base10(&proposer_limit, 8, true)?;
- let quorum = decode_base10(&quorum, 8, true)?;
+ let proposer_limit = decode_base10(&proposer_limit, BALANCE_BASE10_DECIMALS, true)?;
+ let quorum = decode_base10(&quorum, BALANCE_BASE10_DECIMALS, true)?;
if approval_ratio > 1.0 {
eprintln!("Error: Approval ratio cannot be >1.0");
- exit(1);
+ exit(2);
}
let approval_ratio_base = 100_u64;
let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
- let drk = Drk::new(args.endpoint).await?;
- let gov_token_id =
- drk.get_token(gov_token_id).await.with_context(|| "Invalid Token ID")?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
+ let gov_token_id = match drk.get_token(gov_token_id).await {
+ Ok(g) => g,
+ Err(e) => {
+ eprintln!("Invalid Token ID: {e:?}");
+ exit(2);
+ }
+ };
let secret_key = SecretKey::random(&mut OsRng);
let bulla_blind = pallas::Base::random(&mut OsRng);
@@ -929,7 +956,7 @@ async fn main() -> Result<()> {
};
let encoded = bs58::encode(&serialize(&dao_params)).into_string();
- println!("{}", encoded);
+ eprintln!("{encoded}");
Ok(())
}
@@ -939,7 +966,7 @@ async fn main() -> Result<()> {
stdin().read_to_string(&mut buf)?;
let bytes = bs58::decode(&buf.trim()).into_vec()?;
let dao_params: DaoParams = deserialize(&bytes)?;
- println!("{}", dao_params);
+ eprintln!("{dao_params}");
Ok(())
}
@@ -950,39 +977,51 @@ async fn main() -> Result<()> {
let bytes = bs58::decode(&buf.trim()).into_vec()?;
let dao_params: DaoParams = deserialize(&bytes)?;
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
- drk.import_dao(dao_name, dao_params)
- .await
- .with_context(|| "Failed to import DAO")?;
+ if let Err(e) = drk.import_dao(dao_name, dao_params).await {
+ eprintln!("Failed to import DAO: {e:?}");
+ exit(2);
+ }
Ok(())
}
DaoSubcmd::List { dao_alias } => {
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
// We cannot use .map() since get_dao_id() uses ?
let dao_id = match dao_alias {
Some(alias) => Some(drk.get_dao_id(&alias).await?),
None => None,
};
- drk.dao_list(dao_id).await.with_context(|| "Failed to list DAO")?;
+ if let Err(e) = drk.dao_list(dao_id).await {
+ eprintln!("Failed to list DAO: {e:?}");
+ exit(2);
+ }
Ok(())
}
DaoSubcmd::Balance { dao_alias } => {
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
- let balmap =
- drk.dao_balance(dao_id).await.with_context(|| "Failed to fetch DAO balance")?;
+ let balmap = match drk.dao_balance(dao_id).await {
+ Ok(b) => b,
+ Err(e) => {
+ eprintln!("Failed to fetch DAO balance: {e:?}");
+ exit(2);
+ }
+ };
- let aliases_map = drk
- .get_aliases_mapped_by_token()
- .await
- .with_context(|| "Failed to fetch wallet aliases")?;
+ let aliases_map = match drk.get_aliases_mapped_by_token().await {
+ Ok(a) => a,
+ Err(e) => {
+ eprintln!("Failed to fetch wallet aliases: {e:?}");
+ exit(2);
+ }
+ };
// Create a prettytable with the new data:
let mut table = Table::new();
@@ -994,149 +1033,271 @@ async fn main() -> Result<()> {
None => "-",
};
- // FIXME: Don't hardcode to 8 decimals
- table.add_row(row![token_id, aliases, encode_base10(*balance, 8)]);
+ table.add_row(row![
+ token_id,
+ aliases,
+ encode_base10(*balance, BALANCE_BASE10_DECIMALS)
+ ]);
}
if table.is_empty() {
- println!("No unspent balances found");
+ eprintln!("No unspent balances found");
} else {
- println!("{}", table);
+ eprintln!("{table}");
}
Ok(())
}
DaoSubcmd::Mint { dao_alias } => {
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
- let tx = drk.dao_mint(dao_id).await.with_context(|| "Failed to mint DAO")?;
- println!("{}", bs58::encode(&serialize(&tx)).into_string());
+ let tx = match drk.dao_mint(dao_id).await {
+ Ok(tx) => tx,
+ Err(e) => {
+ eprintln!("Failed to mint DAO: {e:?}");
+ exit(2);
+ }
+ };
+ eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
DaoSubcmd::Propose { dao_alias, recipient, amount, token } => {
- let _ = f64::from_str(&amount).with_context(|| "Invalid amount")?;
- let amount = decode_base10(&amount, 8, true)?;
- let rcpt = PublicKey::from_str(&recipient).with_context(|| "Invalid recipient")?;
- let drk = Drk::new(args.endpoint).await?;
+ if let Err(e) = f64::from_str(&amount) {
+ eprintln!("Invalid amount: {e:?}");
+ exit(2);
+ }
+ let amount = decode_base10(&amount, BALANCE_BASE10_DECIMALS, true)?;
+ let rcpt = match PublicKey::from_str(&recipient) {
+ Ok(r) => r,
+ Err(e) => {
+ eprintln!("Invalid recipient: {e:?}");
+ exit(2);
+ }
+ };
+
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
- let token_id = drk.get_token(token).await.with_context(|| "Invalid token alias")?;
+ let token_id = match drk.get_token(token).await {
+ Ok(t) => t,
+ Err(e) => {
+ eprintln!("Invalid token alias: {e:?}");
+ exit(2);
+ }
+ };
- let tx = drk
- .dao_propose(dao_id, rcpt, amount, token_id)
- .await
- .with_context(|| "Failed to create DAO proposal")?;
-
- println!("{}", bs58::encode(&serialize(&tx)).into_string());
+ let tx = match drk.dao_propose(dao_id, rcpt, amount, token_id).await {
+ Ok(tx) => tx,
+ Err(e) => {
+ eprintln!("Failed to create DAO proposal: {e:?}");
+ exit(2);
+ }
+ };
+ eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
DaoSubcmd::Proposals { dao_alias } => {
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
let proposals = drk.get_dao_proposals(dao_id).await?;
for proposal in proposals {
- println!("[{}] {:?}", proposal.id, proposal.bulla());
+ eprintln!("[{}] {:?}", proposal.id, proposal.bulla());
}
Ok(())
}
DaoSubcmd::Proposal { dao_alias, proposal_id } => {
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
let proposals = drk.get_dao_proposals(dao_id).await?;
let Some(proposal) = proposals.iter().find(|x| x.id == proposal_id) else {
eprintln!("No such DAO proposal found");
- exit(1);
+ exit(2);
};
- println!("{}", proposal);
+ eprintln!("{proposal}");
let votes = drk.get_dao_proposal_votes(proposal_id).await?;
- println!("votes:");
+ eprintln!("votes:");
for vote in votes {
let option = if vote.vote_option { "yes" } else { "no " };
- println!(" {} {}", option, vote.all_vote_value);
+ eprintln!(" {option} {}", vote.all_vote_value);
}
Ok(())
}
DaoSubcmd::Vote { dao_alias, proposal_id, vote, vote_weight } => {
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
- let _ = f64::from_str(&vote_weight).with_context(|| "Invalid vote weight")?;
- let weight = decode_base10(&vote_weight, 8, true)?;
+ if let Err(e) = f64::from_str(&vote_weight) {
+ eprintln!("Invalid vote weight: {e:?}");
+ exit(2);
+ }
+ let weight = decode_base10(&vote_weight, BALANCE_BASE10_DECIMALS, true)?;
if vote > 1 {
eprintln!("Vote can be either 0 (NO) or 1 (YES)");
- exit(1);
+ exit(2);
}
let vote = vote != 0;
- let tx = drk
- .dao_vote(dao_id, proposal_id, vote, weight)
- .await
- .with_context(|| "Failed to create DAO Vote transaction")?;
+ let tx = match drk.dao_vote(dao_id, proposal_id, vote, weight).await {
+ Ok(tx) => tx,
+ Err(e) => {
+ eprintln!("Failed to create DAO Vote transaction: {e:?}");
+ exit(2);
+ }
+ };
// TODO: Write our_vote in the proposal sql.
- println!("{}", bs58::encode(&serialize(&tx)).into_string());
+ eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
DaoSubcmd::Exec { dao_alias, proposal_id } => {
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let dao_id = drk.get_dao_id(&dao_alias).await?;
let dao = drk.get_dao_by_id(dao_id).await?;
let proposal = drk.get_dao_proposal_by_id(proposal_id).await?;
assert!(proposal.dao_bulla == dao.bulla());
- let tx = drk
- .dao_exec(dao, proposal)
- .await
- .with_context(|| "Failed to execute DAO proposal")?;
-
- println!("{}", bs58::encode(&serialize(&tx)).into_string());
+ let tx = match drk.dao_exec(dao, proposal).await {
+ Ok(tx) => tx,
+ Err(e) => {
+ eprintln!("Failed to execute DAO proposal: {e:?}");
+ exit(2);
+ }
+ };
+ eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
},
- Subcmd::Explorer(cmd) => match cmd {
+ Subcmd::Inspect => {
+ let mut buf = String::new();
+ stdin().read_to_string(&mut buf)?;
+ let bytes = bs58::decode(&buf.trim()).into_vec()?;
+ let tx: Transaction = deserialize(&bytes)?;
+ eprintln!("{tx:#?}");
+ Ok(())
+ }
+
+ Subcmd::Broadcast => {
+ eprintln!("Reading transaction from stdin...");
+ let mut buf = String::new();
+ stdin().read_to_string(&mut buf)?;
+ let bytes = bs58::decode(&buf.trim()).into_vec()?;
+ let tx = deserialize(&bytes)?;
+
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
+
+ let txid = match drk.broadcast_tx(&tx).await {
+ Ok(t) => t,
+ Err(e) => {
+ eprintln!("Failed to broadcast transaction: {e:?}");
+ exit(2);
+ }
+ };
+
+ eprintln!("Transaction ID: {txid}");
+
+ Ok(())
+ }
+
+ Subcmd::Subscribe => {
+ let drk =
+ Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
+ .await?;
+
+ if let Err(e) = drk.subscribe_blocks(args.endpoint, ex).await {
+ eprintln!("Block subscription failed: {e:?}");
+ exit(2);
+ }
+
+ Ok(())
+ }
+
+ Subcmd::Scan { reset, list, checkpoint } => {
+ let drk =
+ Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
+ .await?;
+
+ if reset {
+ eprintln!("Reset requested.");
+ if let Err(e) = drk.scan_blocks(true).await {
+ eprintln!("Failed during scanning: {e:?}");
+ exit(2);
+ }
+ eprintln!("Finished scanning blockchain");
+
+ return Ok(())
+ }
+
+ if list {
+ eprintln!("List requested.");
+ // TODO: implement
+ unimplemented!()
+ }
+
+ if let Some(c) = checkpoint {
+ eprintln!("Checkpoint requested: {c}");
+ // TODO: implement
+ unimplemented!()
+ }
+
+ if let Err(e) = drk.scan_blocks(false).await {
+ eprintln!("Failed during scanning: {e:?}");
+ exit(2);
+ }
+ eprintln!("Finished scanning blockchain");
+
+ Ok(())
+ }
+
+ Subcmd::Explorer { command } => match command {
ExplorerSubcmd::FetchTx { tx_hash, full, encode } => {
let tx_hash = blake3::Hash::from_hex(&tx_hash)?;
- let drk = Drk::new(args.endpoint).await?;
+ let drk =
+ Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
+ .await?;
- let tx = if let Some(tx) =
- drk.get_tx(&tx_hash).await.with_context(|| "Failed to fetch transaction")?
- {
- tx
- } else {
+ let tx = match drk.get_tx(&tx_hash).await {
+ Ok(tx) => tx,
+ Err(e) => {
+ eprintln!("Failed to fetch transaction: {e:?}");
+ exit(2);
+ }
+ };
+
+ let Some(tx) = tx else {
eprintln!("Transaction was not found");
exit(1);
};
// Make sure the tx is correct
- assert_eq!(tx.hash(), tx_hash);
+ assert_eq!(tx.hash()?, tx_hash);
if encode {
- println!("{}", bs58::encode(&serialize(&tx)).into_string());
+ eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
exit(1)
}
- println!("Transaction ID: {}", tx_hash);
+ eprintln!("Transaction ID: {tx_hash}");
if full {
- println!("{:?}", tx);
+ eprintln!("{tx:?}");
}
Ok(())
@@ -1149,19 +1310,28 @@ async fn main() -> Result<()> {
let bytes = bs58::decode(&buf.trim()).into_vec()?;
let tx = deserialize(&bytes)?;
- let drk = Drk::new(args.endpoint).await?;
+ let drk =
+ Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
+ .await?;
- let is_valid =
- drk.simulate_tx(&tx).await.with_context(|| "Failed to simulate tx")?;
+ let is_valid = match drk.simulate_tx(&tx).await {
+ Ok(b) => b,
+ Err(e) => {
+ eprintln!("Failed to simulate tx: {e:?}");
+ exit(2);
+ }
+ };
- println!("Transaction ID: {}", tx.hash());
- println!("State: {}", if is_valid { "valid" } else { "invalid" });
+ eprintln!("Transaction ID: {}", tx.hash()?);
+ eprintln!("State: {}", if is_valid { "valid" } else { "invalid" });
Ok(())
}
ExplorerSubcmd::TxsHistory { tx_hash, encode } => {
- let drk = Drk::new(args.endpoint).await?;
+ let drk =
+ Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
+ .await?;
if let Some(c) = tx_hash {
let (tx_hash, status, tx) = drk.get_tx_history_record(&c).await?;
@@ -1171,14 +1341,20 @@ async fn main() -> Result<()> {
exit(1)
}
- println!("Transaction ID: {}", tx_hash);
- println!("Status: {}", status);
- println!("{:?}", tx);
+ eprintln!("Transaction ID: {tx_hash}");
+ eprintln!("Status: {status}");
+ eprintln!("{tx:?}");
return Ok(())
}
- let map = drk.get_txs_history().await?;
+ let map = match drk.get_txs_history().await {
+ Ok(m) => m,
+ Err(e) => {
+ eprintln!("Failed to retrieve transactions history records: {e:?}");
+ exit(2);
+ }
+ };
// Create a prettytable with the new data:
let mut table = Table::new();
@@ -1189,39 +1365,52 @@ async fn main() -> Result<()> {
}
if table.is_empty() {
- println!("No transactions found");
+ eprintln!("No transactions found");
} else {
- println!("{}", table);
+ eprintln!("{table}");
}
Ok(())
}
},
- Subcmd::Alias(cmd) => match cmd {
+ Subcmd::Alias { command } => match command {
AliasSubcmd::Add { alias, token } => {
if alias.chars().count() > 5 {
eprintln!("Error: Alias exceeds 5 characters");
- exit(1);
+ exit(2);
}
- let token_id =
- TokenId::from_str(token.as_str()).with_context(|| "Invalid Token ID")?;
- let drk = Drk::new(args.endpoint).await?;
- drk.add_alias(alias, token_id).await?;
+ let token_id = match TokenId::from_str(token.as_str()) {
+ Ok(t) => t,
+ Err(e) => {
+ eprintln!("Invalid Token ID: {e:?}");
+ exit(2);
+ }
+ };
+
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
+ if let Err(e) = drk.add_alias(alias, token_id).await {
+ eprintln!("Failed to add alias: {e:?}");
+ exit(2);
+ }
Ok(())
}
AliasSubcmd::Show { alias, token } => {
let token_id = match token {
- Some(t) => {
- Some(TokenId::from_str(t.as_str()).with_context(|| "Invalid Token ID")?)
- }
+ Some(t) => match TokenId::from_str(t.as_str()) {
+ Ok(t) => Some(t),
+ Err(e) => {
+ eprintln!("Invalid Token ID: {e:?}");
+ exit(2);
+ }
+ },
None => None,
};
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let map = drk.get_aliases(alias, token_id).await?;
// Create a prettytable with the new data:
@@ -1233,34 +1422,45 @@ async fn main() -> Result<()> {
}
if table.is_empty() {
- println!("No aliases found");
+ eprintln!("No aliases found");
} else {
- println!("{}", table);
+ eprintln!("{table}");
}
Ok(())
}
AliasSubcmd::Remove { alias } => {
- let drk = Drk::new(args.endpoint).await?;
- drk.remove_alias(alias).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
+ if let Err(e) = drk.remove_alias(alias).await {
+ eprintln!("Failed to remove alias: {e:?}");
+ exit(2);
+ }
Ok(())
}
},
- Subcmd::Token(cmd) => match cmd {
+ Subcmd::Token { command } => match command {
TokenSubcmd::Import => {
let mut buf = String::new();
stdin().read_to_string(&mut buf)?;
- let mint_authority =
- SecretKey::from_str(buf.trim()).with_context(|| "Invalid secret key")?;
+ let mint_authority = match SecretKey::from_str(buf.trim()) {
+ Ok(ma) => ma,
+ Err(e) => {
+ eprintln!("Invalid secret key: {e:?}");
+ exit(2);
+ }
+ };
- let drk = Drk::new(args.endpoint).await?;
- drk.import_mint_authority(mint_authority).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
+ if let Err(e) = drk.import_mint_authority(mint_authority).await {
+ eprintln!("Importing mint authority failed: {e:?}");
+ exit(2);
+ };
let token_id = TokenId::derive(mint_authority);
- eprintln!("Successfully imported mint authority for token ID: {}", token_id);
+ eprintln!("Successfully imported mint authority for token ID: {token_id}");
Ok(())
}
@@ -1268,22 +1468,29 @@ async fn main() -> Result<()> {
TokenSubcmd::GenerateMint => {
let mint_authority = SecretKey::random(&mut OsRng);
- let drk = Drk::new(args.endpoint).await?;
- drk.import_mint_authority(mint_authority).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
+
+ if let Err(e) = drk.import_mint_authority(mint_authority).await {
+ eprintln!("Importing mint authority failed: {e:?}");
+ exit(2);
+ };
let token_id = TokenId::derive(mint_authority);
- eprintln!("Successfully imported mint authority for token ID: {}", token_id);
+ eprintln!("Successfully imported mint authority for token ID: {token_id}");
Ok(())
}
TokenSubcmd::List => {
- let drk = Drk::new(args.endpoint).await?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
let tokens = drk.list_tokens().await?;
- let aliases_map = drk
- .get_aliases_mapped_by_token()
- .await
- .with_context(|| "Failed to fetch wallet aliases")?;
+ let aliases_map = match drk.get_aliases_mapped_by_token().await {
+ Ok(map) => map,
+ Err(e) => {
+ eprintln!("Failed to fetch wallet aliases: {e:?}");
+ exit(2);
+ }
+ };
let mut table = Table::new();
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
@@ -1299,9 +1506,9 @@ async fn main() -> Result<()> {
}
if table.is_empty() {
- println!("No tokens found");
+ eprintln!("No tokens found");
} else {
- println!("{}", table);
+ eprintln!("{table}");
}
Ok(())
@@ -1309,31 +1516,61 @@ async fn main() -> Result<()> {
// TODO: Mint directly into DAO treasury
TokenSubcmd::Mint { token, amount, recipient } => {
- let drk = Drk::new(args.endpoint).await?;
- let _ = f64::from_str(&amount).with_context(|| "Invalid amount")?;
- let rcpt = PublicKey::from_str(&recipient).with_context(|| "Invalid recipient")?;
- let token_id = drk.get_token(token).await.with_context(|| "Invalid Token ID")?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
- let tx = drk
- .mint_token(&amount, rcpt, token_id)
- .await
- .with_context(|| "Failed to create token mint transaction")?;
+ if let Err(e) = f64::from_str(&amount) {
+ eprintln!("Invalid amount: {e:?}");
+ exit(2);
+ }
- println!("{}", bs58::encode(&serialize(&tx)).into_string());
+ let rcpt = match PublicKey::from_str(&recipient) {
+ Ok(r) => r,
+ Err(e) => {
+ eprintln!("Invalid recipient: {e:?}");
+ exit(2);
+ }
+ };
+
+ let token_id = match drk.get_token(token).await {
+ Ok(t) => t,
+ Err(e) => {
+ eprintln!("Invalid Token ID: {e:?}");
+ exit(2);
+ }
+ };
+
+ let tx = match drk.mint_token(&amount, rcpt, token_id).await {
+ Ok(tx) => tx,
+ Err(e) => {
+ eprintln!("Failed to create token mint transaction: {e:?}");
+ exit(2);
+ }
+ };
+
+ eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
TokenSubcmd::Freeze { token } => {
- let drk = Drk::new(args.endpoint).await?;
- let token_id = drk.get_token(token).await.with_context(|| "Invalid Token ID")?;
+ let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
+ let token_id = match drk.get_token(token).await {
+ Ok(t) => t,
+ Err(e) => {
+ eprintln!("Invalid Token ID: {e:?}");
+ exit(2);
+ }
+ };
- let tx = drk
- .freeze_token(token_id)
- .await
- .with_context(|| "Failed to create token freeze transaction")?;
+ let tx = match drk.freeze_token(token_id).await {
+ Ok(tx) => tx,
+ Err(e) => {
+ eprintln!("Failed to create token freeze transaction: {e:?}");
+ exit(2);
+ }
+ };
- println!("{}", bs58::encode(&serialize(&tx)).into_string());
+ eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
Ok(())
}
diff --git a/bin/drk2/src/money.rs b/bin/drk/src/money.rs
similarity index 100%
rename from bin/drk2/src/money.rs
rename to bin/drk/src/money.rs
diff --git a/bin/drk2/src/rpc.rs b/bin/drk/src/rpc.rs
similarity index 100%
rename from bin/drk2/src/rpc.rs
rename to bin/drk/src/rpc.rs
diff --git a/bin/drk/src/rpc_airdrop.rs b/bin/drk/src/rpc_airdrop.rs
deleted file mode 100644
index a9d4d6ddd..000000000
--- a/bin/drk/src/rpc_airdrop.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-use anyhow::{anyhow, Result};
-use darkfi::rpc::{client::RpcClient, jsonrpc::JsonRequest};
-use darkfi_sdk::{
- crypto::{mimc_vdf, PublicKey},
- num_bigint::BigUint,
- num_traits::Num,
-};
-use serde_json::json;
-use url::Url;
-
-use super::Drk;
-
-impl Drk {
- /// Request an airdrop of `amount` `token_id` tokens from a faucet.
- /// Returns a transaction ID on success.
- pub async fn request_airdrop(
- &self,
- faucet_endpoint: Url,
- amount: f64,
- address: PublicKey,
- ) -> Result {
- let rpc_client = RpcClient::new(faucet_endpoint, None).await?;
-
- // First we request a VDF challenge from the faucet
- let params = json!([format!("{}", address)]);
- let req = JsonRequest::new("challenge", params);
- let rep = rpc_client.request(req).await?;
-
- let Some(rep) = rep.as_array() else {
- return Err(anyhow!("Invalid challenge response from faucet: {:?}", rep))
- };
- if rep.len() != 2 || !rep[0].is_string() || !rep[1].is_u64() {
- return Err(anyhow!("Invalid challenge response from faucet: {:?}", rep))
- }
-
- // Retrieve VDF challenge
- let challenge = BigUint::from_str_radix(rep[0].as_str().unwrap(), 16)?;
- let n_steps = rep[1].as_u64().unwrap();
-
- // Then evaluate the VDF
- eprintln!("Evaluating VDF with n_steps={} ... (this could take about a minute)", n_steps);
- let witness = mimc_vdf::eval(&challenge, n_steps);
- eprintln!("Done! Sending airdrop request...");
-
- // And finally request airdrop with the VDF evaluation witness
- let params = json!([format!("{}", address), amount, witness.to_str_radix(16)]);
- let req = JsonRequest::new("airdrop", params);
- let rep = rpc_client.oneshot_request(req).await?;
-
- let txid = serde_json::from_value(rep)?;
-
- Ok(txid)
- }
-}
diff --git a/bin/drk/src/rpc_blockchain.rs b/bin/drk/src/rpc_blockchain.rs
deleted file mode 100644
index e56333336..000000000
--- a/bin/drk/src/rpc_blockchain.rs
+++ /dev/null
@@ -1,355 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-use anyhow::{anyhow, Result};
-use async_std::{stream::StreamExt, task};
-use darkfi::{
- consensus::BlockInfo,
- rpc::{
- client::RpcClient,
- jsonrpc::{JsonRequest, JsonResult},
- },
- system::Subscriber,
- tx::Transaction,
- wallet::walletdb::QueryType,
-};
-use darkfi_money_contract::client::{MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE};
-use darkfi_sdk::crypto::ContractId;
-use darkfi_serial::{deserialize, serialize};
-use serde_json::json;
-use signal_hook::consts::{SIGINT, SIGQUIT, SIGTERM};
-use signal_hook_async_std::Signals;
-use url::Url;
-
-use super::Drk;
-
-impl Drk {
- /// Subscribes to darkfid's JSON-RPC notification endpoint that serves
- /// new finalized blocks. Upon receiving them, all the transactions are
- /// scanned and we check if any of them call the money contract, and if
- /// the payments are intended for us. If so, we decrypt them and append
- /// the metadata to our wallet.
- pub async fn subscribe_blocks(&self, endpoint: Url) -> Result<()> {
- let req = JsonRequest::new("blockchain.last_known_slot", json!([]));
- let rep = self.rpc_client.request(req).await?;
- let last_known: u64 = serde_json::from_value(rep)?;
- let last_scanned = self.last_scanned_slot().await?;
-
- if last_known != last_scanned {
- eprintln!("Warning: Last scanned slot is not the last known slot.");
- eprintln!("You should first fully scan the blockchain, and then subscribe");
- return Err(anyhow!("Blockchain not fully scanned"))
- }
-
- eprintln!("Subscribing to receive notifications of incoming blocks");
- let subscriber = Subscriber::new();
- let subscription = subscriber.clone().subscribe().await;
-
- let rpc_client = RpcClient::new(endpoint, None).await?;
-
- let req = JsonRequest::new("blockchain.subscribe_blocks", json!([]));
- task::spawn(async move { rpc_client.subscribe(req, subscriber).await.unwrap() });
- eprintln!("Detached subscription to background");
- eprintln!("All is good. Waiting for block notifications...");
-
- let e = loop {
- match subscription.receive().await {
- JsonResult::Notification(n) => {
- eprintln!("Got Block notification from darkfid subscription");
- if n.method != "blockchain.subscribe_blocks" {
- break anyhow!("Got foreign notification from darkfid: {}", n.method)
- }
-
- let Some(params) = n.params.as_array() else {
- break anyhow!("Received notification params are not an array")
- };
-
- if params.len() != 1 {
- break anyhow!("Notification parameters are not len 1")
- }
-
- let params = n.params.as_array().unwrap()[0].as_str().unwrap();
- let bytes = bs58::decode(params).into_vec()?;
-
- let block_data: BlockInfo = deserialize(&bytes)?;
- eprintln!("=======================================");
- eprintln!("Block header:\n{:#?}", block_data.header);
- eprintln!("=======================================");
-
- eprintln!("Deserialized successfully. Scanning block...");
- self.scan_block_money(&block_data).await?;
- self.scan_block_dao(&block_data).await?;
- self.update_tx_history_records_status(&block_data.txs, "Finalized").await?;
- }
-
- JsonResult::Error(e) => {
- // Some error happened in the transmission
- break anyhow!("Got error from JSON-RPC: {:?}", e)
- }
-
- x => {
- // And this is weird
- break anyhow!("Got unexpected data from JSON-RPC: {:?}", x)
- }
- }
- };
-
- Err(e)
- }
-
- /// `scan_block_dao` will go over transactions in a block and fetch the ones dealing
- /// with the dao contract. Then over all of them, try to see if any are related
- /// to us. If any are found, the metadata is extracted and placed into the wallet
- /// for future use.
- async fn scan_block_dao(&self, block: &BlockInfo) -> Result<()> {
- eprintln!("[DAO] Iterating over {} transactions", block.txs.len());
- for tx in block.txs.iter() {
- self.apply_tx_dao_data(tx, true).await?;
- }
-
- Ok(())
- }
-
- /// `scan_block_money` will go over transactions in a block and fetch the ones dealing
- /// with the money contract. Then over all of them, try to see if any are related
- /// to us. If any are found, the metadata is extracted and placed into the wallet
- /// for future use.
- async fn scan_block_money(&self, block: &BlockInfo) -> Result<()> {
- eprintln!("[Money] Iterating over {} transactions", block.txs.len());
-
- for tx in block.txs.iter() {
- self.apply_tx_money_data(tx, true).await?;
- }
-
- // Write this slot into `last_scanned_slot`
- let query =
- format!("UPDATE {} SET {} = ?1;", MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT);
- let params = json!([query, QueryType::Integer as u8, block.header.slot]);
- let req = JsonRequest::new("wallet.exec_sql", params);
- let _ = self.rpc_client.request(req).await?;
-
- Ok(())
- }
-
- /// Try to fetch zkas bincodes for the given `ContractId`.
- pub async fn lookup_zkas(&self, contract_id: &ContractId) -> Result)>> {
- eprintln!("Querying zkas bincode for {}", contract_id);
-
- let params = json!([format!("{}", contract_id)]);
- let req = JsonRequest::new("blockchain.lookup_zkas", params);
-
- let rep = self.rpc_client.request(req).await?;
-
- let ret = serde_json::from_value(rep)?;
- Ok(ret)
- }
-
- /// Broadcast a given transaction to darkfid and forward onto the network.
- /// Returns the transaction ID upon success
- pub async fn broadcast_tx(&self, tx: &Transaction) -> Result {
- eprintln!("Broadcasting transaction...");
-
- let params = json!([bs58::encode(&serialize(tx)).into_string()]);
- let req = JsonRequest::new("tx.broadcast", params);
- let rep = self.rpc_client.request(req).await?;
-
- let txid = serde_json::from_value(rep)?;
-
- // Store transactions history record
- self.insert_tx_history_record(tx).await?;
-
- Ok(txid)
- }
-
- /// Simulate the transaction with the state machine
- pub async fn simulate_tx(&self, tx: &Transaction) -> Result {
- let params = json!([bs58::encode(&serialize(tx)).into_string()]);
- let req = JsonRequest::new("tx.simulate", params);
- let rep = self.rpc_client.request(req).await?;
-
- let is_valid = serde_json::from_value(rep)?;
- Ok(is_valid)
- }
-
- /// Queries darkfid for a block with given slot
- async fn get_block_by_slot(&self, slot: u64) -> Result