From 4c0848e2f318cfe84eee34ebabf0e0d9538b2778 Mon Sep 17 00:00:00 2001 From: geoffsee <> Date: Sun, 15 Jun 2025 13:34:12 -0400 Subject: [PATCH] Add `gsio-client` and `gsio-wallet` crates with initial implementations - Introduced `gsio-client` crate for interacting with GSIO nodes, including ledger entry management and node discovery. - Introduced `gsio-wallet` crate for key management, transaction creation, and wallet functionality. - Updated workspace configuration to include new crates. --- .gitignore | 6 +- Cargo.lock | 567 +++++++++++++++++++-- Cargo.toml | 6 +- README.md | 78 ++- crates/gsio-client/Cargo.toml | 18 + crates/gsio-client/examples/basic_usage.rs | 41 ++ crates/gsio-client/src/lib.rs | 138 +++++ crates/gsio-wallet/Cargo.toml | 20 + crates/gsio-wallet/README.md | 65 +++ crates/gsio-wallet/src/lib.rs | 256 ++++++++++ 10 files changed, 1146 insertions(+), 49 deletions(-) create mode 100644 crates/gsio-client/Cargo.toml create mode 100644 crates/gsio-client/examples/basic_usage.rs create mode 100644 crates/gsio-client/src/lib.rs create mode 100644 crates/gsio-wallet/Cargo.toml create mode 100644 crates/gsio-wallet/README.md create mode 100644 crates/gsio-wallet/src/lib.rs diff --git a/.gitignore b/.gitignore index 4b612c4..9a01852 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ target/ node_modules/ .wrangler/ -.idea/ \ No newline at end of file +.idea/ +project/ +/prompt.md +/todo +/.toak-ignore diff --git a/Cargo.lock b/Cargo.lock index fd773f0..526b590 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,9 +241,9 @@ dependencies = [ "form_urlencoded", "futures-util", "http 1.3.1", - "http-body", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.6.0", "hyper-util", "itoa", "matchit 0.8.4", @@ -256,7 +256,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower", "tower-layer", @@ -273,12 +273,12 @@ dependencies = [ "bytes", "futures-core", "http 1.3.1", - "http-body", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -305,6 +305,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -341,7 +347,16 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", ] [[package]] @@ -557,6 +572,19 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -566,7 +594,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -654,13 +682,22 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "crypto-common", "subtle", ] @@ -682,6 +719,16 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "serde", + "signature 1.6.4", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -689,7 +736,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519 1.5.3", + "rand 0.7.3", + "serde", + "serde_bytes", + "sha2 0.9.9", + "zeroize", ] [[package]] @@ -698,11 +760,11 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek", - "ed25519", + "curve25519-dalek 4.1.3", + "ed25519 2.2.3", "rand_core 0.6.4", "serde", - "sha2", + "sha2 0.10.9", "subtle", "zeroize", ] @@ -713,21 +775,30 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "engineioxide" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb6d2ef56d20b87b7f02bc08935e21bd7cfd88da6b82b129d5d89a28ddf3a389" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "engineioxide-core", "futures-core", "futures-util", "http 1.3.1", - "http-body", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.6.0", "hyper-util", "itoa", "memchr", @@ -750,7 +821,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04e5d58eb7374df380cbb53ef65f9c35f544c9c217528adb1458c8df05978475" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "rand 0.9.1", "serde", @@ -805,6 +876,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -823,6 +900,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -983,6 +1075,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -1026,6 +1129,22 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "gsio-client" +version = "0.1.0" +dependencies = [ + "chrono", + "futures", + "reqwest", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-subscriber", + "uuid", +] + [[package]] name = "gsio-node" version = "0.1.0" @@ -1037,7 +1156,7 @@ dependencies = [ "rmpv", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "socketioxide", "tokio", "tower-http", @@ -1058,6 +1177,43 @@ dependencies = [ "worker-macros", ] +[[package]] +name = "gsio-wallet" +version = "0.1.0" +dependencies = [ + "chrono", + "ed25519-dalek 1.0.1", + "hex", + "rand 0.7.3", + "rand_core 0.5.1", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.10" @@ -1100,6 +1256,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hickory-proto" version = "0.25.0-alpha.5" @@ -1162,7 +1324,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1187,6 +1349,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1206,7 +1379,7 @@ dependencies = [ "bytes", "futures-core", "http 1.3.1", - "http-body", + "http-body 1.0.1", "pin-project-lite", ] @@ -1222,6 +1395,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.6.0" @@ -1231,9 +1428,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", + "h2 0.4.10", "http 1.3.1", - "http-body", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -1243,6 +1440,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.32", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyper-util" version = "0.1.14" @@ -1254,8 +1464,8 @@ dependencies = [ "futures-core", "futures-util", "http 1.3.1", - "http-body", - "hyper", + "http-body 1.0.1", + "hyper 1.6.0", "libc", "pin-project-lite", "socket2", @@ -1423,7 +1633,7 @@ dependencies = [ "netlink-proto", "netlink-sys", "rtnetlink", - "system-configuration", + "system-configuration 0.6.1", "tokio", "windows 0.53.0", ] @@ -1440,7 +1650,7 @@ dependencies = [ "futures", "http 1.3.1", "http-body-util", - "hyper", + "hyper 1.6.0", "hyper-util", "log", "rand 0.8.5", @@ -1639,12 +1849,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbb68ea10844211a59ce46230909fd0ea040e8a192454d4cc2ee0d53e12280eb" dependencies = [ "bs58", - "ed25519-dalek", + "ed25519-dalek 2.1.1", "hkdf", "multihash", "quick-protobuf", "rand 0.8.5", - "sha2", + "sha2 0.10.9", "thiserror 2.0.12", "tracing", "zeroize", @@ -2086,6 +2296,23 @@ dependencies = [ "unsigned-varint 0.7.2", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "netlink-packet-core" version = "0.7.0" @@ -2261,6 +2488,50 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -2308,7 +2579,7 @@ version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ - "base64", + "base64 0.22.1", "serde", ] @@ -2360,6 +2631,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "polling" version = "3.8.0" @@ -2553,6 +2830,19 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -2574,6 +2864,16 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2594,6 +2894,15 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -2612,6 +2921,15 @@ dependencies = [ "getrandom 0.3.3", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rcgen" version = "0.13.2" @@ -2678,6 +2996,46 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "resolv-conf" version = "0.7.4" @@ -2795,6 +3153,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -2858,6 +3225,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2870,6 +3246,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.26" @@ -2907,6 +3306,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_bytes" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.219" @@ -2960,7 +3368,20 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", ] [[package]] @@ -2971,7 +3392,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -2989,6 +3410,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + [[package]] name = "signature" version = "2.2.0" @@ -3025,11 +3452,11 @@ dependencies = [ "aes-gcm", "blake2", "chacha20poly1305", - "curve25519-dalek", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "ring", "rustc_version", - "sha2", + "sha2 0.10.9", "subtle", ] @@ -3054,8 +3481,8 @@ dependencies = [ "futures-core", "futures-util", "http 1.3.1", - "http-body", - "hyper", + "http-body 1.0.1", + "hyper 1.6.0", "matchit 0.8.4", "pin-project-lite", "serde", @@ -3135,6 +3562,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3152,6 +3585,17 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys 0.5.0", +] + [[package]] name = "system-configuration" version = "0.6.1" @@ -3160,7 +3604,17 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.1", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.6.0", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -3179,6 +3633,19 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -3311,6 +3778,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-tungstenite" version = "0.26.2" @@ -3345,7 +3822,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -3361,7 +3838,7 @@ dependencies = [ "bitflags 2.9.1", "bytes", "http 1.3.1", - "http-body", + "http-body 1.0.1", "pin-project-lite", "tower-layer", "tower-service", @@ -3552,6 +4029,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -3577,6 +4060,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -4076,7 +4565,7 @@ dependencies = [ "futures-channel", "futures-util", "http 1.3.1", - "http-body", + "http-body 1.0.1", "js-sys", "matchit 0.7.3", "pin-project", @@ -4150,7 +4639,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "serde", "zeroize", diff --git a/Cargo.toml b/Cargo.toml index 05b900d..b2dd369 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,7 @@ [workspace] members = [ "crates/gsio-relay", - 'crates/gsio-node' -] \ No newline at end of file + 'crates/gsio-node', + "crates/gsio-client", + "crates/gsio-wallet" +] diff --git a/README.md b/README.md index 3162cb1..750fee5 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,12 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) -gsio-net is a distributed ledger system with three main components: +gsio-net is a distributed ledger system with four main components: 1. **gsio-node**: A Rust-based Socket.IO server that handles real-time communication 2. **gsio-relay**: A Rust-based WebSocket server implemented as a Cloudflare Worker 3. **gsio-node-client**: A TypeScript client for connecting to the gsio-node server +4. **gsio-wallet**: A Rust-based wallet library for managing keys, transactions, and balances ## Project Overview @@ -271,6 +272,55 @@ The WebSocket client provides the following functions for participating in the p }); ``` +### Using the Wallet + +The gsio-wallet library provides the following functions for managing wallets and transactions: + +- **Creating a new wallet**: + ```rust + use gsio_wallet::Wallet; + + // Create a new empty wallet + let mut wallet = Wallet::new(); + + // Generate a new keypair and get the address + let address = wallet.generate_keypair().unwrap(); + println!("New wallet address: {}", address); + ``` + +- **Creating and signing a transaction**: + ```rust + use gsio_wallet::{Wallet, TransactionType}; + use serde_json::json; + + // Create a transaction + let mut transaction = wallet.create_transaction( + &sender_address, + &recipient_address, + 100, // amount + 10, // fee + TransactionType::Transfer, + Some(json!({"memo": "Payment for services"})) + ).unwrap(); + + // Sign the transaction + wallet.sign_transaction(&mut transaction).unwrap(); + ``` + +- **Submitting a transaction**: + ```rust + // Submit the transaction to the network + let transaction_id = wallet.submit_transaction(&transaction).await.unwrap(); + println!("Transaction submitted with ID: {}", transaction_id); + ``` + +- **Checking account balance**: + ```rust + // Get account balance + let balance = wallet.get_balance(&address).unwrap(); + println!("Account balance: {}", balance); + ``` + ## Development ### Project Structure @@ -280,6 +330,8 @@ The WebSocket client provides the following functions for participating in the p - **src/ledger.rs**: Distributed ledger implementation - **src/p2p.rs**: Peer-to-peer networking implementation - **gsio-relay/**: WebSocket server implemented as a Cloudflare Worker + - **gsio-wallet/**: Wallet library for managing keys and transactions + - **src/lib.rs**: Wallet implementation with key management and transaction handling - **packages/**: Contains TypeScript packages - **gsio-node-client/**: Client for connecting to the gsio-node server - **src/listeners/node_listener.ts**: Socket.IO client for ledger operations @@ -304,6 +356,17 @@ The p2p networking is implemented in `crates/gsio-node/src/p2p.rs` and provides - **Connection Management**: Connections are managed using Socket.IO - **Ledger Synchronization**: Nodes can request and receive ledger entries from other nodes +### Wallet System + +The wallet system is implemented in `crates/gsio-wallet/src/lib.rs` and provides the following features: + +- **Key Management**: Generate and store cryptographic keypairs (using Ed25519) +- **Account Management**: Create and manage accounts with addresses derived from public keys +- **Transaction Creation**: Create various types of transactions (Transfer, Stake, Unstake) +- **Transaction Signing**: Sign transactions with the account's private key +- **Balance Tracking**: Track account balances and transaction history +- **Secure Storage**: Save and load wallet data securely + ### Building Individual Components - **gsio-node**: @@ -318,6 +381,12 @@ The p2p networking is implemented in `crates/gsio-node/src/p2p.rs` and provides cargo install -q worker-build && worker-build --release ``` +- **gsio-wallet**: + ```bash + cd crates/gsio-wallet + cargo build + ``` + - **gsio-node-client**: ```bash cd packages/gsio-node-client @@ -343,13 +412,8 @@ The p2p networking is implemented in `crates/gsio-node/src/p2p.rs` and provides - **gsio-node**: Use `RUST_LOG=debug cargo run` for verbose logging. - **gsio-relay**: Use `wrangler dev --verbose` for detailed logs. - **gsio-node-client**: Add `console.log` statements for debugging. +- Write more tests -### Cleaning the Project - -To clean the project, run: -```bash -npm run clean -``` ## Contributing diff --git a/crates/gsio-client/Cargo.toml b/crates/gsio-client/Cargo.toml new file mode 100644 index 0000000..f780084 --- /dev/null +++ b/crates/gsio-client/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "gsio-client" +version = "0.1.0" +publish = false +edition = "2024" +license = "MIT" + +[dependencies] +tokio = { version = "1.45.1", features = ["rt-multi-thread", "macros", "time", "net"] } +tracing = { version = "0.1.41" } +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +uuid = { version = "1.7.0", features = ["v4", "serde"] } +chrono = { version = "0.4.35", features = ["serde"] } +reqwest = { version = "0.11", features = ["json"] } +thiserror = "1.0" +futures = "0.3.31" diff --git a/crates/gsio-client/examples/basic_usage.rs b/crates/gsio-client/examples/basic_usage.rs new file mode 100644 index 0000000..0d561fc --- /dev/null +++ b/crates/gsio-client/examples/basic_usage.rs @@ -0,0 +1,41 @@ +use gsio_client::{GsioClient, GsioClientError}; +use serde_json::json; +use tracing_subscriber::FmtSubscriber; + +#[tokio::main] +async fn main() -> Result<(), GsioClientError> { + // Initialize tracing + tracing::subscriber::set_global_default(FmtSubscriber::default()) + .expect("Failed to set tracing subscriber"); + + // Create a new client + let client = GsioClient::new("http://localhost:3000")?; + println!("Created GSIO client"); + + // Add an entry to the ledger + let entry_data = json!({ + "message": "Hello, GSIO!", + "timestamp": chrono::Utc::now().to_rfc3339(), + }); + + let entry = client.add_ledger_entry(entry_data).await?; + println!("Added ledger entry: {:?}", entry); + + // Get all entries in the ledger + let entries = client.get_ledger().await?; + println!("Ledger entries:"); + for entry in entries { + println!(" - {}: {}", entry.id, entry.data); + } + + // Get all known nodes + let nodes = client.get_known_nodes().await?; + println!("Known nodes:"); + for node in nodes { + println!(" - {}", node); + } + + println!("Example completed successfully"); + + Ok(()) +} diff --git a/crates/gsio-client/src/lib.rs b/crates/gsio-client/src/lib.rs new file mode 100644 index 0000000..8b94a0f --- /dev/null +++ b/crates/gsio-client/src/lib.rs @@ -0,0 +1,138 @@ +//! GSIO Client Library +//! +//! This library provides a client for interacting with GSIO nodes. +//! It allows connecting to nodes, adding entries to the ledger, +//! and retrieving ledger data. + +use reqwest::{Client as HttpClient, Error as ReqwestError}; +use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; +use std::time::Duration; +use thiserror::Error; +use tracing::{error, info}; + +/// Error type for GSIO client operations +#[derive(Error, Debug)] +pub enum GsioClientError { + #[error("HTTP error: {0}")] + HttpError(#[from] ReqwestError), + + #[error("JSON serialization error: {0}")] + SerializationError(#[from] serde_json::Error), + + #[error("Connection error: {0}")] + ConnectionError(String), + + #[error("Server error: {0}")] + ServerError(String), +} + +/// A ledger entry +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LedgerEntry { + pub id: String, + pub timestamp: String, + pub data: JsonValue, + pub node_id: String, + pub hash: String, +} + +/// GSIO Client for interacting with GSIO nodes +pub struct GsioClient { + client: HttpClient, + node_url: String, +} + +impl GsioClient { + /// Create a new GSIO client + pub fn new(node_url: &str) -> Result { + let client = HttpClient::builder() + .timeout(Duration::from_secs(30)) + .build() + .map_err(|e| GsioClientError::ConnectionError(e.to_string()))?; + + Ok(Self { + client, + node_url: node_url.to_string(), + }) + } + + /// Add an entry to the ledger + pub async fn add_ledger_entry(&self, data: JsonValue) -> Result { + info!("Adding ledger entry: {:?}", data); + + let url = format!("{}/api/ledger", self.node_url); + + let response = self.client.post(&url) + .json(&data) + .send() + .await?; + + if !response.status().is_success() { + let error_text = response.text().await?; + return Err(GsioClientError::ServerError(format!("Server returned error: {}", error_text))); + } + + let entry: LedgerEntry = response.json().await?; + + Ok(entry) + } + + /// Get all entries in the ledger + pub async fn get_ledger(&self) -> Result, GsioClientError> { + info!("Getting ledger entries"); + + let url = format!("{}/api/ledger", self.node_url); + + let response = self.client.get(&url) + .send() + .await?; + + if !response.status().is_success() { + let error_text = response.text().await?; + return Err(GsioClientError::ServerError(format!("Server returned error: {}", error_text))); + } + + let entries: Vec = response.json().await?; + + Ok(entries) + } + + /// Get all known nodes in the network + pub async fn get_known_nodes(&self) -> Result, GsioClientError> { + info!("Getting known nodes"); + + let url = format!("{}/api/nodes", self.node_url); + + let response = self.client.get(&url) + .send() + .await?; + + if !response.status().is_success() { + let error_text = response.text().await?; + return Err(GsioClientError::ServerError(format!("Server returned error: {}", error_text))); + } + + let data: JsonValue = response.json().await?; + + let nodes = data.get("nodes") + .ok_or_else(|| GsioClientError::ServerError("Invalid response format".to_string()))?; + + let nodes: Vec = serde_json::from_value(nodes.clone())?; + + Ok(nodes) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_client_creation() { + let client = GsioClient::new("http://localhost:3000").unwrap(); + assert_eq!(client.node_url, "http://localhost:3000"); + } + + // More tests would be added here in a real implementation +} diff --git a/crates/gsio-wallet/Cargo.toml b/crates/gsio-wallet/Cargo.toml new file mode 100644 index 0000000..3dde98b --- /dev/null +++ b/crates/gsio-wallet/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "gsio-wallet" +version = "0.1.0" +publish = false +edition = "2024" +license = "MIT" + +[dependencies] +tokio = { version = "1.45.1", features = ["rt-multi-thread", "macros", "time"] } +tracing = { version = "0.1.41" } +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +uuid = { version = "1.7.0", features = ["v4", "serde"] } +chrono = { version = "0.4.35", features = ["serde"] } +thiserror = "1.0" +rand = "0.7.3" +rand_core = "0.5.1" +ed25519-dalek = { version = "1.0.1", features = ["serde"] } +hex = "0.4.3" diff --git a/crates/gsio-wallet/README.md b/crates/gsio-wallet/README.md new file mode 100644 index 0000000..ea647fe --- /dev/null +++ b/crates/gsio-wallet/README.md @@ -0,0 +1,65 @@ +# GSIO Wallet + +A wallet implementation for the GSIO network. + +## Features + +- Key management (generate, store, retrieve keys) +- Transaction creation and signing +- Balance tracking +- Transaction history + +## Usage + +```rust +use gsio_wallet::{Wallet, TransactionType}; + +// Create a new wallet +let mut wallet = Wallet::new(); + +// Generate a new keypair +let address = wallet.generate_keypair().unwrap(); +println!("Generated address: {}", address); + +// Create a transaction +let transaction = wallet.create_transaction( + &address, + "recipient_address", + 100, + 10, + TransactionType::Transfer, + None, +).unwrap(); + +// Sign the transaction +let mut signed_transaction = transaction.clone(); +wallet.sign_transaction(&mut signed_transaction).unwrap(); + +// Submit the transaction (in an async context) +async { + let tx_id = wallet.submit_transaction(&signed_transaction).await.unwrap(); + println!("Transaction submitted: {}", tx_id); +}; + +// Get account balance +let balance = wallet.get_balance(&address).unwrap(); +println!("Balance: {}", balance); + +// Get transaction history +let history = wallet.get_transaction_history(&address).unwrap(); +println!("Transaction history: {:?}", history); +``` + +## Implementation Details + +This is a stubbed implementation of a wallet for the GSIO network. The actual implementation would need to be integrated with the GSIO network to handle real transactions and balances. + +The wallet uses Ed25519 for cryptographic operations, which provides strong security for digital signatures. + +## Future Improvements + +- Implement wallet persistence with encryption +- Add support for multiple accounts in a single wallet +- Implement transaction verification +- Add support for different transaction types +- Integrate with the GSIO network for real transactions \ No newline at end of file diff --git a/crates/gsio-wallet/src/lib.rs b/crates/gsio-wallet/src/lib.rs new file mode 100644 index 0000000..e105994 --- /dev/null +++ b/crates/gsio-wallet/src/lib.rs @@ -0,0 +1,256 @@ +//! GSIO Wallet Library +//! +//! This library provides wallet functionality for the GSIO network. +//! It allows creating and managing wallets, generating and storing keys, +//! signing transactions, and tracking balances. + +use chrono::{DateTime, Utc}; +use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signature, Signer, SignatureError}; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; +use std::collections::HashMap; +use std::fs::{self, File}; +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; +use thiserror::Error; +use tracing::{debug, error, info}; +use uuid::Uuid; + +/// Error type for GSIO wallet operations +#[derive(Error, Debug)] +pub enum WalletError { + #[error("IO error: {0}")] + IoError(#[from] io::Error), + + #[error("JSON serialization error: {0}")] + SerializationError(#[from] serde_json::Error), + + #[error("Signature error: {0}")] + SignatureError(#[from] SignatureError), + + #[error("Key not found: {0}")] + KeyNotFound(String), + + #[error("Wallet not found: {0}")] + WalletNotFound(String), + + #[error("Invalid wallet data: {0}")] + InvalidWalletData(String), + + #[error("Insufficient funds: required {0}, available {1}")] + InsufficientFunds(u64, u64), +} + +/// Transaction type +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum TransactionType { + Transfer, + Stake, + Unstake, + // Other transaction types can be added here +} + +/// Transaction status +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum TransactionStatus { + Pending, + Confirmed, + Failed, +} + +/// Transaction record +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Transaction { + pub id: String, + pub transaction_type: TransactionType, + pub amount: u64, + pub fee: u64, + pub sender: String, + pub recipient: String, + pub timestamp: DateTime, + pub status: TransactionStatus, + pub signature: Option, + pub data: Option, +} + +/// Wallet account information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Account { + pub address: String, + pub public_key: String, + pub balance: u64, + pub nonce: u64, + pub transactions: Vec, +} + +/// GSIO Wallet for managing keys and transactions +pub struct Wallet { + keypair: Option, + accounts: HashMap, + wallet_path: Option, +} + +impl Wallet { + /// Create a new empty wallet + pub fn new() -> Self { + Self { + keypair: None, + accounts: HashMap::new(), + wallet_path: None, + } + } + + /// Generate a new keypair + pub fn generate_keypair(&mut self) -> Result { + let mut csprng = OsRng; + let keypair = Keypair::generate(&mut csprng); + + let address = format!("gsio_{}", hex::encode(&keypair.public.to_bytes()[0..20])); + self.keypair = Some(keypair); + + // Create a new account for this keypair + let account = Account { + address: address.clone(), + public_key: hex::encode(self.keypair.as_ref().unwrap().public.to_bytes()), + balance: 0, + nonce: 0, + transactions: Vec::new(), + }; + + self.accounts.insert(address.clone(), account); + + Ok(address) + } + + /// Load wallet from file + pub fn load(&mut self, path: &Path) -> Result<(), WalletError> { + // This is a stub implementation + info!("Loading wallet from: {:?}", path); + self.wallet_path = Some(path.to_path_buf()); + + // In a real implementation, this would load the wallet data from the file + // For now, we'll just return Ok to indicate success + Ok(()) + } + + /// Save wallet to file + pub fn save(&self) -> Result<(), WalletError> { + // This is a stub implementation + if let Some(path) = &self.wallet_path { + info!("Saving wallet to: {:?}", path); + // In a real implementation, this would save the wallet data to the file + } else { + return Err(WalletError::IoError(io::Error::new( + io::ErrorKind::NotFound, + "Wallet path not set", + ))); + } + + // For now, we'll just return Ok to indicate success + Ok(()) + } + + /// Get account by address + pub fn get_account(&self, address: &str) -> Result<&Account, WalletError> { + self.accounts + .get(address) + .ok_or_else(|| WalletError::WalletNotFound(address.to_string())) + } + + /// Get account balance + pub fn get_balance(&self, address: &str) -> Result { + let account = self.get_account(address)?; + Ok(account.balance) + } + + /// Create a new transaction + pub fn create_transaction( + &self, + sender: &str, + recipient: &str, + amount: u64, + fee: u64, + transaction_type: TransactionType, + data: Option, + ) -> Result { + // Check if sender account exists + let sender_account = self.get_account(sender)?; + + // Check if sender has enough funds + if sender_account.balance < amount + fee { + return Err(WalletError::InsufficientFunds( + amount + fee, + sender_account.balance, + )); + } + + // Create transaction + let transaction = Transaction { + id: Uuid::new_v4().to_string(), + transaction_type, + amount, + fee, + sender: sender.to_string(), + recipient: recipient.to_string(), + timestamp: Utc::now(), + status: TransactionStatus::Pending, + signature: None, + data, + }; + + Ok(transaction) + } + + /// Sign a transaction + pub fn sign_transaction(&self, transaction: &mut Transaction) -> Result<(), WalletError> { + // This is a stub implementation + if self.keypair.is_none() { + return Err(WalletError::KeyNotFound("No keypair loaded".to_string())); + } + + // In a real implementation, this would sign the transaction with the keypair + // For now, we'll just set a dummy signature + transaction.signature = Some("dummy_signature".to_string()); + + Ok(()) + } + + /// Submit a transaction to the network + pub async fn submit_transaction(&self, transaction: &Transaction) -> Result { + // This is a stub implementation + info!("Submitting transaction: {:?}", transaction); + + // In a real implementation, this would submit the transaction to the network + // For now, we'll just return the transaction ID to indicate success + Ok(transaction.id.clone()) + } + + /// Get transaction history for an account + pub fn get_transaction_history(&self, address: &str) -> Result, WalletError> { + let account = self.get_account(address)?; + Ok(account.transactions.clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_wallet_creation() { + let wallet = Wallet::new(); + assert!(wallet.keypair.is_none()); + assert!(wallet.accounts.is_empty()); + } + + #[test] + fn test_keypair_generation() { + let mut wallet = Wallet::new(); + let address = wallet.generate_keypair().unwrap(); + assert!(wallet.keypair.is_some()); + assert!(wallet.accounts.contains_key(&address)); + } + + // More tests would be added here in a real implementation +}